@@ -38,7 +38,7 @@ def exit_with_error(toolkit: RichToolkit, error_msg: str) -> None:
3838 Args:
3939 toolkit (RichToolkit): The RichToolkit instance for printing messages.
4040 error_msg (str): The error message to display.
41-
41+
4242 Returns:
4343 None
4444 """
@@ -52,7 +52,7 @@ def validate_python_version(python: str | None) -> str | None:
5252
5353 Args:
5454 python (str|None): The Python version string.
55-
55+
5656 Returns:
5757 str|None: An error message if the version is unsupported, otherwise None.
5858 """
@@ -77,7 +77,7 @@ def setup_environment(toolkit: RichToolkit, config: ProjectConfig) -> None:
7777 Args:
7878 toolkit (RichToolkit): The RichToolkit instance for printing messages.
7979 config (ProjectConfig): The project configuration.
80-
80+
8181 Returns:
8282 None
8383 """
@@ -110,13 +110,14 @@ def install_dependencies(toolkit: RichToolkit, config: ProjectConfig) -> None:
110110 Args:
111111 toolkit (RichToolkit): The RichToolkit instance for printing messages.
112112 config (ProjectConfig): The project configuration.
113-
113+
114114 Returns:
115115 None
116116 """
117117 toolkit .print ("Installing dependencies & generating requirements.txt..." , tag = "deps" )
118118 try :
119- deps = ["fastapi[standard]" ]
119+ deps = ["fastapi[standard]" , "python-dotenv" ]
120+
120121 if config .orm == "sqlmodel" :
121122 deps .append ("sqlmodel" )
122123 elif config .orm == "sqlalchemy" :
@@ -129,18 +130,23 @@ def install_dependencies(toolkit: RichToolkit, config: ProjectConfig) -> None:
129130 deps .append ("pytest" )
130131 deps .append ("httpx" )
131132
133+ if config .linter == "ruff" :
134+ deps .append ("ruff" )
135+ elif config .linter == "classic" :
136+ deps .extend (["black" , "isort" , "flake8" ])
137+
132138 subprocess .run (["uv" , "add" ] + deps , check = True , capture_output = True , cwd = config .path )
133139
134140 with open (config .path / "requirements.txt" , "w" ) as req_file :
135141 subprocess .run (
136- ["uv" , "export" , "--format" , "requirements-txt" ],
142+ ["uv" , "export" , "--format" , "requirements-txt" , "--no-hashes" , "--no-header" , "--no-annotate" ],
137143 stdout = req_file ,
138144 check = True ,
139145 cwd = config .path
140146 )
141147
142148 except subprocess .CalledProcessError as e :
143- exit_with_error (toolkit , f"Failed to install deps: { e .stderr .decode ()} " )
149+ exit_with_error (toolkit , f"Failed to install deps: { e .stderr .decode () if e . stderr else str ( e ) } " )
144150
145151
146152def create_file (path : pathlib .Path , content : str = "" ) -> None :
@@ -150,51 +156,101 @@ def create_file(path: pathlib.Path, content: str = "") -> None:
150156 Args:
151157 path (pathlib.Path): The file path to create.
152158 content (str): The content to write to the file.
153-
159+
154160 Returns:
155161 None
156162 """
157163 path .parent .mkdir (parents = True , exist_ok = True )
158- path .write_text (content )
164+ path .write_text (content , encoding = "utf-8" )
159165
160166
161167def write_project_files (toolkit : RichToolkit , config : ProjectConfig ) -> None :
162168 """
163169 Write the project files based on the configuration.
170+ Refactored for readability using helper functions.
164171
165172 Args:
166173 toolkit (RichToolkit): The RichToolkit instance for printing messages.
167174 config (ProjectConfig): The project configuration.
168-
175+
169176 Returns:
170177 None
171178 """
172179 toolkit .print ("Scaffolding project structure..." , tag = "template" )
173180 try :
174- create_file (config . path / "main.py" , TEMPLATE_MAIN )
175- create_file (config . path / "README.md" , generate_readme ( config . name ) )
181+ _create_base_files (config )
182+ _setup_git (config )
176183
177184 if config .views :
178- create_file (config . path / "views" / "html" / "index.html" , TEMPLATE_HTML )
179- create_file ( config . path / "views" / "css" / "style.css" , TEMPLATE_CSS )
180- create_file ( config .path / "views" / "js" / "main.js" , TEMPLATE_JS )
181- create_file (config . path / "views" / "assets" / ".gitkeep" , "" )
185+ _create_view_files (config )
186+
187+ if config .orm != "none" :
188+ _configure_database (config )
182189
183190 if config .structure == "advanced" :
184- create_file (config .path / "app" / "__init__.py" )
185- create_file (config .path / "app" / "controllers" / "__init__.py" )
186- create_file (config .path / "app" / "models" / "__init__.py" )
187- create_file (config .path / "app" / "schemas" / "__init__.py" )
191+ _setup_advanced_structure (config )
188192
189- create_file (config .path / "database" / "migrations" / ".gitkeep" )
190- create_file (config .path / "database" / "seeders" / ".gitkeep" )
193+ hello_file = config .path / "hello.py"
194+ if hello_file .exists ():
195+ hello_file .unlink ()
191196
192- if config .tests :
193- create_file (config .path / "tests" / "__init__.py" )
194- create_file (config .path / "tests" / "test_main.py" , "def test_example(): assert True" )
197+ except Exception as e :
198+ exit_with_error (toolkit , f"Failed to write files: { str (e )} " )
195199
196- if (config .path / "hello.py" ).exists ():
197- (config .path / "hello.py" ).unlink ()
198200
199- except Exception as e :
200- exit_with_error (toolkit , f"Failed to write files: { str (e )} " )
201+ def _create_base_files (config : ProjectConfig ) -> None :
202+ """Create the fundamental files for the project."""
203+ create_file (config .path / "main.py" , TEMPLATE_MAIN )
204+ create_file (config .path / "README.md" , generate_readme (config .name ))
205+
206+
207+ def _setup_git (config : ProjectConfig ) -> None :
208+ """Create or update .gitignore."""
209+ gitignore_path = config .path / ".gitignore"
210+
211+ if gitignore_path .exists ():
212+ with open (gitignore_path , "a" ) as f :
213+ f .write ("\n " + TEMPLATE_GITIGNORE )
214+ else :
215+ create_file (gitignore_path , TEMPLATE_GITIGNORE )
216+
217+
218+ def _create_view_files (config : ProjectConfig ) -> None :
219+ """Create HTML, CSS, and JS files."""
220+ base_view_path = config .path / "views"
221+ create_file (base_view_path / "html" / "index.html" , TEMPLATE_HTML )
222+ create_file (base_view_path / "css" / "style.css" , TEMPLATE_CSS )
223+ create_file (base_view_path / "js" / "main.js" , TEMPLATE_JS )
224+ create_file (base_view_path / "assets" / ".gitkeep" , "" )
225+
226+
227+ def _configure_database (config : ProjectConfig ) -> None :
228+ """Setup database connection, env file, and gitignore."""
229+ # Create database config
230+ create_file (config .path / "config" / "database.py" , TEMPLATE_DB_CONNECTION )
231+
232+ # Create .env
233+ env_content = TEMPLATE_ENV .format (project_name = config .name )
234+ create_file (config .path / ".env" , env_content .strip ())
235+
236+
237+ def _setup_advanced_structure (config : ProjectConfig ) -> None :
238+ """Setup MVC folders, Migrations, Tests, and Linter config."""
239+ # 1. App Structure (MVC)
240+ mvc_paths = ["app" , "app/controllers" , "app/models" , "app/schemas" ]
241+
242+ for p in mvc_paths :
243+ create_file (config .path / p / "__init__.py" )
244+
245+ # 2. Database Migrations
246+ create_file (config .path / "database" / "migrations" / ".gitkeep" )
247+ create_file (config .path / "database" / "seeders" / ".gitkeep" )
248+
249+ # 3. Testing
250+ if config .tests :
251+ create_file (config .path / "tests" / "__init__.py" )
252+ create_file (config .path / "tests" / "test_main.py" , TEMPLATE_TESTING )
253+
254+ # 4. Linter
255+ if config .linter == "ruff" :
256+ create_file (config .path / ".ruff.toml" , TEMPLATE_RUFF )
0 commit comments