FastAPI-fastkit 0.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 +10 -0
- fastapi_fastkit/__main__.py +8 -0
- fastapi_fastkit/backend.py +112 -0
- fastapi_fastkit/cli.py +432 -0
- fastapi_fastkit/core/__init__.py +0 -0
- fastapi_fastkit/core/exceptions.py +27 -0
- fastapi_fastkit/core/settings.py +107 -0
- fastapi_fastkit/fastapi_project_template/PROJECT_README_TEMPLATE.md +66 -0
- fastapi_fastkit/fastapi_project_template/README.md +20 -0
- fastapi_fastkit/fastapi_project_template/__init__.py +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/.env.test-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/.gitignore-tpl +191 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +17 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/main.py-tpl +21 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +47 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/script/run-server.sh-tpl +2 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/script/run-test.sh-tpl +2 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/setup.cfg-tpl +13 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +35 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/.DS_Store +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/__init__.py-tpl +88 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/settings.py-tpl +96 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/_base.py-tpl +92 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/user.py-tpl +44 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/exceptions.py-tpl +94 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/global_data.py-tpl +33 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/logging.py-tpl +25 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/pagination.py-tpl +71 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_users.json-tpl +17 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/router/__init__.py-tpl +48 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/router/user.py-tpl +126 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/__init__.py-tpl +48 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/user.py-tpl +81 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/templates/index.html-tpl +27 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/utils/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/utils/documents.py-tpl +20 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/test/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/test/conftest.py-tpl +22 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/test/routes/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/test/routes/test_user.py-tpl +112 -0
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/__init__.py-tpl +0 -0
- fastapi_fastkit/py.typed +0 -0
- fastapi_fastkit/utils/__init__.py +0 -0
- fastapi_fastkit/utils/inspector.py +15 -0
- fastapi_fastkit/utils/logging.py +33 -0
- fastapi_fastkit/utils/transducer.py +70 -0
- fastapi_fastkit-0.1.0.dist-info/METADATA +46 -0
- fastapi_fastkit-0.1.0.dist-info/RECORD +56 -0
- fastapi_fastkit-0.1.0.dist-info/WHEEL +4 -0
- fastapi_fastkit-0.1.0.dist-info/entry_points.txt +5 -0
- fastapi_fastkit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# FastAPI-fastkit package main routine
|
|
3
|
+
#
|
|
4
|
+
# @author bnbong
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
from fastapi_fastkit.cli import fastkit_cli
|
|
7
|
+
|
|
8
|
+
fastkit_cli()
|
|
@@ -0,0 +1,112 @@
|
|
|
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 logging import getLogger
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from click.core import Context
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
|
|
17
|
+
from fastapi_fastkit.core.exceptions import TemplateExceptions
|
|
18
|
+
|
|
19
|
+
from . import console
|
|
20
|
+
|
|
21
|
+
logger = getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
REGEX = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_error(message: str, title: str = "Error") -> None:
|
|
27
|
+
"""Print an error message with specified output style."""
|
|
28
|
+
error_text = Text()
|
|
29
|
+
error_text.append("❌ ", style="bold red")
|
|
30
|
+
error_text.append(message)
|
|
31
|
+
console.print(Panel(error_text, border_style="red", title=title))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def print_success(message: str, title: str = "Success") -> None:
|
|
35
|
+
"""Print a success message with specified output style."""
|
|
36
|
+
success_text = Text()
|
|
37
|
+
success_text.append("✨ ", style="bold yellow")
|
|
38
|
+
success_text.append(message, style="bold green")
|
|
39
|
+
console.print(Panel(success_text, border_style="green", title=title))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def print_warning(message: str, title: str = "Warning") -> None:
|
|
43
|
+
"""Print a warning message with specified output style."""
|
|
44
|
+
warning_text = Text()
|
|
45
|
+
warning_text.append("⚠️ ", style="bold yellow")
|
|
46
|
+
warning_text.append(message)
|
|
47
|
+
console.print(Panel(warning_text, border_style="yellow", title=title))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_info_table(
|
|
51
|
+
title: str, data: dict[str, str], show_header: bool = False
|
|
52
|
+
) -> Table:
|
|
53
|
+
"""Create a table for displaying information."""
|
|
54
|
+
table = Table(title=title, show_header=show_header, title_style="bold magenta")
|
|
55
|
+
table.add_column("Field", style="cyan")
|
|
56
|
+
table.add_column("Value", style="green")
|
|
57
|
+
|
|
58
|
+
for key, value in data.items():
|
|
59
|
+
table.add_row(key, value)
|
|
60
|
+
|
|
61
|
+
return table
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def validate_email(ctx: Context, param: Any, value: Any) -> Any:
|
|
65
|
+
"""Validate email format."""
|
|
66
|
+
try:
|
|
67
|
+
if not re.match(REGEX, value):
|
|
68
|
+
raise ValueError(value)
|
|
69
|
+
return value
|
|
70
|
+
except ValueError as e:
|
|
71
|
+
print_error(f"Incorrect email address given: {e}")
|
|
72
|
+
value = click.prompt(param.prompt)
|
|
73
|
+
return validate_email(ctx, param, value)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def inject_project_metadata(
|
|
77
|
+
target_dir: str,
|
|
78
|
+
project_name: str,
|
|
79
|
+
author: str,
|
|
80
|
+
author_email: str,
|
|
81
|
+
description: str,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Inject project metadata."""
|
|
84
|
+
try:
|
|
85
|
+
main_py_path = os.path.join(target_dir, "main.py")
|
|
86
|
+
setup_py_path = os.path.join(target_dir, "setup.py")
|
|
87
|
+
|
|
88
|
+
with open(main_py_path, "r+") as f:
|
|
89
|
+
content = f.read()
|
|
90
|
+
content = content.replace("app_title", f'"{project_name}"')
|
|
91
|
+
content = content.replace("app_description", f'"{description}"')
|
|
92
|
+
f.seek(0)
|
|
93
|
+
f.write(content)
|
|
94
|
+
f.truncate()
|
|
95
|
+
|
|
96
|
+
with open(setup_py_path, "r+") as f:
|
|
97
|
+
content = f.read()
|
|
98
|
+
content = content.replace("<project_name>", project_name, 1)
|
|
99
|
+
content = content.replace("<description>", description, 1)
|
|
100
|
+
content = content.replace("<author>", author, 1)
|
|
101
|
+
content = content.replace("<author_email>", author_email, 1)
|
|
102
|
+
f.seek(0)
|
|
103
|
+
f.write(content)
|
|
104
|
+
f.truncate()
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print_error(f"Error during metadata injection: {e}")
|
|
107
|
+
raise TemplateExceptions("Failed to inject metadata")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# TODO : modify this function
|
|
111
|
+
# def read_template_stack() -> Union[list, None]:
|
|
112
|
+
# pass
|
fastapi_fastkit/cli.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# The Module defines main and core CLI operations for FastAPI-fastkit.
|
|
3
|
+
#
|
|
4
|
+
# @author bnbong bbbong9@gmail.com
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
from typing import Union
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from click.core import BaseCommand, Context
|
|
14
|
+
from rich import print
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
|
|
17
|
+
from fastapi_fastkit.core.exceptions import CLIExceptions
|
|
18
|
+
from fastapi_fastkit.core.settings import FastkitConfig
|
|
19
|
+
from fastapi_fastkit.utils.inspector import delete_project
|
|
20
|
+
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 (
|
|
25
|
+
create_info_table,
|
|
26
|
+
inject_project_metadata,
|
|
27
|
+
print_error,
|
|
28
|
+
print_success,
|
|
29
|
+
print_warning,
|
|
30
|
+
validate_email,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
logger = getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@click.group()
|
|
37
|
+
@click.option("--debug/--no-debug", default=False)
|
|
38
|
+
@click.version_option(__version__, prog_name="fastapi-fastkit")
|
|
39
|
+
@click.pass_context
|
|
40
|
+
def fastkit_cli(ctx: Context, debug: bool) -> Union["BaseCommand", None]:
|
|
41
|
+
"""
|
|
42
|
+
main FastAPI-fastkit CLI operation group
|
|
43
|
+
|
|
44
|
+
:param ctx: context of passing configurations (NOT specify it at CLI)
|
|
45
|
+
:type ctx: <Object click.Context>
|
|
46
|
+
:param debug: parameter from CLI
|
|
47
|
+
:return: None(will be wrapped with click.core.BaseCommand via @click decorator)
|
|
48
|
+
"""
|
|
49
|
+
settings = FastkitConfig()
|
|
50
|
+
|
|
51
|
+
ctx.ensure_object(dict)
|
|
52
|
+
|
|
53
|
+
if debug:
|
|
54
|
+
print_warning("running at debugging mode!!")
|
|
55
|
+
settings.set_debug_mode()
|
|
56
|
+
|
|
57
|
+
ctx.obj["settings"] = settings
|
|
58
|
+
|
|
59
|
+
setup_logging(settings=settings)
|
|
60
|
+
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@fastkit_cli.command()
|
|
65
|
+
@click.pass_context
|
|
66
|
+
def echo(ctx: Context) -> None:
|
|
67
|
+
"""
|
|
68
|
+
About FastAPI-fastkit
|
|
69
|
+
|
|
70
|
+
:param ctx: context of passing configurations (NOT specify it at CLI)
|
|
71
|
+
:type ctx: <Object click.Context>
|
|
72
|
+
:return: None
|
|
73
|
+
"""
|
|
74
|
+
fastkit_info = f"""
|
|
75
|
+
⚡️ FastAPI fastkit - fastest [bold]FastAPI[/bold] initializer. ⚡️
|
|
76
|
+
|
|
77
|
+
Deploy FastAPI app foundation instantly at your local!
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
- Project Maintainer : [link=mailto:bbbong9@gmail.com]bnbong(JunHyeok Lee)[/link]
|
|
81
|
+
- Current Version : {__version__}
|
|
82
|
+
- Github : [link]https://github.com/bnbong/FastAPI-fastkit[/link]
|
|
83
|
+
"""
|
|
84
|
+
settings = ctx.obj["settings"]
|
|
85
|
+
description_panel = Panel(fastkit_info, title="About FastAPI-fastkit")
|
|
86
|
+
click.echo(print(description_panel))
|
|
87
|
+
|
|
88
|
+
if settings.DEBUG_MODE:
|
|
89
|
+
debug_output = f"FASTKIT_PROJECT_ROOT: {settings.FASTKIT_PROJECT_ROOT}\nUSER_WORKSPACE: {settings.USER_WORKSPACE}"
|
|
90
|
+
|
|
91
|
+
click.echo(debug_output)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@fastkit_cli.command()
|
|
95
|
+
def list_templates() -> None:
|
|
96
|
+
"""
|
|
97
|
+
Display the list of available templates.
|
|
98
|
+
"""
|
|
99
|
+
settings = FastkitConfig()
|
|
100
|
+
template_dir = settings.FASTKIT_TEMPLATE_ROOT
|
|
101
|
+
|
|
102
|
+
if not os.path.exists(template_dir):
|
|
103
|
+
print_error("Template directory not found.")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
templates = [
|
|
107
|
+
d
|
|
108
|
+
for d in os.listdir(template_dir)
|
|
109
|
+
if os.path.isdir(os.path.join(template_dir, d)) and d != "__pycache__"
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
if not templates:
|
|
113
|
+
print_warning("No available templates.")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
table = create_info_table(
|
|
117
|
+
"Available Templates", {template: "No description" for template in templates}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
for template in templates:
|
|
121
|
+
template_path = os.path.join(template_dir, template)
|
|
122
|
+
readme_path = os.path.join(template_path, "README.md-tpl")
|
|
123
|
+
|
|
124
|
+
description = "No description"
|
|
125
|
+
if os.path.exists(readme_path):
|
|
126
|
+
with open(readme_path, "r") as f:
|
|
127
|
+
first_line = f.readline().strip()
|
|
128
|
+
if first_line.startswith("# "):
|
|
129
|
+
description = first_line[2:]
|
|
130
|
+
|
|
131
|
+
table.add_row(template, description)
|
|
132
|
+
|
|
133
|
+
console.print(table)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@fastkit_cli.command(context_settings={"ignore_unknown_options": True})
|
|
137
|
+
@click.argument("template", default="fastapi-default")
|
|
138
|
+
@click.option(
|
|
139
|
+
"--project-name",
|
|
140
|
+
prompt="Enter the project name",
|
|
141
|
+
help="The name of the new FastAPI project.",
|
|
142
|
+
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--author", prompt="Enter the author name", help="The name of the project author."
|
|
145
|
+
)
|
|
146
|
+
@click.option(
|
|
147
|
+
"--author-email",
|
|
148
|
+
prompt="Enter the author email",
|
|
149
|
+
help="The email of the project author.",
|
|
150
|
+
type=str,
|
|
151
|
+
callback=validate_email,
|
|
152
|
+
)
|
|
153
|
+
@click.option(
|
|
154
|
+
"--description",
|
|
155
|
+
prompt="Enter the project description",
|
|
156
|
+
help="The description of the new FastAPI project.",
|
|
157
|
+
)
|
|
158
|
+
@click.pass_context
|
|
159
|
+
def startup(
|
|
160
|
+
ctx: Context,
|
|
161
|
+
template: str,
|
|
162
|
+
project_name: str,
|
|
163
|
+
author: str,
|
|
164
|
+
author_email: str,
|
|
165
|
+
description: str,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Create a new FastAPI project from templates and inject metadata.
|
|
169
|
+
|
|
170
|
+
:param ctx: Click context object
|
|
171
|
+
:param template: Template name
|
|
172
|
+
:param project_name: Project name for the new project
|
|
173
|
+
:param author: Author name
|
|
174
|
+
:param author_email: Author email
|
|
175
|
+
:param description: Project description
|
|
176
|
+
:return: None
|
|
177
|
+
"""
|
|
178
|
+
settings = ctx.obj["settings"]
|
|
179
|
+
|
|
180
|
+
template_dir = settings.FASTKIT_TEMPLATE_ROOT
|
|
181
|
+
click.echo(f"Deploying FastAPI project using '{template}' template")
|
|
182
|
+
target_template = os.path.join(template_dir, template)
|
|
183
|
+
print(f"Template path: {target_template}")
|
|
184
|
+
|
|
185
|
+
if not os.path.exists(target_template):
|
|
186
|
+
print_error(f"Template '{template}' does not exist in '{template_dir}'.")
|
|
187
|
+
raise CLIExceptions(
|
|
188
|
+
f"Template '{template}' does not exist in '{template_dir}'."
|
|
189
|
+
)
|
|
190
|
+
table = create_info_table(
|
|
191
|
+
"Project Information",
|
|
192
|
+
{
|
|
193
|
+
"Project Name": project_name,
|
|
194
|
+
"Author": author,
|
|
195
|
+
"Author Email": author_email,
|
|
196
|
+
"Description": description,
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
console.print("\n")
|
|
201
|
+
console.print(table)
|
|
202
|
+
# click.echo("Project Stack: [FastAPI, Uvicorn, SQLAlchemy, Docker (optional)]") # TODO : impl this?
|
|
203
|
+
|
|
204
|
+
confirm = click.confirm(
|
|
205
|
+
"\nDo you want to proceed with project creation?", default=False
|
|
206
|
+
)
|
|
207
|
+
if not confirm:
|
|
208
|
+
print_error("Project creation aborted!")
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
user_local = settings.USER_WORKSPACE
|
|
213
|
+
project_dir = os.path.join(user_local, project_name)
|
|
214
|
+
|
|
215
|
+
click.echo(f"FastAPI template project will deploy at '{user_local}'")
|
|
216
|
+
|
|
217
|
+
copy_and_convert_template(target_template, user_local, project_name)
|
|
218
|
+
|
|
219
|
+
inject_project_metadata(
|
|
220
|
+
project_dir, project_name, author, author_email, description
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
print_success(
|
|
224
|
+
f"FastAPI project '{project_name}' from '{template}' has been created and saved to {user_local}!"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
print_error(f"Error during project creation: {e}")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@fastkit_cli.command(context_settings={"ignore_unknown_options": True})
|
|
232
|
+
@click.option(
|
|
233
|
+
"--project-name",
|
|
234
|
+
prompt="Enter project name",
|
|
235
|
+
help="Name of the new FastAPI project",
|
|
236
|
+
)
|
|
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:
|
|
244
|
+
"""
|
|
245
|
+
Start a new FastAPI project.
|
|
246
|
+
Dependencies will be automatically installed based on the selected stack.
|
|
247
|
+
|
|
248
|
+
:param project_name: Project name
|
|
249
|
+
:param stack: Project stack configuration
|
|
250
|
+
:return: None
|
|
251
|
+
"""
|
|
252
|
+
settings = FastkitConfig()
|
|
253
|
+
project_dir = os.path.join(settings.USER_WORKSPACE, project_name)
|
|
254
|
+
|
|
255
|
+
if os.path.exists(project_dir):
|
|
256
|
+
print_error(f"Error: Project '{project_name}' already exists.")
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
os.makedirs(project_dir)
|
|
261
|
+
|
|
262
|
+
table = create_info_table(
|
|
263
|
+
f"Creating Project: {project_name}", {"Component": "Status"}
|
|
264
|
+
)
|
|
265
|
+
|
|
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
|
+
with open(os.path.join(project_dir, "requirements.txt"), "w") as f:
|
|
282
|
+
for dep in dependencies[stack]:
|
|
283
|
+
f.write(f"{dep}\n")
|
|
284
|
+
table.add_row(dep, "✓")
|
|
285
|
+
|
|
286
|
+
console.print(table)
|
|
287
|
+
|
|
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
|
+
)
|
|
296
|
+
|
|
297
|
+
print_success(f"Project '{project_name}' has been created successfully!")
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
print_error(f"Error during project creation: {e}")
|
|
301
|
+
shutil.rmtree(project_dir, ignore_errors=True)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def is_fastkit_project(project_dir: str) -> bool:
|
|
305
|
+
"""
|
|
306
|
+
Check if the project was created with fastkit.
|
|
307
|
+
Inspects the contents of the setup.py file.
|
|
308
|
+
|
|
309
|
+
:param project_dir: Project directory
|
|
310
|
+
:return: True if the project was created with fastkit, False otherwise
|
|
311
|
+
"""
|
|
312
|
+
setup_py = os.path.join(project_dir, "setup.py")
|
|
313
|
+
if not os.path.exists(setup_py):
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
with open(setup_py, "r") as f:
|
|
318
|
+
content = f.read()
|
|
319
|
+
return "FastAPI-fastkit" in content
|
|
320
|
+
except:
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@fastkit_cli.command()
|
|
325
|
+
@click.argument("project_name")
|
|
326
|
+
@click.pass_context
|
|
327
|
+
def deleteproject(ctx: Context, project_name: str) -> None:
|
|
328
|
+
"""
|
|
329
|
+
Delete a FastAPI project.
|
|
330
|
+
|
|
331
|
+
:param ctx: Click context object
|
|
332
|
+
:param project_name: Project name
|
|
333
|
+
:return: None
|
|
334
|
+
"""
|
|
335
|
+
settings = ctx.obj["settings"]
|
|
336
|
+
user_local = settings.USER_WORKSPACE
|
|
337
|
+
project_dir = os.path.join(user_local, project_name)
|
|
338
|
+
|
|
339
|
+
if not os.path.exists(project_dir):
|
|
340
|
+
print_error(f"Project '{project_name}' does not exist in '{user_local}'.")
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if not is_fastkit_project(project_dir):
|
|
344
|
+
print_error(f"'{project_name}' is not a FastAPI-fastkit project.")
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
confirm = click.confirm(
|
|
348
|
+
f"\nDo you want to delete project '{project_name}' at '{project_dir}'?",
|
|
349
|
+
default=False,
|
|
350
|
+
)
|
|
351
|
+
if not confirm:
|
|
352
|
+
print_error("Project deletion cancelled!")
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
delete_project(project_dir)
|
|
357
|
+
print_success(f"Project '{project_name}' has been deleted successfully!")
|
|
358
|
+
|
|
359
|
+
except Exception as e:
|
|
360
|
+
print_error(f"Error during project deletion: {e}")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@fastkit_cli.command()
|
|
364
|
+
@click.option(
|
|
365
|
+
"--host",
|
|
366
|
+
default="127.0.0.1",
|
|
367
|
+
show_default=True,
|
|
368
|
+
help="Host to bind the server",
|
|
369
|
+
)
|
|
370
|
+
@click.option(
|
|
371
|
+
"--port",
|
|
372
|
+
default=8000,
|
|
373
|
+
show_default=True,
|
|
374
|
+
help="Port to bind the server",
|
|
375
|
+
)
|
|
376
|
+
@click.option(
|
|
377
|
+
"--reload/--no-reload",
|
|
378
|
+
default=True,
|
|
379
|
+
show_default=True,
|
|
380
|
+
help="Enable/disable auto-reload on code changes",
|
|
381
|
+
)
|
|
382
|
+
@click.option(
|
|
383
|
+
"--workers",
|
|
384
|
+
default=1,
|
|
385
|
+
show_default=True,
|
|
386
|
+
help="Number of worker processes",
|
|
387
|
+
)
|
|
388
|
+
@click.pass_context
|
|
389
|
+
def runserver(
|
|
390
|
+
ctx: Context,
|
|
391
|
+
host: str = "127.0.0.1",
|
|
392
|
+
port: int = 8000,
|
|
393
|
+
reload: bool = True,
|
|
394
|
+
workers: int = 1,
|
|
395
|
+
) -> None:
|
|
396
|
+
"""
|
|
397
|
+
Run the FastAPI server for the current project.
|
|
398
|
+
[1.1.0 update TODO] Alternative Point : using FastAPI-fastkit's 'fastapi dev' command
|
|
399
|
+
|
|
400
|
+
:param ctx: Click context object
|
|
401
|
+
:param host: Host address to bind the server to
|
|
402
|
+
:param port: Port number to bind the server to
|
|
403
|
+
:param reload: Enable or disable auto-reload
|
|
404
|
+
:return: None
|
|
405
|
+
"""
|
|
406
|
+
settings = ctx.obj["settings"]
|
|
407
|
+
project_dir = settings.USER_WORKSPACE
|
|
408
|
+
|
|
409
|
+
app_path = os.path.join(project_dir, "main.py")
|
|
410
|
+
if not os.path.exists(app_path):
|
|
411
|
+
print_error(f"Could not find 'main.py' in '{project_dir}'.")
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
command = [
|
|
415
|
+
"uvicorn",
|
|
416
|
+
"main:app",
|
|
417
|
+
"--host",
|
|
418
|
+
host,
|
|
419
|
+
"--port",
|
|
420
|
+
str(port),
|
|
421
|
+
"--workers",
|
|
422
|
+
str(workers),
|
|
423
|
+
]
|
|
424
|
+
|
|
425
|
+
if reload:
|
|
426
|
+
command.append("--reload")
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
print_success(f"Starting FastAPI server at {host}:{port}...")
|
|
430
|
+
subprocess.run(command, check=True)
|
|
431
|
+
except subprocess.CalledProcessError as e:
|
|
432
|
+
print_error(f"Failed to start FastAPI server.\n{e}")
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# The Module defines exceptions that occurs from CLI operations.
|
|
3
|
+
#
|
|
4
|
+
# @author bnbong bbbong9@gmail.com
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
class CLIExceptions(Exception):
|
|
7
|
+
"""
|
|
8
|
+
Exceptions occurs from CLI operations
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TemplateExceptions(Exception):
|
|
15
|
+
"""
|
|
16
|
+
Exceptions occurs from deploying FastAPI templates
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BackendExceptions(Exception):
|
|
23
|
+
"""
|
|
24
|
+
Exceptions occurs from fastkit backend
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
pass
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# The Module defines fastapi-fastkit project's general Env settings.
|
|
3
|
+
#
|
|
4
|
+
# @author bnbong bbbong9@gmail.com
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .exceptions import BackendExceptions
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FastkitConfig:
|
|
13
|
+
# Overridable values
|
|
14
|
+
FASTKIT_PROJECT_ROOT: str = "" # default : None (will be overridden)
|
|
15
|
+
FASTKIT_TEMPLATE_ROOT: str = "" # default : None (will be overridden)
|
|
16
|
+
LOG_FILE_PATH: str = "" # default : None (will be overridden)
|
|
17
|
+
USER_WORKSPACE: str = "" # default : None (will be overridden)
|
|
18
|
+
|
|
19
|
+
# Default Options
|
|
20
|
+
DEBUG_MODE: bool = False
|
|
21
|
+
LOGGING_LEVEL: str = "DEBUG"
|
|
22
|
+
|
|
23
|
+
# Testing Options
|
|
24
|
+
TEST_SERVER_PORT: int = 8000
|
|
25
|
+
TEST_DEFAULT_TERMINAL_WIDTH: int = 80
|
|
26
|
+
TEST_MAX_TERMINAL_WIDTH: int = 1000
|
|
27
|
+
|
|
28
|
+
def set_debug_mode(self, debug_mode: bool = True) -> None:
|
|
29
|
+
self.DEBUG_MODE = debug_mode
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def __get_fastapi_fastkit_root() -> Path:
|
|
33
|
+
"""
|
|
34
|
+
Returns the root directory of the installed FastAPI-fastkit package.
|
|
35
|
+
For development: returns the project root
|
|
36
|
+
For installed package: returns the package installation directory
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
import fastapi_fastkit
|
|
40
|
+
|
|
41
|
+
package_root = Path(fastapi_fastkit.__file__).parent.parent
|
|
42
|
+
if package_root.name == "site-packages":
|
|
43
|
+
return package_root / "fastapi_fastkit"
|
|
44
|
+
return package_root
|
|
45
|
+
except ImportError:
|
|
46
|
+
# Fallback for development environment
|
|
47
|
+
return Path(__file__).parent.parent.parent.parent
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def __get_template_root() -> Path:
|
|
51
|
+
"""
|
|
52
|
+
Returns the template directory of the installed FastAPI-fastkit package.
|
|
53
|
+
For development: returns the project template directory
|
|
54
|
+
For installed package: returns the package template directory
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
import fastapi_fastkit
|
|
58
|
+
|
|
59
|
+
package_root = Path(fastapi_fastkit.__file__).parent
|
|
60
|
+
template_dir = package_root / "fastapi_project_template"
|
|
61
|
+
if template_dir.exists():
|
|
62
|
+
return template_dir
|
|
63
|
+
# If inside site-packages
|
|
64
|
+
if package_root.parent.name == "site-packages":
|
|
65
|
+
return (
|
|
66
|
+
package_root.parent / "fastapi_fastkit" / "fastapi_project_template"
|
|
67
|
+
)
|
|
68
|
+
return package_root.parent / "fastapi_project_template"
|
|
69
|
+
except ImportError:
|
|
70
|
+
# Fallback for development environment
|
|
71
|
+
return Path(__file__).parent.parent / "fastapi_project_template"
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def __init__(cls) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Initialize the configuration by performing important checks and setups.
|
|
77
|
+
Override directories to correct position.
|
|
78
|
+
"""
|
|
79
|
+
cls.FASTKIT_PROJECT_ROOT = str(cls.__get_fastapi_fastkit_root())
|
|
80
|
+
cls.FASTKIT_TEMPLATE_ROOT = str(cls.__get_template_root())
|
|
81
|
+
cls.LOG_FILE_PATH = os.path.join(
|
|
82
|
+
cls.FASTKIT_PROJECT_ROOT, "logs", "fastkit.log"
|
|
83
|
+
)
|
|
84
|
+
cls.USER_WORKSPACE = os.getcwd()
|
|
85
|
+
|
|
86
|
+
# Validate the configurations
|
|
87
|
+
cls._validate()
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def _validate(cls) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Validate the configuration settings to ensure that they are correct.
|
|
93
|
+
Raises an error if validation fails.
|
|
94
|
+
"""
|
|
95
|
+
if not cls.FASTKIT_PROJECT_ROOT or not os.path.isdir(cls.FASTKIT_PROJECT_ROOT):
|
|
96
|
+
raise BackendExceptions(
|
|
97
|
+
"FASTKIT_PROJECT_ROOT is not allocated to valid directory."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if not cls.FASTKIT_TEMPLATE_ROOT or not os.path.isdir(cls.FASTKIT_PROJECT_ROOT):
|
|
101
|
+
raise BackendExceptions(
|
|
102
|
+
"FASTKIT_TEMPLATE_ROOT is not allocated to valid directory."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
log_dir = os.path.dirname(cls.LOG_FILE_PATH)
|
|
106
|
+
if not os.path.exists(log_dir):
|
|
107
|
+
os.makedirs(log_dir)
|