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.
Files changed (63) hide show
  1. fastapi_fastkit/__init__.py +1 -1
  2. fastapi_fastkit/backend/inspector.py +153 -32
  3. fastapi_fastkit/backend/interactive/__init__.py +2 -0
  4. fastapi_fastkit/backend/interactive/config_builder.py +21 -2
  5. fastapi_fastkit/backend/interactive/prompts.py +52 -1
  6. fastapi_fastkit/backend/interactive/selectors.py +10 -0
  7. fastapi_fastkit/backend/main.py +114 -6
  8. fastapi_fastkit/backend/package_managers/factory.py +1 -1
  9. fastapi_fastkit/backend/package_managers/pdm_manager.py +3 -3
  10. fastapi_fastkit/backend/package_managers/poetry_manager.py +71 -10
  11. fastapi_fastkit/backend/package_managers/uv_manager.py +3 -3
  12. fastapi_fastkit/backend/project_builder/__init__.py +3 -0
  13. fastapi_fastkit/backend/project_builder/config_generator.py +17 -10
  14. fastapi_fastkit/backend/project_builder/preset_layout.py +203 -0
  15. fastapi_fastkit/backend/transducer.py +0 -1
  16. fastapi_fastkit/cli.py +109 -44
  17. fastapi_fastkit/core/settings.py +17 -1
  18. fastapi_fastkit/fastapi_project_template/README.md +72 -23
  19. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/pyproject.toml-tpl +4 -1
  20. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/pyproject.toml-tpl +4 -1
  21. fastapi_fastkit/fastapi_project_template/fastapi-default/pyproject.toml-tpl +4 -1
  22. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/pyproject.toml-tpl +4 -1
  23. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.env-tpl +2 -0
  24. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.gitignore-tpl +31 -0
  25. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/README.md-tpl +128 -0
  26. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/pyproject.toml-tpl +70 -0
  27. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/requirements.txt-tpl +11 -0
  28. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/format.sh-tpl +5 -0
  29. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/lint.sh-tpl +6 -0
  30. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/run-server.sh-tpl +8 -0
  31. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/test.sh-tpl +6 -0
  32. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/__init__.py-tpl +0 -0
  33. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/__init__.py-tpl +0 -0
  34. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/__init__.py-tpl +0 -0
  35. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/health.py-tpl +11 -0
  36. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl +12 -0
  37. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/__init__.py-tpl +0 -0
  38. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/config.py-tpl +49 -0
  39. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/__init__.py-tpl +0 -0
  40. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/memory.py-tpl +48 -0
  41. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/__init__.py-tpl +0 -0
  42. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/__init__.py-tpl +9 -0
  43. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/models.py-tpl +17 -0
  44. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/repository.py-tpl +48 -0
  45. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl +59 -0
  46. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/schemas.py-tpl +23 -0
  47. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/service.py-tpl +52 -0
  48. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/main.py-tpl +25 -0
  49. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/__init__.py-tpl +0 -0
  50. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl +24 -0
  51. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_health.py-tpl +11 -0
  52. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_items.py-tpl +77 -0
  53. fastapi_fastkit/fastapi_project_template/fastapi-empty/README.md-tpl +2 -2
  54. fastapi_fastkit/fastapi_project_template/fastapi-empty/pyproject.toml-tpl +4 -1
  55. fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +4 -1
  56. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/pyproject.toml-tpl +4 -1
  57. fastapi_fastkit/fastapi_project_template/fastapi-single-module/pyproject.toml-tpl +4 -1
  58. fastapi_fastkit/utils/main.py +73 -6
  59. {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/METADATA +33 -3
  60. {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/RECORD +63 -32
  61. {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/WHEEL +1 -1
  62. {fastapi_fastkit-1.2.0.dist-info → fastapi_fastkit-1.3.0.dist-info}/entry_points.txt +0 -0
  63. {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
- from . import __version__
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="Enable interactive mode for guided project setup with feature selection.",
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 with dynamic feature selection.
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
- # Use fastapi-empty template as base
399
- template = "fastapi-empty"
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
- # Generate main.py with selected features
449
- main_py_content = generator.generate_main_py()
450
- main_py_path = os.path.join(project_dir, "src", "main.py")
451
- if not os.path.exists(main_py_path):
452
- main_py_path = os.path.join(project_dir, "main.py")
453
-
454
- with open(main_py_path, "w") as f:
455
- f.write(main_py_content)
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 = os.path.join(
463
- project_dir, "src", "config", "database.py"
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 = os.path.join(
475
- project_dir, "src", "config", "auth.py"
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, "tests", "conftest.py")
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
- generator.generate_docker_files()
495
- print_success(f"Generated Docker deployment files")
496
-
497
- print_success(f"Generated configuration files for selected stack")
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
- if os.path.exists(project_dir):
518
- shutil.rmtree(project_dir, ignore_errors=True)
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
- if os.path.exists(project_dir):
666
- shutil.rmtree(project_dir, ignore_errors=True)
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
- if "src/" in main_path:
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
- f"Failed to run Python from the virtual environment. Make sure uvicorn is installed in the project's virtual environment."
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
- f"uvicorn not found. Make sure it's installed in your system Python."
1002
+ "uvicorn not found. Make sure it's installed in your system Python."
938
1003
  )
@@ -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
- ├── requirements.txt-tpl
24
- ├── setup.py-tpl
25
- └── README.md-tpl
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. `setup.py` must include `fastapi-fastkit` string in project description
32
- for example:
33
- ```
34
- ...
35
- setup(
36
- ...
37
- description = "[fastapi-fastkit templated] <description>",
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
- install_requires: list[str] = [
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
- 4. Basic CRUD operations example
50
- 5. Unit tests implementation
51
- 6. API documentation (OpenAPI/Swagger)
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,2 @@
1
+ SECRET_KEY=changethis
2
+ ENVIRONMENT=development
@@ -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)