fastapi-spawn 0.4.21__tar.gz → 0.4.24__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.
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/PKG-INFO +13 -1
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/README.md +12 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/__init__.py +1 -1
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/cli.py +95 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/interactive.py +95 -15
- fastapi_spawn-0.4.24/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +85 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/router.py.j2 +12 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/uploads/router.py.j2 +2 -1
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/config.py.j2 +7 -1
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/db/session.py.j2 +5 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/main.py.j2 +19 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/env_example.j2 +2 -2
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/pyproject.toml.j2 +2 -2
- fastapi_spawn-0.4.24/fastapi_spawn/templates/docker/docker-compose.yml.j2 +272 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/pyproject.toml +1 -1
- fastapi_spawn-0.4.21/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +0 -40
- fastapi_spawn-0.4.21/fastapi_spawn/templates/docker/docker-compose.yml.j2 +0 -111
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/.gitignore +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/LICENSE +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/config.py +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/constants.py +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/generator.py +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/alembic/alembic.ini.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/alembic/env.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/__init__.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/admin/setup.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/deps.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/graphql.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/auth/sso.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/pagination/router.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/payments/router.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/permissions/router.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/streaming/router.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/ai.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/cache.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/email.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/exceptions.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/logger.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/logging.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/monitoring.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/notifications.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/ocr.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/permissions.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/search.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/security.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/storage.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/vector_db.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/ws_manager.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/frontend/index.html.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/__init__.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/rate_limit.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/request_logger.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/response_format.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/Makefile.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/README.md.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/env.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/gitignore.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/pre_commit.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/github/publish.yml.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/github/tests.yml.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/db/seed.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/docker/Dockerfile.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/docker/dockerignore.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/docker/docker-compose.prod.yml.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/helm/values.yaml.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/terraform/main.tf.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/terraform/variables.tf.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/root/main.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/arq_worker.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/celery_app.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/sample_tasks.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tests/conftest.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tests/test_health.py.j2 +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/utils.py +0 -0
- {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-spawn
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.24
|
|
4
4
|
Summary: A powerful CLI tool to scaffold production-ready FastAPI projects with flexible database, auth, broker, and deployment options.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Bishwajitgarai/fastapi-spawn
|
|
6
6
|
Project-URL: Documentation, https://github.com/Bishwajitgarai/fastapi-spawn#readme
|
|
@@ -72,6 +72,11 @@ uv pip install fastapi-spawn
|
|
|
72
72
|
# Interactive TUI — guided step-by-step
|
|
73
73
|
fastapi-spawn new my-api
|
|
74
74
|
|
|
75
|
+
# Or use a preset template (New in 0.4.24!)
|
|
76
|
+
fastapi-spawn start basic my-api
|
|
77
|
+
fastapi-spawn start full-local my-api
|
|
78
|
+
fastapi-spawn start full my-api
|
|
79
|
+
|
|
75
80
|
# Full stack one-liner
|
|
76
81
|
fastapi-spawn new my-api \
|
|
77
82
|
--db postgresql \
|
|
@@ -587,6 +592,13 @@ $ fastapi-spawn add websockets
|
|
|
587
592
|
| `beanie` | mongodb |
|
|
588
593
|
| `none` | any |
|
|
589
594
|
|
|
595
|
+
## Recent Updates (v0.4.22)
|
|
596
|
+
|
|
597
|
+
- **Ollama Integration**: Updated default model to `tinyllama` for faster testing.
|
|
598
|
+
- **Supabase Fixes**: Fixed missing environment variables in configuration templates.
|
|
599
|
+
- **Frontend & Admin Mounted**: Automatically mounts the static frontend at `/` and the `sqladmin` dashboard at `/admin` (if enabled).
|
|
600
|
+
- **Service Health Checks**: Added connection checks for Elasticsearch, OpenSearch, Kafka, Vespa, Ollama, DuckDB, Chroma, and Supabase in the generated `/health/services` endpoint.
|
|
601
|
+
|
|
590
602
|
---
|
|
591
603
|
|
|
592
604
|
## Contributing
|
|
@@ -31,6 +31,11 @@ uv pip install fastapi-spawn
|
|
|
31
31
|
# Interactive TUI — guided step-by-step
|
|
32
32
|
fastapi-spawn new my-api
|
|
33
33
|
|
|
34
|
+
# Or use a preset template (New in 0.4.24!)
|
|
35
|
+
fastapi-spawn start basic my-api
|
|
36
|
+
fastapi-spawn start full-local my-api
|
|
37
|
+
fastapi-spawn start full my-api
|
|
38
|
+
|
|
34
39
|
# Full stack one-liner
|
|
35
40
|
fastapi-spawn new my-api \
|
|
36
41
|
--db postgresql \
|
|
@@ -546,6 +551,13 @@ $ fastapi-spawn add websockets
|
|
|
546
551
|
| `beanie` | mongodb |
|
|
547
552
|
| `none` | any |
|
|
548
553
|
|
|
554
|
+
## Recent Updates (v0.4.22)
|
|
555
|
+
|
|
556
|
+
- **Ollama Integration**: Updated default model to `tinyllama` for faster testing.
|
|
557
|
+
- **Supabase Fixes**: Fixed missing environment variables in configuration templates.
|
|
558
|
+
- **Frontend & Admin Mounted**: Automatically mounts the static frontend at `/` and the `sqladmin` dashboard at `/admin` (if enabled).
|
|
559
|
+
- **Service Health Checks**: Added connection checks for Elasticsearch, OpenSearch, Kafka, Vespa, Ollama, DuckDB, Chroma, and Supabase in the generated `/health/services` endpoint.
|
|
560
|
+
|
|
549
561
|
---
|
|
550
562
|
|
|
551
563
|
## Contributing
|
|
@@ -219,6 +219,101 @@ def new(
|
|
|
219
219
|
if not dry_run:
|
|
220
220
|
console.print(f"\n[bold green]✓ Project created:[/bold green] {project_path.resolve()}")
|
|
221
221
|
_print_next_steps(config)
|
|
222
|
+
@app.command("start", help="Create a project using a preset template.")
|
|
223
|
+
def start(
|
|
224
|
+
preset: str = typer.Argument(..., help="Preset name (basic, full)"),
|
|
225
|
+
project_name: str = typer.Argument(..., help="Name of the new project"),
|
|
226
|
+
output: Path = typer.Option(Path("."), "--output", "-o", help="Output directory"),
|
|
227
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing directory"),
|
|
228
|
+
) -> None:
|
|
229
|
+
_print_banner()
|
|
230
|
+
|
|
231
|
+
if preset == "basic":
|
|
232
|
+
config = ProjectConfig(
|
|
233
|
+
project_name=project_name,
|
|
234
|
+
db=Database.sqlite,
|
|
235
|
+
orm=ORM.sqlalchemy,
|
|
236
|
+
migration=MigrationTool.alembic,
|
|
237
|
+
auth=AuthType.jwt,
|
|
238
|
+
broker=Broker.none,
|
|
239
|
+
cache=Cache.none,
|
|
240
|
+
storage=Storage.local,
|
|
241
|
+
ai=AIProvider.none,
|
|
242
|
+
api_extra=APIExtra.none,
|
|
243
|
+
monitoring=MonitoringProvider.none,
|
|
244
|
+
log_lib=LogLibrary.loguru,
|
|
245
|
+
log_dest=LogDestination.local,
|
|
246
|
+
email=EmailProvider.none,
|
|
247
|
+
notify=NotificationProvider.none,
|
|
248
|
+
vector_db=VectorDB.none,
|
|
249
|
+
stack=Stack.minimal,
|
|
250
|
+
ci=CIProvider.none,
|
|
251
|
+
include_docker=True,
|
|
252
|
+
include_tests=True,
|
|
253
|
+
force=force,
|
|
254
|
+
)
|
|
255
|
+
elif preset == "full-local":
|
|
256
|
+
config = ProjectConfig(
|
|
257
|
+
project_name=project_name,
|
|
258
|
+
db=Database.postgresql,
|
|
259
|
+
orm=ORM.sqlalchemy,
|
|
260
|
+
migration=MigrationTool.alembic,
|
|
261
|
+
auth=AuthType.jwt,
|
|
262
|
+
broker=Broker.none,
|
|
263
|
+
cache=Cache.none,
|
|
264
|
+
storage=Storage.local,
|
|
265
|
+
ai=AIProvider.none,
|
|
266
|
+
api_extra=APIExtra.none,
|
|
267
|
+
monitoring=MonitoringProvider.none,
|
|
268
|
+
log_lib=LogLibrary.loguru,
|
|
269
|
+
log_dest=LogDestination.local,
|
|
270
|
+
email=EmailProvider.none,
|
|
271
|
+
notify=NotificationProvider.none,
|
|
272
|
+
vector_db=VectorDB.chroma,
|
|
273
|
+
stack=Stack.standard,
|
|
274
|
+
ci=CIProvider.none,
|
|
275
|
+
include_docker=True,
|
|
276
|
+
include_tests=True,
|
|
277
|
+
force=force,
|
|
278
|
+
)
|
|
279
|
+
elif preset == "full":
|
|
280
|
+
config = ProjectConfig(
|
|
281
|
+
project_name=project_name,
|
|
282
|
+
db=Database.postgresql,
|
|
283
|
+
orm=ORM.sqlalchemy,
|
|
284
|
+
migration=MigrationTool.alembic,
|
|
285
|
+
auth=AuthType.jwt,
|
|
286
|
+
broker=Broker.redis,
|
|
287
|
+
cache=Cache.redis,
|
|
288
|
+
storage=Storage.s3,
|
|
289
|
+
ai=AIProvider.openai,
|
|
290
|
+
api_extra=APIExtra.websockets,
|
|
291
|
+
monitoring=MonitoringProvider.both,
|
|
292
|
+
log_lib=LogLibrary.loguru,
|
|
293
|
+
log_dest=LogDestination.local,
|
|
294
|
+
email=EmailProvider.none,
|
|
295
|
+
notify=NotificationProvider.none,
|
|
296
|
+
vector_db=VectorDB.qdrant,
|
|
297
|
+
stack=Stack.full,
|
|
298
|
+
ci=CIProvider.github,
|
|
299
|
+
include_docker=True,
|
|
300
|
+
include_tests=True,
|
|
301
|
+
force=force,
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
rprint(f"[bold red]✗ Error:[/bold red] Unknown preset '{preset}'. Available presets: basic, full-local, full")
|
|
305
|
+
raise typer.Exit(1)
|
|
306
|
+
|
|
307
|
+
_print_summary(config)
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
generator = ProjectGenerator(config, output)
|
|
311
|
+
project_path = generator.generate()
|
|
312
|
+
rprint(f"\n[bold green]✓ Project created:[/bold green] {project_path.resolve()}")
|
|
313
|
+
_print_next_steps(config)
|
|
314
|
+
except Exception as exc:
|
|
315
|
+
rprint(f"\n[bold red]✗ Generation failed:[/bold red] {exc}")
|
|
316
|
+
raise typer.Exit(1) from exc
|
|
222
317
|
|
|
223
318
|
|
|
224
319
|
# ── `list-templates` command ───────────────────────────────────────────────────
|
|
@@ -19,6 +19,10 @@ from fastapi_spawn.constants import (
|
|
|
19
19
|
ORM,
|
|
20
20
|
ORM_DB_COMPAT,
|
|
21
21
|
Stack,
|
|
22
|
+
Storage,
|
|
23
|
+
AIProvider,
|
|
24
|
+
MonitoringProvider,
|
|
25
|
+
VectorDB,
|
|
22
26
|
)
|
|
23
27
|
from fastapi_spawn.validators import questionary_validator, validate_project_name
|
|
24
28
|
|
|
@@ -47,8 +51,8 @@ def prompt_project_name(default: str = "") -> str:
|
|
|
47
51
|
|
|
48
52
|
def prompt_database() -> Database:
|
|
49
53
|
choices = [
|
|
50
|
-
questionary.Choice(title=label, value=db)
|
|
51
|
-
for db, label in DB_LABELS.items()
|
|
54
|
+
questionary.Choice(title=f"{i}) {label}", value=db)
|
|
55
|
+
for i, (db, label) in enumerate(DB_LABELS.items(), 1)
|
|
52
56
|
]
|
|
53
57
|
return questionary.select(
|
|
54
58
|
"Database backend:",
|
|
@@ -68,7 +72,7 @@ def prompt_orm(db: Database) -> ORM:
|
|
|
68
72
|
orm for orm in ORM
|
|
69
73
|
if db in ORM_DB_COMPAT.get(orm, compatible)
|
|
70
74
|
]
|
|
71
|
-
choices = [questionary.Choice(title=o.value, value=o) for o in valid_orms]
|
|
75
|
+
choices = [questionary.Choice(title=f"{i}) {o.value}", value=o) for i, o in enumerate(valid_orms, 1)]
|
|
72
76
|
if not choices:
|
|
73
77
|
return ORM.none
|
|
74
78
|
return questionary.select(
|
|
@@ -80,8 +84,8 @@ def prompt_orm(db: Database) -> ORM:
|
|
|
80
84
|
|
|
81
85
|
def prompt_auth() -> AuthType:
|
|
82
86
|
choices = [
|
|
83
|
-
questionary.Choice(title=label, value=auth)
|
|
84
|
-
for auth, label in AUTH_LABELS.items()
|
|
87
|
+
questionary.Choice(title=f"{i}) {label}", value=auth)
|
|
88
|
+
for i, (auth, label) in enumerate(AUTH_LABELS.items(), 1)
|
|
85
89
|
]
|
|
86
90
|
return questionary.select(
|
|
87
91
|
"Authentication strategy:",
|
|
@@ -92,8 +96,8 @@ def prompt_auth() -> AuthType:
|
|
|
92
96
|
|
|
93
97
|
def prompt_broker() -> Broker:
|
|
94
98
|
choices = [
|
|
95
|
-
questionary.Choice(title=label, value=broker)
|
|
96
|
-
for broker, label in BROKER_LABELS.items()
|
|
99
|
+
questionary.Choice(title=f"{i}) {label}", value=broker)
|
|
100
|
+
for i, (broker, label) in enumerate(BROKER_LABELS.items(), 1)
|
|
97
101
|
]
|
|
98
102
|
return questionary.select(
|
|
99
103
|
"Message broker:",
|
|
@@ -104,8 +108,8 @@ def prompt_broker() -> Broker:
|
|
|
104
108
|
|
|
105
109
|
def prompt_cache() -> Cache:
|
|
106
110
|
choices = [
|
|
107
|
-
questionary.Choice(title=c.value, value=c)
|
|
108
|
-
for c in Cache
|
|
111
|
+
questionary.Choice(title=f"{i}) {c.value}", value=c)
|
|
112
|
+
for i, c in enumerate(Cache, 1)
|
|
109
113
|
]
|
|
110
114
|
return questionary.select(
|
|
111
115
|
"Cache layer:",
|
|
@@ -116,8 +120,8 @@ def prompt_cache() -> Cache:
|
|
|
116
120
|
|
|
117
121
|
def prompt_stack() -> Stack:
|
|
118
122
|
choices = [
|
|
119
|
-
questionary.Choice(title=f"{s.value} — {STACK_DESCRIPTIONS[s]}", value=s)
|
|
120
|
-
for s in Stack
|
|
123
|
+
questionary.Choice(title=f"{i}) {s.value} — {STACK_DESCRIPTIONS[s]}", value=s)
|
|
124
|
+
for i, s in enumerate(Stack, 1)
|
|
121
125
|
]
|
|
122
126
|
return questionary.select(
|
|
123
127
|
"Deployment stack:",
|
|
@@ -128,8 +132,8 @@ def prompt_stack() -> Stack:
|
|
|
128
132
|
|
|
129
133
|
def prompt_ci() -> CIProvider:
|
|
130
134
|
choices = [
|
|
131
|
-
questionary.Choice(title=c.value, value=c)
|
|
132
|
-
for c in CIProvider
|
|
135
|
+
questionary.Choice(title=f"{i}) {c.value}", value=c)
|
|
136
|
+
for i, c in enumerate(CIProvider, 1)
|
|
133
137
|
]
|
|
134
138
|
return questionary.select(
|
|
135
139
|
"CI/CD provider:",
|
|
@@ -140,8 +144,8 @@ def prompt_ci() -> CIProvider:
|
|
|
140
144
|
|
|
141
145
|
def prompt_log_lib() -> LogLibrary:
|
|
142
146
|
choices = [
|
|
143
|
-
questionary.Choice(title=l.value, value=l)
|
|
144
|
-
for l in LogLibrary
|
|
147
|
+
questionary.Choice(title=f"{i}) {l.value}", value=l)
|
|
148
|
+
for i, l in enumerate(LogLibrary, 1)
|
|
145
149
|
]
|
|
146
150
|
return questionary.select(
|
|
147
151
|
"Logging library:",
|
|
@@ -150,6 +154,74 @@ def prompt_log_lib() -> LogLibrary:
|
|
|
150
154
|
).unsafe_ask()
|
|
151
155
|
|
|
152
156
|
|
|
157
|
+
def prompt_storage() -> Storage:
|
|
158
|
+
use_storage = questionary.confirm(
|
|
159
|
+
"Do you need file storage?", default=False, style=SPAWN_STYLE
|
|
160
|
+
).unsafe_ask()
|
|
161
|
+
if not use_storage:
|
|
162
|
+
return Storage.none
|
|
163
|
+
choices = [
|
|
164
|
+
questionary.Choice(title=f"{i}) {s.value}", value=s)
|
|
165
|
+
for i, s in enumerate(Storage, 1) if s != Storage.none
|
|
166
|
+
]
|
|
167
|
+
return questionary.select(
|
|
168
|
+
"Storage provider:",
|
|
169
|
+
choices=choices,
|
|
170
|
+
style=SPAWN_STYLE,
|
|
171
|
+
).unsafe_ask()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def prompt_ai() -> AIProvider:
|
|
175
|
+
use_ai = questionary.confirm(
|
|
176
|
+
"Do you need AI/LLM integration?", default=False, style=SPAWN_STYLE
|
|
177
|
+
).unsafe_ask()
|
|
178
|
+
if not use_ai:
|
|
179
|
+
return AIProvider.none
|
|
180
|
+
choices = [
|
|
181
|
+
questionary.Choice(title=f"{i}) {a.value}", value=a)
|
|
182
|
+
for i, a in enumerate(AIProvider, 1) if a != AIProvider.none
|
|
183
|
+
]
|
|
184
|
+
return questionary.select(
|
|
185
|
+
"AI provider:",
|
|
186
|
+
choices=choices,
|
|
187
|
+
style=SPAWN_STYLE,
|
|
188
|
+
).unsafe_ask()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def prompt_vector_db() -> VectorDB:
|
|
192
|
+
use_vdb = questionary.confirm(
|
|
193
|
+
"Do you need a Vector Database?", default=False, style=SPAWN_STYLE
|
|
194
|
+
).unsafe_ask()
|
|
195
|
+
if not use_vdb:
|
|
196
|
+
return VectorDB.none
|
|
197
|
+
choices = [
|
|
198
|
+
questionary.Choice(title=f"{i}) {v.value}", value=v)
|
|
199
|
+
for i, v in enumerate(VectorDB, 1) if v != VectorDB.none
|
|
200
|
+
]
|
|
201
|
+
return questionary.select(
|
|
202
|
+
"Vector database:",
|
|
203
|
+
choices=choices,
|
|
204
|
+
style=SPAWN_STYLE,
|
|
205
|
+
).unsafe_ask()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def prompt_monitoring() -> MonitoringProvider:
|
|
209
|
+
use_mon = questionary.confirm(
|
|
210
|
+
"Do you need Monitoring?", default=False, style=SPAWN_STYLE
|
|
211
|
+
).unsafe_ask()
|
|
212
|
+
if not use_mon:
|
|
213
|
+
return MonitoringProvider.none
|
|
214
|
+
choices = [
|
|
215
|
+
questionary.Choice(title=f"{i}) {m.value}", value=m)
|
|
216
|
+
for i, m in enumerate(MonitoringProvider, 1) if m != MonitoringProvider.none
|
|
217
|
+
]
|
|
218
|
+
return questionary.select(
|
|
219
|
+
"Monitoring provider:",
|
|
220
|
+
choices=choices,
|
|
221
|
+
style=SPAWN_STYLE,
|
|
222
|
+
).unsafe_ask()
|
|
223
|
+
|
|
224
|
+
|
|
153
225
|
def prompt_flags() -> tuple[bool, bool]:
|
|
154
226
|
"""Returns (include_docker, include_tests)."""
|
|
155
227
|
include_docker = questionary.confirm(
|
|
@@ -175,6 +247,10 @@ def run_interactive_flow(project_name: str = "") -> dict:
|
|
|
175
247
|
stack = prompt_stack()
|
|
176
248
|
ci = prompt_ci()
|
|
177
249
|
log_lib = prompt_log_lib()
|
|
250
|
+
storage = prompt_storage()
|
|
251
|
+
ai = prompt_ai()
|
|
252
|
+
vector_db = prompt_vector_db()
|
|
253
|
+
monitoring = prompt_monitoring()
|
|
178
254
|
include_docker, include_tests = prompt_flags()
|
|
179
255
|
|
|
180
256
|
return {
|
|
@@ -187,6 +263,10 @@ def run_interactive_flow(project_name: str = "") -> dict:
|
|
|
187
263
|
"stack": stack,
|
|
188
264
|
"ci": ci,
|
|
189
265
|
"log_lib": log_lib,
|
|
266
|
+
"storage": storage,
|
|
267
|
+
"ai": ai,
|
|
268
|
+
"vector_db": vector_db,
|
|
269
|
+
"monitoring": monitoring,
|
|
190
270
|
"include_docker": include_docker,
|
|
191
271
|
"include_tests": include_tests,
|
|
192
272
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Health check endpoints — /health, /readiness, /liveness."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
router = APIRouter(prefix="/health")
|
|
11
|
+
|
|
12
|
+
_start_time = time.time()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HealthResponse(BaseModel):
|
|
16
|
+
status: str
|
|
17
|
+
uptime_seconds: float
|
|
18
|
+
version: str = "0.1.0"
|
|
19
|
+
service: str = "{{ project_name }}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@router.get("", response_model=HealthResponse, summary="Basic health check")
|
|
23
|
+
async def health() -> HealthResponse:
|
|
24
|
+
"""Returns service status and uptime."""
|
|
25
|
+
return HealthResponse(
|
|
26
|
+
status="ok",
|
|
27
|
+
uptime_seconds=round(time.time() - _start_time, 2),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.get("/services", summary="Check external services status")
|
|
32
|
+
async def check_services() -> dict:
|
|
33
|
+
"""Checks connection to external services."""
|
|
34
|
+
results = {}
|
|
35
|
+
|
|
36
|
+
{% if vector_db == "elasticsearch" %}
|
|
37
|
+
try:
|
|
38
|
+
import urllib.request
|
|
39
|
+
from app.core.config import settings
|
|
40
|
+
response = urllib.request.urlopen(settings.elasticsearch_url, timeout=5)
|
|
41
|
+
if response.status == 200:
|
|
42
|
+
results["elasticsearch"] = "connected"
|
|
43
|
+
else:
|
|
44
|
+
results["elasticsearch"] = f"failed with status {response.status}"
|
|
45
|
+
except Exception as e:
|
|
46
|
+
results["elasticsearch"] = f"error: {e}"
|
|
47
|
+
{% endif %}
|
|
48
|
+
|
|
49
|
+
{% if vector_db == "opensearch" %}
|
|
50
|
+
try:
|
|
51
|
+
import urllib.request
|
|
52
|
+
from app.core.config import settings
|
|
53
|
+
response = urllib.request.urlopen(settings.opensearch_url, timeout=5)
|
|
54
|
+
if response.status == 200:
|
|
55
|
+
results["opensearch"] = "connected"
|
|
56
|
+
else:
|
|
57
|
+
results["opensearch"] = f"failed with status {response.status}"
|
|
58
|
+
except Exception as e:
|
|
59
|
+
results["opensearch"] = f"error: {e}"
|
|
60
|
+
{% endif %}
|
|
61
|
+
|
|
62
|
+
{% if broker == "kafka" %}
|
|
63
|
+
try:
|
|
64
|
+
from app.core.kafka import kafka_producer
|
|
65
|
+
if kafka_producer.producer:
|
|
66
|
+
results["kafka"] = "connected"
|
|
67
|
+
else:
|
|
68
|
+
results["kafka"] = "not_started"
|
|
69
|
+
except Exception as e:
|
|
70
|
+
results["kafka"] = f"error: {e}"
|
|
71
|
+
{% endif %}
|
|
72
|
+
|
|
73
|
+
return results
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.get("/readiness", summary="Readiness probe (Kubernetes)")
|
|
77
|
+
async def readiness() -> dict:
|
|
78
|
+
"""Indicates whether the service is ready to accept traffic."""
|
|
79
|
+
return {"status": "ready"}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@router.get("/liveness", summary="Liveness probe (Kubernetes)")
|
|
83
|
+
async def liveness() -> dict:
|
|
84
|
+
"""Indicates whether the service process is alive."""
|
|
85
|
+
return {"status": "alive"}
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/router.py.j2
RENAMED
|
@@ -7,6 +7,12 @@ from app.api.v1.auth.router import router as auth_router
|
|
|
7
7
|
{% if has_websockets %}
|
|
8
8
|
from app.api.v1.ws.router import router as ws_router
|
|
9
9
|
{% endif %}
|
|
10
|
+
{% if "rbac" in extras %}
|
|
11
|
+
from app.api.v1.permissions.router import router as permissions_router
|
|
12
|
+
{% endif %}
|
|
13
|
+
{% if "uploads" in extras %}
|
|
14
|
+
from app.api.v1.uploads.router import router as uploads_router
|
|
15
|
+
{% endif %}
|
|
10
16
|
|
|
11
17
|
|
|
12
18
|
router = APIRouter()
|
|
@@ -18,3 +24,9 @@ router.include_router(auth_router)
|
|
|
18
24
|
{% if has_websockets %}
|
|
19
25
|
router.include_router(ws_router)
|
|
20
26
|
{% endif %}
|
|
27
|
+
{% if "rbac" in extras %}
|
|
28
|
+
router.include_router(permissions_router)
|
|
29
|
+
{% endif %}
|
|
30
|
+
{% if "uploads" in extras %}
|
|
31
|
+
router.include_router(uploads_router)
|
|
32
|
+
{% endif %}
|
|
@@ -2,6 +2,7 @@ from fastapi import APIRouter, UploadFile, File, HTTPException
|
|
|
2
2
|
import shutil
|
|
3
3
|
import os
|
|
4
4
|
import uuid
|
|
5
|
+
from app.core.config import settings
|
|
5
6
|
|
|
6
7
|
router = APIRouter(
|
|
7
8
|
prefix="/uploads",
|
|
@@ -25,7 +26,7 @@ async def upload_file(file: UploadFile = File(...)):
|
|
|
25
26
|
safe_filename = f"{uuid.uuid4().hex}{ext}"
|
|
26
27
|
|
|
27
28
|
# Save locally as an example
|
|
28
|
-
upload_dir =
|
|
29
|
+
upload_dir = settings.LOCAL_STORAGE_DIR
|
|
29
30
|
os.makedirs(upload_dir, exist_ok=True)
|
|
30
31
|
file_path = os.path.join(upload_dir, safe_filename)
|
|
31
32
|
|
|
@@ -49,6 +49,7 @@ class Settings(BaseSettings):
|
|
|
49
49
|
LOG_LEVEL: str = "INFO"
|
|
50
50
|
LOG_DIR: str = "logs"
|
|
51
51
|
LOG_BACKUP_DAYS: int = 30
|
|
52
|
+
LOCAL_STORAGE_DIR: str = "media"
|
|
52
53
|
|
|
53
54
|
{% if db == "postgresql" %}
|
|
54
55
|
# ── PostgreSQL ─────────────────────────────────────────────────────────
|
|
@@ -178,13 +179,18 @@ class Settings(BaseSettings):
|
|
|
178
179
|
# ── Ollama (local) ─────────────────────────────────────────────────────
|
|
179
180
|
OLLAMA_HOST: str = "localhost"
|
|
180
181
|
OLLAMA_PORT: str = "11434"
|
|
181
|
-
OLLAMA_MODEL: str = "
|
|
182
|
+
OLLAMA_MODEL: str = "tinyllama"
|
|
182
183
|
|
|
183
184
|
@property
|
|
184
185
|
def ollama_url(self) -> str:
|
|
185
186
|
return f"http://{self.OLLAMA_HOST}:{self.OLLAMA_PORT}"
|
|
186
187
|
|
|
187
188
|
{% endif %}
|
|
189
|
+
{% if db == "supabase" or vector_db == "supabase" %}
|
|
190
|
+
# ── Supabase ───────────────────────────────────────────────────────────
|
|
191
|
+
SUPABASE_URL: str = ""
|
|
192
|
+
SUPABASE_KEY: str = ""
|
|
193
|
+
{% endif %}
|
|
188
194
|
{% if has_sentry %}
|
|
189
195
|
# ── Sentry ─────────────────────────────────────────────────────────────
|
|
190
196
|
SENTRY_DSN: str = ""
|
|
@@ -72,6 +72,11 @@ from beanie import init_beanie
|
|
|
72
72
|
|
|
73
73
|
from app.core.config import settings
|
|
74
74
|
|
|
75
|
+
# Workaround for Beanie/Motor compatibility issue on Python 3.12
|
|
76
|
+
if not hasattr(motor.motor_asyncio.AsyncIOMotorClient, "append_metadata"):
|
|
77
|
+
def append_metadata(self, metadata):
|
|
78
|
+
pass
|
|
79
|
+
motor.motor_asyncio.AsyncIOMotorClient.append_metadata = append_metadata
|
|
75
80
|
|
|
76
81
|
async def init_db() -> None:
|
|
77
82
|
client = motor.motor_asyncio.AsyncIOMotorClient(settings.mongodb_url)
|
|
@@ -66,6 +66,25 @@ def create_application() -> FastAPI:
|
|
|
66
66
|
application.include_router(graphql_router, prefix="/graphql")
|
|
67
67
|
{% endif %}
|
|
68
68
|
|
|
69
|
+
{% if "admin" in extras %}
|
|
70
|
+
# Mount Admin
|
|
71
|
+
try:
|
|
72
|
+
from app.admin.setup import setup_admin
|
|
73
|
+
from app.db.session import engine
|
|
74
|
+
setup_admin(application, engine)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
import logging
|
|
77
|
+
logging.warning(f"Failed to setup admin: {e}")
|
|
78
|
+
{% endif %}
|
|
79
|
+
|
|
80
|
+
# Mount StaticFiles for frontend (must be last)
|
|
81
|
+
from fastapi.staticfiles import StaticFiles
|
|
82
|
+
import os
|
|
83
|
+
current_dir = os.path.dirname(__file__)
|
|
84
|
+
frontend_path = os.path.join(current_dir, "frontend")
|
|
85
|
+
if os.path.exists(frontend_path):
|
|
86
|
+
application.mount("/", StaticFiles(directory=frontend_path, html=True), name="frontend")
|
|
87
|
+
|
|
69
88
|
return application
|
|
70
89
|
|
|
71
90
|
|
|
@@ -174,7 +174,7 @@ GEMINI_MODEL=gemini-1.5-pro
|
|
|
174
174
|
# Ollama — local LLM, no API key (active)
|
|
175
175
|
OLLAMA_HOST=localhost
|
|
176
176
|
OLLAMA_PORT=11434
|
|
177
|
-
OLLAMA_MODEL=
|
|
177
|
+
OLLAMA_MODEL=tinyllama
|
|
178
178
|
{% elif ai == "langchain" %}
|
|
179
179
|
# LangChain (active) — uses OpenAI backend by default
|
|
180
180
|
OPENAI_API_KEY=sk-placeholder
|
|
@@ -192,7 +192,7 @@ OPENAI_BASE_URL=
|
|
|
192
192
|
# OpenAI: OPENAI_API_KEY=sk-placeholder | OPENAI_MODEL=gpt-4o | OPENAI_EMBEDDING_MODEL=text-embedding-3-small | OPENAI_BASE_URL=
|
|
193
193
|
# Anthropic: ANTHROPIC_API_KEY=sk-ant-placeholder | ANTHROPIC_MODEL=claude-3-5-sonnet-20241022
|
|
194
194
|
# Gemini: GEMINI_API_KEY=CHANGE_ME | GEMINI_MODEL=gemini-1.5-pro
|
|
195
|
-
# Ollama: OLLAMA_HOST=localhost | OLLAMA_PORT=11434 | OLLAMA_MODEL=
|
|
195
|
+
# Ollama: OLLAMA_HOST=localhost | OLLAMA_PORT=11434 | OLLAMA_MODEL=tinyllama (no API key)
|
|
196
196
|
# LangChain: uses OPENAI_* vars above
|
|
197
197
|
# LlamaIndex: uses OPENAI_* vars above
|
|
198
198
|
{% endif %}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build: .
|
|
4
|
+
ports:
|
|
5
|
+
- "8000:8000"
|
|
6
|
+
env_file:
|
|
7
|
+
- .env
|
|
8
|
+
{% if (has_relational_db and db != "sqlite") or has_mongo or broker == "redis" or cache == "redis" or broker == "rabbitmq" %}
|
|
9
|
+
depends_on:
|
|
10
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
11
|
+
postgres:
|
|
12
|
+
condition: service_healthy
|
|
13
|
+
{% endif %}
|
|
14
|
+
{% if has_relational_db and db == "mysql" %}
|
|
15
|
+
mysql:
|
|
16
|
+
condition: service_healthy
|
|
17
|
+
{% endif %}
|
|
18
|
+
{% if has_mongo %}
|
|
19
|
+
mongodb:
|
|
20
|
+
condition: service_healthy
|
|
21
|
+
{% endif %}
|
|
22
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
23
|
+
redis:
|
|
24
|
+
condition: service_healthy
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% if broker == "rabbitmq" %}
|
|
27
|
+
rabbitmq:
|
|
28
|
+
condition: service_healthy
|
|
29
|
+
{% endif %}
|
|
30
|
+
{% endif %}
|
|
31
|
+
volumes:
|
|
32
|
+
- .:/app
|
|
33
|
+
{% if (has_relational_db and db != "sqlite") or has_mongo or broker == "redis" or cache == "redis" or broker == "rabbitmq" or vector_db == "qdrant" or vector_db == "elasticsearch" or vector_db == "opensearch" or ai == "ollama" %}
|
|
34
|
+
environment:
|
|
35
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
36
|
+
- POSTGRES_HOST=postgres
|
|
37
|
+
{% endif %}
|
|
38
|
+
{% if has_relational_db and db == "mysql" %}
|
|
39
|
+
- MYSQL_HOST=mysql
|
|
40
|
+
{% endif %}
|
|
41
|
+
{% if has_mongo %}
|
|
42
|
+
- MONGODB_HOST=mongodb
|
|
43
|
+
{% endif %}
|
|
44
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
45
|
+
- REDIS_HOST=redis
|
|
46
|
+
{% endif %}
|
|
47
|
+
{% if broker == "rabbitmq" %}
|
|
48
|
+
- RABBITMQ_HOST=rabbitmq
|
|
49
|
+
{% endif %}
|
|
50
|
+
{% if vector_db == "elasticsearch" %}
|
|
51
|
+
- ELASTICSEARCH_HOST=elasticsearch
|
|
52
|
+
{% endif %}
|
|
53
|
+
{% if vector_db == "opensearch" %}
|
|
54
|
+
- OPENSEARCH_HOST=opensearch
|
|
55
|
+
{% endif %}
|
|
56
|
+
{% if vector_db == "qdrant" %}
|
|
57
|
+
- QDRANT_HOST=qdrant
|
|
58
|
+
{% endif %}
|
|
59
|
+
{% if ai == "ollama" %}
|
|
60
|
+
- OLLAMA_HOST=ollama
|
|
61
|
+
{% endif %}
|
|
62
|
+
{% endif %}
|
|
63
|
+
restart: unless-stopped
|
|
64
|
+
|
|
65
|
+
{% if has_broker and broker != "kafka" %}
|
|
66
|
+
worker:
|
|
67
|
+
build: .
|
|
68
|
+
command: celery -A tasks.celery_app worker --loglevel=info --concurrency=2
|
|
69
|
+
env_file:
|
|
70
|
+
- .env
|
|
71
|
+
depends_on:
|
|
72
|
+
- app
|
|
73
|
+
environment:
|
|
74
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
75
|
+
- POSTGRES_HOST=postgres
|
|
76
|
+
{% endif %}
|
|
77
|
+
{% if has_relational_db and db == "mysql" %}
|
|
78
|
+
- MYSQL_HOST=mysql
|
|
79
|
+
{% endif %}
|
|
80
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
81
|
+
- REDIS_HOST=redis
|
|
82
|
+
{% endif %}
|
|
83
|
+
{% if broker == "rabbitmq" %}
|
|
84
|
+
- RABBITMQ_HOST=rabbitmq
|
|
85
|
+
{% endif %}
|
|
86
|
+
restart: unless-stopped
|
|
87
|
+
{% endif %}
|
|
88
|
+
|
|
89
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
90
|
+
postgres:
|
|
91
|
+
image: postgres:16-alpine
|
|
92
|
+
environment:
|
|
93
|
+
POSTGRES_DB: {{ slug }}_db
|
|
94
|
+
POSTGRES_USER: postgres
|
|
95
|
+
POSTGRES_PASSWORD: postgres
|
|
96
|
+
ports:
|
|
97
|
+
- "5432:5432"
|
|
98
|
+
volumes:
|
|
99
|
+
- postgres_data:/var/lib/postgresql/data
|
|
100
|
+
healthcheck:
|
|
101
|
+
test: ["CMD-SHELL", "pg_isready -U postgres -d {{ slug }}_db"]
|
|
102
|
+
interval: 10s
|
|
103
|
+
timeout: 5s
|
|
104
|
+
retries: 5
|
|
105
|
+
{% endif %}
|
|
106
|
+
|
|
107
|
+
{% if has_relational_db and db == "mysql" %}
|
|
108
|
+
mysql:
|
|
109
|
+
image: mysql:8
|
|
110
|
+
environment:
|
|
111
|
+
MYSQL_DATABASE: {{ slug }}_db
|
|
112
|
+
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD:-mysql}
|
|
113
|
+
ports:
|
|
114
|
+
- "3306:3306"
|
|
115
|
+
volumes:
|
|
116
|
+
- mysql_data:/var/lib/mysql
|
|
117
|
+
healthcheck:
|
|
118
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
119
|
+
interval: 10s
|
|
120
|
+
timeout: 5s
|
|
121
|
+
retries: 5
|
|
122
|
+
{% endif %}
|
|
123
|
+
|
|
124
|
+
{% if has_mongo %}
|
|
125
|
+
mongodb:
|
|
126
|
+
image: mongo:7
|
|
127
|
+
ports:
|
|
128
|
+
- "27017:27017"
|
|
129
|
+
environment:
|
|
130
|
+
- MONGO_INITDB_ROOT_USERNAME=${MONGODB_USER:-mongo}
|
|
131
|
+
- MONGO_INITDB_ROOT_PASSWORD=${MONGODB_PASSWORD:-mongo}
|
|
132
|
+
volumes:
|
|
133
|
+
- mongo_data:/data/db
|
|
134
|
+
healthcheck:
|
|
135
|
+
test: ["CMD-SHELL", "mongosh --eval 'db.runCommand({ping: 1})' --quiet"]
|
|
136
|
+
interval: 10s
|
|
137
|
+
timeout: 5s
|
|
138
|
+
retries: 5
|
|
139
|
+
{% endif %}
|
|
140
|
+
|
|
141
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
142
|
+
redis:
|
|
143
|
+
image: redis:7-alpine
|
|
144
|
+
ports:
|
|
145
|
+
- "6379:6379"
|
|
146
|
+
volumes:
|
|
147
|
+
- redis_data:/data
|
|
148
|
+
healthcheck:
|
|
149
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
150
|
+
interval: 10s
|
|
151
|
+
timeout: 3s
|
|
152
|
+
retries: 3
|
|
153
|
+
{% endif %}
|
|
154
|
+
|
|
155
|
+
{% if broker == "rabbitmq" %}
|
|
156
|
+
rabbitmq:
|
|
157
|
+
image: rabbitmq:3-management-alpine
|
|
158
|
+
ports:
|
|
159
|
+
- "5672:5672"
|
|
160
|
+
- "15672:15672"
|
|
161
|
+
environment:
|
|
162
|
+
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-guest}
|
|
163
|
+
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-guest}
|
|
164
|
+
healthcheck:
|
|
165
|
+
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
|
166
|
+
interval: 10s
|
|
167
|
+
timeout: 5s
|
|
168
|
+
retries: 5
|
|
169
|
+
{% endif %}
|
|
170
|
+
|
|
171
|
+
{% if vector_db == "qdrant" %}
|
|
172
|
+
qdrant:
|
|
173
|
+
image: qdrant/qdrant:latest
|
|
174
|
+
ports:
|
|
175
|
+
- "6333:6333"
|
|
176
|
+
volumes:
|
|
177
|
+
- qdrant_data:/qdrant/storage
|
|
178
|
+
{% endif %}
|
|
179
|
+
|
|
180
|
+
{% if vector_db == "elasticsearch" %}
|
|
181
|
+
elasticsearch:
|
|
182
|
+
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
|
|
183
|
+
environment:
|
|
184
|
+
- discovery.type=single-node
|
|
185
|
+
- xpack.security.enabled=false
|
|
186
|
+
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
187
|
+
ports:
|
|
188
|
+
- "9200:9200"
|
|
189
|
+
volumes:
|
|
190
|
+
- es_data:/usr/share/elasticsearch/data
|
|
191
|
+
{% endif %}
|
|
192
|
+
|
|
193
|
+
{% if vector_db == "opensearch" %}
|
|
194
|
+
opensearch:
|
|
195
|
+
image: opensearchproject/opensearch:2.11.0
|
|
196
|
+
environment:
|
|
197
|
+
- discovery.type=single-node
|
|
198
|
+
- plugins.security.disabled=true
|
|
199
|
+
- OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m
|
|
200
|
+
ports:
|
|
201
|
+
- "9200:9200"
|
|
202
|
+
volumes:
|
|
203
|
+
- opensearch_data:/usr/share/opensearch/data
|
|
204
|
+
{% endif %}
|
|
205
|
+
|
|
206
|
+
{% if ai == "ollama" %}
|
|
207
|
+
ollama:
|
|
208
|
+
image: ollama/ollama:latest
|
|
209
|
+
ports:
|
|
210
|
+
- "11434:11434"
|
|
211
|
+
volumes:
|
|
212
|
+
- ollama_data:/root/.ollama
|
|
213
|
+
{% endif %}
|
|
214
|
+
|
|
215
|
+
{% if broker == "kafka" %}
|
|
216
|
+
kafka:
|
|
217
|
+
image: confluentinc/cp-kafka:7.5.0
|
|
218
|
+
ports:
|
|
219
|
+
- "9092:9092"
|
|
220
|
+
environment:
|
|
221
|
+
- KAFKA_NODE_ID=1
|
|
222
|
+
- KAFKA_PROCESS_ROLES=broker,controller
|
|
223
|
+
- KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
|
|
224
|
+
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092
|
|
225
|
+
- KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER
|
|
226
|
+
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
|
227
|
+
- KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093
|
|
228
|
+
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
|
|
229
|
+
- KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
|
|
230
|
+
- KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1
|
|
231
|
+
- CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk
|
|
232
|
+
{% endif %}
|
|
233
|
+
|
|
234
|
+
{% if vector_db == "vespa" %}
|
|
235
|
+
vespa:
|
|
236
|
+
image: vespaengine/vespa
|
|
237
|
+
ports:
|
|
238
|
+
- "8080:8080"
|
|
239
|
+
volumes:
|
|
240
|
+
- vespa_data:/opt/vespa/var
|
|
241
|
+
{% endif %}
|
|
242
|
+
|
|
243
|
+
{% if (has_relational_db and db != "sqlite") or has_mongo or broker == "redis" or cache == "redis" or vector_db == "qdrant" or vector_db == "elasticsearch" or vector_db == "opensearch" or ai == "ollama" or vector_db == "vespa" %}
|
|
244
|
+
volumes:
|
|
245
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
246
|
+
postgres_data:
|
|
247
|
+
{% endif %}
|
|
248
|
+
{% if has_relational_db and db == "mysql" %}
|
|
249
|
+
mysql_data:
|
|
250
|
+
{% endif %}
|
|
251
|
+
{% if has_mongo %}
|
|
252
|
+
mongo_data:
|
|
253
|
+
{% endif %}
|
|
254
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
255
|
+
redis_data:
|
|
256
|
+
{% endif %}
|
|
257
|
+
{% if vector_db == "qdrant" %}
|
|
258
|
+
qdrant_data:
|
|
259
|
+
{% endif %}
|
|
260
|
+
{% if vector_db == "elasticsearch" %}
|
|
261
|
+
es_data:
|
|
262
|
+
{% endif %}
|
|
263
|
+
{% if vector_db == "opensearch" %}
|
|
264
|
+
opensearch_data:
|
|
265
|
+
{% endif %}
|
|
266
|
+
{% if ai == "ollama" %}
|
|
267
|
+
ollama_data:
|
|
268
|
+
{% endif %}
|
|
269
|
+
{% if vector_db == "vespa" %}
|
|
270
|
+
vespa_data:
|
|
271
|
+
{% endif %}
|
|
272
|
+
{% endif %}
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fastapi-spawn"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.24"
|
|
8
8
|
description = "A powerful CLI tool to scaffold production-ready FastAPI projects with flexible database, auth, broker, and deployment options."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"""Health check endpoints — /health, /readiness, /liveness."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
|
|
7
|
-
from fastapi import APIRouter
|
|
8
|
-
from pydantic import BaseModel
|
|
9
|
-
|
|
10
|
-
router = APIRouter(prefix="/health")
|
|
11
|
-
|
|
12
|
-
_start_time = time.time()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class HealthResponse(BaseModel):
|
|
16
|
-
status: str
|
|
17
|
-
uptime_seconds: float
|
|
18
|
-
version: str = "0.1.0"
|
|
19
|
-
service: str = "{{ project_name }}"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@router.get("", response_model=HealthResponse, summary="Basic health check")
|
|
23
|
-
async def health() -> HealthResponse:
|
|
24
|
-
"""Returns service status and uptime."""
|
|
25
|
-
return HealthResponse(
|
|
26
|
-
status="ok",
|
|
27
|
-
uptime_seconds=round(time.time() - _start_time, 2),
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@router.get("/readiness", summary="Readiness probe (Kubernetes)")
|
|
32
|
-
async def readiness() -> dict:
|
|
33
|
-
"""Indicates whether the service is ready to accept traffic."""
|
|
34
|
-
return {"status": "ready"}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@router.get("/liveness", summary="Liveness probe (Kubernetes)")
|
|
38
|
-
async def liveness() -> dict:
|
|
39
|
-
"""Indicates whether the service process is alive."""
|
|
40
|
-
return {"status": "alive"}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
services:
|
|
2
|
-
app:
|
|
3
|
-
build: .
|
|
4
|
-
ports:
|
|
5
|
-
- "8000:8000"
|
|
6
|
-
env_file:
|
|
7
|
-
- .env
|
|
8
|
-
depends_on:
|
|
9
|
-
{% if has_relational_db and db == "postgresql" %}
|
|
10
|
-
- postgres
|
|
11
|
-
{% endif %}
|
|
12
|
-
{% if has_mongo %}
|
|
13
|
-
- mongodb
|
|
14
|
-
{% endif %}
|
|
15
|
-
{% if broker == "redis" or cache == "redis" %}
|
|
16
|
-
- redis
|
|
17
|
-
{% endif %}
|
|
18
|
-
{% if broker == "rabbitmq" %}
|
|
19
|
-
- rabbitmq
|
|
20
|
-
{% endif %}
|
|
21
|
-
volumes:
|
|
22
|
-
- .:/app
|
|
23
|
-
environment:
|
|
24
|
-
{% if has_relational_db and db == "postgresql" %}
|
|
25
|
-
- POSTGRES_HOST=postgres
|
|
26
|
-
{% endif %}
|
|
27
|
-
{% if broker == "redis" or cache == "redis" %}
|
|
28
|
-
- REDIS_HOST=redis
|
|
29
|
-
{% endif %}
|
|
30
|
-
restart: unless-stopped
|
|
31
|
-
|
|
32
|
-
{% if has_broker and broker != "kafka" %}
|
|
33
|
-
worker:
|
|
34
|
-
build: .
|
|
35
|
-
command: celery -A tasks.celery_app worker --loglevel=info --concurrency=2
|
|
36
|
-
env_file:
|
|
37
|
-
- .env
|
|
38
|
-
depends_on:
|
|
39
|
-
- app
|
|
40
|
-
environment:
|
|
41
|
-
{% if has_relational_db and db == "postgresql" %}
|
|
42
|
-
- POSTGRES_HOST=postgres
|
|
43
|
-
{% endif %}
|
|
44
|
-
{% if broker == "redis" or cache == "redis" %}
|
|
45
|
-
- REDIS_HOST=redis
|
|
46
|
-
{% endif %}
|
|
47
|
-
restart: unless-stopped
|
|
48
|
-
{% endif %}
|
|
49
|
-
|
|
50
|
-
{% if has_relational_db and db == "postgresql" %}
|
|
51
|
-
postgres:
|
|
52
|
-
image: postgres:16-alpine
|
|
53
|
-
environment:
|
|
54
|
-
POSTGRES_DB: {{ slug }}_db
|
|
55
|
-
POSTGRES_USER: postgres
|
|
56
|
-
POSTGRES_PASSWORD: postgres
|
|
57
|
-
ports:
|
|
58
|
-
- "5432:5432"
|
|
59
|
-
volumes:
|
|
60
|
-
- postgres_data:/var/lib/postgresql/data
|
|
61
|
-
healthcheck:
|
|
62
|
-
test: ["CMD-SHELL", "pg_isready -U user -d {{ slug }}_db"]
|
|
63
|
-
interval: 10s
|
|
64
|
-
timeout: 5s
|
|
65
|
-
retries: 5
|
|
66
|
-
{% endif %}
|
|
67
|
-
|
|
68
|
-
{% if has_mongo %}
|
|
69
|
-
mongodb:
|
|
70
|
-
image: mongo:7
|
|
71
|
-
ports:
|
|
72
|
-
- "27017:27017"
|
|
73
|
-
volumes:
|
|
74
|
-
- mongo_data:/data/db
|
|
75
|
-
{% endif %}
|
|
76
|
-
|
|
77
|
-
{% if broker == "redis" or cache == "redis" %}
|
|
78
|
-
redis:
|
|
79
|
-
image: redis:7-alpine
|
|
80
|
-
ports:
|
|
81
|
-
- "6379:6379"
|
|
82
|
-
volumes:
|
|
83
|
-
- redis_data:/data
|
|
84
|
-
healthcheck:
|
|
85
|
-
test: ["CMD", "redis-cli", "ping"]
|
|
86
|
-
interval: 10s
|
|
87
|
-
timeout: 3s
|
|
88
|
-
retries: 3
|
|
89
|
-
{% endif %}
|
|
90
|
-
|
|
91
|
-
{% if broker == "rabbitmq" %}
|
|
92
|
-
rabbitmq:
|
|
93
|
-
image: rabbitmq:3-management-alpine
|
|
94
|
-
ports:
|
|
95
|
-
- "5672:5672"
|
|
96
|
-
- "15672:15672"
|
|
97
|
-
environment:
|
|
98
|
-
RABBITMQ_DEFAULT_USER: guest
|
|
99
|
-
RABBITMQ_DEFAULT_PASS: guest
|
|
100
|
-
{% endif %}
|
|
101
|
-
|
|
102
|
-
volumes:
|
|
103
|
-
{% if has_relational_db and db == "postgresql" %}
|
|
104
|
-
postgres_data:
|
|
105
|
-
{% endif %}
|
|
106
|
-
{% if has_mongo %}
|
|
107
|
-
mongo_data:
|
|
108
|
-
{% endif %}
|
|
109
|
-
{% if broker == "redis" or cache == "redis" %}
|
|
110
|
-
redis_data:
|
|
111
|
-
{% endif %}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/alembic/alembic.ini.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/auth/sso.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/exceptions.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/logging.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/monitoring.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/notifications.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/permissions.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/security.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/storage.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/vector_db.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/ws_manager.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/frontend/index.html.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/__init__.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/github/publish.yml.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/github/tests.yml.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/docker/dockerignore.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/helm/values.yaml.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/terraform/main.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/arq_worker.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/celery_app.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/sample_tasks.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tests/test_health.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|