FastAPI-fastkit 1.4.1__py3-none-any.whl → 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_fastkit/__init__.py +1 -1
- fastapi_fastkit/backend/main.py +35 -40
- fastapi_fastkit/backend/route_generators.py +163 -0
- fastapi_fastkit/backend/route_wiring.py +117 -0
- fastapi_fastkit/cli.py +13 -2
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/pyproject.toml-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/requirements.txt-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl +16 -4
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +2 -2
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.cfg-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/modules/domain/__init__.py-tpl +10 -0
- fastapi_fastkit/fastapi_project_template/modules/domain/models.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/modules/domain/repository.py-tpl +50 -0
- fastapi_fastkit/fastapi_project_template/modules/domain/router.py-tpl +66 -0
- fastapi_fastkit/fastapi_project_template/modules/domain/schemas.py-tpl +16 -0
- fastapi_fastkit/fastapi_project_template/modules/domain/service.py-tpl +47 -0
- {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/METADATA +2 -1
- {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/RECORD +24 -16
- {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/WHEEL +1 -1
- {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/entry_points.txt +0 -0
- {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/licenses/LICENSE +0 -0
fastapi_fastkit/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = 'v1.
|
|
1
|
+
__version__ = 'v1.5.0'
|
fastapi_fastkit/backend/main.py
CHANGED
|
@@ -18,6 +18,7 @@ from fastapi_fastkit.backend.package_managers import PackageManagerFactory
|
|
|
18
18
|
from fastapi_fastkit.backend.project_builder.preset_layout import (
|
|
19
19
|
app_module_from_main_path,
|
|
20
20
|
)
|
|
21
|
+
from fastapi_fastkit.backend.route_wiring import register_router, router_alias
|
|
21
22
|
from fastapi_fastkit.backend.transducer import (
|
|
22
23
|
copy_and_convert_template,
|
|
23
24
|
copy_and_convert_template_file,
|
|
@@ -1464,6 +1465,16 @@ def _handle_api_router_file(
|
|
|
1464
1465
|
if os.path.exists(api_source):
|
|
1465
1466
|
copy_and_convert_template_file(api_source, api_router_file)
|
|
1466
1467
|
|
|
1468
|
+
# The __init__ template may be empty; initialize the API router.
|
|
1469
|
+
if os.path.exists(api_router_file):
|
|
1470
|
+
with open(api_router_file, "r", encoding="utf-8") as f:
|
|
1471
|
+
content = f.read()
|
|
1472
|
+
if not content.strip():
|
|
1473
|
+
with open(api_router_file, "w", encoding="utf-8") as f:
|
|
1474
|
+
f.write(
|
|
1475
|
+
"from fastapi import APIRouter\n\napi_router = APIRouter()\n"
|
|
1476
|
+
)
|
|
1477
|
+
|
|
1467
1478
|
# Update API router to include new route
|
|
1468
1479
|
if os.path.exists(api_router_file):
|
|
1469
1480
|
_update_api_router(api_router_file, route_name)
|
|
@@ -1484,23 +1495,22 @@ def _update_api_router(api_router_file: str, route_name: str) -> None:
|
|
|
1484
1495
|
with open(api_router_file, "r", encoding="utf-8") as f:
|
|
1485
1496
|
content = f.read()
|
|
1486
1497
|
|
|
1487
|
-
|
|
1498
|
+
alias = router_alias(api_router_file, "routes", route_name, route_name)
|
|
1499
|
+
suffix = f" as {alias}" if alias != route_name else ""
|
|
1500
|
+
route_import = f"from .routes import {route_name}{suffix}"
|
|
1488
1501
|
route_include = (
|
|
1489
|
-
f"api_router.include_router({
|
|
1502
|
+
f"api_router.include_router({alias}.router, "
|
|
1490
1503
|
f'prefix="/{route_name}", tags=["{route_name}"])'
|
|
1491
1504
|
)
|
|
1492
1505
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
with open(api_router_file, "w", encoding="utf-8") as f:
|
|
1503
|
-
f.write(content)
|
|
1506
|
+
register_router(
|
|
1507
|
+
{
|
|
1508
|
+
"api_dir": os.path.dirname(api_router_file),
|
|
1509
|
+
"api_router_file": api_router_file,
|
|
1510
|
+
},
|
|
1511
|
+
route_import,
|
|
1512
|
+
route_include,
|
|
1513
|
+
)
|
|
1504
1514
|
|
|
1505
1515
|
debug_log(f"Updated API router to include {route_name}", "info")
|
|
1506
1516
|
|
|
@@ -1594,7 +1604,9 @@ def _update_main_app(
|
|
|
1594
1604
|
print_warning(f"Failed to update main.py: {e}")
|
|
1595
1605
|
|
|
1596
1606
|
|
|
1597
|
-
def add_new_route(
|
|
1607
|
+
def add_new_route(
|
|
1608
|
+
project_dir: str, route_name: str, layout: Optional[str] = None
|
|
1609
|
+
) -> None:
|
|
1598
1610
|
"""
|
|
1599
1611
|
Add a new API route to an existing FastAPI project.
|
|
1600
1612
|
|
|
@@ -1604,37 +1616,20 @@ def add_new_route(project_dir: str, route_name: str) -> None:
|
|
|
1604
1616
|
|
|
1605
1617
|
:param project_dir: Path to the project directory
|
|
1606
1618
|
:param route_name: Name of the new route to add
|
|
1619
|
+
:param layout: Explicit route layout, or None to select from preset metadata
|
|
1607
1620
|
:raises BackendExceptions: If route addition fails
|
|
1608
1621
|
"""
|
|
1609
1622
|
try:
|
|
1610
|
-
#
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
# Ensure project structure exists
|
|
1616
|
-
target_dirs = _ensure_project_structure(src_dir)
|
|
1617
|
-
|
|
1618
|
-
# Create route files
|
|
1619
|
-
_create_route_files(
|
|
1620
|
-
modules_dir, target_dirs, route_name, layout["package_module"]
|
|
1623
|
+
# Import here to avoid a circular import with route_generators.
|
|
1624
|
+
from fastapi_fastkit.backend.route_generators import (
|
|
1625
|
+
get_route_generator,
|
|
1626
|
+
resolve_route_layout,
|
|
1621
1627
|
)
|
|
1622
1628
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
# Process init files
|
|
1629
|
-
module_types = ["api/routes", "crud", "schemas"]
|
|
1630
|
-
_process_init_files(modules_dir, target_dirs, module_types)
|
|
1631
|
-
|
|
1632
|
-
# Update main application
|
|
1633
|
-
_update_main_app(
|
|
1634
|
-
src_dir,
|
|
1635
|
-
route_name,
|
|
1636
|
-
router_module=layout["api_router_module"],
|
|
1637
|
-
main_py_path=layout["main"],
|
|
1629
|
+
route_layout = resolve_route_layout(project_dir, layout)
|
|
1630
|
+
project_layout = resolve_project_layout(project_dir)
|
|
1631
|
+
get_route_generator(route_layout).add_new_route(
|
|
1632
|
+
project_dir, route_name, project_layout
|
|
1638
1633
|
)
|
|
1639
1634
|
|
|
1640
1635
|
debug_log(f"Successfully added new route: {route_name}", "info")
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# Route generators for fastkit addroute.
|
|
3
|
+
# --------------------------------------------------------------------------
|
|
4
|
+
import keyword
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Optional, Union
|
|
8
|
+
|
|
9
|
+
from fastapi_fastkit.backend import main as backend
|
|
10
|
+
from fastapi_fastkit.backend.project_builder.preset_layout import PresetLayoutStrategist
|
|
11
|
+
from fastapi_fastkit.backend.route_wiring import register_router, router_alias
|
|
12
|
+
from fastapi_fastkit.backend.transducer import copy_and_convert_template_file
|
|
13
|
+
from fastapi_fastkit.core.exceptions import BackendExceptions
|
|
14
|
+
from fastapi_fastkit.core.settings import settings
|
|
15
|
+
from fastapi_fastkit.utils.main import print_warning, read_fastkit_metadata
|
|
16
|
+
|
|
17
|
+
ROUTE_LAYOUTS = ("classic-layer", "domain")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_route_layout(project_dir: str, layout: Optional[str] = None) -> str:
|
|
21
|
+
"""Resolve the route layout from the option or project metadata."""
|
|
22
|
+
if layout is not None:
|
|
23
|
+
if layout not in ROUTE_LAYOUTS:
|
|
24
|
+
raise BackendExceptions(f"Unknown route layout: {layout!r}")
|
|
25
|
+
return layout
|
|
26
|
+
|
|
27
|
+
metadata = read_fastkit_metadata(project_dir)
|
|
28
|
+
preset = metadata.get("preset")
|
|
29
|
+
if not preset:
|
|
30
|
+
template = metadata.get("template")
|
|
31
|
+
domain_template = PresetLayoutStrategist("domain-starter").base_template
|
|
32
|
+
if template == domain_template:
|
|
33
|
+
return "domain"
|
|
34
|
+
if preset == "domain-starter":
|
|
35
|
+
return "domain"
|
|
36
|
+
if preset and preset not in ("classic-layered", "minimal", "single-module"):
|
|
37
|
+
print_warning(f"Unknown preset {preset!r}; using classic-layer route layout.")
|
|
38
|
+
return "classic-layer"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _validate_route_name(route_name: str) -> None:
|
|
42
|
+
"""Validate the route module name."""
|
|
43
|
+
if not route_name.isidentifier() or keyword.iskeyword(route_name):
|
|
44
|
+
raise BackendExceptions(f"Route name {route_name!r} is not a Python identifier")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ClassicLayeredRouteGenerator:
|
|
48
|
+
"""Generate api/routes, crud, and schemas modules."""
|
|
49
|
+
|
|
50
|
+
def add_new_route(
|
|
51
|
+
self, project_dir: str, route_name: str, project_layout: Dict[str, str]
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Create route files and register the router."""
|
|
54
|
+
src_dir = project_layout["package_dir"]
|
|
55
|
+
_validate_route_name(route_name)
|
|
56
|
+
modules_dir = os.path.join(settings.FASTKIT_TEMPLATE_ROOT, "modules")
|
|
57
|
+
target_dirs = backend._ensure_project_structure(src_dir)
|
|
58
|
+
backend._create_route_files(
|
|
59
|
+
modules_dir, target_dirs, route_name, project_layout["package_module"]
|
|
60
|
+
)
|
|
61
|
+
backend._handle_api_router_file(
|
|
62
|
+
target_dirs, modules_dir, route_name, project_layout["api_router_file"]
|
|
63
|
+
)
|
|
64
|
+
backend._process_init_files(
|
|
65
|
+
modules_dir, target_dirs, ["api/routes", "crud", "schemas"]
|
|
66
|
+
)
|
|
67
|
+
backend._update_main_app(
|
|
68
|
+
src_dir,
|
|
69
|
+
route_name,
|
|
70
|
+
router_module=project_layout["api_router_module"],
|
|
71
|
+
main_py_path=project_layout["main"],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class DomainRouteGenerator:
|
|
76
|
+
"""Generate a domain module."""
|
|
77
|
+
|
|
78
|
+
def add_new_route(
|
|
79
|
+
self, project_dir: str, route_name: str, project_layout: Dict[str, str]
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Create domain files and register the router."""
|
|
82
|
+
package = Path(project_layout["package_dir"])
|
|
83
|
+
_validate_route_name(route_name)
|
|
84
|
+
if not package.is_dir():
|
|
85
|
+
raise BackendExceptions(f"Source directory not found at {package}")
|
|
86
|
+
|
|
87
|
+
domain_module = _create_domain_files(package, route_name, project_layout)
|
|
88
|
+
alias = router_alias(
|
|
89
|
+
project_layout["api_router_file"],
|
|
90
|
+
domain_module,
|
|
91
|
+
"router",
|
|
92
|
+
f"{route_name}_router",
|
|
93
|
+
)
|
|
94
|
+
register_router(
|
|
95
|
+
project_layout,
|
|
96
|
+
f"from {domain_module} import router as {alias}",
|
|
97
|
+
f'api_router.include_router({alias}.router, prefix="/{route_name}", tags=["{route_name}"])',
|
|
98
|
+
)
|
|
99
|
+
backend._update_main_app(
|
|
100
|
+
str(package),
|
|
101
|
+
route_name,
|
|
102
|
+
router_module=project_layout["api_router_module"],
|
|
103
|
+
main_py_path=project_layout["main"],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _create_domain_files(
|
|
108
|
+
package: Path, route_name: str, project_layout: Dict[str, str]
|
|
109
|
+
) -> str:
|
|
110
|
+
templates = Path(settings.FASTKIT_TEMPLATE_ROOT) / "modules" / "domain"
|
|
111
|
+
filenames = (
|
|
112
|
+
"__init__.py",
|
|
113
|
+
"models.py",
|
|
114
|
+
"schemas.py",
|
|
115
|
+
"repository.py",
|
|
116
|
+
"service.py",
|
|
117
|
+
"router.py",
|
|
118
|
+
)
|
|
119
|
+
for filename in filenames:
|
|
120
|
+
if not (templates / f"{filename}-tpl").is_file():
|
|
121
|
+
raise BackendExceptions(f"Missing domain template: {filename}-tpl")
|
|
122
|
+
|
|
123
|
+
domain_dir = package / "domains" / route_name
|
|
124
|
+
domain_dir.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
domains_init = domain_dir.parent / "__init__.py"
|
|
126
|
+
if not domains_init.exists():
|
|
127
|
+
domains_init.write_text("", encoding="utf-8")
|
|
128
|
+
prefix = project_layout["package_module"]
|
|
129
|
+
domain_module = (
|
|
130
|
+
f"{prefix}.domains.{route_name}" if prefix else f"domains.{route_name}"
|
|
131
|
+
)
|
|
132
|
+
entity_class = (
|
|
133
|
+
"".join(part[:1].upper() + part[1:] for part in route_name.split("_")) or "_"
|
|
134
|
+
)
|
|
135
|
+
if keyword.iskeyword(entity_class):
|
|
136
|
+
entity_class += "Entity"
|
|
137
|
+
replacements = {
|
|
138
|
+
"<new_route>": route_name,
|
|
139
|
+
"<domain_module>": domain_module,
|
|
140
|
+
"<entity_class>": entity_class,
|
|
141
|
+
}
|
|
142
|
+
for filename in filenames:
|
|
143
|
+
target = domain_dir / filename
|
|
144
|
+
if target.exists():
|
|
145
|
+
print_warning(f"File {target} already exists, skipping...")
|
|
146
|
+
continue
|
|
147
|
+
if not copy_and_convert_template_file(
|
|
148
|
+
str(templates / f"{filename}-tpl"), str(target), replacements
|
|
149
|
+
):
|
|
150
|
+
raise BackendExceptions(f"Failed to create domain file: {target}")
|
|
151
|
+
|
|
152
|
+
return domain_module
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_route_generator(
|
|
156
|
+
route_layout: str,
|
|
157
|
+
) -> Union[ClassicLayeredRouteGenerator, DomainRouteGenerator]:
|
|
158
|
+
"""Return the generator for the selected layout."""
|
|
159
|
+
if route_layout == "classic-layer":
|
|
160
|
+
return ClassicLayeredRouteGenerator()
|
|
161
|
+
if route_layout == "domain":
|
|
162
|
+
return DomainRouteGenerator()
|
|
163
|
+
raise BackendExceptions(f"Unknown route layout: {route_layout!r}")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Router registration helpers."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from fastapi_fastkit.utils.main import print_info
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _parse_module(content: str) -> ast.Module:
|
|
11
|
+
try:
|
|
12
|
+
return ast.parse(content)
|
|
13
|
+
except SyntaxError:
|
|
14
|
+
statements: list[ast.stmt] = []
|
|
15
|
+
for line in content.splitlines():
|
|
16
|
+
try:
|
|
17
|
+
statements.extend(ast.parse(line.strip()).body)
|
|
18
|
+
except SyntaxError:
|
|
19
|
+
continue
|
|
20
|
+
return ast.Module(body=statements, type_ignores=[])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _router_argument(call: ast.Call) -> ast.expr | None:
|
|
24
|
+
if call.args:
|
|
25
|
+
return call.args[0]
|
|
26
|
+
return next((item.value for item in call.keywords if item.arg == "router"), None)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_router(
|
|
30
|
+
project_layout: Dict[str, str], import_line: str, statement: str
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Register a router in the API router module."""
|
|
33
|
+
|
|
34
|
+
from fastapi_fastkit.backend.main import insert_import_line, insert_statement_line
|
|
35
|
+
|
|
36
|
+
api_dir = Path(project_layout["api_dir"])
|
|
37
|
+
api_dir.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
init = api_dir / "__init__.py"
|
|
39
|
+
if not init.exists():
|
|
40
|
+
init.write_text("", encoding="utf-8")
|
|
41
|
+
target = Path(project_layout["api_router_file"])
|
|
42
|
+
content = target.read_text(encoding="utf-8") if target.exists() else ""
|
|
43
|
+
if not content.strip():
|
|
44
|
+
content = "from fastapi import APIRouter\n\napi_router = APIRouter()\n"
|
|
45
|
+
registration = ast.parse(statement).body[0]
|
|
46
|
+
existing = None
|
|
47
|
+
if isinstance(registration, ast.Expr) and isinstance(registration.value, ast.Call):
|
|
48
|
+
expected = registration.value
|
|
49
|
+
expected_router = _router_argument(expected)
|
|
50
|
+
existing = next(
|
|
51
|
+
(
|
|
52
|
+
node
|
|
53
|
+
for node in ast.walk(_parse_module(content))
|
|
54
|
+
if isinstance(node, ast.Call)
|
|
55
|
+
and ast.dump(node.func) == ast.dump(expected.func)
|
|
56
|
+
and (argument := _router_argument(node)) is not None
|
|
57
|
+
and expected_router is not None
|
|
58
|
+
and ast.dump(argument) == ast.dump(expected_router)
|
|
59
|
+
),
|
|
60
|
+
None,
|
|
61
|
+
)
|
|
62
|
+
if existing is not None:
|
|
63
|
+
current_options = {
|
|
64
|
+
item.arg: ast.dump(item.value)
|
|
65
|
+
for item in existing.keywords
|
|
66
|
+
if item.arg != "router"
|
|
67
|
+
}
|
|
68
|
+
expected_options = {
|
|
69
|
+
item.arg: ast.dump(item.value)
|
|
70
|
+
for item in expected.keywords
|
|
71
|
+
if item.arg != "router"
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
current_options != expected_options
|
|
75
|
+
or existing.args[1:] != expected.args[1:]
|
|
76
|
+
):
|
|
77
|
+
print_info(
|
|
78
|
+
f"Keeping existing router registration in {target} with its current prefix and options."
|
|
79
|
+
)
|
|
80
|
+
updated = insert_import_line(content, import_line)
|
|
81
|
+
if existing is None:
|
|
82
|
+
updated = insert_statement_line(updated, statement)
|
|
83
|
+
if not target.exists() or updated != content:
|
|
84
|
+
target.write_text(updated, encoding="utf-8")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def router_alias(file: str, module: str, name: str, preferred: str) -> str:
|
|
88
|
+
"""Reuse the router import alias or choose an unused name."""
|
|
89
|
+
|
|
90
|
+
target = Path(file)
|
|
91
|
+
if not target.exists():
|
|
92
|
+
return preferred
|
|
93
|
+
tree = _parse_module(target.read_text(encoding="utf-8"))
|
|
94
|
+
occupied: set[str] = set()
|
|
95
|
+
for node in tree.body:
|
|
96
|
+
if isinstance(node, ast.ImportFrom):
|
|
97
|
+
for imported in node.names:
|
|
98
|
+
if node.module == module and imported.name == name:
|
|
99
|
+
return imported.asname or name
|
|
100
|
+
occupied.add(imported.asname or imported.name)
|
|
101
|
+
elif isinstance(node, ast.Import):
|
|
102
|
+
occupied.update(
|
|
103
|
+
item.asname or item.name.split(".")[0] for item in node.names
|
|
104
|
+
)
|
|
105
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
106
|
+
occupied.add(node.name)
|
|
107
|
+
elif isinstance(node, (ast.Assign, ast.AnnAssign)):
|
|
108
|
+
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
|
|
109
|
+
occupied.update(
|
|
110
|
+
target.id for target in targets if isinstance(target, ast.Name)
|
|
111
|
+
)
|
|
112
|
+
alias = preferred
|
|
113
|
+
counter = 2
|
|
114
|
+
while alias in occupied:
|
|
115
|
+
alias = f"{preferred}_{counter}"
|
|
116
|
+
counter += 1
|
|
117
|
+
return alias
|
fastapi_fastkit/cli.py
CHANGED
|
@@ -37,6 +37,7 @@ from fastapi_fastkit.backend.project_builder.config_schema import (
|
|
|
37
37
|
ConfigSchemaError,
|
|
38
38
|
normalize_project_config,
|
|
39
39
|
)
|
|
40
|
+
from fastapi_fastkit.backend.route_generators import resolve_route_layout
|
|
40
41
|
from fastapi_fastkit.backend.scaffolder import (
|
|
41
42
|
ProjectScaffolder,
|
|
42
43
|
ScaffoldOptions,
|
|
@@ -829,8 +830,16 @@ def _init_from_config(
|
|
|
829
830
|
@fastkit_cli.command()
|
|
830
831
|
@click.argument("route_name")
|
|
831
832
|
@click.argument("project_dir", default=".")
|
|
833
|
+
@click.option(
|
|
834
|
+
"--layout",
|
|
835
|
+
type=click.Choice(["classic-layer", "domain"]),
|
|
836
|
+
default=None,
|
|
837
|
+
help="Route layout. Defaults to domain for domain-starter, otherwise classic-layer.",
|
|
838
|
+
)
|
|
832
839
|
@click.pass_context
|
|
833
|
-
def addroute(
|
|
840
|
+
def addroute(
|
|
841
|
+
ctx: Context, route_name: str, project_dir: str, layout: Optional[str]
|
|
842
|
+
) -> None:
|
|
834
843
|
"""
|
|
835
844
|
Add a new route to the FastAPI project.
|
|
836
845
|
|
|
@@ -879,12 +888,14 @@ def addroute(ctx: Context, route_name: str, project_dir: str) -> None:
|
|
|
879
888
|
return
|
|
880
889
|
|
|
881
890
|
try:
|
|
891
|
+
route_layout = resolve_route_layout(actual_project_dir, layout)
|
|
882
892
|
# Show information about the operation
|
|
883
893
|
table = create_info_table(
|
|
884
894
|
"Adding New Route",
|
|
885
895
|
{
|
|
886
896
|
"Project": project_name,
|
|
887
897
|
"Route Name": route_name,
|
|
898
|
+
"Layout": route_layout,
|
|
888
899
|
"Target Directory": actual_project_dir,
|
|
889
900
|
},
|
|
890
901
|
)
|
|
@@ -903,7 +914,7 @@ def addroute(ctx: Context, route_name: str, project_dir: str) -> None:
|
|
|
903
914
|
return
|
|
904
915
|
|
|
905
916
|
# Add the new route
|
|
906
|
-
add_new_route(actual_project_dir, route_name)
|
|
917
|
+
add_new_route(actual_project_dir, route_name, layout=route_layout)
|
|
907
918
|
|
|
908
919
|
if project_dir == ".":
|
|
909
920
|
print_success(
|
|
@@ -11,5 +11,5 @@ from src.app.domains.items import router as items_router
|
|
|
11
11
|
|
|
12
12
|
api_router = APIRouter()
|
|
13
13
|
api_router.include_router(health.router)
|
|
14
|
-
api_router.include_router(items_router.router)
|
|
14
|
+
api_router.include_router(items_router.router, prefix="/items", tags=["items"])
|
|
15
15
|
# fastkit:routes
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl
CHANGED
|
@@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, Response, status
|
|
|
7
7
|
from src.app.domains.items.schemas import ItemCreate, ItemRead
|
|
8
8
|
from src.app.domains.items.service import ItemNotFoundError, ItemService
|
|
9
9
|
|
|
10
|
-
router = APIRouter(
|
|
10
|
+
router = APIRouter()
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_item_service() -> ItemService:
|
|
@@ -2,19 +2,31 @@
|
|
|
2
2
|
# pytest fixtures for <project_name>.
|
|
3
3
|
# --------------------------------------------------------------------------
|
|
4
4
|
from collections.abc import Generator
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from pkgutil import iter_modules
|
|
5
7
|
|
|
6
8
|
import pytest
|
|
7
9
|
from fastapi.testclient import TestClient
|
|
10
|
+
from src.app import domains
|
|
8
11
|
from src.app.domains.items.repository import ItemRepository
|
|
9
12
|
from src.app.main import app
|
|
10
13
|
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
def reset_items_store() -> Generator[None, None, None]:
|
|
14
|
-
"""Each test starts with an empty items store."""
|
|
15
|
+
def reset_stores() -> None:
|
|
15
16
|
ItemRepository().reset()
|
|
17
|
+
for module in iter_modules(domains.__path__, prefix=f"{domains.__name__}."):
|
|
18
|
+
if module.ispkg:
|
|
19
|
+
domain = import_module(module.name)
|
|
20
|
+
reset = getattr(getattr(domain, "service", None), "reset_store", None)
|
|
21
|
+
if reset is not None:
|
|
22
|
+
reset()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(autouse=True)
|
|
26
|
+
def reset_domain_stores() -> Generator[None, None, None]:
|
|
27
|
+
reset_stores()
|
|
16
28
|
yield
|
|
17
|
-
|
|
29
|
+
reset_stores()
|
|
18
30
|
|
|
19
31
|
|
|
20
32
|
@pytest.fixture
|
|
@@ -40,7 +40,7 @@ pydantic==2.13.5
|
|
|
40
40
|
pydantic-core==2.46.5
|
|
41
41
|
pydantic-settings==2.15.0
|
|
42
42
|
pygments==2.21.0
|
|
43
|
-
PyJWT==2.
|
|
43
|
+
PyJWT==2.15.1
|
|
44
44
|
pytest==9.1.1
|
|
45
45
|
pytest-cov==7.1.0
|
|
46
46
|
python-dotenv==1.2.3
|
|
@@ -59,7 +59,7 @@ tomli==2.4.1
|
|
|
59
59
|
typer==0.27.2
|
|
60
60
|
typing-extensions==4.16.0
|
|
61
61
|
typing-inspection==0.4.4
|
|
62
|
-
urllib3==2.
|
|
62
|
+
urllib3==2.8.0
|
|
63
63
|
uvicorn==0.52.4
|
|
64
64
|
uvloop==0.22.1
|
|
65
65
|
watchfiles==1.2.0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# <new_route> domain.
|
|
3
|
+
# --------------------------------------------------------------------------
|
|
4
|
+
from <domain_module> import ( # noqa: F401
|
|
5
|
+
models,
|
|
6
|
+
repository,
|
|
7
|
+
router,
|
|
8
|
+
schemas,
|
|
9
|
+
service,
|
|
10
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# <new_route> domain models.
|
|
3
|
+
# --------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class <entity_class>:
|
|
10
|
+
id: int
|
|
11
|
+
name: str
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# <new_route> repository.
|
|
3
|
+
#
|
|
4
|
+
# Data is stored in memory, lost on restart, and not shared between workers.
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
from threading import Lock
|
|
8
|
+
|
|
9
|
+
from <domain_module>.models import <entity_class>
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class <entity_class>Repository:
|
|
13
|
+
"""In-memory storage for <entity_class>."""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self._data: dict[int, <entity_class>] = {}
|
|
17
|
+
self._next_id = 1
|
|
18
|
+
self._lock = Lock()
|
|
19
|
+
|
|
20
|
+
def list_all(self) -> list[<entity_class>]:
|
|
21
|
+
with self._lock:
|
|
22
|
+
return list(self._data.values())
|
|
23
|
+
|
|
24
|
+
def get(self, entity_id: int) -> <entity_class> | None:
|
|
25
|
+
with self._lock:
|
|
26
|
+
return self._data.get(entity_id)
|
|
27
|
+
|
|
28
|
+
def add(self, name: str) -> <entity_class>:
|
|
29
|
+
with self._lock:
|
|
30
|
+
entity = <entity_class>(id=self._next_id, name=name)
|
|
31
|
+
self._data[entity.id] = entity
|
|
32
|
+
self._next_id += 1
|
|
33
|
+
return entity
|
|
34
|
+
|
|
35
|
+
def replace(self, entity_id: int, name: str) -> <entity_class> | None:
|
|
36
|
+
with self._lock:
|
|
37
|
+
if entity_id not in self._data:
|
|
38
|
+
return None
|
|
39
|
+
entity = <entity_class>(id=entity_id, name=name)
|
|
40
|
+
self._data[entity_id] = entity
|
|
41
|
+
return entity
|
|
42
|
+
|
|
43
|
+
def delete(self, entity_id: int) -> bool:
|
|
44
|
+
with self._lock:
|
|
45
|
+
return self._data.pop(entity_id, None) is not None
|
|
46
|
+
|
|
47
|
+
def reset(self) -> None:
|
|
48
|
+
with self._lock:
|
|
49
|
+
self._data.clear()
|
|
50
|
+
self._next_id = 1
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# <new_route> API routes.
|
|
3
|
+
# --------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
|
6
|
+
from <domain_module>.schemas import <entity_class>Create, <entity_class>Read
|
|
7
|
+
from <domain_module>.service import <entity_class>NotFoundError, <entity_class>Service
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_<new_route>_service() -> <entity_class>Service:
|
|
13
|
+
return <entity_class>Service()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.get("", response_model=list[<entity_class>Read])
|
|
17
|
+
def list_<new_route>(
|
|
18
|
+
service: <entity_class>Service = Depends(get_<new_route>_service),
|
|
19
|
+
) -> list[<entity_class>Read]:
|
|
20
|
+
return [<entity_class>Read.model_validate(entity) for entity in service.list_<new_route>()]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.get("/{entity_id}", response_model=<entity_class>Read)
|
|
24
|
+
def get_<new_route>(
|
|
25
|
+
entity_id: int, service: <entity_class>Service = Depends(get_<new_route>_service)
|
|
26
|
+
) -> <entity_class>Read:
|
|
27
|
+
try:
|
|
28
|
+
return <entity_class>Read.model_validate(service.get_<new_route>(entity_id))
|
|
29
|
+
except <entity_class>NotFoundError as exc:
|
|
30
|
+
raise HTTPException(
|
|
31
|
+
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
32
|
+
) from exc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.post("", response_model=<entity_class>Read, status_code=status.HTTP_201_CREATED)
|
|
36
|
+
def create_<new_route>(
|
|
37
|
+
payload: <entity_class>Create, service: <entity_class>Service = Depends(get_<new_route>_service)
|
|
38
|
+
) -> <entity_class>Read:
|
|
39
|
+
return <entity_class>Read.model_validate(service.create_<new_route>(payload))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router.put("/{entity_id}", response_model=<entity_class>Read)
|
|
43
|
+
def replace_<new_route>(
|
|
44
|
+
entity_id: int,
|
|
45
|
+
payload: <entity_class>Create,
|
|
46
|
+
service: <entity_class>Service = Depends(get_<new_route>_service),
|
|
47
|
+
) -> <entity_class>Read:
|
|
48
|
+
try:
|
|
49
|
+
return <entity_class>Read.model_validate(service.replace_<new_route>(entity_id, payload))
|
|
50
|
+
except <entity_class>NotFoundError as exc:
|
|
51
|
+
raise HTTPException(
|
|
52
|
+
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
53
|
+
) from exc
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.delete("/{entity_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
57
|
+
def delete_<new_route>(
|
|
58
|
+
entity_id: int, service: <entity_class>Service = Depends(get_<new_route>_service)
|
|
59
|
+
) -> Response:
|
|
60
|
+
try:
|
|
61
|
+
service.delete_<new_route>(entity_id)
|
|
62
|
+
except <entity_class>NotFoundError as exc:
|
|
63
|
+
raise HTTPException(
|
|
64
|
+
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
65
|
+
) from exc
|
|
66
|
+
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# <new_route> API schemas.
|
|
3
|
+
# --------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class <entity_class>Create(BaseModel):
|
|
9
|
+
name: str = Field(min_length=1, max_length=120)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class <entity_class>Read(BaseModel):
|
|
13
|
+
id: int
|
|
14
|
+
name: str
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(from_attributes=True)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# <new_route> service.
|
|
3
|
+
# --------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
from <domain_module>.models import <entity_class>
|
|
6
|
+
from <domain_module>.repository import <entity_class>Repository
|
|
7
|
+
from <domain_module>.schemas import <entity_class>Create
|
|
8
|
+
|
|
9
|
+
# Share the default repository between requests.
|
|
10
|
+
_default_repository = <entity_class>Repository()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def reset_store() -> None:
|
|
14
|
+
_default_repository.reset()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class <entity_class>NotFoundError(Exception):
|
|
18
|
+
"""Raised when <entity_class> is not found."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class <entity_class>Service:
|
|
22
|
+
"""Business logic for <entity_class>."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, repository: <entity_class>Repository | None = None) -> None:
|
|
25
|
+
self._repository = repository if repository is not None else _default_repository
|
|
26
|
+
|
|
27
|
+
def list_<new_route>(self) -> list[<entity_class>]:
|
|
28
|
+
return self._repository.list_all()
|
|
29
|
+
|
|
30
|
+
def get_<new_route>(self, entity_id: int) -> <entity_class>:
|
|
31
|
+
entity = self._repository.get(entity_id)
|
|
32
|
+
if entity is None:
|
|
33
|
+
raise <entity_class>NotFoundError(f"<entity_class> {entity_id} does not exist")
|
|
34
|
+
return entity
|
|
35
|
+
|
|
36
|
+
def create_<new_route>(self, payload: <entity_class>Create) -> <entity_class>:
|
|
37
|
+
return self._repository.add(name=payload.name)
|
|
38
|
+
|
|
39
|
+
def replace_<new_route>(self, entity_id: int, payload: <entity_class>Create) -> <entity_class>:
|
|
40
|
+
entity = self._repository.replace(entity_id=entity_id, name=payload.name)
|
|
41
|
+
if entity is None:
|
|
42
|
+
raise <entity_class>NotFoundError(f"<entity_class> {entity_id} does not exist")
|
|
43
|
+
return entity
|
|
44
|
+
|
|
45
|
+
def delete_<new_route>(self, entity_id: int) -> None:
|
|
46
|
+
if not self._repository.delete(entity_id=entity_id):
|
|
47
|
+
raise <entity_class>NotFoundError(f"<entity_class> {entity_id} does not exist")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: FastAPI-fastkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Fast, easy-to-use starter kit for new users of Python and FastAPI
|
|
5
5
|
Author-Email: bnbong <bbbong9@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -20,6 +20,7 @@ Requires-Dist: pre-commit>=4.0.1; extra == "dev"
|
|
|
20
20
|
Requires-Dist: mypy>=1.12.0; extra == "dev"
|
|
21
21
|
Requires-Dist: isort>=5.13.2; extra == "dev"
|
|
22
22
|
Requires-Dist: pygments>=2.20.0; extra == "dev"
|
|
23
|
+
Requires-Dist: virtualenv>=21.7.13; extra == "dev"
|
|
23
24
|
Requires-Dist: types-pyyaml>=6.0.12.20260408; extra == "dev"
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
25
26
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
fastapi_fastkit-1.
|
|
2
|
-
fastapi_fastkit-1.
|
|
3
|
-
fastapi_fastkit-1.
|
|
4
|
-
fastapi_fastkit-1.
|
|
5
|
-
fastapi_fastkit/__init__.py,sha256=
|
|
1
|
+
fastapi_fastkit-1.5.0.dist-info/METADATA,sha256=L5ZxmtU6Zq81I3bUEQH41-8Sql6LHdIwKjxe5o1vnX4,14109
|
|
2
|
+
fastapi_fastkit-1.5.0.dist-info/WHEEL,sha256=IuIk3wbaEnFH1cx75iM2R1CmliRI3dFLXTbRakgvgFQ,90
|
|
3
|
+
fastapi_fastkit-1.5.0.dist-info/entry_points.txt,sha256=IONmgb7zWPnJWsCOcpF3u1yP6AWnPjrgWIv49mr1DZE,76
|
|
4
|
+
fastapi_fastkit-1.5.0.dist-info/licenses/LICENSE,sha256=2a9cYM3Uy8DW-so6zpYaqUafYT1Dznd3OCWCFe5CKNA,1066
|
|
5
|
+
fastapi_fastkit/__init__.py,sha256=fKcVJUCHyeAQEeedLhq_-ubvCgf_3xUy2Q8Kmmdi0VE,23
|
|
6
6
|
fastapi_fastkit/__main__.py,sha256=-FS9yUe4IEgDbJjFC1xso-pFCxRzUJ447j6bYpsBTBM,271
|
|
7
7
|
fastapi_fastkit/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
fastapi_fastkit/backend/inspection/__init__.py,sha256=hIw6MIOrt5GI52kQtYWsW1INbuVd64vG_OGU_rvfXXE,1088
|
|
@@ -23,7 +23,7 @@ fastapi_fastkit/backend/interactive/config_builder.py,sha256=LKqmUiBw2-Dk5OPk4Ut
|
|
|
23
23
|
fastapi_fastkit/backend/interactive/prompts.py,sha256=z6xLnStWliFAp_NzhbBGtNlRNQwqN7AO3Wb0TowxORA,22135
|
|
24
24
|
fastapi_fastkit/backend/interactive/selectors.py,sha256=XXVurHN0_gesRWFrgzhVIG7igX6cCGGtUS4gY8gmOUI,8334
|
|
25
25
|
fastapi_fastkit/backend/interactive/validators.py,sha256=WaRWuE6H2qQ5UExmHq3NQ41spiCyhbyk7QlYVXcfoHw,4651
|
|
26
|
-
fastapi_fastkit/backend/main.py,sha256=
|
|
26
|
+
fastapi_fastkit/backend/main.py,sha256=XToW5ii-2ET0LXD1vVxzEQPeEFP3-NgQx262HETydrE,61927
|
|
27
27
|
fastapi_fastkit/backend/package_managers/__init__.py,sha256=awSaY-pInI9K4mk1aWqdyKk143AGrcPI4NCy5l_48vw,691
|
|
28
28
|
fastapi_fastkit/backend/package_managers/base.py,sha256=YtpnkjMNBQFJSH8pSQI-VIlqxQ6lbeyY2hZVKjgfi8w,11641
|
|
29
29
|
fastapi_fastkit/backend/package_managers/factory.py,sha256=rqnIzdLn6T5UiXSy_WTIFsibAK2e8aRwyn-OC16sS9w,5579
|
|
@@ -36,9 +36,11 @@ fastapi_fastkit/backend/project_builder/config_generator.py,sha256=riyjO8iOj54N0
|
|
|
36
36
|
fastapi_fastkit/backend/project_builder/config_schema.py,sha256=Vj4Mmkr35MnoVbSTOIFaKcR8z5Rfdgn-GBV7T3h_h8c,14093
|
|
37
37
|
fastapi_fastkit/backend/project_builder/dependency_collector.py,sha256=vj0NYAi3KdvZ5jjgUxFSoP5J5KAHvqIsGdlU5H7xH48,7582
|
|
38
38
|
fastapi_fastkit/backend/project_builder/preset_layout.py,sha256=E-I8JP3goHW5QwpACBC6LC7XX00c-5BNsPjVlDYOhVQ,16319
|
|
39
|
+
fastapi_fastkit/backend/route_generators.py,sha256=ymyJa-IDuWIYHZuiIn-8zUyOaAYs57nCL0ARNI-RY2s,6237
|
|
40
|
+
fastapi_fastkit/backend/route_wiring.py,sha256=BJZbZlT-H3LshCDGDWEpdbiEnVc5ruA71A6w1m11Tn8,4367
|
|
39
41
|
fastapi_fastkit/backend/scaffolder.py,sha256=dduEeBN1cdwt_V3JxSxSepHEUMuEB0j-8Sq5FU8KnJ0,21702
|
|
40
42
|
fastapi_fastkit/backend/transducer.py,sha256=t0tHVNUpPgbQcwF6Ms9WhQ8ALl6MR1zL8CVbvKgD2rs,11977
|
|
41
|
-
fastapi_fastkit/cli.py,sha256=
|
|
43
|
+
fastapi_fastkit/cli.py,sha256=Msb4blZ0QnjcPxRW7Mtl_MI97J8_CI3G-ssRsT93F-I,35734
|
|
42
44
|
fastapi_fastkit/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
45
|
fastapi_fastkit/core/exceptions.py,sha256=W29lbDlKE_yKdS-m_QHaWdDB1j3yGge6mVx5mo_jLrU,972
|
|
44
46
|
fastapi_fastkit/core/settings.py,sha256=v9IiAyU4bIYf8haTCAupr_qiDFscAGlc4g_XqyZ0jrU,15675
|
|
@@ -77,8 +79,8 @@ fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/.gitignore-tpl,sha256=
|
|
|
77
79
|
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/Dockerfile-tpl,sha256=lLAHrVZvnXToGSUcOqGCYQHi3VaKziBsC89yIynUlVo,342
|
|
78
80
|
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/README.md-tpl,sha256=kplQU1S29P9d_K1chs2BiMpDdWfAJ0bFgTZyD2pWy9g,8102
|
|
79
81
|
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/alembic.ini-tpl,sha256=mFmPqRH42TM3_nqNF7uLYD3DKvE9pnb_9Unr0NL2t08,3747
|
|
80
|
-
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/pyproject.toml-tpl,sha256=
|
|
81
|
-
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/requirements.txt-tpl,sha256=
|
|
82
|
+
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/pyproject.toml-tpl,sha256=aWEqiJ7WNzRCrQPN846XWb1WDTt0FVmJ7SEN8sKrSAY,1811
|
|
83
|
+
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/requirements.txt-tpl,sha256=OpqabN8w2cLVvl6uH1pk9By-UAAXdPNRXSgqPleqo2I,428
|
|
82
84
|
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/format.sh-tpl,sha256=wivOKoO8NCXimBSWwLYGh43UW0ufHTRX1LiFm3ZVSmc,44
|
|
83
85
|
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/lint.sh-tpl,sha256=GUCLz6mGOTqrnInfybPoHKShE3_kCpnyzDRZtqelqY4,74
|
|
84
86
|
fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/run-server.sh-tpl,sha256=ju9iKsqTBJJVni3_4knSmUnD72SXCNlG7MblPAIMnjg,97
|
|
@@ -219,7 +221,7 @@ fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/__init__.py-
|
|
|
219
221
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/__init__.py-tpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
220
222
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/__init__.py-tpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
221
223
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/health.py-tpl,sha256=k22K5KNeeIXgKvKRBALpHRsUeE5litdaXH4Z5XkG86c,379
|
|
222
|
-
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl,sha256=
|
|
224
|
+
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl,sha256=dfEoi7wsrQSy44Vx6l8giqnE9xgDb6VF2FE93evvb2E,566
|
|
223
225
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/__init__.py-tpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
224
226
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/config.py-tpl,sha256=NTqTESwvNwYgZ7SwPHFDy2KUVZP7AYpyxwKWZgbU9-k,1587
|
|
225
227
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/__init__.py-tpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -228,12 +230,12 @@ fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/
|
|
|
228
230
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/__init__.py-tpl,sha256=Oc05YNDcfBRuoQRY1zbPfxL3vzEnt4fWwlAqPsgCDP8,220
|
|
229
231
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/models.py-tpl,sha256=Ffnoe6CWURkRfLI3hpzpcp7gVQloNOLTAA0BMsjmYI0,522
|
|
230
232
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/repository.py-tpl,sha256=5_VlE3tLlk3-zr9BevKnoJlzRMrFF-m91duCl6LLv48,1773
|
|
231
|
-
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl,sha256=
|
|
233
|
+
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl,sha256=lOt8EWrx3IqlEZfWZn2EmsJWACV8Ke4rmSLph3PWCQg,2148
|
|
232
234
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/schemas.py-tpl,sha256=fRKitiBpypPs_cI0eygL0VHAQD_2WjaCW0x2bJzfCjo,626
|
|
233
235
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/service.py-tpl,sha256=GnKArMWnZF1FpEBCLjIvum7eg_qM6zuM6LXMY759IdI,1842
|
|
234
236
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/main.py-tpl,sha256=Ww3sbrzI0CdWRp3DegTrO3QGhBjxjKbVYav5j7Gx6cE,801
|
|
235
237
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/__init__.py-tpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
236
|
-
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl,sha256=
|
|
238
|
+
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl,sha256=gWWRNo21GsWTcQNDSECICclaRz1lmaE758M9HQQX2SY,1081
|
|
237
239
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_health.py-tpl,sha256=YXVLzRCXRmD6gHMNMmfyYJdwHHrasppWi-Ga0jCh6rs,421
|
|
238
240
|
fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_items.py-tpl,sha256=RLuMinBf_EqlkTfaUquBfUfRrX_IF2yBPKehITLzYmk,2132
|
|
239
241
|
fastapi_fastkit/fastapi_project_template/fastapi-empty/.env-tpl,sha256=ER5_3eyFYRQUupKW7z1tBOcTVavwEqjzqbbt1kl-Be0,22
|
|
@@ -288,13 +290,13 @@ fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_memory.py-
|
|
|
288
290
|
fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_tools.py-tpl,sha256=lDzyV70_YxpuymCOThFkNqXApYu4yd_tIk7lX6LJlLA,1439
|
|
289
291
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/.env-tpl,sha256=3styxb-X8yguh-V7-XjTWmd95fmnFf2AtLt2Q1kPn_w,695
|
|
290
292
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/README.md-tpl,sha256=zIyfUG1S80WDsdqQVqpR6sxH_QLi7GY3hHT6SdwEfHQ,6426
|
|
291
|
-
fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl,sha256=
|
|
292
|
-
fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl,sha256=
|
|
293
|
+
fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl,sha256=txA3oHOFAFXPRcOLqaoyzI4G1qtTP2viDZOWvDZUGlE,1732
|
|
294
|
+
fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl,sha256=GEG5DejkhErmaME6GvP1YkGM5n4KScMaxO5ve-I9cMo,1172
|
|
293
295
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/format.sh-tpl,sha256=lMHoGl8naqGqYnW3WQ5zKJSkFFrWa4EvooA3xyj5Duk,215
|
|
294
296
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/lint.sh-tpl,sha256=TbnPdvBtO-k4XfAnYxUatrsXn4QniW6THmR0gwqshfs,397
|
|
295
297
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/run-server.sh-tpl,sha256=Uuti1QOukxvii8ss9RjTf8DzkyJ0V7HyJDGRPhjtazA,195
|
|
296
298
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/test.sh-tpl,sha256=FwcP6i9GGhAiUjyrvczr89z7zT4OEAGVOYQUOVM8vXU,185
|
|
297
|
-
fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.cfg-tpl,sha256=
|
|
299
|
+
fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.cfg-tpl,sha256=oUvqaL90uwTrq1105fqiw8WcTvsc6avT0v9BXwbljy4,1932
|
|
298
300
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/__init__.py-tpl,sha256=bhmTXfHwW_cFBb18cxepy6TQxHYAI1swxEHxIlf15z4,78
|
|
299
301
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/__init__.py-tpl,sha256=BDaEDKpisCteGve4mGQWkAsrRPsPToytMgcdyF1IS30,46
|
|
300
302
|
fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/api.py-tpl,sha256=MQknnIJ25KUX_nFgjWZR7bsNHsWWw9pLaus_0hL2b0Q,381
|
|
@@ -411,6 +413,12 @@ fastapi_fastkit/fastapi_project_template/modules/api/routes/__init__.py-tpl,sha2
|
|
|
411
413
|
fastapi_fastkit/fastapi_project_template/modules/api/routes/new_route.py-tpl,sha256=pJWGpBw7x8HcVHjvQud4hYZ2YVpUwGnDGSUoEGGW7Ig,1265
|
|
412
414
|
fastapi_fastkit/fastapi_project_template/modules/crud/__init__.py-tpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
413
415
|
fastapi_fastkit/fastapi_project_template/modules/crud/new_route.py-tpl,sha256=m42zC4ye9WdCHggm1M0kNIuwwebscF05VVN8T3282uw,660
|
|
416
|
+
fastapi_fastkit/fastapi_project_template/modules/domain/__init__.py-tpl,sha256=QAbfAHRjOw8hMAks61qaX8bRoFtlJcaioX1eKp2XAAg,288
|
|
417
|
+
fastapi_fastkit/fastapi_project_template/modules/domain/models.py-tpl,sha256=gIQmozKd9O4lA_mRyn33SMdxuNhOnLauswdz9VWPH8g,279
|
|
418
|
+
fastapi_fastkit/fastapi_project_template/modules/domain/repository.py-tpl,sha256=XCdeXiLy_2C4plwHYInRgPybK1Y7HhG6flYDfn9Ntss,1601
|
|
419
|
+
fastapi_fastkit/fastapi_project_template/modules/domain/router.py-tpl,sha256=o0euKXF6n75gJTA7DlJ7IFQYkS7_VIfdrtTyDg1MsXs,2572
|
|
420
|
+
fastapi_fastkit/fastapi_project_template/modules/domain/schemas.py-tpl,sha256=svucP42bZFVR7ImeseUYyMOrl7kB1sAx50UBG-C7XEk,443
|
|
421
|
+
fastapi_fastkit/fastapi_project_template/modules/domain/service.py-tpl,sha256=i38BiqACL5rcLQgtz99D-OEC311mRMsL5SjMRMyeUIA,1889
|
|
414
422
|
fastapi_fastkit/fastapi_project_template/modules/schemas/__init__.py-tpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
415
423
|
fastapi_fastkit/fastapi_project_template/modules/schemas/new_route.py-tpl,sha256=f83oXEj2wJ5lg620Bc0vRmpcjOTIpfMgejzzrHeerGk,569
|
|
416
424
|
fastapi_fastkit/fragments/README.md,sha256=-Ch-2ZEbSkL6oXUkhaIkn3joR81E0ZLSJj1FaBPghW8,4962
|
|
@@ -481,4 +489,4 @@ fastapi_fastkit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
|
481
489
|
fastapi_fastkit/utils/config_file.py,sha256=0rZEwCJblHabZlFibjYn2iycNZ8Rp4scOdZpfYtjU4g,9654
|
|
482
490
|
fastapi_fastkit/utils/logging.py,sha256=oU7BnWInxzl_Gk8kGQ0f6cKdNHZcOVVcSHY4I9MTmR8,6431
|
|
483
491
|
fastapi_fastkit/utils/main.py,sha256=dCuTSYCME7oCqlvicn-QFGEwA9MeNvMtfKcmThGewyU,13204
|
|
484
|
-
fastapi_fastkit-1.
|
|
492
|
+
fastapi_fastkit-1.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|