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.
Files changed (78) hide show
  1. fastapi_fastkit/__init__.py +1 -10
  2. fastapi_fastkit/backend/inspector.py +203 -0
  3. fastapi_fastkit/backend/main.py +191 -0
  4. fastapi_fastkit/cli.py +63 -51
  5. fastapi_fastkit/core/settings.py +38 -0
  6. fastapi_fastkit/fastapi_project_template/README.md +55 -4
  7. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.gitignore-tpl +191 -0
  8. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +17 -0
  9. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +47 -0
  10. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.cfg-tpl +13 -0
  11. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +35 -0
  12. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/__init__.py-tpl +88 -0
  13. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/__init__.py-tpl +48 -0
  14. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/__init__.py-tpl +0 -0
  15. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/routes/__init__.py-tpl +0 -0
  16. fastapi_fastkit/fastapi_project_template/{fastapi-default/test → fastapi-async-crud/tests}/routes/test_user.py-tpl +1 -1
  17. fastapi_fastkit/fastapi_project_template/fastapi-customized-response/README.md-tpl +0 -0
  18. fastapi_fastkit/fastapi_project_template/fastapi-default/.env-tpl +1 -0
  19. fastapi_fastkit/fastapi_project_template/fastapi-default/.gitignore-tpl +28 -189
  20. fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +15 -7
  21. fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +25 -38
  22. fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/format.sh-tpl +5 -0
  23. fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/lint.sh-tpl +5 -0
  24. fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/test.sh-tpl +6 -0
  25. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.cfg-tpl +5 -5
  26. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +9 -22
  27. fastapi_fastkit/fastapi_project_template/fastapi-default/src/__init__.py-tpl +0 -88
  28. fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/__init__.py-tpl +0 -0
  29. fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/api.py-tpl +9 -0
  30. fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/routes/__init__.py-tpl +0 -0
  31. fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/routes/items.py-tpl +57 -0
  32. fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/config.py-tpl +64 -0
  33. fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/items.py-tpl +18 -0
  34. fastapi_fastkit/fastapi_project_template/fastapi-default/src/main.py-tpl +26 -0
  35. fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_items.json-tpl +8 -0
  36. fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/__init__.py-tpl +0 -48
  37. fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/items.py-tpl +22 -0
  38. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/__init__.py-tpl +0 -0
  39. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +15 -0
  40. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +62 -0
  41. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +0 -0
  42. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/README.md-tpl +0 -0
  43. fastapi_fastkit/utils/logging.py +4 -1
  44. fastapi_fastkit/{backend.py → utils/main.py} +22 -51
  45. fastapi_fastkit-0.1.1.dist-info/METADATA +173 -0
  46. fastapi_fastkit-0.1.1.dist-info/RECORD +84 -0
  47. {fastapi_fastkit-0.1.0.dist-info → fastapi_fastkit-0.1.1.dist-info}/licenses/LICENSE +21 -21
  48. fastapi_fastkit/fastapi_project_template/fastapi-default/src/.DS_Store +0 -0
  49. fastapi_fastkit/utils/inspector.py +0 -15
  50. fastapi_fastkit-0.1.0.dist-info/METADATA +0 -46
  51. fastapi_fastkit-0.1.0.dist-info/RECORD +0 -56
  52. /fastapi_fastkit/{fastapi_project_template/fastapi-default/src/helper/__init__.py-tpl → backend/__init__.py} +0 -0
  53. /fastapi_fastkit/{utils → backend}/transducer.py +0 -0
  54. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/.env.test-tpl +0 -0
  55. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/main.py-tpl +0 -0
  56. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/script/run-server.sh-tpl +0 -0
  57. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/script/run-test.sh-tpl +0 -0
  58. /fastapi_fastkit/fastapi_project_template/{fastapi-default/src/utils → fastapi-async-crud/src/core}/__init__.py-tpl +0 -0
  59. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/core/settings.py-tpl +0 -0
  60. /fastapi_fastkit/fastapi_project_template/{fastapi-default/test → fastapi-async-crud/src/crud}/__init__.py-tpl +0 -0
  61. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/crud/_base.py-tpl +0 -0
  62. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/crud/user.py-tpl +0 -0
  63. /fastapi_fastkit/fastapi_project_template/{fastapi-default/test/routes → fastapi-async-crud/src/helper}/__init__.py-tpl +0 -0
  64. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/exceptions.py-tpl +0 -0
  65. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/global_data.py-tpl +0 -0
  66. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/logging.py-tpl +0 -0
  67. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/helper/pagination.py-tpl +0 -0
  68. /fastapi_fastkit/fastapi_project_template/{fastapi-dockerized → fastapi-async-crud/src/mocks}/__init__.py-tpl +0 -0
  69. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/mocks/mock_users.json-tpl +0 -0
  70. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/router/__init__.py-tpl +0 -0
  71. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/router/user.py-tpl +0 -0
  72. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/schemas/user.py-tpl +0 -0
  73. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/templates/index.html-tpl +0 -0
  74. /fastapi_fastkit/fastapi_project_template/{fastapi-psql-orm → fastapi-async-crud/src/utils}/__init__.py-tpl +0 -0
  75. /fastapi_fastkit/fastapi_project_template/{fastapi-default → fastapi-async-crud}/src/utils/documents.py-tpl +0 -0
  76. /fastapi_fastkit/fastapi_project_template/{fastapi-default/test → fastapi-async-crud/tests}/conftest.py-tpl +0 -0
  77. {fastapi_fastkit-0.1.0.dist-info → fastapi_fastkit-0.1.1.dist-info}/WHEEL +0 -0
  78. {fastapi_fastkit-0.1.0.dist-info → fastapi_fastkit-0.1.1.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1 @@
1
- __version__ = "0.0.1"
2
-
3
- import os
4
-
5
- from rich.console import Console
6
-
7
- if "PYTEST_CURRENT_TEST" in os.environ:
8
- console = Console(no_color=True)
9
- else:
10
- console = Console()
1
+ __version__ = 'v0.1.1'
@@ -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.transducer import copy_and_convert_template
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
- logger = getLogger(__name__)
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
- def list_templates() -> None:
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 = FastkitConfig()
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
- # click.echo("Project Stack: [FastAPI, Uvicorn, SQLAlchemy, Docker (optional)]") # TODO : impl this?
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.option(
238
- "--stack",
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 new FastAPI project.
246
- Dependencies will be automatically installed based on the selected stack.
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 = FastkitConfig()
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": "Status"}
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 dependencies[stack]:
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
- with console.status("[bold green]Setting up project environment..."):
289
- console.print("[yellow]Creating virtual environment...[/yellow]")
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
- delete_project(project_dir)
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
- app_path = os.path.join(project_dir, "main.py")
410
- if not os.path.exists(app_path):
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
- "main:app",
428
+ app_module,
417
429
  "--host",
418
430
  host,
419
431
  "--port",
@@ -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()