7
7
import os
8
8
import click
9
9
import subprocess
10
+ import shutil
10
11
11
12
from typing import Union
12
13
@@ -95,12 +96,41 @@ def echo(ctx: Context) -> None:
95
96
96
97
@fastkit_cli .command ()
97
98
def list_templates () -> None :
98
- # TODO : impl this
99
99
"""
100
- Get available templates list.
100
+ Display the list of available templates.
101
+
101
102
:return: None
102
103
"""
103
- pass
104
+ settings = FastkitConfig ()
105
+ template_dir = settings .FASTKIT_TEMPLATE_ROOT
106
+
107
+ if not os .path .exists (template_dir ):
108
+ click .echo ("Template directory not found." )
109
+ return
110
+
111
+ templates = [
112
+ d
113
+ for d in os .listdir (template_dir )
114
+ if os .path .isdir (os .path .join (template_dir , d ))
115
+ ]
116
+
117
+ if not templates :
118
+ click .echo ("No available templates." )
119
+ return
120
+
121
+ click .echo ("\n Available templates:" )
122
+ for template in templates :
123
+ template_path = os .path .join (template_dir , template )
124
+ readme_path = os .path .join (template_path , "README.md-tpl" )
125
+
126
+ description = "No description"
127
+ if os .path .exists (readme_path ):
128
+ with open (readme_path , "r" ) as f :
129
+ first_line = f .readline ().strip ()
130
+ if first_line .startswith ("# " ):
131
+ description = first_line [2 :]
132
+
133
+ click .echo (f"- { template } : { description } " )
104
134
105
135
106
136
@fastkit_cli .command (context_settings = {"ignore_unknown_options" : True })
@@ -190,39 +220,118 @@ def startup(
190
220
191
221
192
222
@fastkit_cli .command (context_settings = {"ignore_unknown_options" : True })
193
- def startproject () -> None :
194
- # TODO : impl this. this method includes a stack selecting process. when user select a stack, it will be auto installed at venv environment, and make list of installed dependencies at requirements.txt file.
223
+ @click .option (
224
+ "--project-name" ,
225
+ prompt = "Enter project name" ,
226
+ help = "Name of the new FastAPI project" ,
227
+ )
228
+ @click .option (
229
+ "--stack" ,
230
+ type = click .Choice (["minimal" , "standard" , "full" ]),
231
+ prompt = "Select stack" ,
232
+ help = "Project stack configuration" ,
233
+ )
234
+ def startproject (project_name : str , stack : str ) -> None :
235
+ """
236
+ Start a new FastAPI project.
237
+ Dependencies will be automatically installed based on the selected stack.
238
+
239
+ :param project_name: Project name
240
+ :param stack: Project stack configuration
241
+ :return: None
242
+ """
243
+ settings = FastkitConfig ()
244
+ project_dir = os .path .join (settings .USER_WORKSPACE , project_name )
245
+
246
+ if os .path .exists (project_dir ):
247
+ click .echo (f"Error: Project '{ project_name } ' already exists." )
248
+ return
249
+
250
+ try :
251
+ os .makedirs (project_dir )
252
+
253
+ dependencies = {
254
+ "minimal" : ["fastapi" , "uvicorn" ],
255
+ "standard" : ["fastapi" , "uvicorn" , "sqlalchemy" , "alembic" , "pytest" ],
256
+ "full" : [
257
+ "fastapi" ,
258
+ "uvicorn" ,
259
+ "sqlalchemy" ,
260
+ "alembic" ,
261
+ "pytest" ,
262
+ "redis" ,
263
+ "celery" ,
264
+ "docker-compose" ,
265
+ ],
266
+ }
267
+
268
+ with open (os .path .join (project_dir , "requirements.txt" ), "w" ) as f :
269
+ for dep in dependencies [stack ]:
270
+ f .write (f"{ dep } \n " )
271
+
272
+ click .echo ("Creating virtual environment and installing dependencies..." )
273
+ subprocess .run (["python" , "-m" , "venv" , os .path .join (project_dir , "venv" )])
274
+ subprocess .run (["pip" , "install" , "-r" , "requirements.txt" ], cwd = project_dir )
275
+
276
+ click .echo (f"Project '{ project_name } ' has been created successfully!" )
277
+
278
+ except Exception as e :
279
+ click .echo (f"Error during project creation: { e } " )
280
+ shutil .rmtree (project_dir , ignore_errors = True )
281
+
282
+
283
+ def is_fastkit_project (project_dir : str ) -> bool :
195
284
"""
196
- Start a empty FastAPI project.
197
- :return:
285
+ Check if the project was created with fastkit.
286
+ Inspects the contents of the setup.py file.
287
+
288
+ :param project_dir: Project directory
289
+ :return: True if the project was created with fastkit, False otherwise
198
290
"""
199
- pass
291
+ setup_py = os .path .join (project_dir , "setup.py" )
292
+ if not os .path .exists (setup_py ):
293
+ return False
294
+
295
+ try :
296
+ with open (setup_py , "r" ) as f :
297
+ content = f .read ()
298
+ return "FastAPI-fastkit" in content
299
+ except :
300
+ return False
200
301
201
302
202
303
@fastkit_cli .command ()
203
304
@click .argument ("project_name" )
204
305
@click .pass_context
205
306
def deleteproject (ctx : Context , project_name : str ) -> None :
206
- # TODO : add checking step - if target project is not from fastkit, discard the attempt.
207
- settings = ctx . obj [ "settings" ]
307
+ """
308
+ Delete a FastAPI project.
208
309
310
+ :param ctx: Click context object
311
+ :param project_name: Project name
312
+ :return: None
313
+ """
314
+ settings = ctx .obj ["settings" ]
209
315
user_local = settings .USER_WORKSPACE
210
316
project_dir = os .path .join (user_local , project_name )
211
317
212
318
if not os .path .exists (project_dir ):
213
- click .echo (f"Error: Project '{ project_name } ' does not exist at '{ user_local } '." )
319
+ click .echo (f"Error: Project '{ project_name } ' does not exist in '{ user_local } '." )
320
+ return
321
+
322
+ if not is_fastkit_project (project_dir ):
323
+ click .echo (f"Error: '{ project_name } ' is not a FastAPI-fastkit project." )
214
324
return
215
325
216
326
confirm = click .confirm (
217
- f"\n Are you sure you want to delete the project '{ project_name } ' at '{ project_dir } '?" ,
327
+ f"\n Do you want to delete project '{ project_name } ' at '{ project_dir } '?" ,
218
328
default = False ,
219
329
)
220
330
if not confirm :
221
- click .echo ("Project deletion aborted !" )
331
+ click .echo ("Project deletion cancelled !" )
222
332
return
223
333
224
334
try :
225
- # TODO : adjust this
226
335
delete_project (project_dir )
227
336
click .echo (
228
337
f"Project '{ project_name } ' has been successfully deleted from '{ user_local } '."
@@ -236,28 +345,37 @@ def deleteproject(ctx: Context, project_name: str) -> None:
236
345
"--host" ,
237
346
default = "127.0.0.1" ,
238
347
show_default = True ,
239
- help = "The host to bind the server to. " ,
348
+ help = "Host to bind the server" ,
240
349
)
241
350
@click .option (
242
351
"--port" ,
243
352
default = 8000 ,
244
353
show_default = True ,
245
- help = "The port to bind the server to. " ,
354
+ help = "Port to bind the server" ,
246
355
)
247
356
@click .option (
248
357
"--reload/--no-reload" ,
249
358
default = True ,
250
359
show_default = True ,
251
- help = "Enable or disable auto-reloading on code changes." ,
360
+ help = "Enable/disable auto-reload on code changes" ,
361
+ )
362
+ @click .option (
363
+ "--workers" ,
364
+ default = 1 ,
365
+ show_default = True ,
366
+ help = "Number of worker processes" ,
252
367
)
253
368
@click .pass_context
254
369
def runserver (
255
- ctx : Context , host : str = "127.0.0.1" , port : int = 8000 , reload : bool = True
370
+ ctx : Context ,
371
+ host : str = "127.0.0.1" ,
372
+ port : int = 8000 ,
373
+ reload : bool = True ,
374
+ workers : int = 1 ,
256
375
) -> None :
257
- # TODO : add & apply click option
258
- # TODO : edit template 'fastapi-default'. fix modules
259
376
"""
260
377
Run the FastAPI server for the current project.
378
+ [TODO] Alternative Point : using FastAPI-fastkit's 'fastapi dev' command
261
379
262
380
:param ctx: Click context object
263
381
:param host: Host address to bind the server to
@@ -270,16 +388,25 @@ def runserver(
270
388
271
389
app_path = os .path .join (project_dir , "main.py" )
272
390
if not os .path .exists (app_path ):
273
- click .echo (
274
- f"Error: No 'main.py' found in the project directory '{ project_dir } '."
275
- )
391
+ click .echo (f"Error: Could not find 'main.py' in '{ project_dir } '." )
276
392
return
277
393
278
- # TODO : edit this - add click's params
279
- command = ["fastapi" , "dev" , "main.py" ]
394
+ command = [
395
+ "uvicorn" ,
396
+ "main:app" ,
397
+ "--host" ,
398
+ host ,
399
+ "--port" ,
400
+ str (port ),
401
+ "--workers" ,
402
+ str (workers ),
403
+ ]
404
+
405
+ if reload :
406
+ command .append ("--reload" )
280
407
281
408
try :
282
409
click .echo (f"Starting FastAPI server at { host } :{ port } ..." )
283
410
subprocess .run (command , check = True )
284
411
except subprocess .CalledProcessError as e :
285
- click .echo (f"Error: Failed to start the FastAPI server.\n { e } " )
412
+ click .echo (f"Error: Failed to start FastAPI server.\n { e } " )
0 commit comments