inifastapi 0.1.0__tar.gz

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 (64) hide show
  1. inifastapi-0.1.0/.claude/settings.local.json +7 -0
  2. inifastapi-0.1.0/.gitignore +21 -0
  3. inifastapi-0.1.0/.vscode/settings.json +13 -0
  4. inifastapi-0.1.0/AGENTS.md +28 -0
  5. inifastapi-0.1.0/PKG-INFO +85 -0
  6. inifastapi-0.1.0/README.md +74 -0
  7. inifastapi-0.1.0/pypi-key.txt +1 -0
  8. inifastapi-0.1.0/pyproject.toml +52 -0
  9. inifastapi-0.1.0/pyrightconfig.json +11 -0
  10. inifastapi-0.1.0/src/inifastapi/__init__.py +7 -0
  11. inifastapi-0.1.0/src/inifastapi/__main__.py +5 -0
  12. inifastapi-0.1.0/src/inifastapi/bootstrap.py +94 -0
  13. inifastapi-0.1.0/src/inifastapi/cli.py +173 -0
  14. inifastapi-0.1.0/src/inifastapi/options.py +134 -0
  15. inifastapi-0.1.0/src/inifastapi/scaffold.py +153 -0
  16. inifastapi-0.1.0/src/inifastapi/templates/auth/dependencies_auth.py.j2 +19 -0
  17. inifastapi-0.1.0/src/inifastapi/templates/auth/routes_auth.py.j2 +26 -0
  18. inifastapi-0.1.0/src/inifastapi/templates/auth/schemas_auth.py.j2 +10 -0
  19. inifastapi-0.1.0/src/inifastapi/templates/auth/security.py.j2 +21 -0
  20. inifastapi-0.1.0/src/inifastapi/templates/cache/cache.py.j2 +9 -0
  21. inifastapi-0.1.0/src/inifastapi/templates/ci/github.yml.j2 +20 -0
  22. inifastapi-0.1.0/src/inifastapi/templates/ci/gitlab-ci.yml.j2 +11 -0
  23. inifastapi-0.1.0/src/inifastapi/templates/database/alembic.ini.j2 +35 -0
  24. inifastapi-0.1.0/src/inifastapi/templates/database/alembic_env.py.j2 +58 -0
  25. inifastapi-0.1.0/src/inifastapi/templates/database/alembic_readme.py.j2 +1 -0
  26. inifastapi-0.1.0/src/inifastapi/templates/database/alembic_script.py.mako.j2 +14 -0
  27. inifastapi-0.1.0/src/inifastapi/templates/database/base.py.j2 +7 -0
  28. inifastapi-0.1.0/src/inifastapi/templates/database/db_init.py.j2 +1 -0
  29. inifastapi-0.1.0/src/inifastapi/templates/database/session.py.j2 +14 -0
  30. inifastapi-0.1.0/src/inifastapi/templates/docker/.dockerignore.j2 +6 -0
  31. inifastapi-0.1.0/src/inifastapi/templates/docker/Dockerfile.j2 +15 -0
  32. inifastapi-0.1.0/src/inifastapi/templates/docker/docker-compose.yml.j2 +47 -0
  33. inifastapi-0.1.0/src/inifastapi/templates/project/.env.example.j2 +31 -0
  34. inifastapi-0.1.0/src/inifastapi/templates/project/.gitignore.j2 +10 -0
  35. inifastapi-0.1.0/src/inifastapi/templates/project/.python-version.j2 +1 -0
  36. inifastapi-0.1.0/src/inifastapi/templates/project/README.md.j2 +47 -0
  37. inifastapi-0.1.0/src/inifastapi/templates/project/api_init.py.j2 +1 -0
  38. inifastapi-0.1.0/src/inifastapi/templates/project/api_router.py.j2 +13 -0
  39. inifastapi-0.1.0/src/inifastapi/templates/project/api_routes_init.py.j2 +1 -0
  40. inifastapi-0.1.0/src/inifastapi/templates/project/config.py.j2 +45 -0
  41. inifastapi-0.1.0/src/inifastapi/templates/project/core_init.py.j2 +1 -0
  42. inifastapi-0.1.0/src/inifastapi/templates/project/dependencies_init.py.j2 +1 -0
  43. inifastapi-0.1.0/src/inifastapi/templates/project/lifespan.py.j2 +25 -0
  44. inifastapi-0.1.0/src/inifastapi/templates/project/logging.py.j2 +15 -0
  45. inifastapi-0.1.0/src/inifastapi/templates/project/main.py.j2 +32 -0
  46. inifastapi-0.1.0/src/inifastapi/templates/project/middleware.py.j2 +51 -0
  47. inifastapi-0.1.0/src/inifastapi/templates/project/models_init.py.j2 +1 -0
  48. inifastapi-0.1.0/src/inifastapi/templates/project/package_init.py.j2 +1 -0
  49. inifastapi-0.1.0/src/inifastapi/templates/project/pyproject.toml.j2 +18 -0
  50. inifastapi-0.1.0/src/inifastapi/templates/project/pyrightconfig.json.j2 +15 -0
  51. inifastapi-0.1.0/src/inifastapi/templates/project/routes_health.py.j2 +9 -0
  52. inifastapi-0.1.0/src/inifastapi/templates/project/schemas_init.py.j2 +1 -0
  53. inifastapi-0.1.0/src/inifastapi/templates/project/services_init.py.j2 +1 -0
  54. inifastapi-0.1.0/src/inifastapi/templates/project/tests_health.py.j2 +24 -0
  55. inifastapi-0.1.0/src/inifastapi/templates/project/vscode_settings.json.j2 +13 -0
  56. inifastapi-0.1.0/src/inifastapi/templates/task_queue/celery_app.py.j2 +7 -0
  57. inifastapi-0.1.0/src/inifastapi/templates/task_queue/routes_tasks.py.j2 +15 -0
  58. inifastapi-0.1.0/src/inifastapi/templates/task_queue/sample_task.py.j2 +6 -0
  59. inifastapi-0.1.0/src/inifastapi/templates/task_queue/tasks_init.py.j2 +1 -0
  60. inifastapi-0.1.0/tests/test_bootstrap.py +149 -0
  61. inifastapi-0.1.0/tests/test_cli.py +261 -0
  62. inifastapi-0.1.0/tests/test_main.py +18 -0
  63. inifastapi-0.1.0/tests/test_options.py +96 -0
  64. inifastapi-0.1.0/tests/test_scaffold.py +133 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "mcp__tavily__tavily_search"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,21 @@
1
+
2
+ coverage/
3
+ .DS_Store
4
+ *.log
5
+
6
+ !.env.example
7
+ playwright-report/
8
+ test-results/
9
+ .idea/
10
+
11
+ .mypy_cache
12
+ .pytest_cache
13
+ .ruff_cache
14
+
15
+ .venv
16
+ __pycache__
17
+ *.lock
18
+
19
+ coverage.*
20
+ .coverage
21
+ .storage
@@ -0,0 +1,13 @@
1
+ {
2
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
3
+ "python.terminal.activateEnvironment": true,
4
+ "python.analysis.extraPaths": [
5
+ "${workspaceFolder}/src"
6
+ ],
7
+ "python.testing.pytestEnabled": true,
8
+ "python.testing.unittestEnabled": false,
9
+ "python.testing.cwd": "${workspaceFolder}",
10
+ "python.testing.pytestArgs": [
11
+ "tests"
12
+ ]
13
+ }
@@ -0,0 +1,28 @@
1
+ # Repository Guidelines
2
+
3
+ ## Project Structure & Module Organization
4
+ Core package code lives in `src/inifastapi/`. `cli.py` exposes the Typer entrypoint, `scaffold.py` builds the output plan, `bootstrap.py` handles environment setup, and `options.py` defines normalized project settings. Template assets for generated projects live under `src/inifastapi/templates/` and are grouped by feature (`project/`, `database/`, `auth/`, `task_queue/`, `docker/`, `ci/`). Tests are in `tests/` and mirror the package surface with files such as `test_cli.py` and `test_scaffold.py`. `output/` is generated example output; treat it as a fixture, not primary source code.
5
+
6
+ ## Build, Test, and Development Commands
7
+ Use Python `3.12.10` with `uv`.
8
+
9
+ - `uv sync --group dev` installs runtime and development dependencies.
10
+ - `uv run pytest` runs the full test suite.
11
+ - `uv run pytest --cov=src/inifastapi --cov-report=term-missing` checks coverage against the enforced 100% threshold.
12
+ - `uv run ruff check .` runs linting.
13
+ - `uv run pyright` runs static type checking with `.venv` and `src` configured.
14
+ - `uv run inifastapi init demo-service --no-bootstrap-env` exercises the CLI without creating a managed environment.
15
+
16
+ ## Coding Style & Naming Conventions
17
+ Follow existing Python style: 4-space indentation, explicit type hints, and small focused functions. Use `snake_case` for modules, functions, variables, and tests. Use `PascalCase` for dataclasses such as `ProjectOptions`. Keep CLI flags and template paths descriptive and aligned with existing names like `--with-task-queue` and `templates/project/main.py.j2`. Prefer standard-library helpers before adding dependencies.
18
+
19
+ ## Testing Guidelines
20
+ Tests use `pytest`, `typer.testing.CliRunner`, and coverage. New tests should be named `test_<behavior>.py` or `test_<behavior>` and should assert observable outcomes, especially generated file paths and CLI exit codes. Keep coverage at `100`; if you add a branch in `src/inifastapi/`, add a test for it in the same change.
21
+
22
+ ## Commit & Pull Request Guidelines
23
+ This directory does not include `.git`, so no local commit history is available to infer conventions. Use short imperative commit subjects such as `Add GitLab CI template option`. Keep pull requests focused and include:
24
+
25
+ - a brief summary of behavior changes
26
+ - the commands you ran (`uv run pytest`, `uv run ruff check .`, `uv run pyright`)
27
+ - sample CLI invocations when scaffold output changes
28
+ - updated generated fixtures or templates when applicable
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: inifastapi
3
+ Version: 0.1.0
4
+ Summary: A reusable FastAPI project initializer with production-oriented templates.
5
+ Author: Haoyue
6
+ License: MIT
7
+ Requires-Python: ==3.12.10
8
+ Requires-Dist: jinja2>=3.1.6
9
+ Requires-Dist: typer>=0.24.1
10
+ Description-Content-Type: text/markdown
11
+
12
+ # inifastapi
13
+
14
+ `inifastapi` is a reusable FastAPI project initializer. It generates a production-oriented starter project with explicit router aggregation, settings, middleware, tests, and optional feature modules.
15
+
16
+ ## Requirements
17
+
18
+ `uv` must be installed on the machine. The scaffold uses `uv` both to run the CLI and to bootstrap the generated FastAPI project.
19
+
20
+ ## Usage
21
+
22
+ You do not need to install `inifastapi` first if it has been published. The recommended one-off command is:
23
+
24
+ ```bash
25
+ uvx inifastapi init my-service
26
+ ```
27
+
28
+ More examples:
29
+
30
+ ```bash
31
+ uvx inifastapi init my-service --package-name app --with-database --with-auth
32
+ uvx inifastapi init my-service --layout src --with-task-queue --with-ci --ci-provider github
33
+ ```
34
+
35
+ ## Published Package
36
+
37
+ If `inifastapi` is available on PyPI, prefer `uvx` for one-off usage:
38
+
39
+ ```bash
40
+ uvx inifastapi init my-service
41
+ ```
42
+
43
+ This downloads and runs the scaffold temporarily. It does not add `inifastapi` to the generated project's dependencies.
44
+
45
+ ## Local Development
46
+
47
+ If you are working from this repository before publishing, use the local environment:
48
+
49
+ ```bash
50
+ uv sync --group dev
51
+ uv run inifastapi init my-service
52
+ ```
53
+
54
+ You can also run the local source tree without installing it globally:
55
+
56
+ ```bash
57
+ uvx --from . inifastapi init my-service
58
+ ```
59
+
60
+ ## Install
61
+
62
+ If you want a persistent local development environment for the scaffold itself:
63
+
64
+ ```bash
65
+ uv sync --group dev
66
+ ```
67
+
68
+ ## Features
69
+
70
+ - Explicit `APIRouter` aggregation
71
+ - `pydantic-settings` configuration
72
+ - `lifespan` startup/shutdown scaffolding
73
+ - Request logging and security middleware
74
+ - Optional PostgreSQL + SQLAlchemy + Alembic
75
+ - Optional JWT auth skeleton
76
+ - Optional Redis cache skeleton
77
+ - Optional Celery + RabbitMQ skeleton
78
+ - Optional Docker and CI templates
79
+ - `uv`-managed `pyproject.toml`, `uv.lock`, and `.venv`
80
+
81
+ ## Development
82
+
83
+ ```bash
84
+ uv run python -m pytest
85
+ ```
@@ -0,0 +1,74 @@
1
+ # inifastapi
2
+
3
+ `inifastapi` is a reusable FastAPI project initializer. It generates a production-oriented starter project with explicit router aggregation, settings, middleware, tests, and optional feature modules.
4
+
5
+ ## Requirements
6
+
7
+ `uv` must be installed on the machine. The scaffold uses `uv` both to run the CLI and to bootstrap the generated FastAPI project.
8
+
9
+ ## Usage
10
+
11
+ You do not need to install `inifastapi` first if it has been published. The recommended one-off command is:
12
+
13
+ ```bash
14
+ uvx inifastapi init my-service
15
+ ```
16
+
17
+ More examples:
18
+
19
+ ```bash
20
+ uvx inifastapi init my-service --package-name app --with-database --with-auth
21
+ uvx inifastapi init my-service --layout src --with-task-queue --with-ci --ci-provider github
22
+ ```
23
+
24
+ ## Published Package
25
+
26
+ If `inifastapi` is available on PyPI, prefer `uvx` for one-off usage:
27
+
28
+ ```bash
29
+ uvx inifastapi init my-service
30
+ ```
31
+
32
+ This downloads and runs the scaffold temporarily. It does not add `inifastapi` to the generated project's dependencies.
33
+
34
+ ## Local Development
35
+
36
+ If you are working from this repository before publishing, use the local environment:
37
+
38
+ ```bash
39
+ uv sync --group dev
40
+ uv run inifastapi init my-service
41
+ ```
42
+
43
+ You can also run the local source tree without installing it globally:
44
+
45
+ ```bash
46
+ uvx --from . inifastapi init my-service
47
+ ```
48
+
49
+ ## Install
50
+
51
+ If you want a persistent local development environment for the scaffold itself:
52
+
53
+ ```bash
54
+ uv sync --group dev
55
+ ```
56
+
57
+ ## Features
58
+
59
+ - Explicit `APIRouter` aggregation
60
+ - `pydantic-settings` configuration
61
+ - `lifespan` startup/shutdown scaffolding
62
+ - Request logging and security middleware
63
+ - Optional PostgreSQL + SQLAlchemy + Alembic
64
+ - Optional JWT auth skeleton
65
+ - Optional Redis cache skeleton
66
+ - Optional Celery + RabbitMQ skeleton
67
+ - Optional Docker and CI templates
68
+ - `uv`-managed `pyproject.toml`, `uv.lock`, and `.venv`
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ uv run python -m pytest
74
+ ```
@@ -0,0 +1 @@
1
+ pypi-AgEIcHlwaS5vcmcCJDViMTcxMDUxLTA2M2MtNGYyMS1hYmNlLTZmYWFmZDY4YzE5ZQACKlszLCJhMDUwY2E5YS03ZjdmLTQ4NjUtOGZlYy1mMzczZTIwZjUyYzUiXQAABiANaxfHNgnAfiWdGjf97j26kI6sOqtKc23C-dW9ccXHKg
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "inifastapi"
7
+ version = "0.1.0"
8
+ description = "A reusable FastAPI project initializer with production-oriented templates."
9
+ readme = "README.md"
10
+ requires-python = "==3.12.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Haoyue" }
14
+ ]
15
+ dependencies = [
16
+ "jinja2>=3.1.6",
17
+ "typer>=0.24.1",
18
+ ]
19
+
20
+ [project.scripts]
21
+ inifastapi = "inifastapi.cli:app"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["src/inifastapi"]
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+
29
+ [tool.ruff]
30
+ extend-exclude = ["output"]
31
+
32
+ [tool.coverage.run]
33
+ source = ["src/inifastapi"]
34
+ omit = [
35
+ "src/inifastapi/templates/*",
36
+ "src/inifastapi/templates/**/*",
37
+ ]
38
+
39
+ [tool.coverage.report]
40
+ fail_under = 100
41
+ show_missing = true
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "coverage>=7.13.5",
46
+ "prek>=0.3.8",
47
+ "pytest>=9.0.2",
48
+ "pytest-alembic>=0.12.1",
49
+ "pytest-asyncio>=1.3.0",
50
+ "pytest-cov>=7.1.0",
51
+ "ruff>=0.15.9",
52
+ ]
@@ -0,0 +1,11 @@
1
+ {
2
+ "venvPath": ".",
3
+ "venv": ".venv",
4
+ "include": [
5
+ "src",
6
+ "tests"
7
+ ],
8
+ "extraPaths": [
9
+ "src"
10
+ ]
11
+ }
@@ -0,0 +1,7 @@
1
+ """inifastapi package."""
2
+
3
+ from .bootstrap import BootstrapError, bootstrap_project
4
+ from .options import ProjectOptions
5
+ from .scaffold import build_plan, scaffold_project
6
+
7
+ __all__ = ["BootstrapError", "ProjectOptions", "bootstrap_project", "build_plan", "scaffold_project"]
@@ -0,0 +1,5 @@
1
+ from .cli import app
2
+
3
+
4
+ if __name__ == "__main__":
5
+ app()
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import shutil
5
+ import subprocess
6
+
7
+ from .options import ProjectOptions
8
+
9
+
10
+ BASE_DEPENDENCIES = [
11
+ "fastapi[standard]",
12
+ "pydantic-settings",
13
+ ]
14
+
15
+ AUTH_DEPENDENCIES = [
16
+ "python-jose[cryptography]",
17
+ "passlib[bcrypt]",
18
+ "python-multipart",
19
+ ]
20
+
21
+ DATABASE_DEPENDENCIES = [
22
+ "sqlalchemy[asyncio]",
23
+ "alembic",
24
+ "asyncpg",
25
+ ]
26
+
27
+ CACHE_DEPENDENCIES = [
28
+ "redis[hiredis]",
29
+ ]
30
+
31
+ TASK_QUEUE_DEPENDENCIES = [
32
+ "celery",
33
+ ]
34
+
35
+ DEV_DEPENDENCIES = [
36
+ "coverage>=7.13.5",
37
+ "prek>=0.3.8",
38
+ "pytest>=9.0.2",
39
+ "pytest-alembic>=0.12.1",
40
+ "pytest-asyncio>=1.3.0",
41
+ "pytest-cov>=7.1.0",
42
+ "ruff>=0.15.9",
43
+ ]
44
+
45
+
46
+ class BootstrapError(RuntimeError):
47
+ """Raised when project environment bootstrap cannot be completed."""
48
+
49
+
50
+ def runtime_dependencies(options: ProjectOptions) -> list[str]:
51
+ dependencies = list(BASE_DEPENDENCIES)
52
+ if options.auth_enabled:
53
+ dependencies.extend(AUTH_DEPENDENCIES)
54
+ if options.database_enabled:
55
+ dependencies.extend(DATABASE_DEPENDENCIES)
56
+ if options.cache_enabled:
57
+ dependencies.extend(CACHE_DEPENDENCIES)
58
+ if options.task_queue_enabled:
59
+ dependencies.extend(TASK_QUEUE_DEPENDENCIES)
60
+ return dependencies
61
+
62
+
63
+ def dev_dependencies() -> list[str]:
64
+ return list(DEV_DEPENDENCIES)
65
+
66
+
67
+ def bootstrap_project(options: ProjectOptions) -> None:
68
+ if shutil.which("uv") is None:
69
+ raise BootstrapError(
70
+ "Generated project files, but could not bootstrap the environment because `uv` "
71
+ "is not installed. Install `uv`, then run `uv sync` in "
72
+ f"'{options.target_dir}'. For one-off usage, prefer "
73
+ f"`uvx inifastapi init {options.project_name}`."
74
+ )
75
+
76
+ runtime = runtime_dependencies(options)
77
+ if runtime:
78
+ run_uv(["uv", "add", "--no-sync", *runtime], cwd=options.target_dir)
79
+
80
+ dev = dev_dependencies()
81
+ if dev:
82
+ run_uv(["uv", "add", "--dev", "--no-sync", *dev], cwd=options.target_dir)
83
+
84
+ run_uv(["uv", "sync"], cwd=options.target_dir)
85
+
86
+
87
+ def run_uv(command: list[str], *, cwd: Path) -> subprocess.CompletedProcess[str]:
88
+ return subprocess.run(
89
+ command,
90
+ cwd=cwd,
91
+ check=True,
92
+ text=True,
93
+ capture_output=True,
94
+ )
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ import typer
7
+
8
+ from .bootstrap import BootstrapError
9
+ from .options import ProjectOptions, normalize_package_name
10
+ from .scaffold import build_plan, scaffold_project
11
+
12
+
13
+ app = typer.Typer(help="Initialize a production-oriented FastAPI project.", no_args_is_help=True)
14
+
15
+
16
+ @app.callback()
17
+ def main() -> None:
18
+ """CLI entrypoint."""
19
+
20
+
21
+ def _prompt_text(value: Optional[str], message: str, default: str) -> str:
22
+ if value:
23
+ return value
24
+ return typer.prompt(message, default=default)
25
+
26
+
27
+ def _build_options(
28
+ project_name: str,
29
+ package_name: Optional[str],
30
+ target_dir: Optional[Path],
31
+ app_name: Optional[str],
32
+ description: Optional[str],
33
+ python_version: str,
34
+ host: str,
35
+ port: int,
36
+ api_prefix: str,
37
+ docs_enabled: Optional[bool],
38
+ cors_enabled: Optional[bool],
39
+ tests_enabled: Optional[bool],
40
+ docker_enabled: Optional[bool],
41
+ ci_enabled: Optional[bool],
42
+ auth_enabled: Optional[bool],
43
+ database_enabled: Optional[bool],
44
+ cache_enabled: Optional[bool],
45
+ task_queue_enabled: Optional[bool],
46
+ layout: Optional[str],
47
+ ci_provider: Optional[str],
48
+ force: bool,
49
+ dry_run: bool,
50
+ bootstrap_env: bool,
51
+ ) -> ProjectOptions:
52
+ suggested_package = normalize_package_name(package_name or "app")
53
+ chosen_package = _prompt_text(package_name, "Python package name", suggested_package)
54
+ chosen_target = target_dir or Path(
55
+ _prompt_text(None, "Target directory", str(Path.cwd() / project_name))
56
+ )
57
+ chosen_layout = layout or typer.prompt("Project layout", default="app")
58
+ chosen_app_name = _prompt_text(app_name, "Application title", project_name.replace("-", " ").title())
59
+ chosen_description = _prompt_text(description, "Project description", f"{chosen_app_name} service")
60
+ chosen_docs_enabled = True if docs_enabled is None else docs_enabled
61
+ chosen_cors_enabled = True if cors_enabled is None else cors_enabled
62
+ chosen_tests_enabled = True if tests_enabled is None else tests_enabled
63
+ chosen_database_enabled = False if database_enabled is None else database_enabled
64
+ chosen_auth_enabled = False if auth_enabled is None else auth_enabled
65
+ chosen_cache_enabled = False if cache_enabled is None else cache_enabled
66
+ chosen_task_queue_enabled = False if task_queue_enabled is None else task_queue_enabled
67
+ chosen_docker_enabled = False if docker_enabled is None else docker_enabled
68
+ chosen_ci_enabled = False if ci_enabled is None else ci_enabled
69
+ chosen_ci_provider = ci_provider or "github"
70
+ if chosen_ci_enabled and ci_provider is None and not dry_run:
71
+ chosen_ci_provider = typer.prompt("CI provider", default="github")
72
+
73
+ return ProjectOptions(
74
+ project_name=project_name,
75
+ package_name=chosen_package,
76
+ target_dir=chosen_target,
77
+ app_name=chosen_app_name,
78
+ description=chosen_description,
79
+ python_version=python_version,
80
+ host=host,
81
+ port=port,
82
+ api_prefix=api_prefix,
83
+ docs_enabled=chosen_docs_enabled,
84
+ cors_enabled=chosen_cors_enabled,
85
+ tests_enabled=chosen_tests_enabled,
86
+ docker_enabled=chosen_docker_enabled,
87
+ ci_enabled=chosen_ci_enabled,
88
+ auth_enabled=chosen_auth_enabled,
89
+ database_enabled=chosen_database_enabled,
90
+ cache_enabled=chosen_cache_enabled,
91
+ task_queue_enabled=chosen_task_queue_enabled,
92
+ layout=chosen_layout,
93
+ ci_provider=chosen_ci_provider,
94
+ force=force,
95
+ dry_run=dry_run,
96
+ bootstrap_env=bootstrap_env,
97
+ )
98
+
99
+
100
+ @app.command()
101
+ def init(
102
+ project_name: str = typer.Argument(..., help="Distribution name for the generated project."),
103
+ package_name: Optional[str] = typer.Option(None, "--package-name", help="Python package name."),
104
+ target_dir: Optional[Path] = typer.Option(None, "--target-dir", help="Directory to generate into."),
105
+ app_name: Optional[str] = typer.Option(None, "--app-name", help="Human-readable app name."),
106
+ description: Optional[str] = typer.Option(None, "--description", help="Project description."),
107
+ python_version: str = typer.Option("3.12.10", "--python-version", help="Python version."),
108
+ host: str = typer.Option("0.0.0.0", "--host", help="Default host."),
109
+ port: int = typer.Option(8000, "--port", help="Default port."),
110
+ api_prefix: str = typer.Option("/api/v1", "--api-prefix", help="Global API prefix."),
111
+ docs_enabled: Optional[bool] = typer.Option(None, "--with-docs/--no-docs"),
112
+ cors_enabled: Optional[bool] = typer.Option(None, "--with-cors/--no-cors"),
113
+ tests_enabled: Optional[bool] = typer.Option(None, "--with-tests/--no-tests"),
114
+ docker_enabled: Optional[bool] = typer.Option(None, "--with-docker/--no-docker"),
115
+ ci_enabled: Optional[bool] = typer.Option(None, "--with-ci/--no-ci"),
116
+ auth_enabled: Optional[bool] = typer.Option(None, "--with-auth/--no-auth"),
117
+ database_enabled: Optional[bool] = typer.Option(None, "--with-database/--no-database"),
118
+ cache_enabled: Optional[bool] = typer.Option(None, "--with-cache/--no-cache"),
119
+ task_queue_enabled: Optional[bool] = typer.Option(None, "--with-task-queue/--no-task-queue"),
120
+ layout: Optional[str] = typer.Option(None, "--layout", help="app or src"),
121
+ ci_provider: Optional[str] = typer.Option(None, "--ci-provider", help="github or gitlab"),
122
+ force: bool = typer.Option(False, "--force", help="Overwrite in non-empty directories."),
123
+ dry_run: bool = typer.Option(False, "--dry-run", help="Only print the files that would be generated."),
124
+ bootstrap_env: bool = typer.Option(True, "--bootstrap-env/--no-bootstrap-env", help="Create .venv, uv.lock, and install dependencies with uv."),
125
+ ) -> None:
126
+ """Generate a FastAPI project."""
127
+ options = _build_options(
128
+ project_name=project_name,
129
+ package_name=package_name,
130
+ target_dir=target_dir,
131
+ app_name=app_name,
132
+ description=description,
133
+ python_version=python_version,
134
+ host=host,
135
+ port=port,
136
+ api_prefix=api_prefix,
137
+ docs_enabled=docs_enabled,
138
+ cors_enabled=cors_enabled,
139
+ tests_enabled=tests_enabled,
140
+ docker_enabled=docker_enabled,
141
+ ci_enabled=ci_enabled,
142
+ auth_enabled=auth_enabled,
143
+ database_enabled=database_enabled,
144
+ cache_enabled=cache_enabled,
145
+ task_queue_enabled=task_queue_enabled,
146
+ layout=layout,
147
+ ci_provider=ci_provider,
148
+ force=force,
149
+ dry_run=dry_run,
150
+ bootstrap_env=bootstrap_env,
151
+ )
152
+
153
+ try:
154
+ outputs = scaffold_project(options)
155
+ except FileExistsError as exc:
156
+ typer.secho(str(exc), fg=typer.colors.RED, err=True)
157
+ raise typer.Exit(code=1) from exc
158
+ except BootstrapError as exc:
159
+ typer.secho(str(exc), fg=typer.colors.RED, err=True)
160
+ raise typer.Exit(code=1) from exc
161
+
162
+ if options.dry_run:
163
+ typer.echo(f"Dry run for {options.project_name}:")
164
+ for path in build_plan(options):
165
+ typer.echo(f" would create {path}")
166
+ return
167
+
168
+ typer.secho(f"Generated FastAPI project at {options.target_dir}", fg=typer.colors.GREEN)
169
+ if options.bootstrap_env:
170
+ typer.echo("Environment bootstrapped with uv.")
171
+ typer.echo("Files created:")
172
+ for path in outputs:
173
+ typer.echo(f" {path.relative_to(options.target_dir)}")