FastAPI-fastkit 0.1.0__py3-none-any.whl → 0.1.1__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 +203 -0
- fastapi_fastkit/backend/main.py +191 -0
- fastapi_fastkit/cli.py +63 -51
- fastapi_fastkit/core/settings.py +38 -0
- fastapi_fastkit/fastapi_project_template/README.md +55 -4
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.gitignore-tpl +191 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +17 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +47 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.cfg-tpl +13 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +35 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/__init__.py-tpl +88 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/__init__.py-tpl +48 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/routes/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/{fastapi-default/test → fastapi-async-crud/tests}/routes/test_user.py-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-customized-response/README.md-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/.env-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/.gitignore-tpl +28 -189
- fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +15 -7
- fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +25 -38
- fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/format.sh-tpl +5 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/lint.sh-tpl +5 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/test.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/setup.cfg-tpl +5 -5
- fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +9 -22
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/__init__.py-tpl +0 -88
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/api.py-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/routes/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/routes/items.py-tpl +57 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/config.py-tpl +64 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/items.py-tpl +18 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/main.py-tpl +26 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_items.json-tpl +8 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/__init__.py-tpl +0 -48
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/items.py-tpl +22 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +15 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +62 -0
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/README.md-tpl +0 -0
- fastapi_fastkit/utils/logging.py +4 -1
- fastapi_fastkit/{backend.py → utils/main.py} +22 -51
- fastapi_fastkit-0.1.1.dist-info/METADATA +173 -0
- fastapi_fastkit-0.1.1.dist-info/RECORD +84 -0
- {fastapi_fastkit-0.1.0.dist-info → fastapi_fastkit-0.1.1.dist-info}/licenses/LICENSE +21 -21
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/.DS_Store +0 -0
- fastapi_fastkit/utils/inspector.py +0 -15
- fastapi_fastkit-0.1.0.dist-info/METADATA +0 -46
- fastapi_fastkit-0.1.0.dist-info/RECORD +0 -56
- /fastapi_fastkit/{fastapi_project_template/fastapi-default/src/helper/__init__.py-tpl → backend/__init__.py} +0 -0
- /fastapi_fastkit/{utils → backend}/transducer.py +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/.env.test-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/main.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/script/run-server.sh-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/script/run-test.sh-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default/src/utils → fastapi-async-crud/src/core}/__init__.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/core/settings.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default/test → fastapi-async-crud/src/crud}/__init__.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/crud/_base.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/crud/user.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default/test/routes → fastapi-async-crud/src/helper}/__init__.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/exceptions.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/global_data.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/logging.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/pagination.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-dockerized → fastapi-async-crud/src/mocks}/__init__.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/mocks/mock_users.json-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/router/__init__.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/router/user.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/schemas/user.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/templates/index.html-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-psql-orm → fastapi-async-crud/src/utils}/__init__.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/utils/documents.py-tpl +0 -0
- /fastapi_fastkit/fastapi_project_template/{fastapi-default/test → fastapi-async-crud/tests}/conftest.py-tpl +0 -0
- {fastapi_fastkit-0.1.0.dist-info → fastapi_fastkit-0.1.1.dist-info}/WHEEL +0 -0
- {fastapi_fastkit-0.1.0.dist-info → fastapi_fastkit-0.1.1.dist-info}/entry_points.txt +0 -0
fastapi_fastkit/__init__.py
CHANGED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# The Module inspect FastAPI template is appropriate
|
|
3
|
+
#
|
|
4
|
+
# This module will be read by Github Action when contributor
|
|
5
|
+
# makes a PR of adding new FastAPI template.
|
|
6
|
+
#
|
|
7
|
+
# First, check a FastAPI template is formed a valid template form with .py-tpl extension
|
|
8
|
+
# & dependencies requirements.
|
|
9
|
+
# Second, check a FastAPI template has a proper FastAPI server implementation.
|
|
10
|
+
# main.py module must have a FastAPI app creation. like `app = FastAPI()`
|
|
11
|
+
# Third, check a FastAPI template has passed all the tests.
|
|
12
|
+
#
|
|
13
|
+
# This module create temporary named 'temp' directory at src/fastapi_fastkit/backend
|
|
14
|
+
# and copy a template to Funtional FastAPI application into the temp directory.
|
|
15
|
+
# After the inspection, it will be deleted.
|
|
16
|
+
#
|
|
17
|
+
# This module include virtual environment creation & installation of dependencies.
|
|
18
|
+
# Depending on the volume in which the template is implemented and the number of dependencies,
|
|
19
|
+
# it may take some time to complete the inspection.
|
|
20
|
+
#
|
|
21
|
+
# @author bnbong
|
|
22
|
+
# --------------------------------------------------------------------------
|
|
23
|
+
import os
|
|
24
|
+
import shutil
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Dict, List
|
|
29
|
+
|
|
30
|
+
from fastapi_fastkit.backend.main import (
|
|
31
|
+
create_venv,
|
|
32
|
+
find_template_core_modules,
|
|
33
|
+
install_dependencies,
|
|
34
|
+
)
|
|
35
|
+
from fastapi_fastkit.backend.transducer import copy_and_convert_template
|
|
36
|
+
from fastapi_fastkit.utils.main import print_error, print_success, print_warning
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TemplateInspector:
|
|
40
|
+
def __init__(self, template_path: str):
|
|
41
|
+
self.template_path = Path(template_path)
|
|
42
|
+
self.errors: List[str] = []
|
|
43
|
+
self.warnings: List[str] = []
|
|
44
|
+
self.temp_dir = os.path.join(os.path.dirname(__file__), "temp")
|
|
45
|
+
|
|
46
|
+
# Create temp directory and copy template
|
|
47
|
+
os.makedirs(self.temp_dir, exist_ok=True)
|
|
48
|
+
copy_and_convert_template(str(self.template_path), self.temp_dir)
|
|
49
|
+
|
|
50
|
+
def __del__(self) -> None:
|
|
51
|
+
"""Cleanup temp directory when inspector is destroyed."""
|
|
52
|
+
if os.path.exists(self.temp_dir):
|
|
53
|
+
shutil.rmtree(self.temp_dir)
|
|
54
|
+
|
|
55
|
+
def inspect_template(self) -> bool:
|
|
56
|
+
"""Inspect the template is valid FastAPI application."""
|
|
57
|
+
checks = [
|
|
58
|
+
self._check_file_structure,
|
|
59
|
+
self._check_file_extensions,
|
|
60
|
+
self._check_dependencies,
|
|
61
|
+
self._check_fastapi_implementation,
|
|
62
|
+
self._test_template,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
for check in checks:
|
|
66
|
+
if not check():
|
|
67
|
+
return False
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
def _check_file_structure(self) -> bool:
|
|
71
|
+
"""Check the required file and directory structure."""
|
|
72
|
+
required_paths = [
|
|
73
|
+
"tests",
|
|
74
|
+
"requirements.txt-tpl",
|
|
75
|
+
"setup.py-tpl",
|
|
76
|
+
"README.md-tpl",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
for path in required_paths:
|
|
80
|
+
if not (self.template_path / path).exists():
|
|
81
|
+
self.errors.append(f"Missing required path: {path}")
|
|
82
|
+
return False
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
def _check_file_extensions(self) -> bool:
|
|
86
|
+
"""Check all Python files have .py-tpl extension."""
|
|
87
|
+
for path in self.template_path.rglob("*"):
|
|
88
|
+
if path.is_file() and path.suffix == ".py":
|
|
89
|
+
self.errors.append(f"Found .py file instead of .py-tpl: {path}")
|
|
90
|
+
return False
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def _check_dependencies(self) -> bool:
|
|
94
|
+
"""Check the dependencies in both setup.py-tpl and requirements.txt-tpl."""
|
|
95
|
+
req_path = self.template_path / "requirements.txt-tpl"
|
|
96
|
+
setup_path = self.template_path / "setup.py-tpl"
|
|
97
|
+
|
|
98
|
+
if not req_path.exists():
|
|
99
|
+
self.errors.append("requirements.txt-tpl not found")
|
|
100
|
+
return False
|
|
101
|
+
if not setup_path.exists():
|
|
102
|
+
self.errors.append("setup.py-tpl not found")
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
with open(req_path) as f:
|
|
106
|
+
deps = f.read().splitlines()
|
|
107
|
+
package_names = [dep.split("==")[0] for dep in deps if dep]
|
|
108
|
+
if "fastapi" not in package_names:
|
|
109
|
+
self.errors.append(
|
|
110
|
+
"FastAPI dependency not found in requirements.txt-tpl"
|
|
111
|
+
)
|
|
112
|
+
return False
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
def _check_fastapi_implementation(self) -> bool:
|
|
116
|
+
"""Check if the template has a proper FastAPI server implementation."""
|
|
117
|
+
core_modules = find_template_core_modules(self.temp_dir)
|
|
118
|
+
|
|
119
|
+
if not core_modules["main"]:
|
|
120
|
+
self.errors.append("main.py not found in template")
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
with open(core_modules["main"]) as f:
|
|
124
|
+
content = f.read()
|
|
125
|
+
if "FastAPI" not in content or "app" not in content:
|
|
126
|
+
self.errors.append("FastAPI app creation not found in main.py")
|
|
127
|
+
return False
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
def _test_template(self) -> bool:
|
|
131
|
+
"""Run the tests in the converted template directory."""
|
|
132
|
+
test_dir = Path(self.temp_dir) / "tests"
|
|
133
|
+
if not test_dir.exists():
|
|
134
|
+
self.errors.append("Tests directory not found")
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
# Create virtual environment
|
|
139
|
+
venv_path = create_venv(self.temp_dir)
|
|
140
|
+
|
|
141
|
+
# Install dependencies
|
|
142
|
+
install_dependencies(self.temp_dir, venv_path)
|
|
143
|
+
|
|
144
|
+
# Run tests using the venv's pytest
|
|
145
|
+
if os.name == "nt": # Windows
|
|
146
|
+
pytest_path = os.path.join(venv_path, "Scripts", "pytest")
|
|
147
|
+
else: # Linux/Mac
|
|
148
|
+
pytest_path = os.path.join(venv_path, "bin", "pytest")
|
|
149
|
+
|
|
150
|
+
result = subprocess.run(
|
|
151
|
+
[pytest_path, str(test_dir)],
|
|
152
|
+
capture_output=True,
|
|
153
|
+
text=True,
|
|
154
|
+
cwd=self.temp_dir,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if result.returncode != 0:
|
|
158
|
+
self.errors.append(f"Tests failed: {result.stderr}")
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
self.errors.append(f"Error running tests: {e}")
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def inspect_template(template_path: str) -> Dict[str, Any]:
|
|
169
|
+
"""Run the template inspection and return the result."""
|
|
170
|
+
inspector = TemplateInspector(template_path)
|
|
171
|
+
is_valid = inspector.inspect_template()
|
|
172
|
+
result: dict[str, Any] = {
|
|
173
|
+
"valid": is_valid,
|
|
174
|
+
"errors": inspector.errors,
|
|
175
|
+
"warnings": inspector.warnings,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if result["valid"]:
|
|
179
|
+
print_success("Template inspection passed successfully! ✨")
|
|
180
|
+
elif result["errors"]:
|
|
181
|
+
error_messages = [str(error) for error in result["errors"]]
|
|
182
|
+
print_error(
|
|
183
|
+
"Template inspection failed with following errors:\n"
|
|
184
|
+
+ "\n".join(f"- {error}" for error in error_messages)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if result["warnings"]:
|
|
188
|
+
warning_messages = [str(warning) for warning in result["warnings"]]
|
|
189
|
+
print_warning(
|
|
190
|
+
"Template has following warnings:\n"
|
|
191
|
+
+ "\n".join(f"- {warning}" for warning in warning_messages)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
if len(sys.argv) != 2:
|
|
199
|
+
print("Usage: python inspector.py <template_dir>")
|
|
200
|
+
sys.exit(1)
|
|
201
|
+
|
|
202
|
+
template_dir = sys.argv[1]
|
|
203
|
+
inspect_template(template_dir)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# The Module defines backend operations for FastAPI-fastkit CLI.
|
|
3
|
+
#
|
|
4
|
+
# @author bnbong bbbong9@gmail.com
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
from fastapi_fastkit import console
|
|
10
|
+
from fastapi_fastkit.core.exceptions import BackendExceptions, TemplateExceptions
|
|
11
|
+
from fastapi_fastkit.core.settings import settings
|
|
12
|
+
from fastapi_fastkit.utils.main import print_error, print_info
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def find_template_core_modules(project_dir: str) -> dict[str, str]:
|
|
16
|
+
"""
|
|
17
|
+
Find core module files in the template project structure.
|
|
18
|
+
Returns a dictionary with paths to main.py, setup.py, and config files.
|
|
19
|
+
"""
|
|
20
|
+
core_modules = {"main": "", "setup": "", "config": ""}
|
|
21
|
+
template_config = settings.TEMPLATE_PATHS["config"]
|
|
22
|
+
|
|
23
|
+
# Find main.py
|
|
24
|
+
for path in settings.TEMPLATE_PATHS["main"]:
|
|
25
|
+
full_path = os.path.join(project_dir, path)
|
|
26
|
+
if os.path.exists(full_path):
|
|
27
|
+
core_modules["main"] = full_path
|
|
28
|
+
break
|
|
29
|
+
|
|
30
|
+
# Find setup.py
|
|
31
|
+
for path in settings.TEMPLATE_PATHS["setup"]:
|
|
32
|
+
full_path = os.path.join(project_dir, path)
|
|
33
|
+
if os.path.exists(full_path):
|
|
34
|
+
core_modules["setup"] = full_path
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
# Find config files (settings.py or config.py)
|
|
38
|
+
if isinstance(template_config, dict):
|
|
39
|
+
for config_file in template_config.get("files", []):
|
|
40
|
+
for base_path in template_config.get("paths", []):
|
|
41
|
+
full_path = os.path.join(project_dir, base_path, config_file)
|
|
42
|
+
if os.path.exists(full_path):
|
|
43
|
+
core_modules["config"] = full_path
|
|
44
|
+
break
|
|
45
|
+
if core_modules["config"]:
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
return core_modules
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def inject_project_metadata(
|
|
52
|
+
target_dir: str,
|
|
53
|
+
project_name: str,
|
|
54
|
+
author: str,
|
|
55
|
+
author_email: str,
|
|
56
|
+
description: str,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Inject project metadata into template files after template conversion.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
core_modules = find_template_core_modules(target_dir)
|
|
63
|
+
|
|
64
|
+
# Inject metadata to main.py
|
|
65
|
+
if core_modules["main"]:
|
|
66
|
+
with open(core_modules["main"], "r+") as f:
|
|
67
|
+
content = f.read()
|
|
68
|
+
content = content.replace("app_title", f'"{project_name}"')
|
|
69
|
+
content = content.replace("app_description", f'"{description}"')
|
|
70
|
+
f.seek(0)
|
|
71
|
+
f.write(content)
|
|
72
|
+
f.truncate()
|
|
73
|
+
|
|
74
|
+
# Inject metadata to setup.py
|
|
75
|
+
if core_modules["setup"]:
|
|
76
|
+
with open(core_modules["setup"], "r+") as f:
|
|
77
|
+
content = f.read()
|
|
78
|
+
content = content.replace("<project_name>", project_name, 1)
|
|
79
|
+
content = content.replace("<description>", description, 1)
|
|
80
|
+
content = content.replace("<author>", author, 1)
|
|
81
|
+
content = content.replace("<author_email>", author_email, 1)
|
|
82
|
+
f.seek(0)
|
|
83
|
+
f.write(content)
|
|
84
|
+
f.truncate()
|
|
85
|
+
|
|
86
|
+
# Inject metadata to config files
|
|
87
|
+
if core_modules["config"]:
|
|
88
|
+
with open(core_modules["config"], "r+") as f:
|
|
89
|
+
content = f.read()
|
|
90
|
+
content = content.replace("<project_name>", project_name)
|
|
91
|
+
content = content.replace("<description>", description)
|
|
92
|
+
f.seek(0)
|
|
93
|
+
f.write(content)
|
|
94
|
+
f.truncate()
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print_error(f"Error during metadata injection: {e}")
|
|
98
|
+
raise TemplateExceptions("Failed to inject metadata")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def create_venv(project_dir: str) -> str:
|
|
102
|
+
"""Create a virtual environment."""
|
|
103
|
+
try:
|
|
104
|
+
with console.status("[bold green]Setting up project environment..."):
|
|
105
|
+
console.print("[yellow]Creating virtual environment...[/yellow]")
|
|
106
|
+
venv_path = os.path.join(project_dir, ".venv")
|
|
107
|
+
subprocess.run(["python", "-m", "venv", venv_path], check=True)
|
|
108
|
+
|
|
109
|
+
if os.name == "nt":
|
|
110
|
+
activate_venv = f" {os.path.join(venv_path, 'Scripts', 'activate.bat')}"
|
|
111
|
+
else:
|
|
112
|
+
activate_venv = f" source {os.path.join(venv_path, 'bin', 'activate')}"
|
|
113
|
+
|
|
114
|
+
print_info(
|
|
115
|
+
"venv created at "
|
|
116
|
+
+ venv_path
|
|
117
|
+
+ "\nTo activate the virtual environment, run:\n\n"
|
|
118
|
+
+ activate_venv,
|
|
119
|
+
)
|
|
120
|
+
return venv_path
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print_error(f"Error during venv creation: {e}")
|
|
124
|
+
raise BackendExceptions("Failed to create venv")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def install_dependencies(project_dir: str, venv_path: str) -> None:
|
|
128
|
+
"""Install project dependencies in the virtual environment."""
|
|
129
|
+
try:
|
|
130
|
+
if os.name == "nt": # Windows
|
|
131
|
+
pip_path = os.path.join(venv_path, "Scripts", "pip")
|
|
132
|
+
else: # Linux/Mac
|
|
133
|
+
pip_path = os.path.join(venv_path, "bin", "pip")
|
|
134
|
+
|
|
135
|
+
with console.status("[bold green]Installing dependencies..."):
|
|
136
|
+
subprocess.run(
|
|
137
|
+
[pip_path, "install", "-r", "requirements.txt"],
|
|
138
|
+
cwd=project_dir,
|
|
139
|
+
check=True,
|
|
140
|
+
)
|
|
141
|
+
if os.name == "nt":
|
|
142
|
+
activate_venv = f" {os.path.join(venv_path, 'Scripts', 'activate.bat')}"
|
|
143
|
+
else:
|
|
144
|
+
activate_venv = f" source {os.path.join(venv_path, 'bin', 'activate')}"
|
|
145
|
+
|
|
146
|
+
print_info(
|
|
147
|
+
"Dependencies installed successfully."
|
|
148
|
+
+ "\nTo activate the virtual environment, run:\n\n"
|
|
149
|
+
+ activate_venv,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print_error(f"Error during dependency installation: {e}")
|
|
154
|
+
raise BackendExceptions("Failed to install dependencies")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def read_template_stack(template_path: str) -> list[str]:
|
|
158
|
+
"""
|
|
159
|
+
Read the install_requires from setup.py-tpl in the template directory.
|
|
160
|
+
Returns a list of required packages.
|
|
161
|
+
|
|
162
|
+
:param template_path: Path to the template directory
|
|
163
|
+
:return: List of required packages
|
|
164
|
+
"""
|
|
165
|
+
setup_path = os.path.join(template_path, "setup.py-tpl")
|
|
166
|
+
if not os.path.exists(setup_path):
|
|
167
|
+
return []
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
with open(setup_path, "r") as f:
|
|
171
|
+
content = f.read()
|
|
172
|
+
# Find the install_requires section using proper string matching
|
|
173
|
+
if "install_requires: list[str] = [" in content:
|
|
174
|
+
start_idx = content.find("install_requires: list[str] = [") + len(
|
|
175
|
+
"install_requires: list[str] = ["
|
|
176
|
+
)
|
|
177
|
+
end_idx = content.find("]", start_idx)
|
|
178
|
+
if start_idx != -1 and end_idx != -1:
|
|
179
|
+
deps_str = content[start_idx:end_idx]
|
|
180
|
+
# Clean and parse the dependencies
|
|
181
|
+
deps = [
|
|
182
|
+
dep.strip().strip("'").strip('"')
|
|
183
|
+
for dep in deps_str.split(",")
|
|
184
|
+
if dep.strip() and not dep.isspace()
|
|
185
|
+
]
|
|
186
|
+
return [dep for dep in deps if dep] # Remove empty strings
|
|
187
|
+
except Exception as e:
|
|
188
|
+
print_error(f"Error reading template dependencies: {e}")
|
|
189
|
+
return []
|
|
190
|
+
|
|
191
|
+
return []
|
fastapi_fastkit/cli.py
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import os
|
|
7
7
|
import shutil
|
|
8
8
|
import subprocess
|
|
9
|
-
from logging import getLogger
|
|
10
9
|
from typing import Union
|
|
11
10
|
|
|
12
11
|
import click
|
|
@@ -14,23 +13,26 @@ from click.core import BaseCommand, Context
|
|
|
14
13
|
from rich import print
|
|
15
14
|
from rich.panel import Panel
|
|
16
15
|
|
|
16
|
+
from fastapi_fastkit.backend.main import (
|
|
17
|
+
create_venv,
|
|
18
|
+
find_template_core_modules,
|
|
19
|
+
inject_project_metadata,
|
|
20
|
+
install_dependencies,
|
|
21
|
+
read_template_stack,
|
|
22
|
+
)
|
|
23
|
+
from fastapi_fastkit.backend.transducer import copy_and_convert_template
|
|
17
24
|
from fastapi_fastkit.core.exceptions import CLIExceptions
|
|
18
25
|
from fastapi_fastkit.core.settings import FastkitConfig
|
|
19
|
-
from fastapi_fastkit.utils.inspector import delete_project
|
|
20
26
|
from fastapi_fastkit.utils.logging import setup_logging
|
|
21
|
-
from fastapi_fastkit.utils.
|
|
22
|
-
|
|
23
|
-
from . import __version__, console
|
|
24
|
-
from .backend import (
|
|
27
|
+
from fastapi_fastkit.utils.main import (
|
|
25
28
|
create_info_table,
|
|
26
|
-
inject_project_metadata,
|
|
27
29
|
print_error,
|
|
28
30
|
print_success,
|
|
29
31
|
print_warning,
|
|
30
32
|
validate_email,
|
|
31
33
|
)
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
from . import __version__, console
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
@click.group()
|
|
@@ -92,11 +94,12 @@ def echo(ctx: Context) -> None:
|
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
@fastkit_cli.command()
|
|
95
|
-
|
|
97
|
+
@click.pass_context
|
|
98
|
+
def list_templates(ctx: Context) -> None:
|
|
96
99
|
"""
|
|
97
100
|
Display the list of available templates.
|
|
98
101
|
"""
|
|
99
|
-
settings =
|
|
102
|
+
settings = ctx.obj["settings"]
|
|
100
103
|
template_dir = settings.FASTKIT_TEMPLATE_ROOT
|
|
101
104
|
|
|
102
105
|
if not os.path.exists(template_dir):
|
|
@@ -199,7 +202,15 @@ def startup(
|
|
|
199
202
|
|
|
200
203
|
console.print("\n")
|
|
201
204
|
console.print(table)
|
|
202
|
-
|
|
205
|
+
|
|
206
|
+
template_deps = read_template_stack(target_template)
|
|
207
|
+
if template_deps:
|
|
208
|
+
deps_table = create_info_table(
|
|
209
|
+
"Template Dependencies",
|
|
210
|
+
{f"Dependency {i+1}": dep for i, dep in enumerate(template_deps)},
|
|
211
|
+
)
|
|
212
|
+
console.print("\n")
|
|
213
|
+
console.print(deps_table)
|
|
203
214
|
|
|
204
215
|
confirm = click.confirm(
|
|
205
216
|
"\nDo you want to proceed with project creation?", default=False
|
|
@@ -216,10 +227,14 @@ def startup(
|
|
|
216
227
|
|
|
217
228
|
copy_and_convert_template(target_template, user_local, project_name)
|
|
218
229
|
|
|
230
|
+
venv_path = create_venv(project_dir)
|
|
231
|
+
|
|
219
232
|
inject_project_metadata(
|
|
220
233
|
project_dir, project_name, author, author_email, description
|
|
221
234
|
)
|
|
222
235
|
|
|
236
|
+
install_dependencies(project_dir, venv_path)
|
|
237
|
+
|
|
223
238
|
print_success(
|
|
224
239
|
f"FastAPI project '{project_name}' from '{template}' has been created and saved to {user_local}!"
|
|
225
240
|
)
|
|
@@ -234,65 +249,55 @@ def startup(
|
|
|
234
249
|
prompt="Enter project name",
|
|
235
250
|
help="Name of the new FastAPI project",
|
|
236
251
|
)
|
|
237
|
-
@click.
|
|
238
|
-
|
|
239
|
-
type=click.Choice(["minimal", "standard", "full"]),
|
|
240
|
-
prompt="Select stack",
|
|
241
|
-
help="Project stack configuration",
|
|
242
|
-
)
|
|
243
|
-
def startproject(project_name: str, stack: str) -> None:
|
|
252
|
+
@click.pass_context
|
|
253
|
+
def startproject(ctx: Context, project_name: str) -> None:
|
|
244
254
|
"""
|
|
245
|
-
Start a
|
|
246
|
-
|
|
255
|
+
Start a empty FastAPI project setup.
|
|
256
|
+
This command will automatically create a new FastAPI project directory and a python virtual environment.
|
|
257
|
+
Dependencies will be automatically installed based on the selected stack at venv.
|
|
247
258
|
|
|
248
259
|
:param project_name: Project name
|
|
249
|
-
:param stack: Project stack configuration
|
|
250
260
|
:return: None
|
|
251
261
|
"""
|
|
252
|
-
settings =
|
|
262
|
+
settings = ctx.obj["settings"]
|
|
253
263
|
project_dir = os.path.join(settings.USER_WORKSPACE, project_name)
|
|
254
264
|
|
|
255
265
|
if os.path.exists(project_dir):
|
|
256
266
|
print_error(f"Error: Project '{project_name}' already exists.")
|
|
257
267
|
return
|
|
258
268
|
|
|
269
|
+
console.print("\n[bold]Available Stacks and Dependencies:[/bold]")
|
|
270
|
+
|
|
271
|
+
for stack_name, deps in settings.PROJECT_STACKS.items():
|
|
272
|
+
table = create_info_table(
|
|
273
|
+
f"{stack_name.upper()} Stack",
|
|
274
|
+
{f"Dependency {i+1}": dep for i, dep in enumerate(deps)},
|
|
275
|
+
)
|
|
276
|
+
console.print(table)
|
|
277
|
+
console.print("\n")
|
|
278
|
+
|
|
279
|
+
stack = click.prompt(
|
|
280
|
+
"Select stack",
|
|
281
|
+
type=click.Choice(list(settings.PROJECT_STACKS.keys())),
|
|
282
|
+
show_choices=True,
|
|
283
|
+
)
|
|
284
|
+
|
|
259
285
|
try:
|
|
260
286
|
os.makedirs(project_dir)
|
|
261
287
|
|
|
262
288
|
table = create_info_table(
|
|
263
|
-
f"Creating Project: {project_name}", {"Component": "
|
|
289
|
+
f"Creating Project: {project_name}", {"Component": "Collected"}
|
|
264
290
|
)
|
|
265
291
|
|
|
266
|
-
dependencies = {
|
|
267
|
-
"minimal": ["fastapi", "uvicorn"],
|
|
268
|
-
"standard": ["fastapi", "uvicorn", "sqlalchemy", "alembic", "pytest"],
|
|
269
|
-
"full": [
|
|
270
|
-
"fastapi",
|
|
271
|
-
"uvicorn",
|
|
272
|
-
"sqlalchemy",
|
|
273
|
-
"alembic",
|
|
274
|
-
"pytest",
|
|
275
|
-
"redis",
|
|
276
|
-
"celery",
|
|
277
|
-
"docker-compose",
|
|
278
|
-
],
|
|
279
|
-
}
|
|
280
|
-
|
|
281
292
|
with open(os.path.join(project_dir, "requirements.txt"), "w") as f:
|
|
282
|
-
for dep in
|
|
293
|
+
for dep in settings.PROJECT_STACKS[stack]:
|
|
283
294
|
f.write(f"{dep}\n")
|
|
284
295
|
table.add_row(dep, "✓")
|
|
285
296
|
|
|
286
297
|
console.print(table)
|
|
287
298
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
subprocess.run(["python", "-m", "venv", os.path.join(project_dir, "venv")])
|
|
291
|
-
|
|
292
|
-
console.print("[yellow]Installing dependencies...[/yellow]")
|
|
293
|
-
subprocess.run(
|
|
294
|
-
["pip", "install", "-r", "requirements.txt"], cwd=project_dir
|
|
295
|
-
)
|
|
299
|
+
venv_path = create_venv(project_dir)
|
|
300
|
+
install_dependencies(project_dir, venv_path)
|
|
296
301
|
|
|
297
302
|
print_success(f"Project '{project_name}' has been created successfully!")
|
|
298
303
|
|
|
@@ -353,7 +358,7 @@ def deleteproject(ctx: Context, project_name: str) -> None:
|
|
|
353
358
|
return
|
|
354
359
|
|
|
355
360
|
try:
|
|
356
|
-
|
|
361
|
+
shutil.rmtree(project_dir)
|
|
357
362
|
print_success(f"Project '{project_name}' has been deleted successfully!")
|
|
358
363
|
|
|
359
364
|
except Exception as e:
|
|
@@ -393,6 +398,7 @@ def runserver(
|
|
|
393
398
|
reload: bool = True,
|
|
394
399
|
workers: int = 1,
|
|
395
400
|
) -> None:
|
|
401
|
+
# TODO : check where main.py is located and run it
|
|
396
402
|
"""
|
|
397
403
|
Run the FastAPI server for the current project.
|
|
398
404
|
[1.1.0 update TODO] Alternative Point : using FastAPI-fastkit's 'fastapi dev' command
|
|
@@ -406,14 +412,20 @@ def runserver(
|
|
|
406
412
|
settings = ctx.obj["settings"]
|
|
407
413
|
project_dir = settings.USER_WORKSPACE
|
|
408
414
|
|
|
409
|
-
|
|
410
|
-
if not
|
|
415
|
+
core_modules = find_template_core_modules(project_dir)
|
|
416
|
+
if not core_modules["main"]:
|
|
411
417
|
print_error(f"Could not find 'main.py' in '{project_dir}'.")
|
|
412
418
|
return
|
|
413
419
|
|
|
420
|
+
main_path = core_modules["main"]
|
|
421
|
+
if "src/" in main_path:
|
|
422
|
+
app_module = "src.main:app"
|
|
423
|
+
else:
|
|
424
|
+
app_module = "main:app"
|
|
425
|
+
|
|
414
426
|
command = [
|
|
415
427
|
"uvicorn",
|
|
416
|
-
|
|
428
|
+
app_module,
|
|
417
429
|
"--host",
|
|
418
430
|
host,
|
|
419
431
|
"--port",
|
fastapi_fastkit/core/settings.py
CHANGED
|
@@ -20,6 +20,41 @@ class FastkitConfig:
|
|
|
20
20
|
DEBUG_MODE: bool = False
|
|
21
21
|
LOGGING_LEVEL: str = "DEBUG"
|
|
22
22
|
|
|
23
|
+
# Template Metadata Options
|
|
24
|
+
TEMPLATE_PATHS: dict[str, list[str] | dict[str, list[str]]] = {
|
|
25
|
+
"main": [
|
|
26
|
+
"src/main.py",
|
|
27
|
+
"main.py",
|
|
28
|
+
],
|
|
29
|
+
"setup": [
|
|
30
|
+
"setup.py",
|
|
31
|
+
"src/setup.py",
|
|
32
|
+
],
|
|
33
|
+
"config": {
|
|
34
|
+
"files": ["settings.py", "config.py"],
|
|
35
|
+
"paths": [
|
|
36
|
+
"src/core",
|
|
37
|
+
"src",
|
|
38
|
+
"",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Startproject Options
|
|
44
|
+
PROJECT_STACKS: dict[str, list[str]] = {
|
|
45
|
+
"minimal": ["fastapi", "uvicorn"],
|
|
46
|
+
"standard": ["fastapi", "uvicorn", "sqlalchemy", "alembic", "pytest"],
|
|
47
|
+
"full": [
|
|
48
|
+
"fastapi",
|
|
49
|
+
"uvicorn",
|
|
50
|
+
"sqlalchemy",
|
|
51
|
+
"alembic",
|
|
52
|
+
"pytest",
|
|
53
|
+
"redis",
|
|
54
|
+
"celery",
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
# Testing Options
|
|
24
59
|
TEST_SERVER_PORT: int = 8000
|
|
25
60
|
TEST_DEFAULT_TERMINAL_WIDTH: int = 80
|
|
@@ -105,3 +140,6 @@ class FastkitConfig:
|
|
|
105
140
|
log_dir = os.path.dirname(cls.LOG_FILE_PATH)
|
|
106
141
|
if not os.path.exists(log_dir):
|
|
107
142
|
os.makedirs(log_dir)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
settings = FastkitConfig()
|