FastAPI-fastkit 1.2.0__py3-none-any.whl → 1.3.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/inspector.py +153 -32
- fastapi_fastkit/backend/interactive/__init__.py +2 -0
- fastapi_fastkit/backend/interactive/config_builder.py +21 -2
- fastapi_fastkit/backend/interactive/prompts.py +52 -1
- fastapi_fastkit/backend/interactive/selectors.py +10 -0
- fastapi_fastkit/backend/main.py +114 -6
- fastapi_fastkit/backend/package_managers/factory.py +1 -1
- fastapi_fastkit/backend/package_managers/pdm_manager.py +3 -3
- fastapi_fastkit/backend/package_managers/poetry_manager.py +71 -10
- fastapi_fastkit/backend/package_managers/uv_manager.py +3 -3
- fastapi_fastkit/backend/project_builder/__init__.py +3 -0
- fastapi_fastkit/backend/project_builder/config_generator.py +17 -10
- fastapi_fastkit/backend/project_builder/preset_layout.py +203 -0
- fastapi_fastkit/backend/transducer.py +0 -1
- fastapi_fastkit/cli.py +109 -44
- fastapi_fastkit/core/settings.py +17 -1
- fastapi_fastkit/fastapi_project_template/README.md +72 -23
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/pyproject.toml-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/pyproject.toml-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-default/pyproject.toml-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/pyproject.toml-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.env-tpl +2 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.gitignore-tpl +31 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/README.md-tpl +128 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/pyproject.toml-tpl +70 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/requirements.txt-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/format.sh-tpl +5 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/lint.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/run-server.sh-tpl +8 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/test.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/health.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl +12 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/config.py-tpl +49 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/memory.py-tpl +48 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/__init__.py-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/models.py-tpl +17 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/repository.py-tpl +48 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl +59 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/schemas.py-tpl +23 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/service.py-tpl +52 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/main.py-tpl +25 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl +24 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_health.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_items.py-tpl +77 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/README.md-tpl +2 -2
- fastapi_fastkit/fastapi_project_template/fastapi-empty/pyproject.toml-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/pyproject.toml-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/pyproject.toml-tpl +4 -1
- fastapi_fastkit/utils/main.py +73 -6
- {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/METADATA +33 -3
- {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/RECORD +63 -32
- {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/WHEEL +1 -1
- {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/entry_points.txt +0 -0
- {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/licenses/LICENSE +0 -0
fastapi_fastkit/cli.py
CHANGED
|
@@ -43,9 +43,28 @@ from fastapi_fastkit.utils.main import (
|
|
|
43
43
|
validate_email,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
from . import __version__
|
|
47
|
+
|
|
46
48
|
console = utils_console
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
|
|
51
|
+
def _cleanup_failed_project(
|
|
52
|
+
project_dir: str, user_workspace: str, create_project_folder: bool
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Clean up a partially created project after an error.
|
|
56
|
+
|
|
57
|
+
Only deletes a freshly created project folder. When the project was deployed
|
|
58
|
+
in-place (create_project_folder=False), project_dir equals the user's workspace,
|
|
59
|
+
and removing it would destroy unrelated files, so no cleanup is performed.
|
|
60
|
+
"""
|
|
61
|
+
if not create_project_folder:
|
|
62
|
+
return
|
|
63
|
+
if not project_dir or not os.path.exists(project_dir):
|
|
64
|
+
return
|
|
65
|
+
if os.path.abspath(project_dir) == os.path.abspath(user_workspace):
|
|
66
|
+
return
|
|
67
|
+
shutil.rmtree(project_dir, ignore_errors=True)
|
|
49
68
|
|
|
50
69
|
|
|
51
70
|
@click.group()
|
|
@@ -248,7 +267,7 @@ def startdemo(
|
|
|
248
267
|
if template_deps:
|
|
249
268
|
deps_table = create_info_table(
|
|
250
269
|
"Template Dependencies",
|
|
251
|
-
{f"Dependency {i+1}": dep for i, dep in enumerate(template_deps)},
|
|
270
|
+
{f"Dependency {i + 1}": dep for i, dep in enumerate(template_deps)},
|
|
252
271
|
)
|
|
253
272
|
console.print("\n")
|
|
254
273
|
console.print(deps_table)
|
|
@@ -315,7 +334,11 @@ def startdemo(
|
|
|
315
334
|
"--interactive",
|
|
316
335
|
is_flag=True,
|
|
317
336
|
default=False,
|
|
318
|
-
help=
|
|
337
|
+
help=(
|
|
338
|
+
"Enable interactive mode for guided project setup. Walks through an "
|
|
339
|
+
"architecture preset (minimal / single-module / classic-layered / "
|
|
340
|
+
"domain-starter, default: domain-starter), then feature selection."
|
|
341
|
+
),
|
|
319
342
|
)
|
|
320
343
|
@click.option(
|
|
321
344
|
"--project-name",
|
|
@@ -353,7 +376,11 @@ def init(
|
|
|
353
376
|
"""
|
|
354
377
|
Start a FastAPI project setup.
|
|
355
378
|
|
|
356
|
-
Use --interactive for guided setup
|
|
379
|
+
Use --interactive for guided setup. Interactive mode prompts for an
|
|
380
|
+
architecture preset (``minimal`` / ``single-module`` / ``classic-layered``
|
|
381
|
+
/ ``domain-starter`` — default: ``domain-starter``) and then walks
|
|
382
|
+
through feature selection (database, auth, testing, deployment, ...).
|
|
383
|
+
|
|
357
384
|
Without --interactive, creates an empty project with predefined stacks.
|
|
358
385
|
|
|
359
386
|
This command will automatically create a new FastAPI project directory
|
|
@@ -395,8 +422,17 @@ def init(
|
|
|
395
422
|
try:
|
|
396
423
|
user_local = settings.USER_WORKSPACE
|
|
397
424
|
|
|
398
|
-
#
|
|
399
|
-
|
|
425
|
+
# Pick the base template from the architecture preset chosen
|
|
426
|
+
# earlier in the interactive flow. Older callers without a
|
|
427
|
+
# preset fall back to ``minimal`` (= fastapi-empty), preserving
|
|
428
|
+
# pre-#45 behaviour.
|
|
429
|
+
from fastapi_fastkit.backend.project_builder import (
|
|
430
|
+
PresetLayoutStrategist,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
preset_id = config.get("architecture_preset")
|
|
434
|
+
strategist = PresetLayoutStrategist(preset_id)
|
|
435
|
+
template = strategist.base_template
|
|
400
436
|
template_dir = settings.FASTKIT_TEMPLATE_ROOT
|
|
401
437
|
target_template = os.path.join(template_dir, template)
|
|
402
438
|
|
|
@@ -445,56 +481,72 @@ def init(
|
|
|
445
481
|
|
|
446
482
|
generator = DynamicConfigGenerator(config, project_dir)
|
|
447
483
|
|
|
448
|
-
#
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
484
|
+
# main.py overlay — only regenerated for presets that ship a
|
|
485
|
+
# placeholder app (minimal, single-module). For richer presets
|
|
486
|
+
# (classic-layered, domain-starter) we keep the template's
|
|
487
|
+
# router-aware main.py intact.
|
|
488
|
+
#
|
|
489
|
+
# The strategist's ``main_py_target`` is always ``src/main.py``
|
|
490
|
+
# for both regenerate-main presets, and both fastapi-empty and
|
|
491
|
+
# fastapi-single-module ship that file, so we can write
|
|
492
|
+
# straight to the strategist's path without a flat-``main.py``
|
|
493
|
+
# fallback branch.
|
|
494
|
+
if strategist.should_regenerate_main:
|
|
495
|
+
main_py_path = strategist.main_py_target(project_dir)
|
|
496
|
+
main_py_path.parent.mkdir(parents=True, exist_ok=True)
|
|
497
|
+
main_py_path.write_text(generator.generate_main_py())
|
|
498
|
+
else:
|
|
499
|
+
print_info(
|
|
500
|
+
f"Preserving template-shipped main.py for preset "
|
|
501
|
+
f"'{strategist.preset_id}'."
|
|
502
|
+
)
|
|
456
503
|
|
|
457
|
-
# Generate database configuration if selected
|
|
504
|
+
# Generate database configuration if selected — preset chooses
|
|
505
|
+
# where the file lives so it sits next to the existing structure.
|
|
458
506
|
db_info = config.get("database", {})
|
|
459
507
|
if isinstance(db_info, dict) and db_info.get("type") != "None":
|
|
460
508
|
db_config_content = generator.generate_database_config()
|
|
461
509
|
if db_config_content:
|
|
462
|
-
db_config_path =
|
|
463
|
-
|
|
464
|
-
)
|
|
465
|
-
os.makedirs(os.path.dirname(db_config_path), exist_ok=True)
|
|
466
|
-
with open(db_config_path, "w") as f:
|
|
467
|
-
f.write(db_config_content)
|
|
510
|
+
db_config_path = strategist.db_config_target(project_dir)
|
|
511
|
+
db_config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
512
|
+
db_config_path.write_text(db_config_content)
|
|
468
513
|
|
|
469
514
|
# Generate auth configuration if selected
|
|
470
515
|
auth_type = config.get("authentication", "None")
|
|
471
516
|
if auth_type != "None":
|
|
472
517
|
auth_config_content = generator.generate_auth_config()
|
|
473
518
|
if auth_config_content:
|
|
474
|
-
auth_config_path =
|
|
475
|
-
|
|
476
|
-
)
|
|
477
|
-
os.makedirs(os.path.dirname(auth_config_path), exist_ok=True)
|
|
478
|
-
with open(auth_config_path, "w") as f:
|
|
479
|
-
f.write(auth_config_content)
|
|
519
|
+
auth_config_path = strategist.auth_config_target(project_dir)
|
|
520
|
+
auth_config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
521
|
+
auth_config_path.write_text(auth_config_content)
|
|
480
522
|
|
|
481
523
|
# Generate test configuration if testing selected
|
|
482
524
|
testing_type = config.get("testing", "None")
|
|
483
525
|
if testing_type != "None":
|
|
484
526
|
test_config_content = generator.generate_test_config()
|
|
485
527
|
if test_config_content:
|
|
486
|
-
test_config_path = os.path.join(project_dir, "
|
|
487
|
-
os.makedirs(os.path.dirname(test_config_path), exist_ok=True)
|
|
528
|
+
test_config_path = os.path.join(project_dir, "pytest.ini")
|
|
488
529
|
with open(test_config_path, "w") as f:
|
|
489
530
|
f.write(test_config_content)
|
|
490
531
|
|
|
491
532
|
# Generate Docker files if deployment selected
|
|
492
533
|
deployment = config.get("deployment", [])
|
|
493
534
|
if deployment and deployment != ["None"]:
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
535
|
+
# Thread the preset-aware app module so the generated
|
|
536
|
+
# Dockerfile's ``CMD ["uvicorn", "<module>:app", ...]``
|
|
537
|
+
# matches the layout the user actually generated. Default
|
|
538
|
+
# ``src.main:app`` only works for minimal / single-module /
|
|
539
|
+
# classic-layered; domain-starter needs ``src.app.main:app``.
|
|
540
|
+
generator.generate_docker_files(app_module=strategist.app_module)
|
|
541
|
+
print_success("Generated Docker deployment files")
|
|
542
|
+
|
|
543
|
+
# Surface preset-specific warnings (e.g. "you picked a preset
|
|
544
|
+
# whose shipped main.py we kept; CORS/Prometheus must be wired
|
|
545
|
+
# manually").
|
|
546
|
+
for warning in strategist.compatibility_warnings(config):
|
|
547
|
+
print_warning(warning, title="Preset compatibility")
|
|
548
|
+
|
|
549
|
+
print_success("Generated configuration files for selected stack")
|
|
498
550
|
|
|
499
551
|
# Create virtual environment and install dependencies
|
|
500
552
|
venv_path = create_venv_with_manager(project_dir, package_manager)
|
|
@@ -514,8 +566,9 @@ def init(
|
|
|
514
566
|
logger = get_logger()
|
|
515
567
|
logger.exception(f"Error during project creation in init: {str(e)}")
|
|
516
568
|
print_error(f"Error during project creation: {str(e)}")
|
|
517
|
-
|
|
518
|
-
|
|
569
|
+
_cleanup_failed_project(
|
|
570
|
+
project_dir, settings.USER_WORKSPACE, create_project_folder
|
|
571
|
+
)
|
|
519
572
|
|
|
520
573
|
return
|
|
521
574
|
|
|
@@ -560,7 +613,7 @@ def init(
|
|
|
560
613
|
for stack_name, deps in settings.PROJECT_STACKS.items():
|
|
561
614
|
table = create_info_table(
|
|
562
615
|
f"{stack_name.upper()} Stack",
|
|
563
|
-
{f"Dependency {i+1}": dep for i, dep in enumerate(deps)},
|
|
616
|
+
{f"Dependency {i + 1}": dep for i, dep in enumerate(deps)},
|
|
564
617
|
)
|
|
565
618
|
console.print(table)
|
|
566
619
|
console.print("\n")
|
|
@@ -662,8 +715,9 @@ def init(
|
|
|
662
715
|
logger = get_logger()
|
|
663
716
|
logger.exception(f"Error during project creation in init: {str(e)}")
|
|
664
717
|
print_error(f"Error during project creation: {str(e)}")
|
|
665
|
-
|
|
666
|
-
|
|
718
|
+
_cleanup_failed_project(
|
|
719
|
+
project_dir, settings.USER_WORKSPACE, create_project_folder
|
|
720
|
+
)
|
|
667
721
|
|
|
668
722
|
|
|
669
723
|
@fastkit_cli.command()
|
|
@@ -799,6 +853,20 @@ def deleteproject(ctx: Context, project_name: str) -> None:
|
|
|
799
853
|
print_error(f"Error during project deletion: {e}")
|
|
800
854
|
|
|
801
855
|
|
|
856
|
+
def _derive_app_module(project_dir: str, main_path: str) -> str:
|
|
857
|
+
"""Convert a discovered ``main.py`` path into a uvicorn ``module:attr``.
|
|
858
|
+
|
|
859
|
+
Templates can place ``main.py`` anywhere under the project (``main.py``,
|
|
860
|
+
``src/main.py``, ``src/app/main.py``, ...). The previous ``"src/"`` /
|
|
861
|
+
``""`` heuristic mis-mapped the domain-starter layout (``src/app/main.py``
|
|
862
|
+
→ wrongly produced ``src.main:app``); deriving the dotted path from the
|
|
863
|
+
actual relative location avoids that drift for any future layout too.
|
|
864
|
+
"""
|
|
865
|
+
rel_path = os.path.relpath(main_path, project_dir)
|
|
866
|
+
module_part = os.path.splitext(rel_path)[0].replace(os.sep, ".")
|
|
867
|
+
return f"{module_part}:app"
|
|
868
|
+
|
|
869
|
+
|
|
802
870
|
@fastkit_cli.command()
|
|
803
871
|
@click.option(
|
|
804
872
|
"--host",
|
|
@@ -873,10 +941,7 @@ def runserver(
|
|
|
873
941
|
return
|
|
874
942
|
|
|
875
943
|
main_path = core_modules["main"]
|
|
876
|
-
|
|
877
|
-
app_module = "src.main:app"
|
|
878
|
-
else:
|
|
879
|
-
app_module = "main:app"
|
|
944
|
+
app_module = _derive_app_module(project_dir, main_path)
|
|
880
945
|
|
|
881
946
|
if venv_python:
|
|
882
947
|
print_info(f"Using Python from virtual environment: {venv_python}")
|
|
@@ -930,9 +995,9 @@ def runserver(
|
|
|
930
995
|
logger.exception(f"FileNotFoundError when starting server: {e}")
|
|
931
996
|
if venv_python:
|
|
932
997
|
print_error(
|
|
933
|
-
|
|
998
|
+
"Failed to run Python from the virtual environment. Make sure uvicorn is installed in the project's virtual environment."
|
|
934
999
|
)
|
|
935
1000
|
else:
|
|
936
1001
|
print_error(
|
|
937
|
-
|
|
1002
|
+
"uvicorn not found. Make sure it's installed in your system Python."
|
|
938
1003
|
)
|
fastapi_fastkit/core/settings.py
CHANGED
|
@@ -24,6 +24,7 @@ class FastkitConfig:
|
|
|
24
24
|
TEMPLATE_PATHS: dict[str, list[str] | dict[str, list[str]]] = {
|
|
25
25
|
"main": [
|
|
26
26
|
"src/main.py",
|
|
27
|
+
"src/app/main.py",
|
|
27
28
|
"main.py",
|
|
28
29
|
],
|
|
29
30
|
"setup": [
|
|
@@ -37,12 +38,27 @@ class FastkitConfig:
|
|
|
37
38
|
"files": ["settings.py", "config.py"],
|
|
38
39
|
"paths": [
|
|
39
40
|
"src/core",
|
|
41
|
+
"src/app/core",
|
|
40
42
|
"src",
|
|
41
43
|
"",
|
|
42
44
|
],
|
|
43
45
|
},
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
# Architecture Presets (interactive ``init`` wizard)
|
|
49
|
+
#
|
|
50
|
+
# The preset shapes how the generated project is laid out (single file vs.
|
|
51
|
+
# layered vs. domain-oriented). Preset-specific generation logic lives in
|
|
52
|
+
# later issues — this catalog is the user-facing menu and the canonical
|
|
53
|
+
# set of preset ids persisted in the interactive config.
|
|
54
|
+
ARCHITECTURE_PRESETS: dict[str, str] = {
|
|
55
|
+
"minimal": "Smallest viable FastAPI app — a single app + a couple of files.",
|
|
56
|
+
"single-module": "Everything in one module; ideal for tiny scripts and prototypes.",
|
|
57
|
+
"classic-layered": "Layered split: api/routes, crud, schemas, core (a la fastapi-default).",
|
|
58
|
+
"domain-starter": "Domain-oriented: src/app/domains/<concept>/ with router/service/repository (recommended).",
|
|
59
|
+
}
|
|
60
|
+
DEFAULT_ARCHITECTURE_PRESET: str = "domain-starter"
|
|
61
|
+
|
|
46
62
|
# Startproject Options
|
|
47
63
|
PROJECT_STACKS: dict[str, list[str]] = {
|
|
48
64
|
"minimal": ["fastapi", "uvicorn", "pydantic", "pydantic-settings"],
|
|
@@ -102,7 +118,7 @@ class FastkitConfig:
|
|
|
102
118
|
"MySQL": ["pymysql", "aiomysql", "sqlalchemy", "alembic"],
|
|
103
119
|
"MongoDB": ["motor", "beanie"],
|
|
104
120
|
"Redis": ["redis[hiredis]", "aioredis"],
|
|
105
|
-
"SQLite": ["sqlalchemy", "alembic"],
|
|
121
|
+
"SQLite": ["sqlalchemy", "aiosqlite", "alembic"],
|
|
106
122
|
"None": [],
|
|
107
123
|
},
|
|
108
124
|
"authentication": {
|
|
@@ -18,37 +18,86 @@ template-name/
|
|
|
18
18
|
│ ├── models/
|
|
19
19
|
│ ├── routes/
|
|
20
20
|
│ └── utils/
|
|
21
|
-
├── tests/
|
|
21
|
+
├── tests/ # required
|
|
22
22
|
├── scripts/
|
|
23
|
-
├──
|
|
24
|
-
├── setup.py-tpl
|
|
25
|
-
|
|
23
|
+
├── pyproject.toml-tpl # preferred primary metadata file (PEP 621)
|
|
24
|
+
├── setup.py-tpl # legacy alternative, still accepted
|
|
25
|
+
├── requirements.txt-tpl # optional when pyproject.toml-tpl declares deps
|
|
26
|
+
└── README.md-tpl # required
|
|
26
27
|
```
|
|
27
28
|
|
|
29
|
+
The minimum required files for a modern template are `tests/`, `README.md-tpl`,
|
|
30
|
+
and at least one metadata file (`pyproject.toml-tpl` or `setup.py-tpl`).
|
|
31
|
+
`requirements.txt-tpl` is optional when the template's dependencies are
|
|
32
|
+
declared under `[project].dependencies` in `pyproject.toml-tpl`.
|
|
33
|
+
|
|
34
|
+
Modern templates **SHOULD** ship `pyproject.toml-tpl` as the primary metadata
|
|
35
|
+
file. `setup.py-tpl` remains supported for backward compatibility.
|
|
36
|
+
|
|
28
37
|
### Key Requirements:
|
|
29
38
|
|
|
30
39
|
1. All source files must use `.py-tpl` extension
|
|
31
|
-
2.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
```
|
|
41
|
-
3. `setup.py` must include `install_requires` section, it must include essential dependencies for the template project. Also, note that install_requires list must be type annotated.
|
|
42
|
-
for example:
|
|
40
|
+
2. The template must declare `fastapi` as a dependency in at least one of:
|
|
41
|
+
- `pyproject.toml-tpl` under `[project].dependencies` (preferred)
|
|
42
|
+
- `requirements.txt-tpl`
|
|
43
|
+
- `setup.py-tpl` under `install_requires`
|
|
44
|
+
3. `pyproject.toml-tpl` (preferred) should use PEP 621 metadata and carry the
|
|
45
|
+
FastAPI-fastkit identity markers so that `is_fastkit_project()` can tell
|
|
46
|
+
generated projects apart from unrelated FastAPI projects in the user's
|
|
47
|
+
workspace:
|
|
43
48
|
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
]
|
|
49
|
+
[project]
|
|
50
|
+
name = "<project_name>"
|
|
51
|
+
version = "0.1.0"
|
|
52
|
+
description = "[FastAPI-fastkit templated] <description>"
|
|
53
|
+
dependencies = [
|
|
54
|
+
"fastapi>=0.115.0",
|
|
55
|
+
...
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.fastapi-fastkit]
|
|
59
|
+
managed = true
|
|
48
60
|
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
The `[FastAPI-fastkit templated]` marker in `description` and the
|
|
62
|
+
`[tool.fastapi-fastkit]` table are both recognized by detection (any one
|
|
63
|
+
suffices; matching is case-insensitive). Metadata injection will also add
|
|
64
|
+
these markers at project-generation time if a template forgets them, but
|
|
65
|
+
authors should include them explicitly.
|
|
66
|
+
4. Legacy templates using `setup.py-tpl` should:
|
|
67
|
+
- declare dependencies via a type-annotated `install_requires` list, e.g.
|
|
68
|
+
```
|
|
69
|
+
install_requires: list[str] = [
|
|
70
|
+
...
|
|
71
|
+
]
|
|
72
|
+
```
|
|
73
|
+
- include the `[FastAPI-fastkit templated]` marker in the project
|
|
74
|
+
description. Detection falls back to a case-insensitive scan for
|
|
75
|
+
`fastapi-fastkit` in `setup.py`, so this marker keeps legacy projects
|
|
76
|
+
identifiable:
|
|
77
|
+
```
|
|
78
|
+
setup(
|
|
79
|
+
...
|
|
80
|
+
description = "[FastAPI-fastkit templated] <description>",
|
|
81
|
+
...
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
5. Basic CRUD operations example
|
|
85
|
+
6. Unit tests implementation
|
|
86
|
+
7. API documentation (OpenAPI/Swagger)
|
|
87
|
+
|
|
88
|
+
## Available templates
|
|
89
|
+
|
|
90
|
+
| Template | When to choose |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `fastapi-default` | Quick CRUD demo with the classic layered layout (`api/routes`, `crud`, `schemas`). Good first stop. |
|
|
93
|
+
| `fastapi-empty` | Minimal scaffold for users who want to add their own structure on top. |
|
|
94
|
+
| `fastapi-single-module` | Single-file sandbox for tiny prototypes / scripts. |
|
|
95
|
+
| `fastapi-async-crud` | Async-flavoured equivalent of `fastapi-default`. |
|
|
96
|
+
| `fastapi-custom-response` | Demonstrates custom response formatting / envelope patterns. |
|
|
97
|
+
| `fastapi-dockerized` | Adds a production-ready Dockerfile to the default layout. |
|
|
98
|
+
| `fastapi-psql-orm` | PostgreSQL + SQLAlchemy + Alembic; pick this when you need a real database. |
|
|
99
|
+
| `fastapi-mcp` | Model Context Protocol integration. |
|
|
100
|
+
| `fastapi-domain-starter` | **Recommended modern default for medium-sized APIs.** Pyproject-first, domain-oriented layout (`src/app/domains/<concept>/`) with a clean transport / service / repository split, plus a built-in `/health` probe. |
|
|
52
101
|
|
|
53
102
|
## Base structure of modules template
|
|
54
103
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "<project_name>"
|
|
3
3
|
version = "0.1.0"
|
|
4
|
-
description = "<description>"
|
|
4
|
+
description = "[FastAPI-fastkit templated] <description>"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "<author>", email = "<author_email>"},
|
|
7
7
|
]
|
|
@@ -41,6 +41,9 @@ dev = [
|
|
|
41
41
|
"PyYAML>=6.0.2",
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
+
[tool.fastapi-fastkit]
|
|
45
|
+
managed = true
|
|
46
|
+
|
|
44
47
|
[build-system]
|
|
45
48
|
requires = ["hatchling"]
|
|
46
49
|
build-backend = "hatchling.build"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "<project_name>"
|
|
3
3
|
version = "0.1.0"
|
|
4
|
-
description = "<description>"
|
|
4
|
+
description = "[FastAPI-fastkit templated] <description>"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "<author>", email = "<author_email>"},
|
|
7
7
|
]
|
|
@@ -41,6 +41,9 @@ dev = [
|
|
|
41
41
|
"PyYAML>=6.0.2",
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
+
[tool.fastapi-fastkit]
|
|
45
|
+
managed = true
|
|
46
|
+
|
|
44
47
|
[build-system]
|
|
45
48
|
requires = ["hatchling"]
|
|
46
49
|
build-backend = "hatchling.build"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "<project_name>"
|
|
3
3
|
version = "0.1.0"
|
|
4
|
-
description = "<description>"
|
|
4
|
+
description = "[FastAPI-fastkit templated] <description>"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "<author>", email = "<author_email>"},
|
|
7
7
|
]
|
|
@@ -38,6 +38,9 @@ dev = [
|
|
|
38
38
|
"PyYAML>=6.0.2",
|
|
39
39
|
]
|
|
40
40
|
|
|
41
|
+
[tool.fastapi-fastkit]
|
|
42
|
+
managed = true
|
|
43
|
+
|
|
41
44
|
[build-system]
|
|
42
45
|
requires = ["hatchling"]
|
|
43
46
|
build-backend = "hatchling.build"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "<project_name>"
|
|
3
3
|
version = "0.1.0"
|
|
4
|
-
description = "<description>"
|
|
4
|
+
description = "[FastAPI-fastkit templated] <description>"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "<author>", email = "<author_email>"},
|
|
7
7
|
]
|
|
@@ -41,6 +41,9 @@ dev = [
|
|
|
41
41
|
"setuptools-scm>=8.1.0",
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
+
[tool.fastapi-fastkit]
|
|
45
|
+
managed = true
|
|
46
|
+
|
|
44
47
|
[build-system]
|
|
45
48
|
requires = ["hatchling"]
|
|
46
49
|
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.idea
|
|
2
|
+
.ipynb_checkpoints
|
|
3
|
+
.mypy_cache
|
|
4
|
+
.vscode
|
|
5
|
+
__pycache__
|
|
6
|
+
.pytest_cache
|
|
7
|
+
htmlcov
|
|
8
|
+
dist
|
|
9
|
+
site
|
|
10
|
+
.coverage*
|
|
11
|
+
coverage.xml
|
|
12
|
+
.netlify
|
|
13
|
+
test.db
|
|
14
|
+
log.txt
|
|
15
|
+
Pipfile.lock
|
|
16
|
+
env3.*
|
|
17
|
+
env
|
|
18
|
+
docs_build
|
|
19
|
+
site_build
|
|
20
|
+
venv
|
|
21
|
+
.venv
|
|
22
|
+
docs.zip
|
|
23
|
+
archive.zip
|
|
24
|
+
|
|
25
|
+
# vim temporary files
|
|
26
|
+
*~
|
|
27
|
+
.*.sw?
|
|
28
|
+
.cache
|
|
29
|
+
|
|
30
|
+
# macOS
|
|
31
|
+
.DS_Store
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# FastAPI Domain Starter
|
|
2
|
+
|
|
3
|
+
Modern, domain-oriented FastAPI starter — the recommended default for
|
|
4
|
+
medium-sized API projects.
|
|
5
|
+
|
|
6
|
+
> Generated project: **<project_name>** — <description>
|
|
7
|
+
|
|
8
|
+
## When to choose this starter
|
|
9
|
+
|
|
10
|
+
Pick this template when you want:
|
|
11
|
+
|
|
12
|
+
- A **domain-oriented layout** (one folder per business concept under
|
|
13
|
+
`src/app/domains/`) rather than the layered `routes/ + crud/ + schemas/`
|
|
14
|
+
split. Domains scale better as the API grows.
|
|
15
|
+
- A clear separation between **transport** (`router.py`), **business
|
|
16
|
+
logic** (`service.py`), and **data access** (`repository.py`) so each
|
|
17
|
+
layer can evolve independently.
|
|
18
|
+
- A **`pyproject.toml`-first** project (PEP 621) compatible with `uv`,
|
|
19
|
+
`pdm`, or `poetry` out of the box — no `setup.py` to maintain.
|
|
20
|
+
- Sensible defaults for settings, testing, formatting, and a built-in
|
|
21
|
+
`/health` probe.
|
|
22
|
+
|
|
23
|
+
If you only need a quick CRUD demo, start with `fastapi-default`. If you
|
|
24
|
+
need PostgreSQL, start with `fastapi-psql-orm`. If you want a single-file
|
|
25
|
+
sandbox, use `fastapi-single-module`.
|
|
26
|
+
|
|
27
|
+
## Project structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
.
|
|
31
|
+
├── README.md
|
|
32
|
+
├── pyproject.toml
|
|
33
|
+
├── .env
|
|
34
|
+
├── .gitignore
|
|
35
|
+
├── scripts/
|
|
36
|
+
│ ├── format.sh
|
|
37
|
+
│ ├── lint.sh
|
|
38
|
+
│ ├── run-server.sh
|
|
39
|
+
│ └── test.sh
|
|
40
|
+
├── src/
|
|
41
|
+
│ └── app/
|
|
42
|
+
│ ├── main.py # FastAPI app entry point
|
|
43
|
+
│ ├── core/
|
|
44
|
+
│ │ └── config.py # pydantic-settings configuration
|
|
45
|
+
│ ├── db/
|
|
46
|
+
│ │ └── memory.py # in-memory store stand-in for a real DB
|
|
47
|
+
│ ├── api/
|
|
48
|
+
│ │ ├── router.py # aggregates health + every domain router
|
|
49
|
+
│ │ └── health.py # GET /health
|
|
50
|
+
│ └── domains/
|
|
51
|
+
│ └── items/ # example domain (CRUD over an item entity)
|
|
52
|
+
│ ├── models.py # entity dataclass
|
|
53
|
+
│ ├── schemas.py # API I/O schemas (pydantic)
|
|
54
|
+
│ ├── repository.py # data access layer
|
|
55
|
+
│ ├── service.py # business logic
|
|
56
|
+
│ └── router.py # FastAPI router for the domain
|
|
57
|
+
└── tests/
|
|
58
|
+
├── conftest.py
|
|
59
|
+
├── test_health.py
|
|
60
|
+
└── test_items.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The recipe for adding a new domain is:
|
|
64
|
+
|
|
65
|
+
1. Create `src/app/domains/<your_domain>/`.
|
|
66
|
+
2. Mirror the `items` layout (`models.py`, `schemas.py`, `repository.py`,
|
|
67
|
+
`service.py`, `router.py`).
|
|
68
|
+
3. Register the router in `src/app/api/router.py`.
|
|
69
|
+
4. Add tests under `tests/test_<your_domain>.py`.
|
|
70
|
+
|
|
71
|
+
## Running the app
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# create a virtualenv and install dependencies
|
|
75
|
+
$ uv sync # or: pip install -e ".[dev]"
|
|
76
|
+
|
|
77
|
+
# launch the dev server
|
|
78
|
+
$ bash scripts/run-server.sh
|
|
79
|
+
# or directly:
|
|
80
|
+
$ uvicorn src.app.main:app --reload
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
API docs are then served at:
|
|
84
|
+
|
|
85
|
+
- Swagger UI: <http://127.0.0.1:8000/docs>
|
|
86
|
+
- ReDoc: <http://127.0.0.1:8000/redoc>
|
|
87
|
+
|
|
88
|
+
## API endpoints
|
|
89
|
+
|
|
90
|
+
| Method | Endpoint | Description |
|
|
91
|
+
|--------|---------------------------|------------------------------|
|
|
92
|
+
| GET | `/api/v1/health` | Liveness probe |
|
|
93
|
+
| GET | `/api/v1/items` | List items |
|
|
94
|
+
| GET | `/api/v1/items/{item_id}` | Read a single item |
|
|
95
|
+
| POST | `/api/v1/items` | Create an item |
|
|
96
|
+
| PUT | `/api/v1/items/{item_id}` | Replace an item |
|
|
97
|
+
| DELETE | `/api/v1/items/{item_id}` | Delete an item |
|
|
98
|
+
|
|
99
|
+
## Running tests
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
$ bash scripts/test.sh
|
|
103
|
+
# or directly:
|
|
104
|
+
$ pytest
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Configuration
|
|
108
|
+
|
|
109
|
+
`src/app/core/config.py` reads settings from environment variables (or a
|
|
110
|
+
local `.env` file). The provided `.env` only sets a placeholder
|
|
111
|
+
`SECRET_KEY` — replace it before deploying.
|
|
112
|
+
|
|
113
|
+
## Project Origin
|
|
114
|
+
|
|
115
|
+
This project was created from the **`fastapi-domain-starter`** template
|
|
116
|
+
shipped with [FastAPI-fastkit](https://github.com/bnbong/FastAPI-fastkit).
|
|
117
|
+
|
|
118
|
+
The `FastAPI-fastkit` is an open-source project that helps Python and
|
|
119
|
+
FastAPI beginners quickly set up a FastAPI-based application development
|
|
120
|
+
environment in a framework-like structure.
|
|
121
|
+
|
|
122
|
+
For an end-to-end walkthrough of this template — the generated tree,
|
|
123
|
+
the bundled `items` example, and how to add your next domain — see the
|
|
124
|
+
[**Domain-oriented Project tutorial**](https://bnbong.github.io/FastAPI-fastkit/tutorial/domain-starter/).
|
|
125
|
+
|
|
126
|
+
### Template Information
|
|
127
|
+
- Template creator: [bnbong](mailto:bbbong9@gmail.com)
|
|
128
|
+
- FastAPI-fastkit project maintainer: [bnbong](mailto:bbbong9@gmail.com)
|