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