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.
- fastapi_fastkit/__init__.py +1 -10
- fastapi_fastkit/backend/inspector.py +933 -83
- fastapi_fastkit/backend/main.py +640 -482
- fastapi_fastkit/backend/package_managers/__init__.py +22 -0
- fastapi_fastkit/backend/package_managers/base.py +150 -0
- fastapi_fastkit/backend/package_managers/factory.py +151 -0
- fastapi_fastkit/backend/package_managers/pdm_manager.py +275 -0
- fastapi_fastkit/backend/package_managers/pip_manager.py +205 -0
- fastapi_fastkit/backend/package_managers/poetry_manager.py +378 -0
- fastapi_fastkit/backend/package_managers/uv_manager.py +327 -0
- fastapi_fastkit/backend/transducer.py +210 -106
- fastapi_fastkit/cli.py +702 -664
- fastapi_fastkit/core/exceptions.py +14 -3
- fastapi_fastkit/core/settings.py +181 -155
- fastapi_fastkit/fastapi_project_template/README.md +90 -90
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.env-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.gitignore-tpl +30 -30
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +107 -107
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +36 -36
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/format.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/lint.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/run-server.sh-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/test.sh-tpl +6 -6
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.cfg-tpl +14 -14
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +25 -25
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/api.py-tpl +9 -9
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/routes/items.py-tpl +57 -57
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/core/config.py-tpl +64 -64
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/items.py-tpl +21 -21
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/main.py-tpl +26 -26
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/mocks/mock_items.json-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/items.py-tpl +22 -22
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/conftest.py-tpl +42 -42
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/test_items.py-tpl +48 -48
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.env-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.gitignore-tpl +191 -191
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/README.md-tpl +156 -156
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +36 -36
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/format.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/lint.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/run-server.sh-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/test.sh-tpl +6 -6
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.cfg-tpl +12 -12
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.py-tpl +27 -27
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/api.py-tpl +9 -9
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/routes/items.py-tpl +172 -172
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/core/config.py-tpl +64 -64
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/crud/items.py-tpl +21 -21
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/exceptions.py-tpl +103 -103
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/pagination.py-tpl +72 -72
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/main.py-tpl +39 -39
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/mocks/mock_items.json-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/base.py-tpl +49 -49
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/items.py-tpl +22 -22
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/utils/documents.py-tpl +20 -20
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/conftest.py-tpl +42 -42
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/test_items.py-tpl +61 -61
- fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +101 -101
- fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/run-server.sh-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +22 -22
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_items.json-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +30 -30
- fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +44 -44
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.env-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.gitignore-tpl +30 -30
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/Dockerfile-tpl +23 -23
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +119 -119
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +36 -36
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/format.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/lint.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/run-server.sh-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/test.sh-tpl +6 -6
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.cfg-tpl +13 -13
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.py-tpl +22 -22
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/api.py-tpl +6 -6
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/routes/items.py-tpl +54 -54
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/core/config.py-tpl +64 -64
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/crud/items.py-tpl +15 -15
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/main.py-tpl +26 -26
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/mocks/mock_items.json-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/schemas/items.py-tpl +19 -19
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/conftest.py-tpl +30 -30
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/test_items.py-tpl +44 -44
- fastapi_fastkit/fastapi_project_template/fastapi-empty/.env-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-empty/.gitignore-tpl +30 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/README.md-tpl +85 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/requirements.txt-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.cfg-tpl +13 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.py-tpl +22 -22
- fastapi_fastkit/fastapi_project_template/fastapi-empty/src/core/config.py-tpl +75 -75
- fastapi_fastkit/fastapi_project_template/fastapi-empty/src/main.py-tpl +48 -36
- fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/conftest.py-tpl +16 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/test_main.py-tpl +37 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/.env-tpl +29 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/README.md-tpl +249 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +40 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/format.sh-tpl +12 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/lint.sh-tpl +18 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/run-server.sh-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/test.sh-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.cfg-tpl +90 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.py-tpl +51 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/__init__.py-tpl +4 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/__init__.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/api.py-tpl +14 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/__init__.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/auth.py-tpl +134 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/items.py-tpl +189 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/auth/__init__.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/auth/dependencies.py-tpl +88 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/core/__init__.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/core/config.py-tpl +60 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/main.py-tpl +66 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/mcp/__init__.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/mcp/router.py-tpl +100 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/schemas/__init__.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/schemas/items.py-tpl +67 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/__init__.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/conftest.py-tpl +49 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_auth.py-tpl +101 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_items.py-tpl +96 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_mcp.py-tpl +81 -0
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.env-tpl +9 -9
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.gitignore-tpl +30 -30
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/Dockerfile-tpl +23 -23
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/README.md-tpl +144 -144
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/alembic.ini-tpl +119 -119
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/docker-compose.yml-tpl +44 -44
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +41 -41
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/format.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/lint.sh-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/pre-start.sh-tpl +16 -16
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/run-server.sh-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/test.sh-tpl +10 -10
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.cfg-tpl +13 -13
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.py-tpl +25 -25
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/README-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/env.py-tpl +85 -85
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/script.py.mako-tpl +26 -26
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/versions/bedcdc35b64a_first_alembic.py-tpl +37 -37
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/api.py-tpl +11 -11
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/deps.py-tpl +19 -19
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/routes/items.py-tpl +109 -109
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/config.py-tpl +88 -88
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/db.py-tpl +53 -53
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/crud/items.py-tpl +102 -102
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/main.py-tpl +45 -45
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/schemas/items.py-tpl +54 -54
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/backend_pre_start.py-tpl +39 -39
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/init_data.py-tpl +23 -23
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/tests_pre_start.py-tpl +39 -39
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/template-config.yml-tpl +24 -0
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/conftest.py-tpl +63 -63
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/test_items.py-tpl +44 -44
- fastapi_fastkit/fastapi_project_template/modules/api/routes/new_route.py-tpl +64 -64
- fastapi_fastkit/fastapi_project_template/modules/crud/new_route.py-tpl +29 -29
- fastapi_fastkit/fastapi_project_template/modules/schemas/new_route.py-tpl +23 -23
- fastapi_fastkit/utils/logging.py +64 -28
- fastapi_fastkit/utils/main.py +180 -140
- fastapi_fastkit-1.1.0.dist-info/METADATA +182 -0
- {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/RECORD +165 -129
- {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/WHEEL +1 -1
- {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/licenses/LICENSE +21 -21
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/.DS_Store +0 -0
- fastapi_fastkit/logs/fastkit_debug_20250702_161423.log +0 -21
- fastapi_fastkit/logs/fastkit_debug_20250702_162706.log +0 -20
- fastapi_fastkit/logs/fastkit_debug_20250702_163010.log +0 -21
- fastapi_fastkit/logs/fastkit_debug_20250702_163526.log +0 -21
- fastapi_fastkit/logs/fastkit_debug_20250702_164053.log +0 -21
- fastapi_fastkit/logs/fastkit_debug_20250702_165220.log +0 -21
- fastapi_fastkit/logs/fastkit_debug_20250702_165647.log +0 -21
- fastapi_fastkit-1.0.2.dist-info/METADATA +0 -320
- {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/entry_points.txt +0 -0
fastapi_fastkit/backend/main.py
CHANGED
|
@@ -1,482 +1,640 @@
|
|
|
1
|
-
# --------------------------------------------------------------------------
|
|
2
|
-
# The Module defines backend operations for FastAPI-fastkit CLI.
|
|
3
|
-
#
|
|
4
|
-
# @author bnbong bbbong9@gmail.com
|
|
5
|
-
# --------------------------------------------------------------------------
|
|
6
|
-
import os
|
|
7
|
-
import
|
|
8
|
-
from typing import Dict, List
|
|
9
|
-
|
|
10
|
-
from fastapi_fastkit import console
|
|
11
|
-
from fastapi_fastkit.backend.
|
|
12
|
-
from fastapi_fastkit.
|
|
13
|
-
from fastapi_fastkit.core.
|
|
14
|
-
from fastapi_fastkit.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
except
|
|
244
|
-
|
|
245
|
-
raise BackendExceptions(f"Failed to
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
"
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
""
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
:param
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if os.path.exists(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"""
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
#
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# The Module defines backend operations for FastAPI-fastkit CLI.
|
|
3
|
+
#
|
|
4
|
+
# @author bnbong bbbong9@gmail.com
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
|
|
10
|
+
from fastapi_fastkit import console
|
|
11
|
+
from fastapi_fastkit.backend.package_managers import PackageManagerFactory
|
|
12
|
+
from fastapi_fastkit.backend.transducer import copy_and_convert_template_file
|
|
13
|
+
from fastapi_fastkit.core.exceptions import BackendExceptions, TemplateExceptions
|
|
14
|
+
from fastapi_fastkit.core.settings import settings
|
|
15
|
+
from fastapi_fastkit.utils.logging import debug_log, get_logger
|
|
16
|
+
from fastapi_fastkit.utils.main import (
|
|
17
|
+
handle_exception,
|
|
18
|
+
print_error,
|
|
19
|
+
print_info,
|
|
20
|
+
print_success,
|
|
21
|
+
print_warning,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = get_logger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ------------------------------------------------------------
|
|
28
|
+
# Template Discovery Functions
|
|
29
|
+
# ------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_template_core_modules(project_dir: str) -> Dict[str, str]:
|
|
33
|
+
"""
|
|
34
|
+
Find core module files in the template project structure.
|
|
35
|
+
Returns a dictionary with paths to main.py, setup.py, and config files.
|
|
36
|
+
|
|
37
|
+
:param project_dir: Path to the project directory
|
|
38
|
+
:return: Dictionary with paths to core modules
|
|
39
|
+
"""
|
|
40
|
+
core_modules = {"main": "", "setup": "", "config": ""}
|
|
41
|
+
template_paths = settings.TEMPLATE_PATHS
|
|
42
|
+
|
|
43
|
+
# Find main.py
|
|
44
|
+
for main_path in template_paths["main"]:
|
|
45
|
+
full_path = os.path.join(project_dir, main_path)
|
|
46
|
+
if os.path.exists(full_path):
|
|
47
|
+
core_modules["main"] = full_path
|
|
48
|
+
break
|
|
49
|
+
|
|
50
|
+
# Find setup.py
|
|
51
|
+
for setup_path in template_paths["setup"]:
|
|
52
|
+
full_path = os.path.join(project_dir, setup_path)
|
|
53
|
+
if os.path.exists(full_path):
|
|
54
|
+
core_modules["setup"] = full_path
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
# Find config file
|
|
58
|
+
config_info = template_paths["config"]
|
|
59
|
+
if isinstance(config_info, dict):
|
|
60
|
+
for config_path in config_info["paths"]:
|
|
61
|
+
for config_file in config_info["files"]:
|
|
62
|
+
full_path = os.path.join(project_dir, config_path, config_file)
|
|
63
|
+
if os.path.exists(full_path):
|
|
64
|
+
core_modules["config"] = full_path
|
|
65
|
+
break
|
|
66
|
+
if core_modules["config"]:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
return core_modules
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def read_template_stack(template_path: str) -> List[str]:
|
|
73
|
+
"""
|
|
74
|
+
Read dependencies from template requirements.txt-tpl file or setup.py-tpl file.
|
|
75
|
+
|
|
76
|
+
:param template_path: Path to the template directory
|
|
77
|
+
:return: List of dependencies
|
|
78
|
+
"""
|
|
79
|
+
# First try requirements.txt-tpl
|
|
80
|
+
req_file = os.path.join(template_path, "requirements.txt-tpl")
|
|
81
|
+
if os.path.exists(req_file):
|
|
82
|
+
try:
|
|
83
|
+
with open(req_file, "r", encoding="utf-8") as f:
|
|
84
|
+
deps = [dep.strip() for dep in f.readlines() if dep.strip()]
|
|
85
|
+
return deps
|
|
86
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
87
|
+
debug_log(
|
|
88
|
+
f"Error reading template dependencies from {req_file}: {e}", "error"
|
|
89
|
+
)
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
# Fallback: try to read from setup.py-tpl for legacy templates
|
|
93
|
+
setup_file = os.path.join(template_path, "setup.py-tpl")
|
|
94
|
+
if os.path.exists(setup_file):
|
|
95
|
+
try:
|
|
96
|
+
with open(setup_file, "r", encoding="utf-8") as f:
|
|
97
|
+
content = f.read()
|
|
98
|
+
return _parse_setup_dependencies(content)
|
|
99
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
100
|
+
debug_log(f"Error reading setup.py template: {e}", "error")
|
|
101
|
+
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _parse_setup_dependencies(content: str) -> List[str]:
|
|
106
|
+
"""
|
|
107
|
+
Parse dependencies from setup.py content.
|
|
108
|
+
|
|
109
|
+
:param content: setup.py file content
|
|
110
|
+
:return: List of dependencies
|
|
111
|
+
"""
|
|
112
|
+
# First try to find install_requires with list[str] type annotation (new format)
|
|
113
|
+
list_match = re.search(
|
|
114
|
+
r"install_requires:\s*list\[str\]\s*=\s*\[(.*?)\]",
|
|
115
|
+
content,
|
|
116
|
+
re.DOTALL,
|
|
117
|
+
)
|
|
118
|
+
if list_match:
|
|
119
|
+
deps_str = list_match.group(1)
|
|
120
|
+
# Split by lines and commas, clean up quotes
|
|
121
|
+
deps = []
|
|
122
|
+
for line in deps_str.split("\n"):
|
|
123
|
+
line = line.strip()
|
|
124
|
+
if line and not line.startswith("#"):
|
|
125
|
+
# Remove quotes and trailing commas
|
|
126
|
+
line = line.strip(" \",'")
|
|
127
|
+
if line:
|
|
128
|
+
deps.append(line)
|
|
129
|
+
return [dep for dep in deps if dep] # Remove empty strings
|
|
130
|
+
|
|
131
|
+
# Fallback to original install_requires parsing
|
|
132
|
+
match = re.search(r"install_requires\s*=\s*\[(.*?)\]", content, re.DOTALL)
|
|
133
|
+
if match:
|
|
134
|
+
deps_str = match.group(1).replace("\n", "").replace(" ", "")
|
|
135
|
+
deps = [
|
|
136
|
+
dep.strip().strip("'").strip('"')
|
|
137
|
+
for dep in deps_str.split(",")
|
|
138
|
+
if dep.strip() and not dep.isspace()
|
|
139
|
+
]
|
|
140
|
+
return [dep for dep in deps if dep] # Remove empty strings
|
|
141
|
+
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------------
|
|
146
|
+
# Project Setup Functions
|
|
147
|
+
# ------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def inject_project_metadata(
|
|
151
|
+
target_dir: str,
|
|
152
|
+
project_name: str,
|
|
153
|
+
author: str,
|
|
154
|
+
author_email: str,
|
|
155
|
+
description: str,
|
|
156
|
+
) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Inject project metadata into template files after template conversion.
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
core_modules = find_template_core_modules(target_dir)
|
|
162
|
+
_process_setup_file(
|
|
163
|
+
core_modules.get("setup", ""),
|
|
164
|
+
project_name,
|
|
165
|
+
author,
|
|
166
|
+
author_email,
|
|
167
|
+
description,
|
|
168
|
+
)
|
|
169
|
+
_process_config_file(core_modules.get("config", ""), project_name)
|
|
170
|
+
|
|
171
|
+
print_success("Project metadata injected successfully")
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
debug_log(f"Failed to inject project metadata: {e}", "error")
|
|
175
|
+
handle_exception(e, "Failed to inject project metadata")
|
|
176
|
+
raise BackendExceptions("Failed to inject project metadata")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _process_setup_file(
|
|
180
|
+
setup_py: str, project_name: str, author: str, author_email: str, description: str
|
|
181
|
+
) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Process setup.py file and inject metadata.
|
|
184
|
+
|
|
185
|
+
:param setup_py: Path to setup.py file
|
|
186
|
+
:param project_name: Project name
|
|
187
|
+
:param author: Author name
|
|
188
|
+
:param author_email: Author email
|
|
189
|
+
:param description: Project description
|
|
190
|
+
"""
|
|
191
|
+
if not setup_py or not os.path.exists(setup_py):
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
with open(setup_py, "r", encoding="utf-8") as f:
|
|
196
|
+
content = f.read()
|
|
197
|
+
|
|
198
|
+
# Replace placeholders
|
|
199
|
+
replacements = {
|
|
200
|
+
"<project_name>": project_name,
|
|
201
|
+
"<author>": author,
|
|
202
|
+
"<author_email>": author_email,
|
|
203
|
+
"<description>": description,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for placeholder, value in replacements.items():
|
|
207
|
+
content = content.replace(placeholder, value)
|
|
208
|
+
|
|
209
|
+
with open(setup_py, "w", encoding="utf-8") as f:
|
|
210
|
+
f.write(content)
|
|
211
|
+
|
|
212
|
+
debug_log("Injected metadata into setup.py", "info")
|
|
213
|
+
print_info("Injected metadata into setup.py")
|
|
214
|
+
|
|
215
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
216
|
+
debug_log(f"Error processing setup.py: {e}", "error")
|
|
217
|
+
raise BackendExceptions(f"Failed to process setup.py: {e}")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _process_config_file(config_py: str, project_name: str) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Process config file and inject project name.
|
|
223
|
+
|
|
224
|
+
:param config_py: Path to config file
|
|
225
|
+
:param project_name: Project name
|
|
226
|
+
"""
|
|
227
|
+
if not config_py or not os.path.exists(config_py):
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
with open(config_py, "r", encoding="utf-8") as f:
|
|
232
|
+
content = f.read()
|
|
233
|
+
|
|
234
|
+
# Replace project name placeholder
|
|
235
|
+
content = content.replace("<project_name>", project_name)
|
|
236
|
+
|
|
237
|
+
with open(config_py, "w", encoding="utf-8") as f:
|
|
238
|
+
f.write(content)
|
|
239
|
+
|
|
240
|
+
debug_log("Injected project name into config file", "info")
|
|
241
|
+
print_info("Injected project name into config file")
|
|
242
|
+
|
|
243
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
244
|
+
debug_log(f"Error processing config file: {e}", "error")
|
|
245
|
+
raise BackendExceptions(f"Failed to process config file: {e}")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def create_venv_with_manager(project_dir: str, manager_type: str = "pip") -> str:
|
|
249
|
+
"""
|
|
250
|
+
Create a virtual environment using the specified package manager.
|
|
251
|
+
|
|
252
|
+
:param project_dir: Path to the project directory
|
|
253
|
+
:param manager_type: Type of package manager to use
|
|
254
|
+
:return: Path to the virtual environment
|
|
255
|
+
:raises: BackendExceptions if virtual environment creation fails
|
|
256
|
+
"""
|
|
257
|
+
try:
|
|
258
|
+
package_manager = PackageManagerFactory.create_manager(
|
|
259
|
+
manager_type, project_dir, auto_detect=True
|
|
260
|
+
)
|
|
261
|
+
return package_manager.create_virtual_environment()
|
|
262
|
+
except Exception as e:
|
|
263
|
+
debug_log(
|
|
264
|
+
f"Error creating virtual environment with {manager_type}: {e}", "error"
|
|
265
|
+
)
|
|
266
|
+
raise BackendExceptions(f"Failed to create virtual environment: {str(e)}")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def create_venv(project_dir: str) -> str:
|
|
270
|
+
"""
|
|
271
|
+
Create a Python virtual environment in the project directory.
|
|
272
|
+
|
|
273
|
+
This is a backward compatibility wrapper that uses pip by default.
|
|
274
|
+
|
|
275
|
+
:param project_dir: Path to the project directory
|
|
276
|
+
:return: Path to the virtual environment
|
|
277
|
+
"""
|
|
278
|
+
return create_venv_with_manager(project_dir, "pip")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def install_dependencies_with_manager(
|
|
282
|
+
project_dir: str, venv_path: str, manager_type: str = "pip"
|
|
283
|
+
) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Install dependencies using the specified package manager.
|
|
286
|
+
|
|
287
|
+
:param project_dir: Path to the project directory
|
|
288
|
+
:param venv_path: Path to the virtual environment
|
|
289
|
+
:param manager_type: Type of package manager to use
|
|
290
|
+
:return: None
|
|
291
|
+
:raises: BackendExceptions if dependency installation fails
|
|
292
|
+
"""
|
|
293
|
+
try:
|
|
294
|
+
package_manager = PackageManagerFactory.create_manager(
|
|
295
|
+
manager_type, project_dir, auto_detect=True
|
|
296
|
+
)
|
|
297
|
+
package_manager.install_dependencies(venv_path)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
debug_log(f"Error installing dependencies with {manager_type}: {e}", "error")
|
|
300
|
+
raise BackendExceptions(f"Failed to install dependencies: {str(e)}")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def install_dependencies(project_dir: str, venv_path: str) -> None:
|
|
304
|
+
"""
|
|
305
|
+
Install dependencies in the virtual environment.
|
|
306
|
+
|
|
307
|
+
This is a backward compatibility wrapper that uses pip by default.
|
|
308
|
+
|
|
309
|
+
:param project_dir: Path to the project directory
|
|
310
|
+
:param venv_path: Path to the virtual environment
|
|
311
|
+
:return: None
|
|
312
|
+
"""
|
|
313
|
+
install_dependencies_with_manager(project_dir, venv_path, "pip")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def generate_dependency_file_with_manager(
|
|
317
|
+
project_dir: str,
|
|
318
|
+
dependencies: List[str],
|
|
319
|
+
manager_type: str = "pip",
|
|
320
|
+
project_name: str = "",
|
|
321
|
+
author: str = "",
|
|
322
|
+
author_email: str = "",
|
|
323
|
+
description: str = "",
|
|
324
|
+
) -> None:
|
|
325
|
+
"""
|
|
326
|
+
Generate a dependency file using the specified package manager.
|
|
327
|
+
|
|
328
|
+
:param project_dir: Path to the project directory
|
|
329
|
+
:param dependencies: List of dependency specifications
|
|
330
|
+
:param manager_type: Type of package manager to use
|
|
331
|
+
:param project_name: Name of the project
|
|
332
|
+
:param author: Author name
|
|
333
|
+
:param author_email: Author email
|
|
334
|
+
:param description: Project description
|
|
335
|
+
:return: None
|
|
336
|
+
:raises: BackendExceptions if dependency file generation fails
|
|
337
|
+
"""
|
|
338
|
+
try:
|
|
339
|
+
package_manager = PackageManagerFactory.create_manager(
|
|
340
|
+
manager_type, project_dir, auto_detect=True
|
|
341
|
+
)
|
|
342
|
+
package_manager.generate_dependency_file(
|
|
343
|
+
dependencies, project_name, author, author_email, description
|
|
344
|
+
)
|
|
345
|
+
except Exception as e:
|
|
346
|
+
debug_log(f"Error generating dependency file with {manager_type}: {e}", "error")
|
|
347
|
+
raise BackendExceptions(f"Failed to generate dependency file: {str(e)}")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ------------------------------------------------------------
|
|
351
|
+
# Add Route Functions
|
|
352
|
+
# ------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _ensure_project_structure(src_dir: str) -> Dict[str, str]:
|
|
356
|
+
"""
|
|
357
|
+
Ensure the project structure exists for adding a new route.
|
|
358
|
+
Creates necessary directories if they don't exist.
|
|
359
|
+
|
|
360
|
+
:param src_dir: Source directory of the project
|
|
361
|
+
:return: Dictionary with paths to target directories
|
|
362
|
+
"""
|
|
363
|
+
if not os.path.exists(src_dir):
|
|
364
|
+
raise BackendExceptions(f"Source directory not found at {src_dir}")
|
|
365
|
+
|
|
366
|
+
# Define target directories
|
|
367
|
+
target_dirs = {
|
|
368
|
+
"api": os.path.join(src_dir, "api"),
|
|
369
|
+
"api_routes": os.path.join(src_dir, "api", "routes"),
|
|
370
|
+
"crud": os.path.join(src_dir, "crud"),
|
|
371
|
+
"schemas": os.path.join(src_dir, "schemas"),
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
# Create directories and __init__.py files if needed
|
|
375
|
+
for dir_name, dir_path in target_dirs.items():
|
|
376
|
+
if not os.path.exists(dir_path):
|
|
377
|
+
os.makedirs(dir_path)
|
|
378
|
+
|
|
379
|
+
# Create __init__.py if it doesn't exist
|
|
380
|
+
init_file = os.path.join(dir_path, "__init__.py")
|
|
381
|
+
if not os.path.exists(init_file):
|
|
382
|
+
with open(init_file, "w") as f:
|
|
383
|
+
pass
|
|
384
|
+
|
|
385
|
+
return target_dirs
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _create_route_files(
|
|
389
|
+
modules_dir: str, target_dirs: Dict[str, str], route_name: str
|
|
390
|
+
) -> None:
|
|
391
|
+
"""
|
|
392
|
+
Create route files from templates.
|
|
393
|
+
|
|
394
|
+
:param modules_dir: Path to the modules directory
|
|
395
|
+
:param target_dirs: Dictionary with paths to target directories
|
|
396
|
+
:param route_name: Name of the route to create
|
|
397
|
+
"""
|
|
398
|
+
replacements = {"<new_route>": route_name}
|
|
399
|
+
module_types = ["api/routes", "crud", "schemas"]
|
|
400
|
+
|
|
401
|
+
for module_type in module_types:
|
|
402
|
+
# Source template
|
|
403
|
+
source = os.path.join(modules_dir, module_type, "new_route.py-tpl")
|
|
404
|
+
|
|
405
|
+
# Target directory
|
|
406
|
+
if module_type == "api/routes":
|
|
407
|
+
target_dir = target_dirs["api_routes"]
|
|
408
|
+
else:
|
|
409
|
+
target_dir = target_dirs[module_type.split("/")[-1]]
|
|
410
|
+
|
|
411
|
+
# Target file
|
|
412
|
+
target = os.path.join(target_dir, f"{route_name}.py")
|
|
413
|
+
|
|
414
|
+
if os.path.exists(target):
|
|
415
|
+
print_warning(f"File {target} already exists, skipping...")
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
if not copy_and_convert_template_file(source, target, replacements):
|
|
419
|
+
print_warning(f"Failed to copy template file {source} to {target}")
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _handle_api_router_file(
|
|
423
|
+
target_dirs: Dict[str, str], modules_dir: str, route_name: str
|
|
424
|
+
) -> None:
|
|
425
|
+
"""
|
|
426
|
+
Handle API router file creation or update.
|
|
427
|
+
|
|
428
|
+
:param target_dirs: Dictionary with paths to target directories
|
|
429
|
+
:param modules_dir: Path to the modules directory
|
|
430
|
+
:param route_name: Name of the route
|
|
431
|
+
"""
|
|
432
|
+
api_dir = target_dirs["api"]
|
|
433
|
+
api_router_file = os.path.join(api_dir, "api.py")
|
|
434
|
+
|
|
435
|
+
if not os.path.exists(api_router_file):
|
|
436
|
+
# Create api.py if it doesn't exist
|
|
437
|
+
api_source = os.path.join(modules_dir, "api", "__init__.py-tpl")
|
|
438
|
+
if os.path.exists(api_source):
|
|
439
|
+
copy_and_convert_template_file(api_source, api_router_file)
|
|
440
|
+
|
|
441
|
+
# Update API router to include new route
|
|
442
|
+
if os.path.exists(api_router_file):
|
|
443
|
+
_update_api_router(api_router_file, route_name)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _update_api_router(api_router_file: str, route_name: str) -> None:
|
|
447
|
+
"""
|
|
448
|
+
Update API router file to include new route.
|
|
449
|
+
|
|
450
|
+
:param api_router_file: Path to API router file
|
|
451
|
+
:param route_name: Name of the route
|
|
452
|
+
"""
|
|
453
|
+
try:
|
|
454
|
+
with open(api_router_file, "r", encoding="utf-8") as f:
|
|
455
|
+
content = f.read()
|
|
456
|
+
|
|
457
|
+
# Check if route is already included
|
|
458
|
+
route_import = f"from .routes import {route_name}"
|
|
459
|
+
route_include = f"api_router.include_router({route_name}.router"
|
|
460
|
+
|
|
461
|
+
if route_import in content and route_include in content:
|
|
462
|
+
return # Already included
|
|
463
|
+
|
|
464
|
+
# Add import if not present
|
|
465
|
+
if route_import not in content:
|
|
466
|
+
# Find where to add the import
|
|
467
|
+
lines = content.split("\n")
|
|
468
|
+
last_import_idx = -1
|
|
469
|
+
for i, line in enumerate(lines):
|
|
470
|
+
if line.startswith("from .routes import") or line.startswith(
|
|
471
|
+
"from routes import"
|
|
472
|
+
):
|
|
473
|
+
last_import_idx = i
|
|
474
|
+
|
|
475
|
+
if last_import_idx >= 0:
|
|
476
|
+
lines.insert(last_import_idx + 1, route_import)
|
|
477
|
+
else:
|
|
478
|
+
# Add at the end of imports section
|
|
479
|
+
for i, line in enumerate(lines):
|
|
480
|
+
if (
|
|
481
|
+
not line.startswith("from")
|
|
482
|
+
and not line.startswith("import")
|
|
483
|
+
and line.strip()
|
|
484
|
+
):
|
|
485
|
+
lines.insert(i, route_import)
|
|
486
|
+
break
|
|
487
|
+
|
|
488
|
+
content = "\n".join(lines)
|
|
489
|
+
|
|
490
|
+
# Add router inclusion if not present
|
|
491
|
+
if route_include not in content:
|
|
492
|
+
# Add before the last line or at the end
|
|
493
|
+
if content.strip():
|
|
494
|
+
content += f'\napi_router.include_router({route_name}.router, prefix="/{route_name}", tags=["{route_name}"])\n'
|
|
495
|
+
else:
|
|
496
|
+
content = f'api_router.include_router({route_name}.router, prefix="/{route_name}", tags=["{route_name}"])\n'
|
|
497
|
+
|
|
498
|
+
with open(api_router_file, "w", encoding="utf-8") as f:
|
|
499
|
+
f.write(content)
|
|
500
|
+
|
|
501
|
+
debug_log(f"Updated API router to include {route_name}", "info")
|
|
502
|
+
|
|
503
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
504
|
+
debug_log(f"Error updating API router: {e}", "error")
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _process_init_files(
|
|
508
|
+
modules_dir: str, target_dirs: Dict[str, str], module_types: List[str]
|
|
509
|
+
) -> None:
|
|
510
|
+
"""
|
|
511
|
+
Process __init__.py files if they don't exist.
|
|
512
|
+
|
|
513
|
+
:param modules_dir: Path to the modules directory
|
|
514
|
+
:param target_dirs: Dictionary with paths to target directories
|
|
515
|
+
:param module_types: List of module types to process
|
|
516
|
+
"""
|
|
517
|
+
for module_type in module_types:
|
|
518
|
+
module_base = module_type.split("/")[0]
|
|
519
|
+
init_source = os.path.join(modules_dir, module_base, "__init__.py-tpl")
|
|
520
|
+
|
|
521
|
+
if os.path.exists(init_source):
|
|
522
|
+
init_target_dir = target_dirs.get(module_base, None) or target_dirs.get(
|
|
523
|
+
f"{module_base}_routes"
|
|
524
|
+
)
|
|
525
|
+
if init_target_dir:
|
|
526
|
+
init_target = os.path.join(init_target_dir, "__init__.py")
|
|
527
|
+
if not os.path.exists(init_target):
|
|
528
|
+
copy_and_convert_template_file(init_source, init_target)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _update_main_app(src_dir: str, route_name: str) -> None:
|
|
532
|
+
"""
|
|
533
|
+
Update the main application file to include the API router.
|
|
534
|
+
Adds the router import and include statement at the end of the file.
|
|
535
|
+
|
|
536
|
+
:param src_dir: Source directory of the project
|
|
537
|
+
:param route_name: Name of the route to add
|
|
538
|
+
"""
|
|
539
|
+
main_py_path = os.path.join(src_dir, "main.py")
|
|
540
|
+
if not os.path.exists(main_py_path):
|
|
541
|
+
print_warning("main.py not found. Please manually add the API router.")
|
|
542
|
+
return
|
|
543
|
+
|
|
544
|
+
try:
|
|
545
|
+
with open(main_py_path, "r", encoding="utf-8") as f:
|
|
546
|
+
main_content = f.read()
|
|
547
|
+
|
|
548
|
+
# Check if router is already imported or included
|
|
549
|
+
router_import = "from src.api.api import api_router"
|
|
550
|
+
router_include = "app.include_router(api_router"
|
|
551
|
+
|
|
552
|
+
if router_import in main_content and router_include in main_content:
|
|
553
|
+
# Router already fully configured, nothing to do
|
|
554
|
+
return
|
|
555
|
+
|
|
556
|
+
# Check if FastAPI app is defined
|
|
557
|
+
if "app = FastAPI" not in main_content:
|
|
558
|
+
print_warning(
|
|
559
|
+
"FastAPI app instance not found in main.py. Please manually add router."
|
|
560
|
+
)
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
# Add the router import if needed
|
|
564
|
+
if router_import not in main_content:
|
|
565
|
+
# Add import at the end of imports section or beginning of file
|
|
566
|
+
lines = main_content.split("\n")
|
|
567
|
+
|
|
568
|
+
# Find last import line
|
|
569
|
+
last_import_idx = -1
|
|
570
|
+
for i, line in enumerate(lines):
|
|
571
|
+
if line.startswith("import ") or line.startswith("from "):
|
|
572
|
+
last_import_idx = i
|
|
573
|
+
|
|
574
|
+
if last_import_idx >= 0:
|
|
575
|
+
# Insert after the last import
|
|
576
|
+
lines.insert(last_import_idx + 1, router_import)
|
|
577
|
+
else:
|
|
578
|
+
# Insert at the beginning
|
|
579
|
+
lines.insert(0, router_import)
|
|
580
|
+
|
|
581
|
+
main_content = "\n".join(lines)
|
|
582
|
+
|
|
583
|
+
# Add router inclusion if needed
|
|
584
|
+
if router_include not in main_content:
|
|
585
|
+
# Add at the end of the file
|
|
586
|
+
main_content += "\napp.include_router(api_router)\n"
|
|
587
|
+
|
|
588
|
+
with open(main_py_path, "w", encoding="utf-8") as f:
|
|
589
|
+
f.write(main_content)
|
|
590
|
+
|
|
591
|
+
debug_log(f"Updated main.py to include router for {route_name}", "info")
|
|
592
|
+
|
|
593
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
594
|
+
debug_log(f"Error updating main.py: {e}", "error")
|
|
595
|
+
print_warning(f"Failed to update main.py: {e}")
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def add_new_route(project_dir: str, route_name: str) -> None:
|
|
599
|
+
"""
|
|
600
|
+
Add a new API route to an existing FastAPI project.
|
|
601
|
+
|
|
602
|
+
:param project_dir: Path to the project directory
|
|
603
|
+
:param route_name: Name of the new route to add
|
|
604
|
+
:raises BackendExceptions: If route addition fails
|
|
605
|
+
"""
|
|
606
|
+
try:
|
|
607
|
+
# Setup paths
|
|
608
|
+
modules_dir = os.path.join(settings.FASTKIT_TEMPLATE_ROOT, "modules")
|
|
609
|
+
src_dir = os.path.join(project_dir, "src")
|
|
610
|
+
|
|
611
|
+
# Ensure project structure exists
|
|
612
|
+
target_dirs = _ensure_project_structure(src_dir)
|
|
613
|
+
|
|
614
|
+
# Create route files
|
|
615
|
+
_create_route_files(modules_dir, target_dirs, route_name)
|
|
616
|
+
|
|
617
|
+
# Handle API router file
|
|
618
|
+
_handle_api_router_file(target_dirs, modules_dir, route_name)
|
|
619
|
+
|
|
620
|
+
# Process init files
|
|
621
|
+
module_types = ["api/routes", "crud", "schemas"]
|
|
622
|
+
_process_init_files(modules_dir, target_dirs, module_types)
|
|
623
|
+
|
|
624
|
+
# Update main application
|
|
625
|
+
_update_main_app(src_dir, route_name)
|
|
626
|
+
|
|
627
|
+
debug_log(f"Successfully added new route: {route_name}", "info")
|
|
628
|
+
print_success(f"Route '{route_name}' has been added successfully!")
|
|
629
|
+
|
|
630
|
+
except (OSError, PermissionError) as e:
|
|
631
|
+
debug_log(f"File system error while adding route {route_name}: {e}", "error")
|
|
632
|
+
handle_exception(e, f"Error adding new route: {str(e)}")
|
|
633
|
+
raise BackendExceptions(f"Failed to add new route: {str(e)}")
|
|
634
|
+
except BackendExceptions:
|
|
635
|
+
# Re-raise our own exceptions
|
|
636
|
+
raise
|
|
637
|
+
except Exception as e:
|
|
638
|
+
debug_log(f"Unexpected error while adding route {route_name}: {e}", "error")
|
|
639
|
+
handle_exception(e, f"Error adding new route: {str(e)}")
|
|
640
|
+
raise BackendExceptions(f"Failed to add new route: {str(e)}")
|