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.
- inifastapi-0.1.0/.claude/settings.local.json +7 -0
- inifastapi-0.1.0/.gitignore +21 -0
- inifastapi-0.1.0/.vscode/settings.json +13 -0
- inifastapi-0.1.0/AGENTS.md +28 -0
- inifastapi-0.1.0/PKG-INFO +85 -0
- inifastapi-0.1.0/README.md +74 -0
- inifastapi-0.1.0/pypi-key.txt +1 -0
- inifastapi-0.1.0/pyproject.toml +52 -0
- inifastapi-0.1.0/pyrightconfig.json +11 -0
- inifastapi-0.1.0/src/inifastapi/__init__.py +7 -0
- inifastapi-0.1.0/src/inifastapi/__main__.py +5 -0
- inifastapi-0.1.0/src/inifastapi/bootstrap.py +94 -0
- inifastapi-0.1.0/src/inifastapi/cli.py +173 -0
- inifastapi-0.1.0/src/inifastapi/options.py +134 -0
- inifastapi-0.1.0/src/inifastapi/scaffold.py +153 -0
- inifastapi-0.1.0/src/inifastapi/templates/auth/dependencies_auth.py.j2 +19 -0
- inifastapi-0.1.0/src/inifastapi/templates/auth/routes_auth.py.j2 +26 -0
- inifastapi-0.1.0/src/inifastapi/templates/auth/schemas_auth.py.j2 +10 -0
- inifastapi-0.1.0/src/inifastapi/templates/auth/security.py.j2 +21 -0
- inifastapi-0.1.0/src/inifastapi/templates/cache/cache.py.j2 +9 -0
- inifastapi-0.1.0/src/inifastapi/templates/ci/github.yml.j2 +20 -0
- inifastapi-0.1.0/src/inifastapi/templates/ci/gitlab-ci.yml.j2 +11 -0
- inifastapi-0.1.0/src/inifastapi/templates/database/alembic.ini.j2 +35 -0
- inifastapi-0.1.0/src/inifastapi/templates/database/alembic_env.py.j2 +58 -0
- inifastapi-0.1.0/src/inifastapi/templates/database/alembic_readme.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/database/alembic_script.py.mako.j2 +14 -0
- inifastapi-0.1.0/src/inifastapi/templates/database/base.py.j2 +7 -0
- inifastapi-0.1.0/src/inifastapi/templates/database/db_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/database/session.py.j2 +14 -0
- inifastapi-0.1.0/src/inifastapi/templates/docker/.dockerignore.j2 +6 -0
- inifastapi-0.1.0/src/inifastapi/templates/docker/Dockerfile.j2 +15 -0
- inifastapi-0.1.0/src/inifastapi/templates/docker/docker-compose.yml.j2 +47 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/.env.example.j2 +31 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/.gitignore.j2 +10 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/.python-version.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/README.md.j2 +47 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/api_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/api_router.py.j2 +13 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/api_routes_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/config.py.j2 +45 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/core_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/dependencies_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/lifespan.py.j2 +25 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/logging.py.j2 +15 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/main.py.j2 +32 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/middleware.py.j2 +51 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/models_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/package_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/pyproject.toml.j2 +18 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/pyrightconfig.json.j2 +15 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/routes_health.py.j2 +9 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/schemas_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/services_init.py.j2 +1 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/tests_health.py.j2 +24 -0
- inifastapi-0.1.0/src/inifastapi/templates/project/vscode_settings.json.j2 +13 -0
- inifastapi-0.1.0/src/inifastapi/templates/task_queue/celery_app.py.j2 +7 -0
- inifastapi-0.1.0/src/inifastapi/templates/task_queue/routes_tasks.py.j2 +15 -0
- inifastapi-0.1.0/src/inifastapi/templates/task_queue/sample_task.py.j2 +6 -0
- inifastapi-0.1.0/src/inifastapi/templates/task_queue/tasks_init.py.j2 +1 -0
- inifastapi-0.1.0/tests/test_bootstrap.py +149 -0
- inifastapi-0.1.0/tests/test_cli.py +261 -0
- inifastapi-0.1.0/tests/test_main.py +18 -0
- inifastapi-0.1.0/tests/test_options.py +96 -0
- inifastapi-0.1.0/tests/test_scaffold.py +133 -0
|
@@ -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,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,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)}")
|