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.
Files changed (70) hide show
  1. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/PKG-INFO +11 -2
  2. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/README.md +10 -1
  3. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/__init__.py +1 -1
  4. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/cli.py +17 -1
  5. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/config.py +3 -0
  6. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/constants.py +2 -0
  7. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/generator.py +26 -0
  8. fastapi_spawn-0.4.8/fastapi_spawn/templates/app/api/v1/auth/sso.py.j2 +36 -0
  9. fastapi_spawn-0.4.8/fastapi_spawn/templates/app/api/v1/payments/router.py.j2 +58 -0
  10. fastapi_spawn-0.4.8/fastapi_spawn/templates/app/api/v1/streaming/router.py.j2 +26 -0
  11. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/email.py.j2 +23 -0
  12. fastapi_spawn-0.4.8/fastapi_spawn/templates/app/core/ocr.py.j2 +40 -0
  13. fastapi_spawn-0.4.8/fastapi_spawn/templates/app/core/search.py.j2 +23 -0
  14. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/pyproject.toml.j2 +24 -0
  15. fastapi_spawn-0.4.8/fastapi_spawn/templates/db/seed.py.j2 +61 -0
  16. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/pyproject.toml +1 -1
  17. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/.gitignore +0 -0
  18. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/LICENSE +0 -0
  19. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/interactive.py +0 -0
  20. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/alembic/alembic.ini.j2 +0 -0
  21. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/alembic/env.py.j2 +0 -0
  22. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/__init__.py.j2 +0 -0
  23. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/deps.py.j2 +0 -0
  24. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/graphql.py.j2 +0 -0
  25. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2 +0 -0
  26. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +0 -0
  27. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/router.py.j2 +0 -0
  28. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2 +0 -0
  29. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/ai.py.j2 +0 -0
  30. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/config.py.j2 +0 -0
  31. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/exceptions.py.j2 +0 -0
  32. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/logger.py.j2 +0 -0
  33. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/logging.py.j2 +0 -0
  34. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/monitoring.py.j2 +0 -0
  35. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/notifications.py.j2 +0 -0
  36. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/security.py.j2 +0 -0
  37. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/storage.py.j2 +0 -0
  38. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/vector_db.py.j2 +0 -0
  39. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/core/ws_manager.py.j2 +0 -0
  40. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/db/session.py.j2 +0 -0
  41. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/frontend/index.html.j2 +0 -0
  42. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/main.py.j2 +0 -0
  43. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/__init__.py.j2 +0 -0
  44. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/rate_limit.py.j2 +0 -0
  45. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/app/middleware/request_logger.py.j2 +0 -0
  46. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/Makefile.j2 +0 -0
  47. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/README.md.j2 +0 -0
  48. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/env.j2 +0 -0
  49. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/env_example.j2 +0 -0
  50. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/gitignore.j2 +0 -0
  51. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/base/pre_commit.j2 +0 -0
  52. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/github/publish.yml.j2 +0 -0
  53. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/github/tests.yml.j2 +0 -0
  54. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2 +0 -0
  55. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/docker/Dockerfile.j2 +0 -0
  56. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/docker/docker-compose.yml.j2 +0 -0
  57. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/docker/dockerignore.j2 +0 -0
  58. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/docker/docker-compose.prod.yml.j2 +0 -0
  59. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2 +0 -0
  60. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/helm/values.yaml.j2 +0 -0
  61. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/terraform/main.tf.j2 +0 -0
  62. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/infra/terraform/variables.tf.j2 +0 -0
  63. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/root/main.py.j2 +0 -0
  64. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tasks/arq_worker.py.j2 +0 -0
  65. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tasks/celery_app.py.j2 +0 -0
  66. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tasks/sample_tasks.py.j2 +0 -0
  67. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tests/conftest.py.j2 +0 -0
  68. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/templates/tests/test_health.py.j2 +0 -0
  69. {fastapi_spawn-0.4.4 → fastapi_spawn-0.4.8}/fastapi_spawn/utils.py +0 -0
  70. {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.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
  ```
@@ -1,6 +1,6 @@
1
1
  """fastapi-spawn — Production-ready FastAPI project scaffolding CLI."""
2
2
 
3
- __version__ = "0.4.4"
3
+ __version__ = "0.4.8"
4
4
  __author__ = "Bishwajit Garai"
5
5
  __email__ = "bishwajitgarai@gmail.com"
6
6
  __license__ = "MIT"
@@ -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
- "websockets":["No extra dep (built into FastAPI)", "Create app/core/ws_manager.py (ConnectionManager)", "Create app/api/v1/ws.py — /ws/connect, /ws/connect/{room_id}"],
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.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