fastapi-spawn 0.4.4__tar.gz → 0.4.8__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.4 → fastapi_spawn-0.4.8}/PKG-INFO +11 -2
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/README.md +10 -1
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/__init__.py +1 -1
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/cli.py +17 -1
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/config.py +3 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/constants.py +2 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/generator.py +26 -0
- fastapi_spawn-0.4.8/fastapi_spawn/templates/app/api/v1/auth/sso.py.j2 +36 -0
- fastapi_spawn-0.4.8/fastapi_spawn/templates/app/api/v1/payments/router.py.j2 +58 -0
- fastapi_spawn-0.4.8/fastapi_spawn/templates/app/api/v1/streaming/router.py.j2 +26 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/email.py.j2 +23 -0
- fastapi_spawn-0.4.8/fastapi_spawn/templates/app/core/ocr.py.j2 +40 -0
- fastapi_spawn-0.4.8/fastapi_spawn/templates/app/core/search.py.j2 +23 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/pyproject.toml.j2 +24 -0
- fastapi_spawn-0.4.8/fastapi_spawn/templates/db/seed.py.j2 +61 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/pyproject.toml +1 -1
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/.gitignore +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/LICENSE +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/interactive.py +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/alembic/alembic.ini.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/alembic/env.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/__init__.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/deps.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/graphql.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/router.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/ai.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/config.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/exceptions.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/logger.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/logging.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/monitoring.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/notifications.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/security.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/storage.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/vector_db.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/ws_manager.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/db/session.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/frontend/index.html.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/main.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/__init__.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/rate_limit.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/request_logger.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/Makefile.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/README.md.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/env.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/env_example.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/gitignore.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/pre_commit.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/github/publish.yml.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/github/tests.yml.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/docker/Dockerfile.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/docker/docker-compose.yml.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/docker/dockerignore.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/docker/docker-compose.prod.yml.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/helm/values.yaml.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/terraform/main.tf.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/terraform/variables.tf.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/root/main.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tasks/arq_worker.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tasks/celery_app.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tasks/sample_tasks.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tests/conftest.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tests/test_health.py.j2 +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/utils.py +0 -0
- {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/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.8
|
|
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
|
|
@@ -473,16 +473,25 @@ fastapi-spawn add gemini # Google Generative AI client
|
|
|
473
473
|
fastapi-spawn add qdrant # Qdrant vector DB
|
|
474
474
|
fastapi-spawn add chroma # ChromaDB local vector DB
|
|
475
475
|
fastapi-spawn add pinecone # Pinecone cloud vector DB
|
|
476
|
+
fastapi-spawn add meilisearch # Meilisearch typo-tolerant search
|
|
477
|
+
fastapi-spawn add ocr # PDF & OCR data extraction pipeline
|
|
478
|
+
|
|
479
|
+
# Payments & Identity
|
|
480
|
+
fastapi-spawn add stripe # Stripe payments & webhooks
|
|
481
|
+
fastapi-spawn add sso # FastAPI SSO (Google/GitHub login)
|
|
476
482
|
|
|
477
483
|
# Messaging & Async Workers
|
|
478
484
|
fastapi-spawn add celery # Celery worker + tasks/
|
|
479
485
|
fastapi-spawn add arq # Arq async job queues using Redis
|
|
480
486
|
fastapi-spawn add websockets # WebSocket connection manager
|
|
487
|
+
fastapi-spawn add sse # Server-Sent Events streaming
|
|
481
488
|
|
|
482
|
-
# Storage, APIs & Monitoring
|
|
489
|
+
# Storage, APIs, Seeding & Monitoring
|
|
483
490
|
fastapi-spawn add s3 # AWS S3 / MinIO storage
|
|
484
491
|
fastapi-spawn add graphql # Strawberry GraphQL schema
|
|
485
492
|
fastapi-spawn add alembic # Alembic async migrations
|
|
493
|
+
fastapi-spawn add seed # Faker database seeding script
|
|
494
|
+
fastapi-spawn add resend # Resend modern email client
|
|
486
495
|
fastapi-spawn add sentry # Sentry APM integration
|
|
487
496
|
fastapi-spawn add prometheus # Prometheus metrics
|
|
488
497
|
```
|
|
@@ -432,16 +432,25 @@ fastapi-spawn add gemini # Google Generative AI client
|
|
|
432
432
|
fastapi-spawn add qdrant # Qdrant vector DB
|
|
433
433
|
fastapi-spawn add chroma # ChromaDB local vector DB
|
|
434
434
|
fastapi-spawn add pinecone # Pinecone cloud vector DB
|
|
435
|
+
fastapi-spawn add meilisearch # Meilisearch typo-tolerant search
|
|
436
|
+
fastapi-spawn add ocr # PDF & OCR data extraction pipeline
|
|
437
|
+
|
|
438
|
+
# Payments & Identity
|
|
439
|
+
fastapi-spawn add stripe # Stripe payments & webhooks
|
|
440
|
+
fastapi-spawn add sso # FastAPI SSO (Google/GitHub login)
|
|
435
441
|
|
|
436
442
|
# Messaging & Async Workers
|
|
437
443
|
fastapi-spawn add celery # Celery worker + tasks/
|
|
438
444
|
fastapi-spawn add arq # Arq async job queues using Redis
|
|
439
445
|
fastapi-spawn add websockets # WebSocket connection manager
|
|
446
|
+
fastapi-spawn add sse # Server-Sent Events streaming
|
|
440
447
|
|
|
441
|
-
# Storage, APIs & Monitoring
|
|
448
|
+
# Storage, APIs, Seeding & Monitoring
|
|
442
449
|
fastapi-spawn add s3 # AWS S3 / MinIO storage
|
|
443
450
|
fastapi-spawn add graphql # Strawberry GraphQL schema
|
|
444
451
|
fastapi-spawn add alembic # Alembic async migrations
|
|
452
|
+
fastapi-spawn add seed # Faker database seeding script
|
|
453
|
+
fastapi-spawn add resend # Resend modern email client
|
|
445
454
|
fastapi-spawn add sentry # Sentry APM integration
|
|
446
455
|
fastapi-spawn add prometheus # Prometheus metrics
|
|
447
456
|
```
|
|
@@ -98,6 +98,7 @@ def new(
|
|
|
98
98
|
no_tests: bool = typer.Option(False, "--no-tests", help="Skip test suite"),
|
|
99
99
|
dry_run: bool = typer.Option(False, "--dry-run", help="Preview structure without writing files"),
|
|
100
100
|
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing directory"),
|
|
101
|
+
extra: Optional[list[str]] = typer.Option(None, "--extra", help="Extra integrations (e.g. stripe, sso, sse, seed, ocr, meilisearch)"),
|
|
101
102
|
output: Path = typer.Option(Path("."), "--output", "-o", help="Output directory"),
|
|
102
103
|
version: Optional[bool] = typer.Option(
|
|
103
104
|
None, "--version", "-v", callback=version_callback, is_eager=True, help="Show version"
|
|
@@ -192,6 +193,7 @@ def new(
|
|
|
192
193
|
include_tests=include_tests,
|
|
193
194
|
dry_run=dry_run,
|
|
194
195
|
force=force,
|
|
196
|
+
extras=extra or [],
|
|
195
197
|
)
|
|
196
198
|
|
|
197
199
|
_print_summary(config)
|
|
@@ -277,14 +279,21 @@ _ADDABLE_FEATURES = {
|
|
|
277
279
|
"sendgrid": "SendGrid email",
|
|
278
280
|
"smtp": "SMTP email (fastapi-mail)",
|
|
279
281
|
"ses": "AWS SES email",
|
|
282
|
+
"resend": "Resend email client (modern React/HTML emails)",
|
|
280
283
|
"slack": "Slack webhook notifications",
|
|
281
284
|
"discord": "Discord webhook notifications",
|
|
282
285
|
"qdrant": "Qdrant vector database",
|
|
283
286
|
"chroma": "ChromaDB local vector database",
|
|
284
287
|
"pinecone": "Pinecone managed vector database",
|
|
285
288
|
"elasticsearch": "Elasticsearch KNN search",
|
|
289
|
+
"meilisearch": "Meilisearch ultra-fast typo-tolerant search",
|
|
286
290
|
"websockets": "WebSocket connection manager + endpoints",
|
|
291
|
+
"sse": "Server-Sent Events (SSE) streaming endpoint",
|
|
287
292
|
"graphql": "Strawberry GraphQL schema + subscriptions",
|
|
293
|
+
"stripe": "Stripe payments & webhook signature validation",
|
|
294
|
+
"sso": "FastAPI SSO (Login with Google / GitHub)",
|
|
295
|
+
"seed": "Database seeding script using Faker",
|
|
296
|
+
"ocr": "PDF & OCR data pipeline (PyMuPDF / Tesseract)",
|
|
288
297
|
"docker": "Dockerfile + docker-compose.yml",
|
|
289
298
|
"ci": "GitHub Actions CI/CD workflows",
|
|
290
299
|
"helm": "Helm chart (infra/helm/)",
|
|
@@ -339,14 +348,21 @@ def _feature_guidance(feature: str, _project_dir: Path) -> None:
|
|
|
339
348
|
"sendgrid": ["Add dep: sendgrid>=6.11.0", "Create app/core/email.py", "Add to .env: SENDGRID_API_KEY, SENDGRID_FROM_EMAIL"],
|
|
340
349
|
"smtp": ["Add dep: fastapi-mail>=1.4.1", "Create app/core/email.py", "Add to .env: SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM_EMAIL"],
|
|
341
350
|
"ses": ["Add dep: boto3>=1.34.0", "Create app/core/email.py", "Add to .env: AWS_* credentials, SES_FROM_EMAIL"],
|
|
351
|
+
"resend": ["Add dep: resend>=2.1.0", "Create app/core/email.py (Resend client)", "Add to .env: RESEND_API_KEY"],
|
|
342
352
|
"slack": ["No extra dep (uses httpx)", "Create app/core/notifications.py", "Add to .env: SLACK_WEBHOOK_URL=https://hooks.slack.com/services/..."],
|
|
343
353
|
"discord": ["No extra dep (uses httpx)", "Create app/core/notifications.py", "Add to .env: DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/..."],
|
|
344
354
|
"qdrant": ["Add dep: qdrant-client[fastembed]>=1.9.0", "Create app/core/vector_db.py", "Add to .env: QDRANT_HOST=localhost, QDRANT_PORT=6333, QDRANT_API_KEY= (blank for local)"],
|
|
345
355
|
"chroma": ["Add dep: chromadb>=0.5.0", "Create app/core/vector_db.py (persistent local client)", "No env vars needed — data stored in ./chroma_data"],
|
|
346
356
|
"pinecone": ["Add dep: pinecone-client>=3.2.0", "Create app/core/vector_db.py", "Add to .env: PINECONE_API_KEY, PINECONE_INDEX_NAME"],
|
|
347
357
|
"elasticsearch": ["Add dep: elasticsearch[async]>=8.13.0", "Create app/core/vector_db.py (kNN search)", "Add to .env: ELASTICSEARCH_HOST, ELASTICSEARCH_PORT, ELASTICSEARCH_API_KEY"],
|
|
348
|
-
"
|
|
358
|
+
"meilisearch": ["Add dep: meilisearch>=0.30.0", "Create app/core/search.py (Meilisearch client)", "Add to .env: MEILISEARCH_HOST=http://localhost:7700, MEILISEARCH_API_KEY"],
|
|
359
|
+
"websockets":["No extra dep (built into FastAPI)", "Create app/core/ws_manager.py (ConnectionManager)", "Create app/api/v1/ws/router.py — /ws/connect, /ws/connect/{room_id}"],
|
|
360
|
+
"sse": ["Add dep: sse-starlette>=2.1.0", "Create app/api/v1/streaming/router.py", "Return EventSourceResponse(async_generator)"],
|
|
349
361
|
"graphql": ["Add dep: strawberry-graphql[fastapi]>=0.227.0", "Create app/api/graphql.py (Query + Mutation + Subscription)", "Mount: app.include_router(graphql_router, prefix='/graphql')"],
|
|
362
|
+
"stripe": ["Add dep: stripe>=9.0.0", "Create app/api/v1/payments/router.py (webhook endpoint)", "Add to .env: STRIPE_API_KEY, STRIPE_WEBHOOK_SECRET"],
|
|
363
|
+
"sso": ["Add dep: fastapi-sso>=0.14.0", "Create app/api/v1/auth/sso.py (GoogleSSO / GithubSSO)", "Add to .env: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET"],
|
|
364
|
+
"seed": ["Add dep: faker>=25.0.0", "Create db/seed.py (generate 100 mock users/posts)", "Run: uv run python db/seed.py"],
|
|
365
|
+
"ocr": ["Add deps: pymupdf>=1.24.0, pytesseract>=0.3.10", "Create app/core/ocr.py (PDF parsing pipeline)", "Install system deps: sudo apt install tesseract-ocr"],
|
|
350
366
|
"docker": ["Create Dockerfile (multi-stage, uv-based)", "Create docker-compose.yml with all selected services", "Create .dockerignore"],
|
|
351
367
|
"ci": ["Create .github/workflows/tests.yml (matrix: 3.10, 3.11, 3.12)", "Create .github/workflows/publish.yml (v* tags → PyPI)", "Add PYPI_API_TOKEN to GitHub repo secrets"],
|
|
352
368
|
"helm": ["Create infra/helm/Chart.yaml", "Create infra/helm/values.yaml (replicas, image, resources)", "Run: helm install my-release ./infra/helm"],
|
|
@@ -47,6 +47,7 @@ class ProjectConfig:
|
|
|
47
47
|
include_makefile: bool = True
|
|
48
48
|
dry_run: bool = False
|
|
49
49
|
force: bool = False
|
|
50
|
+
extras: list[str] = field(default_factory=list)
|
|
50
51
|
# Derived (post-init)
|
|
51
52
|
package_name: str = field(default="", init=False)
|
|
52
53
|
slug: str = field(default="", init=False)
|
|
@@ -194,6 +195,7 @@ class ProjectConfig:
|
|
|
194
195
|
"has_log_file": self.has_log_file,
|
|
195
196
|
"include_tests": self.include_tests,
|
|
196
197
|
"include_makefile": self.include_makefile,
|
|
198
|
+
"extras": self.extras,
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
def summary_lines(self) -> list[tuple[str, str]]:
|
|
@@ -219,5 +221,6 @@ class ProjectConfig:
|
|
|
219
221
|
("API extras", self.api_extra.value),
|
|
220
222
|
("Docker", "yes" if self.has_docker else "no"),
|
|
221
223
|
("Tests", "yes" if self.include_tests else "no"),
|
|
224
|
+
("Extras", ", ".join(self.extras) if self.extras else "none"),
|
|
222
225
|
("Dry-run", "yes" if self.dry_run else "no"),
|
|
223
226
|
]
|
|
@@ -93,6 +93,7 @@ class EmailProvider(str, Enum):
|
|
|
93
93
|
sendgrid = "sendgrid"
|
|
94
94
|
smtp = "smtp"
|
|
95
95
|
ses = "ses"
|
|
96
|
+
resend = "resend"
|
|
96
97
|
none = "none"
|
|
97
98
|
|
|
98
99
|
|
|
@@ -234,6 +235,7 @@ EMAIL_LABELS = {
|
|
|
234
235
|
EmailProvider.sendgrid: "SendGrid",
|
|
235
236
|
EmailProvider.smtp: "SMTP (fastapi-mail)",
|
|
236
237
|
EmailProvider.ses: "AWS SES",
|
|
238
|
+
EmailProvider.resend: "Resend",
|
|
237
239
|
EmailProvider.none: "No email",
|
|
238
240
|
}
|
|
239
241
|
|
|
@@ -209,6 +209,32 @@ class ProjectGenerator:
|
|
|
209
209
|
(root / "logs").mkdir(exist_ok=True)
|
|
210
210
|
(root / "logs" / ".gitkeep").write_text("", encoding="utf-8")
|
|
211
211
|
|
|
212
|
+
# Extras rendering
|
|
213
|
+
extras = self.config.extras
|
|
214
|
+
if "stripe" in extras:
|
|
215
|
+
(v1 / "payments").mkdir(parents=True, exist_ok=True)
|
|
216
|
+
self._render_to(v1 / "payments" / "router.py", "app/api/v1/payments/router.py.j2")
|
|
217
|
+
self._render_to(v1 / "payments" / "__init__.py", "app/__init__.py.j2")
|
|
218
|
+
|
|
219
|
+
if "sso" in extras:
|
|
220
|
+
(v1 / "auth").mkdir(parents=True, exist_ok=True)
|
|
221
|
+
self._render_to(v1 / "auth" / "sso.py", "app/api/v1/auth/sso.py.j2")
|
|
222
|
+
|
|
223
|
+
if "sse" in extras:
|
|
224
|
+
(v1 / "streaming").mkdir(parents=True, exist_ok=True)
|
|
225
|
+
self._render_to(v1 / "streaming" / "router.py", "app/api/v1/streaming/router.py.j2")
|
|
226
|
+
self._render_to(v1 / "streaming" / "__init__.py", "app/__init__.py.j2")
|
|
227
|
+
|
|
228
|
+
if "seed" in extras:
|
|
229
|
+
(root / "db").mkdir(parents=True, exist_ok=True)
|
|
230
|
+
self._render_to(root / "db" / "seed.py", "db/seed.py.j2")
|
|
231
|
+
|
|
232
|
+
if "ocr" in extras:
|
|
233
|
+
self._render_to(core / "ocr.py", "app/core/ocr.py.j2")
|
|
234
|
+
|
|
235
|
+
if "meilisearch" in extras:
|
|
236
|
+
self._render_to(core / "search.py", "app/core/search.py.j2")
|
|
237
|
+
|
|
212
238
|
|
|
213
239
|
def _generate_tasks(self, root: Path) -> None:
|
|
214
240
|
"""Root-level tasks/ directory."""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request, HTTPException
|
|
2
|
+
from fastapi_sso.sso.google import GoogleSSO
|
|
3
|
+
from app.core.config import settings
|
|
4
|
+
|
|
5
|
+
router = APIRouter(prefix="/sso", tags=["auth"])
|
|
6
|
+
|
|
7
|
+
# Requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env
|
|
8
|
+
google_sso = GoogleSSO(
|
|
9
|
+
client_id=getattr(settings, "GOOGLE_CLIENT_ID", "placeholder"),
|
|
10
|
+
client_secret=getattr(settings, "GOOGLE_CLIENT_SECRET", "placeholder"),
|
|
11
|
+
redirect_uri="http://localhost:8000/api/v1/auth/sso/google/callback",
|
|
12
|
+
allow_insecure_http=True, # Set to False in production
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
@router.get("/google/login")
|
|
16
|
+
async def google_login():
|
|
17
|
+
"""Redirects the user to the Google login page."""
|
|
18
|
+
with google_sso:
|
|
19
|
+
return await google_sso.get_login_redirect()
|
|
20
|
+
|
|
21
|
+
@router.get("/google/callback")
|
|
22
|
+
async def google_callback(request: Request):
|
|
23
|
+
"""Process login response from Google and return user info."""
|
|
24
|
+
with google_sso:
|
|
25
|
+
user = await google_sso.verify_and_process(request)
|
|
26
|
+
if not user:
|
|
27
|
+
raise HTTPException(status_code=400, detail="Failed to login via Google")
|
|
28
|
+
|
|
29
|
+
# TODO: Create or update user in database, then generate and return JWT
|
|
30
|
+
return {
|
|
31
|
+
"id": user.id,
|
|
32
|
+
"email": user.email,
|
|
33
|
+
"first_name": user.first_name,
|
|
34
|
+
"last_name": user.last_name,
|
|
35
|
+
"picture": user.picture
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request, HTTPException, Depends
|
|
2
|
+
import stripe
|
|
3
|
+
from app.core.config import settings
|
|
4
|
+
from app.core.logger import logger
|
|
5
|
+
|
|
6
|
+
router = APIRouter(prefix="/payments", tags=["payments"])
|
|
7
|
+
|
|
8
|
+
stripe.api_key = getattr(settings, "STRIPE_API_KEY", "sk_test_placeholder")
|
|
9
|
+
webhook_secret = getattr(settings, "STRIPE_WEBHOOK_SECRET", "whsec_placeholder")
|
|
10
|
+
|
|
11
|
+
@router.post("/webhook")
|
|
12
|
+
async def stripe_webhook(request: Request):
|
|
13
|
+
payload = await request.body()
|
|
14
|
+
sig_header = request.headers.get("stripe-signature")
|
|
15
|
+
|
|
16
|
+
if not sig_header:
|
|
17
|
+
raise HTTPException(status_code=400, detail="Missing signature")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
event = stripe.Webhook.construct_event(
|
|
21
|
+
payload, sig_header, webhook_secret
|
|
22
|
+
)
|
|
23
|
+
except ValueError as e:
|
|
24
|
+
logger.error(f"Invalid payload: {e}")
|
|
25
|
+
raise HTTPException(status_code=400, detail="Invalid payload")
|
|
26
|
+
except stripe.error.SignatureVerificationError as e:
|
|
27
|
+
logger.error(f"Invalid signature: {e}")
|
|
28
|
+
raise HTTPException(status_code=400, detail="Invalid signature")
|
|
29
|
+
|
|
30
|
+
# Handle the event
|
|
31
|
+
if event.type == "checkout.session.completed":
|
|
32
|
+
session = event.data.object
|
|
33
|
+
logger.info(f"Payment successful for session {session.id}")
|
|
34
|
+
# TODO: Fulfill the purchase...
|
|
35
|
+
|
|
36
|
+
return {"status": "success"}
|
|
37
|
+
|
|
38
|
+
@router.post("/create-checkout-session")
|
|
39
|
+
async def create_checkout_session():
|
|
40
|
+
try:
|
|
41
|
+
checkout_session = stripe.checkout.Session.create(
|
|
42
|
+
line_items=[
|
|
43
|
+
{
|
|
44
|
+
"price_data": {
|
|
45
|
+
"currency": "usd",
|
|
46
|
+
"product_data": {"name": "Pro Subscription"},
|
|
47
|
+
"unit_amount": 2000,
|
|
48
|
+
},
|
|
49
|
+
"quantity": 1,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
mode="payment",
|
|
53
|
+
success_url="http://localhost:3000/success",
|
|
54
|
+
cancel_url="http://localhost:3000/cancel",
|
|
55
|
+
)
|
|
56
|
+
return {"url": checkout_session.url}
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request, HTTPException
|
|
2
|
+
from sse_starlette.sse import EventSourceResponse
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
router = APIRouter(prefix="/streaming", tags=["streaming"])
|
|
6
|
+
|
|
7
|
+
async def message_generator():
|
|
8
|
+
"""Simulates a stream of LLM tokens."""
|
|
9
|
+
tokens = ["Hello", " ", "there", "!", " ", "This", " ", "is", " ", "a", " ", "stream", "."]
|
|
10
|
+
for token in tokens:
|
|
11
|
+
await asyncio.sleep(0.1) # Simulate processing delay
|
|
12
|
+
yield {
|
|
13
|
+
"event": "message",
|
|
14
|
+
"id": "message_id",
|
|
15
|
+
"retry": 15000,
|
|
16
|
+
"data": token
|
|
17
|
+
}
|
|
18
|
+
yield {"event": "done", "data": "[DONE]"}
|
|
19
|
+
|
|
20
|
+
@router.get("/chat")
|
|
21
|
+
async def stream_chat(request: Request):
|
|
22
|
+
"""
|
|
23
|
+
Server-Sent Events endpoint for streaming chat responses.
|
|
24
|
+
Client can consume this using EventSource API in JS.
|
|
25
|
+
"""
|
|
26
|
+
return EventSourceResponse(message_generator())
|
|
@@ -84,4 +84,27 @@ async def send_email(
|
|
|
84
84
|
except ClientError:
|
|
85
85
|
return False
|
|
86
86
|
|
|
87
|
+
{% elif email == "resend" %}
|
|
88
|
+
import resend
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def send_email(
|
|
92
|
+
to: str,
|
|
93
|
+
subject: str,
|
|
94
|
+
html_content: str,
|
|
95
|
+
from_email: str | None = None,
|
|
96
|
+
) -> bool:
|
|
97
|
+
"""Send an email via Resend. Returns True on success."""
|
|
98
|
+
resend.api_key = settings.RESEND_API_KEY
|
|
99
|
+
try:
|
|
100
|
+
resend.Emails.send({
|
|
101
|
+
"from": from_email or settings.RESEND_FROM_EMAIL,
|
|
102
|
+
"to": to,
|
|
103
|
+
"subject": subject,
|
|
104
|
+
"html": html_content
|
|
105
|
+
})
|
|
106
|
+
return True
|
|
107
|
+
except Exception:
|
|
108
|
+
return False
|
|
109
|
+
|
|
87
110
|
{% endif %}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import io
|
|
2
|
+
from typing import Optional
|
|
3
|
+
try:
|
|
4
|
+
import fitz # PyMuPDF
|
|
5
|
+
except ImportError:
|
|
6
|
+
fitz = None
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import pytesseract
|
|
10
|
+
from PIL import Image
|
|
11
|
+
except ImportError:
|
|
12
|
+
pytesseract = None
|
|
13
|
+
|
|
14
|
+
class OCRService:
|
|
15
|
+
"""
|
|
16
|
+
Handles PDF parsing and Optical Character Recognition.
|
|
17
|
+
Requires `pymupdf` and `pytesseract` to be installed.
|
|
18
|
+
System requirement: `sudo apt install tesseract-ocr`
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def extract_text_from_pdf(pdf_bytes: bytes) -> str:
|
|
23
|
+
"""Extract text from a native PDF."""
|
|
24
|
+
if fitz is None:
|
|
25
|
+
raise RuntimeError("PyMuPDF is not installed. Run: pip install pymupdf")
|
|
26
|
+
|
|
27
|
+
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
|
28
|
+
text_chunks = []
|
|
29
|
+
for page in doc:
|
|
30
|
+
text_chunks.append(page.get_text())
|
|
31
|
+
return "\n".join(text_chunks)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def extract_text_from_image(image_bytes: bytes) -> str:
|
|
35
|
+
"""Perform OCR on an image."""
|
|
36
|
+
if pytesseract is None:
|
|
37
|
+
raise RuntimeError("pytesseract is not installed. Run: pip install pytesseract pillow")
|
|
38
|
+
|
|
39
|
+
img = Image.open(io.BytesIO(image_bytes))
|
|
40
|
+
return pytesseract.image_to_string(img)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import meilisearch
|
|
2
|
+
from app.core.config import settings
|
|
3
|
+
|
|
4
|
+
class MeilisearchService:
|
|
5
|
+
"""
|
|
6
|
+
Client for Meilisearch — an open-source, lightning-fast, and hyper-relevant search engine.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self):
|
|
9
|
+
host = getattr(settings, "MEILISEARCH_HOST", "http://localhost:7700")
|
|
10
|
+
api_key = getattr(settings, "MEILISEARCH_API_KEY", "masterKey")
|
|
11
|
+
self.client = meilisearch.Client(host, api_key)
|
|
12
|
+
|
|
13
|
+
def add_documents(self, index_name: str, documents: list[dict]):
|
|
14
|
+
"""Add or update documents in an index."""
|
|
15
|
+
index = self.client.index(index_name)
|
|
16
|
+
return index.add_documents(documents)
|
|
17
|
+
|
|
18
|
+
def search(self, index_name: str, query: str, limit: int = 20):
|
|
19
|
+
"""Perform a typo-tolerant search."""
|
|
20
|
+
index = self.client.index(index_name)
|
|
21
|
+
return index.search(query, {"limit": limit})
|
|
22
|
+
|
|
23
|
+
search_client = MeilisearchService()
|
|
@@ -93,6 +93,8 @@ dependencies = [
|
|
|
93
93
|
{% elif email == "ses" %}
|
|
94
94
|
# Uses boto3 (already included above if has_s3, else add it)
|
|
95
95
|
"boto3>=1.34.0",
|
|
96
|
+
{% elif email == "resend" %}
|
|
97
|
+
"resend>=2.1.0",
|
|
96
98
|
{% endif %}
|
|
97
99
|
|
|
98
100
|
{% if has_notify %}
|
|
@@ -150,6 +152,28 @@ dependencies = [
|
|
|
150
152
|
{% elif log_dest == "datadog" %}
|
|
151
153
|
"datadog-lambda>=6.0.0",
|
|
152
154
|
{% endif %}
|
|
155
|
+
|
|
156
|
+
{% if extras %}
|
|
157
|
+
{% if "stripe" in extras %}
|
|
158
|
+
"stripe>=9.0.0",
|
|
159
|
+
{% endif %}
|
|
160
|
+
{% if "sso" in extras %}
|
|
161
|
+
"fastapi-sso>=0.14.0",
|
|
162
|
+
{% endif %}
|
|
163
|
+
{% if "sse" in extras %}
|
|
164
|
+
"sse-starlette>=2.1.0",
|
|
165
|
+
{% endif %}
|
|
166
|
+
{% if "seed" in extras %}
|
|
167
|
+
"faker>=25.0.0",
|
|
168
|
+
{% endif %}
|
|
169
|
+
{% if "ocr" in extras %}
|
|
170
|
+
"pymupdf>=1.24.0",
|
|
171
|
+
"pytesseract>=0.3.10",
|
|
172
|
+
{% endif %}
|
|
173
|
+
{% if "meilisearch" in extras %}
|
|
174
|
+
"meilisearch>=0.30.0",
|
|
175
|
+
{% endif %}
|
|
176
|
+
{% endif %}
|
|
153
177
|
]
|
|
154
178
|
|
|
155
179
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from faker import Faker
|
|
4
|
+
|
|
5
|
+
logging.basicConfig(level=logging.INFO)
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
async def seed_database():
|
|
10
|
+
"""
|
|
11
|
+
Seed the database with mock data.
|
|
12
|
+
Replace the stubs below with your actual database models and sessions.
|
|
13
|
+
"""
|
|
14
|
+
logger.info("Seeding database...")
|
|
15
|
+
|
|
16
|
+
# Example for SQLAlchemy:
|
|
17
|
+
# from app.db.session import async_session_maker
|
|
18
|
+
# from app.models.user import User
|
|
19
|
+
# from app.models.post import Post
|
|
20
|
+
# from app.models.comment import Comment
|
|
21
|
+
|
|
22
|
+
# async with async_session_maker() as session:
|
|
23
|
+
# users = []
|
|
24
|
+
# # 1. Create Users
|
|
25
|
+
# for _ in range(50):
|
|
26
|
+
# user = User(
|
|
27
|
+
# email=fake.email(),
|
|
28
|
+
# full_name=fake.name(),
|
|
29
|
+
# is_active=True
|
|
30
|
+
# )
|
|
31
|
+
# session.add(user)
|
|
32
|
+
# users.append(user)
|
|
33
|
+
# await session.commit()
|
|
34
|
+
#
|
|
35
|
+
# # 2. Create Posts
|
|
36
|
+
# posts = []
|
|
37
|
+
# for _ in range(200):
|
|
38
|
+
# post = Post(
|
|
39
|
+
# title=fake.sentence(),
|
|
40
|
+
# content=fake.text(),
|
|
41
|
+
# author_id=fake.random_element(elements=users).id
|
|
42
|
+
# )
|
|
43
|
+
# session.add(post)
|
|
44
|
+
# posts.append(post)
|
|
45
|
+
# await session.commit()
|
|
46
|
+
#
|
|
47
|
+
# # 3. Create Comments
|
|
48
|
+
# for _ in range(500):
|
|
49
|
+
# comment = Comment(
|
|
50
|
+
# text=fake.paragraph(),
|
|
51
|
+
# post_id=fake.random_element(elements=posts).id,
|
|
52
|
+
# user_id=fake.random_element(elements=users).id
|
|
53
|
+
# )
|
|
54
|
+
# session.add(comment)
|
|
55
|
+
# await session.commit()
|
|
56
|
+
|
|
57
|
+
logger.info("Generated 50 mock users, 200 posts, and 500 comments (Stub)")
|
|
58
|
+
logger.info("Database seeding complete!")
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
asyncio.run(seed_database())
|
|
@@ -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.8"
|
|
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" }
|
|
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.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/health/router.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/exceptions.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/monitoring.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/notifications.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/vector_db.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/ws_manager.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/frontend/index.html.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/__init__.py.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/rate_limit.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
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/github/publish.yml.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/docker/docker-compose.yml.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/helm/values.yaml.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/terraform/main.tf.j2
RENAMED
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/terraform/variables.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tasks/sample_tasks.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|