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.
Files changed (79) hide show
  1. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/PKG-INFO +13 -1
  2. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/README.md +12 -0
  3. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/__init__.py +1 -1
  4. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/cli.py +95 -0
  5. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/interactive.py +95 -15
  6. fastapi_spawn-0.4.24/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +85 -0
  7. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/router.py.j2 +12 -0
  8. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/uploads/router.py.j2 +2 -1
  9. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/config.py.j2 +7 -1
  10. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/db/session.py.j2 +5 -0
  11. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/main.py.j2 +19 -0
  12. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/env_example.j2 +2 -2
  13. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/pyproject.toml.j2 +2 -2
  14. fastapi_spawn-0.4.24/fastapi_spawn/templates/docker/docker-compose.yml.j2 +272 -0
  15. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/pyproject.toml +1 -1
  16. fastapi_spawn-0.4.21/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +0 -40
  17. fastapi_spawn-0.4.21/fastapi_spawn/templates/docker/docker-compose.yml.j2 +0 -111
  18. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/.gitignore +0 -0
  19. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/LICENSE +0 -0
  20. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/config.py +0 -0
  21. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/constants.py +0 -0
  22. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/generator.py +0 -0
  23. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/alembic/alembic.ini.j2 +0 -0
  24. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/alembic/env.py.j2 +0 -0
  25. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/__init__.py.j2 +0 -0
  26. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/admin/setup.py.j2 +0 -0
  27. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/deps.py.j2 +0 -0
  28. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/graphql.py.j2 +0 -0
  29. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2 +0 -0
  30. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/auth/sso.py.j2 +0 -0
  31. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/pagination/router.py.j2 +0 -0
  32. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/payments/router.py.j2 +0 -0
  33. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/permissions/router.py.j2 +0 -0
  34. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/streaming/router.py.j2 +0 -0
  35. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2 +0 -0
  36. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/ai.py.j2 +0 -0
  37. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/cache.py.j2 +0 -0
  38. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/email.py.j2 +0 -0
  39. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/exceptions.py.j2 +0 -0
  40. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/logger.py.j2 +0 -0
  41. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/logging.py.j2 +0 -0
  42. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/monitoring.py.j2 +0 -0
  43. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/notifications.py.j2 +0 -0
  44. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/ocr.py.j2 +0 -0
  45. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/permissions.py.j2 +0 -0
  46. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/search.py.j2 +0 -0
  47. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/security.py.j2 +0 -0
  48. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/storage.py.j2 +0 -0
  49. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/vector_db.py.j2 +0 -0
  50. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/core/ws_manager.py.j2 +0 -0
  51. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/frontend/index.html.j2 +0 -0
  52. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/__init__.py.j2 +0 -0
  53. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/rate_limit.py.j2 +0 -0
  54. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/request_logger.py.j2 +0 -0
  55. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/app/middleware/response_format.py.j2 +0 -0
  56. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/Makefile.j2 +0 -0
  57. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/README.md.j2 +0 -0
  58. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/env.j2 +0 -0
  59. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/gitignore.j2 +0 -0
  60. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/base/pre_commit.j2 +0 -0
  61. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/github/publish.yml.j2 +0 -0
  62. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/github/tests.yml.j2 +0 -0
  63. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2 +0 -0
  64. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/db/seed.py.j2 +0 -0
  65. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/docker/Dockerfile.j2 +0 -0
  66. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/docker/dockerignore.j2 +0 -0
  67. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/docker/docker-compose.prod.yml.j2 +0 -0
  68. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2 +0 -0
  69. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/helm/values.yaml.j2 +0 -0
  70. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/terraform/main.tf.j2 +0 -0
  71. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/infra/terraform/variables.tf.j2 +0 -0
  72. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/root/main.py.j2 +0 -0
  73. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/arq_worker.py.j2 +0 -0
  74. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/celery_app.py.j2 +0 -0
  75. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tasks/sample_tasks.py.j2 +0 -0
  76. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tests/conftest.py.j2 +0 -0
  77. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/templates/tests/test_health.py.j2 +0 -0
  78. {fastapi_spawn-0.4.21 → fastapi_spawn-0.4.24}/fastapi_spawn/utils.py +0 -0
  79. {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.21
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
@@ -1,6 +1,6 @@
1
1
  """fastapi-spawn — Production-ready FastAPI project scaffolding CLI."""
2
2
 
3
- __version__ = "0.4.21"
3
+ __version__ = "0.4.22"
4
4
  __author__ = "Bishwajit Garai"
5
5
  __email__ = "bishwajitgarai@gmail.com"
6
6
  __license__ = "MIT"
@@ -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"}
@@ -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 = "media"
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 = "llama3"
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=llama3
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=llama3 (no API key)
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 %}
@@ -42,8 +42,8 @@ dependencies = [
42
42
  "aerich>=0.7.2",
43
43
  {% endif %}
44
44
  {% if has_mongo and orm == "beanie" %}
45
- "beanie>=1.25.0",
46
- "motor>=3.4.0",
45
+ "beanie>=1.27.0",
46
+ "motor>=3.3.0",
47
47
  {% endif %}
48
48
 
49
49
  {% if auth == "jwt" or auth == "oauth2" %}
@@ -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.21"
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