FastAPI-fastkit 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. fastapi_fastkit/__init__.py +10 -1
  2. fastapi_fastkit/backend/main.py +482 -482
  3. fastapi_fastkit/backend/transducer.py +106 -106
  4. fastapi_fastkit/cli.py +664 -631
  5. fastapi_fastkit/core/settings.py +155 -155
  6. fastapi_fastkit/fastapi_project_template/README.md +90 -90
  7. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.env-tpl +1 -1
  8. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.gitignore-tpl +30 -30
  9. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +107 -107
  10. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +36 -36
  11. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/format.sh-tpl +5 -5
  12. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/lint.sh-tpl +5 -5
  13. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/run-server.sh-tpl +8 -8
  14. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/test.sh-tpl +6 -6
  15. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.cfg-tpl +14 -14
  16. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +25 -25
  17. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/api.py-tpl +9 -9
  18. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/routes/items.py-tpl +57 -57
  19. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/core/config.py-tpl +64 -64
  20. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/items.py-tpl +21 -21
  21. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/main.py-tpl +26 -26
  22. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/mocks/mock_items.json-tpl +8 -8
  23. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/items.py-tpl +22 -22
  24. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/conftest.py-tpl +42 -42
  25. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/test_items.py-tpl +48 -48
  26. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.env-tpl +1 -1
  27. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.gitignore-tpl +191 -191
  28. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/README.md-tpl +156 -156
  29. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +36 -36
  30. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/format.sh-tpl +5 -5
  31. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/lint.sh-tpl +5 -5
  32. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/run-server.sh-tpl +8 -8
  33. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/test.sh-tpl +6 -6
  34. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.cfg-tpl +12 -12
  35. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.py-tpl +27 -27
  36. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/.DS_Store +0 -0
  37. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/api.py-tpl +9 -9
  38. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/routes/items.py-tpl +172 -172
  39. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/core/config.py-tpl +64 -64
  40. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/crud/items.py-tpl +21 -21
  41. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/exceptions.py-tpl +103 -103
  42. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/pagination.py-tpl +72 -72
  43. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/main.py-tpl +39 -39
  44. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/mocks/mock_items.json-tpl +8 -8
  45. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/base.py-tpl +49 -49
  46. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/items.py-tpl +22 -22
  47. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/utils/documents.py-tpl +20 -20
  48. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/conftest.py-tpl +42 -42
  49. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/test_items.py-tpl +61 -61
  50. fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +101 -101
  51. fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/run-server.sh-tpl +8 -8
  52. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +22 -22
  53. fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_items.json-tpl +8 -8
  54. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +30 -30
  55. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +44 -44
  56. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.env-tpl +1 -1
  57. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.gitignore-tpl +30 -30
  58. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/Dockerfile-tpl +23 -23
  59. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +119 -119
  60. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +36 -36
  61. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/format.sh-tpl +5 -5
  62. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/lint.sh-tpl +5 -5
  63. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/run-server.sh-tpl +8 -8
  64. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/test.sh-tpl +6 -6
  65. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.cfg-tpl +13 -13
  66. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.py-tpl +22 -22
  67. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/api.py-tpl +6 -6
  68. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/routes/items.py-tpl +54 -54
  69. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/core/config.py-tpl +64 -64
  70. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/crud/items.py-tpl +15 -15
  71. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/main.py-tpl +26 -26
  72. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/mocks/mock_items.json-tpl +8 -8
  73. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/schemas/items.py-tpl +19 -19
  74. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/conftest.py-tpl +30 -30
  75. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/test_items.py-tpl +44 -44
  76. fastapi_fastkit/fastapi_project_template/fastapi-empty/.env-tpl +1 -1
  77. fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.py-tpl +22 -22
  78. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/core/config.py-tpl +75 -75
  79. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/main.py-tpl +36 -36
  80. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.env-tpl +9 -9
  81. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.gitignore-tpl +30 -30
  82. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/Dockerfile-tpl +23 -23
  83. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/README.md-tpl +144 -144
  84. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/alembic.ini-tpl +119 -119
  85. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/docker-compose.yml-tpl +44 -44
  86. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +41 -41
  87. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/format.sh-tpl +5 -5
  88. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/lint.sh-tpl +5 -5
  89. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/pre-start.sh-tpl +16 -16
  90. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/run-server.sh-tpl +8 -8
  91. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/test.sh-tpl +10 -10
  92. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.cfg-tpl +13 -13
  93. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.py-tpl +25 -25
  94. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/README-tpl +1 -1
  95. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/env.py-tpl +85 -85
  96. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/script.py.mako-tpl +26 -26
  97. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/versions/bedcdc35b64a_first_alembic.py-tpl +37 -37
  98. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/api.py-tpl +11 -11
  99. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/deps.py-tpl +19 -19
  100. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/routes/items.py-tpl +109 -109
  101. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/config.py-tpl +88 -88
  102. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/db.py-tpl +53 -53
  103. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/crud/items.py-tpl +102 -102
  104. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/main.py-tpl +45 -45
  105. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/schemas/items.py-tpl +54 -54
  106. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/backend_pre_start.py-tpl +39 -39
  107. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/init_data.py-tpl +23 -23
  108. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/tests_pre_start.py-tpl +39 -39
  109. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/conftest.py-tpl +63 -63
  110. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/test_items.py-tpl +44 -44
  111. fastapi_fastkit/fastapi_project_template/modules/api/routes/new_route.py-tpl +64 -64
  112. fastapi_fastkit/fastapi_project_template/modules/crud/new_route.py-tpl +29 -29
  113. fastapi_fastkit/fastapi_project_template/modules/schemas/new_route.py-tpl +23 -23
  114. fastapi_fastkit/logs/fastkit_debug_20250702_161423.log +21 -0
  115. fastapi_fastkit/logs/fastkit_debug_20250702_162706.log +20 -0
  116. fastapi_fastkit/logs/fastkit_debug_20250702_163010.log +21 -0
  117. fastapi_fastkit/logs/fastkit_debug_20250702_163526.log +21 -0
  118. fastapi_fastkit/logs/fastkit_debug_20250702_164053.log +21 -0
  119. fastapi_fastkit/logs/fastkit_debug_20250702_165220.log +21 -0
  120. fastapi_fastkit/logs/fastkit_debug_20250702_165647.log +21 -0
  121. fastapi_fastkit/utils/logging.py +112 -1
  122. fastapi_fastkit/utils/main.py +140 -129
  123. fastapi_fastkit-1.0.2.dist-info/METADATA +320 -0
  124. {fastapi_fastkit-1.0.0.dist-info → fastapi_fastkit-1.0.2.dist-info}/RECORD +127 -119
  125. {fastapi_fastkit-1.0.0.dist-info → fastapi_fastkit-1.0.2.dist-info}/WHEEL +1 -1
  126. {fastapi_fastkit-1.0.0.dist-info → fastapi_fastkit-1.0.2.dist-info}/licenses/LICENSE +21 -21
  127. fastapi_fastkit-1.0.0.dist-info/METADATA +0 -208
  128. {fastapi_fastkit-1.0.0.dist-info → fastapi_fastkit-1.0.2.dist-info}/entry_points.txt +0 -0
fastapi_fastkit/cli.py CHANGED
@@ -1,631 +1,664 @@
1
- # --------------------------------------------------------------------------
2
- # The Module defines main and core CLI operations for FastAPI-fastkit.
3
- #
4
- # @author bnbong bbbong9@gmail.com
5
- # --------------------------------------------------------------------------
6
- import os
7
- import shutil
8
- import subprocess
9
- from typing import Union
10
-
11
- import click
12
- from click.core import BaseCommand, Context
13
- from rich import print
14
- from rich.panel import Panel
15
-
16
- from fastapi_fastkit.backend.main import (
17
- add_new_route,
18
- create_venv,
19
- find_template_core_modules,
20
- inject_project_metadata,
21
- install_dependencies,
22
- read_template_stack,
23
- )
24
- from fastapi_fastkit.backend.transducer import copy_and_convert_template
25
- from fastapi_fastkit.core.exceptions import CLIExceptions
26
- from fastapi_fastkit.core.settings import FastkitConfig
27
- from fastapi_fastkit.utils.logging import setup_logging
28
- from fastapi_fastkit.utils.main import (
29
- create_info_table,
30
- is_fastkit_project,
31
- print_error,
32
- print_info,
33
- print_success,
34
- print_warning,
35
- validate_email,
36
- )
37
-
38
- from . import __version__, console
39
-
40
-
41
- @click.group()
42
- @click.option("--debug/--no-debug", default=False)
43
- @click.version_option(__version__, prog_name="fastapi-fastkit")
44
- @click.pass_context
45
- def fastkit_cli(ctx: Context, debug: bool) -> Union["BaseCommand", None]:
46
- """
47
- main FastAPI-fastkit CLI operation group
48
-
49
- :param ctx: context of passing configurations (NOT specify it at CLI)
50
- :type ctx: <Object click.Context>
51
- :param debug: parameter from CLI
52
- :return: None(will be wrapped with click.core.BaseCommand via @click decorator)
53
- """
54
- settings = FastkitConfig()
55
-
56
- ctx.ensure_object(dict)
57
-
58
- if debug:
59
- print_warning("running at debugging mode!!")
60
- settings.set_debug_mode()
61
-
62
- ctx.obj["settings"] = settings
63
-
64
- setup_logging(settings=settings)
65
-
66
- return None
67
-
68
-
69
- @fastkit_cli.command()
70
- @click.pass_context
71
- def echo(ctx: Context) -> None:
72
- """
73
- About FastAPI-fastkit
74
-
75
- :param ctx: context of passing configurations (NOT specify it at CLI)
76
- :type ctx: <Object click.Context>
77
- :return: None
78
- """
79
- fastkit_info = f"""
80
- ⚡️ FastAPI fastkit - fastest [bold]FastAPI[/bold] initializer. ⚡️
81
-
82
- Deploy FastAPI app foundation instantly at your local!
83
-
84
- ---
85
- - Project Maintainer : [link=mailto:bbbong9@gmail.com]bnbong(JunHyeok Lee)[/link]
86
- - Current Version : {__version__}
87
- - Github : [link]https://github.com/bnbong/FastAPI-fastkit[/link]
88
- """
89
- settings = ctx.obj["settings"]
90
- description_panel = Panel(fastkit_info, title="About FastAPI-fastkit")
91
- click.echo(print(description_panel))
92
-
93
- if settings.DEBUG_MODE:
94
- debug_output = f"FASTKIT_PROJECT_ROOT: {settings.FASTKIT_PROJECT_ROOT}\nUSER_WORKSPACE: {settings.USER_WORKSPACE}"
95
-
96
- click.echo(debug_output)
97
-
98
-
99
- @fastkit_cli.command()
100
- @click.pass_context
101
- def list_templates(ctx: Context) -> None:
102
- """
103
- Display the list of available templates.
104
- """
105
- settings = ctx.obj["settings"]
106
- template_dir = settings.FASTKIT_TEMPLATE_ROOT
107
-
108
- if not os.path.exists(template_dir):
109
- print_error("Template directory not found.")
110
- return
111
-
112
- excluded_dirs = ["__pycache__", "modules"]
113
- templates = [
114
- d
115
- for d in os.listdir(template_dir)
116
- if os.path.isdir(os.path.join(template_dir, d)) and d not in excluded_dirs
117
- ]
118
-
119
- if not templates:
120
- print_warning("No available templates.")
121
- return
122
-
123
- table = create_info_table("Available Templates")
124
-
125
- for template in templates:
126
- template_path = os.path.join(template_dir, template)
127
- readme_path = os.path.join(template_path, "README.md-tpl")
128
-
129
- description = "No description"
130
- if os.path.exists(readme_path):
131
- with open(readme_path, "r") as f:
132
- first_line = f.readline().strip()
133
- if first_line.startswith("# "):
134
- description = first_line[2:]
135
-
136
- table.add_row(template, description)
137
-
138
- console.print(table)
139
-
140
-
141
- @fastkit_cli.command(context_settings={"ignore_unknown_options": True})
142
- @click.argument("template", default="fastapi-default")
143
- @click.option(
144
- "--project-name",
145
- prompt="Enter the project name",
146
- help="The name of the new FastAPI project.",
147
- )
148
- @click.option(
149
- "--author", prompt="Enter the author name", help="The name of the project author."
150
- )
151
- @click.option(
152
- "--author-email",
153
- prompt="Enter the author email",
154
- help="The email of the project author.",
155
- type=str,
156
- callback=validate_email,
157
- )
158
- @click.option(
159
- "--description",
160
- prompt="Enter the project description",
161
- help="The description of the new FastAPI project.",
162
- )
163
- @click.pass_context
164
- def startdemo(
165
- ctx: Context,
166
- template: str,
167
- project_name: str,
168
- author: str,
169
- author_email: str,
170
- description: str,
171
- ) -> None:
172
- """
173
- Create a new FastAPI project from templates and inject metadata.
174
-
175
- :param ctx: Click context object
176
- :param template: Template name
177
- :param project_name: Project name for the new project
178
- :param author: Author name
179
- :param author_email: Author email
180
- :param description: Project description
181
- :return: None
182
- """
183
- settings = ctx.obj["settings"]
184
-
185
- template_dir = settings.FASTKIT_TEMPLATE_ROOT
186
- click.echo(f"Deploying FastAPI project using '{template}' template")
187
- target_template = os.path.join(template_dir, template)
188
- print(f"Template path: {target_template}")
189
-
190
- if not os.path.exists(target_template):
191
- print_error(f"Template '{template}' does not exist in '{template_dir}'.")
192
- raise CLIExceptions(
193
- f"Template '{template}' does not exist in '{template_dir}'."
194
- )
195
- table = create_info_table(
196
- "Project Information",
197
- {
198
- "Project Name": project_name,
199
- "Author": author,
200
- "Author Email": author_email,
201
- "Description": description,
202
- },
203
- )
204
-
205
- console.print("\n")
206
- console.print(table)
207
-
208
- template_deps = read_template_stack(target_template)
209
- if template_deps:
210
- deps_table = create_info_table(
211
- "Template Dependencies",
212
- {f"Dependency {i+1}": dep for i, dep in enumerate(template_deps)},
213
- )
214
- console.print("\n")
215
- console.print(deps_table)
216
-
217
- confirm = click.confirm(
218
- "\nDo you want to proceed with project creation?", default=False
219
- )
220
- if not confirm:
221
- print_error("Project creation aborted!")
222
- return
223
-
224
- try:
225
- user_local = settings.USER_WORKSPACE
226
- project_dir = os.path.join(user_local, project_name)
227
-
228
- click.echo(f"FastAPI template project will deploy at '{user_local}'")
229
-
230
- copy_and_convert_template(target_template, user_local, project_name)
231
-
232
- venv_path = create_venv(project_dir)
233
-
234
- inject_project_metadata(
235
- project_dir, project_name, author, author_email, description
236
- )
237
-
238
- install_dependencies(project_dir, venv_path)
239
-
240
- print_success(
241
- f"FastAPI project '{project_name}' from '{template}' has been created and saved to {user_local}!"
242
- )
243
-
244
- except Exception as e:
245
- print_error(f"Error during project creation: {str(e)}")
246
-
247
-
248
- @fastkit_cli.command(context_settings={"ignore_unknown_options": True})
249
- @click.option(
250
- "--project-name",
251
- prompt="Enter the project name",
252
- help="The name of the new FastAPI project.",
253
- )
254
- @click.option(
255
- "--author", prompt="Enter the author name", help="The name of the project author."
256
- )
257
- @click.option(
258
- "--author-email",
259
- prompt="Enter the author email",
260
- help="The email of the project author.",
261
- type=str,
262
- callback=validate_email,
263
- )
264
- @click.option(
265
- "--description",
266
- prompt="Enter the project description",
267
- help="The description of the new FastAPI project.",
268
- )
269
- @click.pass_context
270
- def init(
271
- ctx: Context, project_name: str, author: str, author_email: str, description: str
272
- ) -> None:
273
- """
274
- Start a empty FastAPI project setup.
275
- This command will automatically create a new FastAPI project directory and a python virtual environment.
276
- Dependencies will be automatically installed based on the selected stack at venv.
277
- Project metadata will be injected to the project files.
278
-
279
- :param ctx: Click context object
280
- :param project_name: Project name for the new project
281
- :param author: Author name
282
- :param author_email: Author email
283
- :param description: Project description
284
- :return: None
285
- """
286
- settings = ctx.obj["settings"]
287
- project_dir = os.path.join(settings.USER_WORKSPACE, project_name)
288
-
289
- if os.path.exists(project_dir):
290
- print_error(f"Error: Project '{project_name}' already exists.")
291
- return
292
-
293
- # Display project information
294
- project_info_table = create_info_table(
295
- "Project Information",
296
- {
297
- "Project Name": project_name,
298
- "Author": author,
299
- "Author Email": author_email,
300
- "Description": description,
301
- },
302
- )
303
- console.print("\n")
304
- console.print(project_info_table)
305
-
306
- # Display available stacks
307
- console.print("\n[bold]Available Stacks and Dependencies:[/bold]")
308
-
309
- for stack_name, deps in settings.PROJECT_STACKS.items():
310
- table = create_info_table(
311
- f"{stack_name.upper()} Stack",
312
- {f"Dependency {i+1}": dep for i, dep in enumerate(deps)},
313
- )
314
- console.print(table)
315
- console.print("\n")
316
-
317
- stack = click.prompt(
318
- "Select stack",
319
- type=click.Choice(list(settings.PROJECT_STACKS.keys())),
320
- show_choices=True,
321
- )
322
-
323
- template = "fastapi-empty"
324
- template_dir = settings.FASTKIT_TEMPLATE_ROOT
325
- target_template = os.path.join(template_dir, template)
326
-
327
- if not os.path.exists(target_template):
328
- print_error(f"Template '{template}' does not exist in '{template_dir}'.")
329
- raise CLIExceptions(
330
- f"Template '{template}' does not exist in '{template_dir}'."
331
- )
332
-
333
- confirm = click.confirm(
334
- "\nDo you want to proceed with project creation?", default=False
335
- )
336
- if not confirm:
337
- print_error("Project creation aborted!")
338
- return
339
-
340
- try:
341
- user_local = settings.USER_WORKSPACE
342
- project_dir = os.path.join(user_local, project_name)
343
-
344
- click.echo(f"FastAPI project will deploy at '{user_local}'")
345
-
346
- copy_and_convert_template(target_template, user_local, project_name)
347
-
348
- inject_project_metadata(
349
- project_dir, project_name, author, author_email, description
350
- )
351
-
352
- deps_table = create_info_table(
353
- f"Creating Project: {project_name}", {"Component": "Collected"}
354
- )
355
-
356
- with open(os.path.join(project_dir, "requirements.txt"), "w") as f:
357
- for dep in settings.PROJECT_STACKS[stack]:
358
- f.write(f"{dep}\n")
359
- deps_table.add_row(dep, "✓")
360
-
361
- console.print(deps_table)
362
-
363
- venv_path = create_venv(project_dir)
364
- install_dependencies(project_dir, venv_path)
365
-
366
- print_success(
367
- f"FastAPI project '{project_name}' has been created successfully and saved to {user_local}!"
368
- )
369
-
370
- print_info(
371
- "To start your project, run 'fastkit runserver' at newly created FastAPI project directory"
372
- )
373
-
374
- except Exception as e:
375
- print_error(f"Error during project creation: {str(e)}")
376
- if os.path.exists(project_dir):
377
- shutil.rmtree(project_dir, ignore_errors=True)
378
-
379
-
380
- @fastkit_cli.command()
381
- @click.argument("project_name")
382
- @click.argument("route_name")
383
- @click.pass_context
384
- def addroute(ctx: Context, project_name: str, route_name: str) -> None:
385
- """
386
- Add a new route to the FastAPI project.
387
-
388
- :param ctx: Click context object
389
- :param project_name: Project name
390
- :param route_name: Name of the new route to add
391
- :return: None
392
- """
393
- settings = ctx.obj["settings"]
394
- user_local = settings.USER_WORKSPACE
395
- project_dir = os.path.join(user_local, project_name)
396
-
397
- # Check if project exists
398
- if not os.path.exists(project_dir):
399
- print_error(f"Project '{project_name}' does not exist in '{user_local}'.")
400
- return
401
-
402
- # Verify it's a fastkit project
403
- if not is_fastkit_project(project_dir):
404
- print_error(f"'{project_name}' is not a FastAPI-fastkit project.")
405
- return
406
-
407
- # Validate route name
408
- if not route_name.isidentifier():
409
- print_error(f"Route name '{route_name}' is not a valid Python identifier.")
410
- return
411
-
412
- # Route name shouldn't match reserved keywords
413
- import keyword
414
-
415
- if keyword.iskeyword(route_name):
416
- print_error(
417
- f"Route name '{route_name}' is a Python keyword and cannot be used."
418
- )
419
- return
420
-
421
- try:
422
- # Show information about the operation
423
- table = create_info_table(
424
- "Adding New Route",
425
- {
426
- "Project": project_name,
427
- "Route Name": route_name,
428
- "Target Directory": project_dir,
429
- },
430
- )
431
- console.print(table)
432
-
433
- # Confirm before proceeding
434
- confirm = click.confirm(
435
- f"\nDo you want to add route '{route_name}' to project '{project_name}'?",
436
- default=True,
437
- )
438
- if not confirm:
439
- print_error("Operation cancelled!")
440
- return
441
-
442
- # Add the new route
443
- add_new_route(project_dir, route_name)
444
-
445
- print_success(
446
- f"Successfully added new route '{route_name}' to project `{project_name}`"
447
- )
448
-
449
- except Exception as e:
450
- print_error(f"Error during route addition: {str(e)}")
451
-
452
-
453
- @fastkit_cli.command()
454
- @click.argument("project_name")
455
- @click.pass_context
456
- def deleteproject(ctx: Context, project_name: str) -> None:
457
- """
458
- Delete a FastAPI project.
459
-
460
- :param ctx: Click context object
461
- :param project_name: Project name
462
- :return: None
463
- """
464
- settings = ctx.obj["settings"]
465
- user_local = settings.USER_WORKSPACE
466
- project_dir = os.path.join(user_local, project_name)
467
-
468
- if not os.path.exists(project_dir):
469
- print_error(f"Project '{project_name}' does not exist in '{user_local}'.")
470
- return
471
-
472
- if not is_fastkit_project(project_dir):
473
- print_error(f"'{project_name}' is not a FastAPI-fastkit project.")
474
- return
475
-
476
- confirm = click.confirm(
477
- f"\nDo you want to delete project '{project_name}' at '{project_dir}'?",
478
- default=False,
479
- )
480
- if not confirm:
481
- print_error("Project deletion cancelled!")
482
- return
483
-
484
- try:
485
- shutil.rmtree(project_dir)
486
- print_success(f"Project '{project_name}' has been deleted successfully!")
487
-
488
- except Exception as e:
489
- print_error(f"Error during project deletion: {e}")
490
-
491
-
492
- @fastkit_cli.command()
493
- @click.option(
494
- "--host",
495
- default="127.0.0.1",
496
- show_default=True,
497
- help="Host to bind the server",
498
- )
499
- @click.option(
500
- "--port",
501
- default=8000,
502
- show_default=True,
503
- help="Port to bind the server",
504
- )
505
- @click.option(
506
- "--reload/--no-reload",
507
- default=True,
508
- show_default=True,
509
- help="Enable/disable auto-reload on code changes",
510
- )
511
- @click.option(
512
- "--workers",
513
- default=1,
514
- show_default=True,
515
- help="Number of worker processes",
516
- )
517
- @click.pass_context
518
- def runserver(
519
- ctx: Context,
520
- host: str = "127.0.0.1",
521
- port: int = 8000,
522
- reload: bool = True,
523
- workers: int = 1,
524
- ) -> None:
525
- """
526
- Run the FastAPI server for the current project.
527
-
528
- :param ctx: Click context object
529
- :param host: Host address to bind the server to
530
- :param port: Port number to bind the server to
531
- :param reload: Enable or disable auto-reload
532
- :param workers: Number of worker processes
533
- :return: None
534
- """
535
- settings = ctx.obj["settings"]
536
- project_dir = settings.USER_WORKSPACE
537
-
538
- # Check for virtual environment
539
- venv_path = os.path.join(project_dir, ".venv")
540
- if not os.path.exists(venv_path) or not os.path.isdir(venv_path):
541
- print_error(
542
- "Virtual environment not found. Is this a project deployed with Fastkit?"
543
- )
544
- confirm = click.confirm(
545
- "Do you want to continue with system Python?", default=False
546
- )
547
- if not confirm:
548
- return
549
- venv_python = None
550
- else:
551
- if os.name == "nt": # Windows
552
- venv_python = os.path.join(venv_path, "Scripts", "python.exe")
553
- else: # Unix/Linux/Mac
554
- venv_python = os.path.join(venv_path, "bin", "python")
555
-
556
- if not os.path.exists(venv_python):
557
- print_error(
558
- f"Python interpreter not found in virtual environment: {venv_python}"
559
- )
560
- confirm = click.confirm(
561
- "Do you want to continue with system Python?", default=False
562
- )
563
- if not confirm:
564
- return
565
- venv_python = None
566
-
567
- core_modules = find_template_core_modules(project_dir)
568
- if not core_modules["main"]:
569
- print_error(f"Could not find 'main.py' in '{project_dir}'.")
570
- return
571
-
572
- main_path = core_modules["main"]
573
- if "src/" in main_path:
574
- app_module = "src.main:app"
575
- else:
576
- app_module = "main:app"
577
-
578
- if venv_python:
579
- print_info(f"Using Python from virtual environment: {venv_python}")
580
- command = [
581
- venv_python,
582
- "-m",
583
- "uvicorn",
584
- app_module,
585
- "--host",
586
- host,
587
- "--port",
588
- str(port),
589
- "--workers",
590
- str(workers),
591
- ]
592
- else:
593
- command = [
594
- "uvicorn",
595
- app_module,
596
- "--host",
597
- host,
598
- "--port",
599
- str(port),
600
- "--workers",
601
- str(workers),
602
- ]
603
-
604
- if reload:
605
- command.append("--reload")
606
-
607
- try:
608
- print_success(f"Starting FastAPI server at {host}:{port}...")
609
-
610
- # Set up environment variables for the subprocess
611
- env = os.environ.copy()
612
- if venv_python:
613
- python_path = os.path.dirname(os.path.dirname(venv_python))
614
- if "PYTHONPATH" in env:
615
- env["PYTHONPATH"] = f"{python_path}:{env['PYTHONPATH']}"
616
- else:
617
- env["PYTHONPATH"] = python_path
618
-
619
- # Run the server with the configured environment
620
- subprocess.run(command, check=True, env=env)
621
- except subprocess.CalledProcessError as e:
622
- print_error(f"Failed to start FastAPI server.\n{e}")
623
- except FileNotFoundError:
624
- if venv_python:
625
- print_error(
626
- f"Failed to run Python from the virtual environment. Make sure uvicorn is installed in the project's virtual environment."
627
- )
628
- else:
629
- print_error(
630
- f"uvicorn not found. Make sure it's installed in your system Python."
631
- )
1
+ # --------------------------------------------------------------------------
2
+ # The Module defines main and core CLI operations for FastAPI-fastkit.
3
+ #
4
+ # @author bnbong bbbong9@gmail.com
5
+ # --------------------------------------------------------------------------
6
+ import atexit
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from typing import Union
12
+
13
+ import click
14
+ from click.core import BaseCommand, Context
15
+ from rich import print
16
+ from rich.panel import Panel
17
+
18
+ from fastapi_fastkit.backend.main import (
19
+ add_new_route,
20
+ create_venv,
21
+ find_template_core_modules,
22
+ inject_project_metadata,
23
+ install_dependencies,
24
+ read_template_stack,
25
+ )
26
+ from fastapi_fastkit.backend.transducer import copy_and_convert_template
27
+ from fastapi_fastkit.core.exceptions import CLIExceptions
28
+ from fastapi_fastkit.core.settings import FastkitConfig
29
+ from fastapi_fastkit.utils.logging import get_logger, setup_logging
30
+ from fastapi_fastkit.utils.main import (
31
+ create_info_table,
32
+ is_fastkit_project,
33
+ print_error,
34
+ print_info,
35
+ print_success,
36
+ print_warning,
37
+ validate_email,
38
+ )
39
+
40
+ from . import __version__, console
41
+
42
+
43
+ @click.group()
44
+ @click.option("--debug/--no-debug", default=False)
45
+ @click.version_option(__version__, prog_name="fastapi-fastkit")
46
+ @click.pass_context
47
+ def fastkit_cli(ctx: Context, debug: bool) -> Union["BaseCommand", None]:
48
+ """
49
+ main FastAPI-fastkit CLI operation group
50
+
51
+ :param ctx: context of passing configurations (NOT specify it at CLI)
52
+ :type ctx: <Object click.Context>
53
+ :param debug: parameter from CLI
54
+ :return: None(will be wrapped with click.core.BaseCommand via @click decorator)
55
+ """
56
+ settings = FastkitConfig()
57
+
58
+ ctx.ensure_object(dict)
59
+
60
+ if debug:
61
+ print_warning("running at debugging mode!!")
62
+ settings.set_debug_mode()
63
+
64
+ ctx.obj["settings"] = settings
65
+
66
+ # Setup logging and get debug capture if debug mode is enabled
67
+ debug_capture = setup_logging(settings=settings)
68
+ ctx.obj["debug_capture"] = debug_capture
69
+
70
+ # If debug mode is enabled, start capturing output
71
+ if debug_capture:
72
+ debug_capture.__enter__()
73
+ # Log CLI invocation
74
+ logger = get_logger()
75
+ logger.info(f"CLI invoked with debug mode: {' '.join(sys.argv)}")
76
+
77
+ # Register cleanup function for when the CLI exits
78
+ def cleanup_debug_capture() -> None:
79
+ try:
80
+ debug_capture.__exit__(None, None, None)
81
+ logger.info("FastAPI-fastkit CLI session ended")
82
+ except Exception:
83
+ pass # Fail silently during cleanup
84
+
85
+ atexit.register(cleanup_debug_capture)
86
+
87
+ return None
88
+
89
+
90
+ @fastkit_cli.command()
91
+ @click.pass_context
92
+ def echo(ctx: Context) -> None:
93
+ """
94
+ About FastAPI-fastkit
95
+
96
+ :param ctx: context of passing configurations (NOT specify it at CLI)
97
+ :type ctx: <Object click.Context>
98
+ :return: None
99
+ """
100
+ fastkit_info = f"""
101
+ ⚡️ FastAPI fastkit - fastest [bold]FastAPI[/bold] initializer. ⚡️
102
+
103
+ Deploy FastAPI app foundation instantly at your local!
104
+
105
+ ---
106
+ - Project Maintainer : [link=mailto:bbbong9@gmail.com]bnbong(JunHyeok Lee)[/link]
107
+ - Current Version : {__version__}
108
+ - Github : [link]https://github.com/bnbong/FastAPI-fastkit[/link]
109
+ """
110
+ settings = ctx.obj["settings"]
111
+ description_panel = Panel(fastkit_info, title="About FastAPI-fastkit")
112
+ click.echo(print(description_panel))
113
+
114
+ if settings.DEBUG_MODE:
115
+ debug_output = f"FASTKIT_PROJECT_ROOT: {settings.FASTKIT_PROJECT_ROOT}\nUSER_WORKSPACE: {settings.USER_WORKSPACE}"
116
+
117
+ click.echo(debug_output)
118
+
119
+
120
+ @fastkit_cli.command()
121
+ @click.pass_context
122
+ def list_templates(ctx: Context) -> None:
123
+ """
124
+ Display the list of available templates.
125
+ """
126
+ settings = ctx.obj["settings"]
127
+ template_dir = settings.FASTKIT_TEMPLATE_ROOT
128
+
129
+ if not os.path.exists(template_dir):
130
+ print_error("Template directory not found.")
131
+ return
132
+
133
+ excluded_dirs = ["__pycache__", "modules"]
134
+ templates = [
135
+ d
136
+ for d in os.listdir(template_dir)
137
+ if os.path.isdir(os.path.join(template_dir, d)) and d not in excluded_dirs
138
+ ]
139
+
140
+ if not templates:
141
+ print_warning("No available templates.")
142
+ return
143
+
144
+ table = create_info_table("Available Templates")
145
+
146
+ for template in templates:
147
+ template_path = os.path.join(template_dir, template)
148
+ readme_path = os.path.join(template_path, "README.md-tpl")
149
+
150
+ description = "No description"
151
+ if os.path.exists(readme_path):
152
+ with open(readme_path, "r") as f:
153
+ first_line = f.readline().strip()
154
+ if first_line.startswith("# "):
155
+ description = first_line[2:]
156
+
157
+ table.add_row(template, description)
158
+
159
+ console.print(table)
160
+
161
+
162
+ @fastkit_cli.command(context_settings={"ignore_unknown_options": True})
163
+ @click.argument("template", default="fastapi-default")
164
+ @click.option(
165
+ "--project-name",
166
+ prompt="Enter the project name",
167
+ help="The name of the new FastAPI project.",
168
+ )
169
+ @click.option(
170
+ "--author", prompt="Enter the author name", help="The name of the project author."
171
+ )
172
+ @click.option(
173
+ "--author-email",
174
+ prompt="Enter the author email",
175
+ help="The email of the project author.",
176
+ type=str,
177
+ callback=validate_email,
178
+ )
179
+ @click.option(
180
+ "--description",
181
+ prompt="Enter the project description",
182
+ help="The description of the new FastAPI project.",
183
+ )
184
+ @click.pass_context
185
+ def startdemo(
186
+ ctx: Context,
187
+ template: str,
188
+ project_name: str,
189
+ author: str,
190
+ author_email: str,
191
+ description: str,
192
+ ) -> None:
193
+ """
194
+ Create a new FastAPI project from templates and inject metadata.
195
+
196
+ :param ctx: Click context object
197
+ :param template: Template name
198
+ :param project_name: Project name for the new project
199
+ :param author: Author name
200
+ :param author_email: Author email
201
+ :param description: Project description
202
+ :return: None
203
+ """
204
+ settings = ctx.obj["settings"]
205
+
206
+ template_dir = settings.FASTKIT_TEMPLATE_ROOT
207
+ click.echo(f"Deploying FastAPI project using '{template}' template")
208
+ target_template = os.path.join(template_dir, template)
209
+ print(f"Template path: {target_template}")
210
+
211
+ if not os.path.exists(target_template):
212
+ print_error(f"Template '{template}' does not exist in '{template_dir}'.")
213
+ raise CLIExceptions(
214
+ f"Template '{template}' does not exist in '{template_dir}'."
215
+ )
216
+ table = create_info_table(
217
+ "Project Information",
218
+ {
219
+ "Project Name": project_name,
220
+ "Author": author,
221
+ "Author Email": author_email,
222
+ "Description": description,
223
+ },
224
+ )
225
+
226
+ console.print("\n")
227
+ console.print(table)
228
+
229
+ template_deps = read_template_stack(target_template)
230
+ if template_deps:
231
+ deps_table = create_info_table(
232
+ "Template Dependencies",
233
+ {f"Dependency {i+1}": dep for i, dep in enumerate(template_deps)},
234
+ )
235
+ console.print("\n")
236
+ console.print(deps_table)
237
+
238
+ confirm = click.confirm(
239
+ "\nDo you want to proceed with project creation?", default=False
240
+ )
241
+ if not confirm:
242
+ print_error("Project creation aborted!")
243
+ return
244
+
245
+ try:
246
+ user_local = settings.USER_WORKSPACE
247
+ project_dir = os.path.join(user_local, project_name)
248
+
249
+ click.echo(f"FastAPI template project will deploy at '{user_local}'")
250
+
251
+ copy_and_convert_template(target_template, user_local, project_name)
252
+
253
+ venv_path = create_venv(project_dir)
254
+
255
+ inject_project_metadata(
256
+ project_dir, project_name, author, author_email, description
257
+ )
258
+
259
+ install_dependencies(project_dir, venv_path)
260
+
261
+ print_success(
262
+ f"FastAPI project '{project_name}' from '{template}' has been created and saved to {user_local}!"
263
+ )
264
+
265
+ except Exception as e:
266
+ logger = get_logger()
267
+ logger.exception(f"Error during project creation in startdemo: {str(e)}")
268
+ print_error(f"Error during project creation: {str(e)}")
269
+
270
+
271
+ @fastkit_cli.command(context_settings={"ignore_unknown_options": True})
272
+ @click.option(
273
+ "--project-name",
274
+ prompt="Enter the project name",
275
+ help="The name of the new FastAPI project.",
276
+ )
277
+ @click.option(
278
+ "--author", prompt="Enter the author name", help="The name of the project author."
279
+ )
280
+ @click.option(
281
+ "--author-email",
282
+ prompt="Enter the author email",
283
+ help="The email of the project author.",
284
+ type=str,
285
+ callback=validate_email,
286
+ )
287
+ @click.option(
288
+ "--description",
289
+ prompt="Enter the project description",
290
+ help="The description of the new FastAPI project.",
291
+ )
292
+ @click.pass_context
293
+ def init(
294
+ ctx: Context, project_name: str, author: str, author_email: str, description: str
295
+ ) -> None:
296
+ """
297
+ Start a empty FastAPI project setup.
298
+ This command will automatically create a new FastAPI project directory and a python virtual environment.
299
+ Dependencies will be automatically installed based on the selected stack at venv.
300
+ Project metadata will be injected to the project files.
301
+
302
+ :param ctx: Click context object
303
+ :param project_name: Project name for the new project
304
+ :param author: Author name
305
+ :param author_email: Author email
306
+ :param description: Project description
307
+ :return: None
308
+ """
309
+ settings = ctx.obj["settings"]
310
+ project_dir = os.path.join(settings.USER_WORKSPACE, project_name)
311
+
312
+ if os.path.exists(project_dir):
313
+ print_error(f"Error: Project '{project_name}' already exists.")
314
+ return
315
+
316
+ # Display project information
317
+ project_info_table = create_info_table(
318
+ "Project Information",
319
+ {
320
+ "Project Name": project_name,
321
+ "Author": author,
322
+ "Author Email": author_email,
323
+ "Description": description,
324
+ },
325
+ )
326
+ console.print("\n")
327
+ console.print(project_info_table)
328
+
329
+ # Display available stacks
330
+ console.print("\n[bold]Available Stacks and Dependencies:[/bold]")
331
+
332
+ for stack_name, deps in settings.PROJECT_STACKS.items():
333
+ table = create_info_table(
334
+ f"{stack_name.upper()} Stack",
335
+ {f"Dependency {i+1}": dep for i, dep in enumerate(deps)},
336
+ )
337
+ console.print(table)
338
+ console.print("\n")
339
+
340
+ stack = click.prompt(
341
+ "Select stack",
342
+ type=click.Choice(list(settings.PROJECT_STACKS.keys())),
343
+ show_choices=True,
344
+ )
345
+
346
+ template = "fastapi-empty"
347
+ template_dir = settings.FASTKIT_TEMPLATE_ROOT
348
+ target_template = os.path.join(template_dir, template)
349
+
350
+ if not os.path.exists(target_template):
351
+ print_error(f"Template '{template}' does not exist in '{template_dir}'.")
352
+ raise CLIExceptions(
353
+ f"Template '{template}' does not exist in '{template_dir}'."
354
+ )
355
+
356
+ confirm = click.confirm(
357
+ "\nDo you want to proceed with project creation?", default=False
358
+ )
359
+ if not confirm:
360
+ print_error("Project creation aborted!")
361
+ return
362
+
363
+ try:
364
+ user_local = settings.USER_WORKSPACE
365
+ project_dir = os.path.join(user_local, project_name)
366
+
367
+ click.echo(f"FastAPI project will deploy at '{user_local}'")
368
+
369
+ copy_and_convert_template(target_template, user_local, project_name)
370
+
371
+ inject_project_metadata(
372
+ project_dir, project_name, author, author_email, description
373
+ )
374
+
375
+ deps_table = create_info_table(
376
+ f"Creating Project: {project_name}", {"Component": "Collected"}
377
+ )
378
+
379
+ with open(os.path.join(project_dir, "requirements.txt"), "w") as f:
380
+ for dep in settings.PROJECT_STACKS[stack]:
381
+ f.write(f"{dep}\n")
382
+ deps_table.add_row(dep, "✓")
383
+
384
+ console.print(deps_table)
385
+
386
+ venv_path = create_venv(project_dir)
387
+ install_dependencies(project_dir, venv_path)
388
+
389
+ print_success(
390
+ f"FastAPI project '{project_name}' has been created successfully and saved to {user_local}!"
391
+ )
392
+
393
+ print_info(
394
+ "To start your project, run 'fastkit runserver' at newly created FastAPI project directory"
395
+ )
396
+
397
+ except Exception as e:
398
+ logger = get_logger()
399
+ logger.exception(f"Error during project creation in init: {str(e)}")
400
+ print_error(f"Error during project creation: {str(e)}")
401
+ if os.path.exists(project_dir):
402
+ shutil.rmtree(project_dir, ignore_errors=True)
403
+
404
+
405
+ @fastkit_cli.command()
406
+ @click.argument("project_name")
407
+ @click.argument("route_name")
408
+ @click.pass_context
409
+ def addroute(ctx: Context, project_name: str, route_name: str) -> None:
410
+ """
411
+ Add a new route to the FastAPI project.
412
+
413
+ :param ctx: Click context object
414
+ :param project_name: Project name
415
+ :param route_name: Name of the new route to add
416
+ :return: None
417
+ """
418
+ settings = ctx.obj["settings"]
419
+ user_local = settings.USER_WORKSPACE
420
+ project_dir = os.path.join(user_local, project_name)
421
+
422
+ # Check if project exists
423
+ if not os.path.exists(project_dir):
424
+ print_error(f"Project '{project_name}' does not exist in '{user_local}'.")
425
+ return
426
+
427
+ # Verify it's a fastkit project
428
+ if not is_fastkit_project(project_dir):
429
+ print_error(f"'{project_name}' is not a FastAPI-fastkit project.")
430
+ return
431
+
432
+ # Validate route name
433
+ if not route_name.isidentifier():
434
+ print_error(f"Route name '{route_name}' is not a valid Python identifier.")
435
+ return
436
+
437
+ # Route name shouldn't match reserved keywords
438
+ import keyword
439
+
440
+ if keyword.iskeyword(route_name):
441
+ print_error(
442
+ f"Route name '{route_name}' is a Python keyword and cannot be used."
443
+ )
444
+ return
445
+
446
+ try:
447
+ # Show information about the operation
448
+ table = create_info_table(
449
+ "Adding New Route",
450
+ {
451
+ "Project": project_name,
452
+ "Route Name": route_name,
453
+ "Target Directory": project_dir,
454
+ },
455
+ )
456
+ console.print(table)
457
+
458
+ # Confirm before proceeding
459
+ confirm = click.confirm(
460
+ f"\nDo you want to add route '{route_name}' to project '{project_name}'?",
461
+ default=True,
462
+ )
463
+ if not confirm:
464
+ print_error("Operation cancelled!")
465
+ return
466
+
467
+ # Add the new route
468
+ add_new_route(project_dir, route_name)
469
+
470
+ print_success(
471
+ f"Successfully added new route '{route_name}' to project `{project_name}`"
472
+ )
473
+
474
+ except Exception as e:
475
+ logger = get_logger()
476
+ logger.exception(f"Error during route addition: {str(e)}")
477
+ print_error(f"Error during route addition: {str(e)}")
478
+
479
+
480
+ @fastkit_cli.command()
481
+ @click.argument("project_name")
482
+ @click.pass_context
483
+ def deleteproject(ctx: Context, project_name: str) -> None:
484
+ """
485
+ Delete a FastAPI project.
486
+
487
+ :param ctx: Click context object
488
+ :param project_name: Project name
489
+ :return: None
490
+ """
491
+ settings = ctx.obj["settings"]
492
+ user_local = settings.USER_WORKSPACE
493
+ project_dir = os.path.join(user_local, project_name)
494
+
495
+ if not os.path.exists(project_dir):
496
+ print_error(f"Project '{project_name}' does not exist in '{user_local}'.")
497
+ return
498
+
499
+ if not is_fastkit_project(project_dir):
500
+ print_error(f"'{project_name}' is not a FastAPI-fastkit project.")
501
+ return
502
+
503
+ confirm = click.confirm(
504
+ f"\nDo you want to delete project '{project_name}' at '{project_dir}'?",
505
+ default=False,
506
+ )
507
+ if not confirm:
508
+ print_error("Project deletion cancelled!")
509
+ return
510
+
511
+ try:
512
+ shutil.rmtree(project_dir)
513
+ print_success(f"Project '{project_name}' has been deleted successfully!")
514
+
515
+ except Exception as e:
516
+ logger = get_logger()
517
+ logger.exception(f"Error during project deletion: {e}")
518
+ print_error(f"Error during project deletion: {e}")
519
+
520
+
521
+ @fastkit_cli.command()
522
+ @click.option(
523
+ "--host",
524
+ default="127.0.0.1",
525
+ show_default=True,
526
+ help="Host to bind the server",
527
+ )
528
+ @click.option(
529
+ "--port",
530
+ default=8000,
531
+ show_default=True,
532
+ help="Port to bind the server",
533
+ )
534
+ @click.option(
535
+ "--reload/--no-reload",
536
+ default=True,
537
+ show_default=True,
538
+ help="Enable/disable auto-reload on code changes",
539
+ )
540
+ @click.option(
541
+ "--workers",
542
+ default=1,
543
+ show_default=True,
544
+ help="Number of worker processes",
545
+ )
546
+ @click.pass_context
547
+ def runserver(
548
+ ctx: Context,
549
+ host: str = "127.0.0.1",
550
+ port: int = 8000,
551
+ reload: bool = True,
552
+ workers: int = 1,
553
+ ) -> None:
554
+ """
555
+ Run the FastAPI server for the current project.
556
+
557
+ :param ctx: Click context object
558
+ :param host: Host address to bind the server to
559
+ :param port: Port number to bind the server to
560
+ :param reload: Enable or disable auto-reload
561
+ :param workers: Number of worker processes
562
+ :return: None
563
+ """
564
+ settings = ctx.obj["settings"]
565
+ project_dir = settings.USER_WORKSPACE
566
+
567
+ # Check for virtual environment
568
+ venv_path = os.path.join(project_dir, ".venv")
569
+ if not os.path.exists(venv_path) or not os.path.isdir(venv_path):
570
+ print_error(
571
+ "Virtual environment not found. Is this a project deployed with Fastkit?"
572
+ )
573
+ confirm = click.confirm(
574
+ "Do you want to continue with system Python?", default=False
575
+ )
576
+ if not confirm:
577
+ return
578
+ venv_python = None
579
+ else:
580
+ if os.name == "nt": # Windows
581
+ venv_python = os.path.join(venv_path, "Scripts", "python.exe")
582
+ else: # Unix/Linux/Mac
583
+ venv_python = os.path.join(venv_path, "bin", "python")
584
+
585
+ if not os.path.exists(venv_python):
586
+ print_error(
587
+ f"Python interpreter not found in virtual environment: {venv_python}"
588
+ )
589
+ confirm = click.confirm(
590
+ "Do you want to continue with system Python?", default=False
591
+ )
592
+ if not confirm:
593
+ return
594
+ venv_python = None
595
+
596
+ core_modules = find_template_core_modules(project_dir)
597
+ if not core_modules["main"]:
598
+ print_error(f"Could not find 'main.py' in '{project_dir}'.")
599
+ return
600
+
601
+ main_path = core_modules["main"]
602
+ if "src/" in main_path:
603
+ app_module = "src.main:app"
604
+ else:
605
+ app_module = "main:app"
606
+
607
+ if venv_python:
608
+ print_info(f"Using Python from virtual environment: {venv_python}")
609
+ command = [
610
+ venv_python,
611
+ "-m",
612
+ "uvicorn",
613
+ app_module,
614
+ "--host",
615
+ host,
616
+ "--port",
617
+ str(port),
618
+ "--workers",
619
+ str(workers),
620
+ ]
621
+ else:
622
+ command = [
623
+ "uvicorn",
624
+ app_module,
625
+ "--host",
626
+ host,
627
+ "--port",
628
+ str(port),
629
+ "--workers",
630
+ str(workers),
631
+ ]
632
+
633
+ if reload:
634
+ command.append("--reload")
635
+
636
+ try:
637
+ print_success(f"Starting FastAPI server at {host}:{port}...")
638
+
639
+ # Set up environment variables for the subprocess
640
+ env = os.environ.copy()
641
+ if venv_python:
642
+ python_path = os.path.dirname(os.path.dirname(venv_python))
643
+ if "PYTHONPATH" in env:
644
+ env["PYTHONPATH"] = f"{python_path}:{env['PYTHONPATH']}"
645
+ else:
646
+ env["PYTHONPATH"] = python_path
647
+
648
+ # Run the server with the configured environment
649
+ subprocess.run(command, check=True, env=env)
650
+ except subprocess.CalledProcessError as e:
651
+ logger = get_logger()
652
+ logger.exception(f"Failed to start FastAPI server: {e}")
653
+ print_error(f"Failed to start FastAPI server.\n{e}")
654
+ except FileNotFoundError as e:
655
+ logger = get_logger()
656
+ logger.exception(f"FileNotFoundError when starting server: {e}")
657
+ if venv_python:
658
+ print_error(
659
+ f"Failed to run Python from the virtual environment. Make sure uvicorn is installed in the project's virtual environment."
660
+ )
661
+ else:
662
+ print_error(
663
+ f"uvicorn not found. Make sure it's installed in your system Python."
664
+ )