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.
Files changed (24) hide show
  1. fastapi_fastkit/__init__.py +1 -1
  2. fastapi_fastkit/backend/main.py +35 -40
  3. fastapi_fastkit/backend/route_generators.py +163 -0
  4. fastapi_fastkit/backend/route_wiring.py +117 -0
  5. fastapi_fastkit/cli.py +13 -2
  6. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/pyproject.toml-tpl +1 -1
  7. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/requirements.txt-tpl +1 -1
  8. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl +1 -1
  9. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl +1 -1
  10. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl +16 -4
  11. fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +1 -1
  12. fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +2 -2
  13. fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.cfg-tpl +1 -1
  14. fastapi_fastkit/fastapi_project_template/modules/domain/__init__.py-tpl +10 -0
  15. fastapi_fastkit/fastapi_project_template/modules/domain/models.py-tpl +11 -0
  16. fastapi_fastkit/fastapi_project_template/modules/domain/repository.py-tpl +50 -0
  17. fastapi_fastkit/fastapi_project_template/modules/domain/router.py-tpl +66 -0
  18. fastapi_fastkit/fastapi_project_template/modules/domain/schemas.py-tpl +16 -0
  19. fastapi_fastkit/fastapi_project_template/modules/domain/service.py-tpl +47 -0
  20. {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/METADATA +2 -1
  21. {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/RECORD +24 -16
  22. {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/WHEEL +1 -1
  23. {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/entry_points.txt +0 -0
  24. {fastapi_fastkit-1.4.1.dist-info → fastapi_fastkit-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
1
- __version__ = 'v1.4.1'
1
+ __version__ = 'v1.5.0'
@@ -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
- route_import = f"from .routes import {route_name}"
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({route_name}.router, "
1502
+ f"api_router.include_router({alias}.router, "
1490
1503
  f'prefix="/{route_name}", tags=["{route_name}"])'
1491
1504
  )
1492
1505
 
1493
- if (
1494
- route_import in content
1495
- and f"api_router.include_router({route_name}.router" in content
1496
- ):
1497
- return # Already included
1498
-
1499
- content = insert_import_line(content, route_import)
1500
- content = insert_statement_line(content, route_include)
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(project_dir: str, route_name: str) -> None:
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
- # Setup paths
1611
- modules_dir = os.path.join(settings.FASTKIT_TEMPLATE_ROOT, "modules")
1612
- layout = resolve_project_layout(project_dir)
1613
- src_dir = layout["package_dir"]
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
- # Handle API router file
1624
- _handle_api_router_file(
1625
- target_dirs, modules_dir, route_name, layout["api_router_file"]
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(ctx: Context, route_name: str, project_dir: str) -> None:
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(
@@ -19,7 +19,7 @@ dependencies = [
19
19
  "sqlmodel>=0.0.42",
20
20
  "SQLAlchemy>=2.0.52",
21
21
  "alembic>=1.19.1",
22
- "PyJWT>=2.13.0",
22
+ "PyJWT>=2.15.1",
23
23
  "pwdlib[argon2]>=0.3.1",
24
24
  "psycopg[binary]>=3.3.5",
25
25
  ]
@@ -12,7 +12,7 @@ SQLAlchemy==2.0.52
12
12
  alembic==1.19.1
13
13
  psycopg[binary]==3.3.5
14
14
  # Authentication
15
- PyJWT==2.13.0
15
+ PyJWT==2.15.1
16
16
  pwdlib[argon2]==0.3.1
17
17
  # Development & tests
18
18
  pytest==9.1.1
@@ -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
@@ -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(prefix="/items", tags=["items"])
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
- @pytest.fixture(autouse=True)
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
- ItemRepository().reset()
29
+ reset_stores()
18
30
 
19
31
 
20
32
  @pytest.fixture
@@ -18,7 +18,7 @@ dependencies = [
18
18
  "fastapi-mcp>=0.3.7,<0.4.0",
19
19
  "mcp>=1.29.1,<2.0.0",
20
20
  "pwdlib[argon2]>=0.3.1",
21
- "PyJWT[crypto]>=2.13.0",
21
+ "PyJWT[crypto]>=2.15.1",
22
22
  "python-multipart>=0.0.32",
23
23
  ]
24
24
 
@@ -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.13.0
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.7.0
62
+ urllib3==2.8.0
63
63
  uvicorn==0.52.4
64
64
  uvloop==0.22.1
65
65
  watchfiles==1.2.0
@@ -29,7 +29,7 @@ install_requires =
29
29
  pydantic>=2.10.6
30
30
  pydantic-settings>=2.7.1
31
31
  python-dotenv>=1.0.1
32
- PyJWT[crypto]>=2.10.0
32
+ PyJWT[crypto]>=2.15.1
33
33
  pwdlib[argon2]>=0.3.1
34
34
  python-multipart>=0.0.17
35
35
 
@@ -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.4.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.4.1.dist-info/METADATA,sha256=KRHsuGhz10aD8Nckit_OsoJwMcSiwKbYM9zcfZttGak,14058
2
- fastapi_fastkit-1.4.1.dist-info/WHEEL,sha256=VP-D4TPS230sME9Z3vb3INXvo1yt0924YRm5AOsk_dE,90
3
- fastapi_fastkit-1.4.1.dist-info/entry_points.txt,sha256=IONmgb7zWPnJWsCOcpF3u1yP6AWnPjrgWIv49mr1DZE,76
4
- fastapi_fastkit-1.4.1.dist-info/licenses/LICENSE,sha256=2a9cYM3Uy8DW-so6zpYaqUafYT1Dznd3OCWCFe5CKNA,1066
5
- fastapi_fastkit/__init__.py,sha256=muhECMM1myXdjnRz3xB2_5q1rOYCUaw2jAkKMCZqrJ8,23
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=0_ekIbxF6qBvwHtpZ2gJd--q39BZ-IXzV4ox4822DeE,61782
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=UxwJcLOkCspwVFnNgJQkbYQyTjsrcNdRpjNnRTE3Rgk,35305
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=UekapeGgLjkeJbazCyzzlA86481d4F7-l0T5iZKLYnk,1811
81
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/requirements.txt-tpl,sha256=jpl4SsNQK6SpJ_oEr9_ejCyfCYagtHg2LxouCDku3-Q,428
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=5Q8_zh8w0do-fcPeDFx9ECT4C-FbbRQYU9DtucnkREU,533
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=oumRbsQ9VBPNgvSHSyK5W3KilNmaM58X362juHwrZ_4,2179
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=c1b_Fj15LGW-N66CuRjdbxAeIculFpvO2uupsQ3V4vU,702
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=arYdZzGNJSjCouTAQKnmaeorVf-d4P98xqIItELedFA,1732
292
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl,sha256=yaOrA-JiT_UrNPyFCYxp3rL7ifVrZ__549Wh-3EmXvg,1172
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=PsOhp-z9fEJFlHJI99AX3Gh6Ub0mZt52puvOf_r6ZLs,1932
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.4.1.dist-info/RECORD,,
492
+ fastapi_fastkit-1.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.4.9)
2
+ Generator: pdm-backend (2.5.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any