fastapi-spawn 0.4.6__tar.gz → 0.4.9__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.6 → fastapi_spawn-0.4.9}/PKG-INFO +1 -1
  2. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/__init__.py +1 -1
  3. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/cli.py +2 -0
  4. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/config.py +3 -0
  5. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/constants.py +2 -0
  6. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/generator.py +26 -0
  7. fastapi_spawn-0.4.9/fastapi_spawn/templates/app/api/v1/auth/sso.py.j2 +71 -0
  8. fastapi_spawn-0.4.9/fastapi_spawn/templates/app/api/v1/payments/router.py.j2 +58 -0
  9. fastapi_spawn-0.4.9/fastapi_spawn/templates/app/api/v1/streaming/router.py.j2 +26 -0
  10. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/email.py.j2 +23 -0
  11. fastapi_spawn-0.4.9/fastapi_spawn/templates/app/core/ocr.py.j2 +40 -0
  12. fastapi_spawn-0.4.9/fastapi_spawn/templates/app/core/search.py.j2 +23 -0
  13. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/base/pyproject.toml.j2 +24 -0
  14. fastapi_spawn-0.4.9/fastapi_spawn/templates/db/seed.py.j2 +61 -0
  15. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/pyproject.toml +1 -1
  16. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/.gitignore +0 -0
  17. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/LICENSE +0 -0
  18. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/README.md +0 -0
  19. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/interactive.py +0 -0
  20. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/alembic/alembic.ini.j2 +0 -0
  21. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/alembic/env.py.j2 +0 -0
  22. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/__init__.py.j2 +0 -0
  23. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/api/deps.py.j2 +0 -0
  24. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/api/graphql.py.j2 +0 -0
  25. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2 +0 -0
  26. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +0 -0
  27. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/api/v1/router.py.j2 +0 -0
  28. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2 +0 -0
  29. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/ai.py.j2 +0 -0
  30. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/config.py.j2 +0 -0
  31. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/exceptions.py.j2 +0 -0
  32. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/logger.py.j2 +0 -0
  33. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/logging.py.j2 +0 -0
  34. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/monitoring.py.j2 +0 -0
  35. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/notifications.py.j2 +0 -0
  36. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/security.py.j2 +0 -0
  37. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/storage.py.j2 +0 -0
  38. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/vector_db.py.j2 +0 -0
  39. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/core/ws_manager.py.j2 +0 -0
  40. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/db/session.py.j2 +0 -0
  41. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/frontend/index.html.j2 +0 -0
  42. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/main.py.j2 +0 -0
  43. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/middleware/__init__.py.j2 +0 -0
  44. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/middleware/rate_limit.py.j2 +0 -0
  45. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/app/middleware/request_logger.py.j2 +0 -0
  46. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/base/Makefile.j2 +0 -0
  47. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/base/README.md.j2 +0 -0
  48. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/base/env.j2 +0 -0
  49. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/base/env_example.j2 +0 -0
  50. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/base/gitignore.j2 +0 -0
  51. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/base/pre_commit.j2 +0 -0
  52. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/ci/github/publish.yml.j2 +0 -0
  53. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/ci/github/tests.yml.j2 +0 -0
  54. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2 +0 -0
  55. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/docker/Dockerfile.j2 +0 -0
  56. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/docker/docker-compose.yml.j2 +0 -0
  57. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/docker/dockerignore.j2 +0 -0
  58. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/infra/docker/docker-compose.prod.yml.j2 +0 -0
  59. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2 +0 -0
  60. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/infra/helm/values.yaml.j2 +0 -0
  61. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/infra/terraform/main.tf.j2 +0 -0
  62. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/infra/terraform/variables.tf.j2 +0 -0
  63. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/root/main.py.j2 +0 -0
  64. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/tasks/arq_worker.py.j2 +0 -0
  65. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/tasks/celery_app.py.j2 +0 -0
  66. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/tasks/sample_tasks.py.j2 +0 -0
  67. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/tests/conftest.py.j2 +0 -0
  68. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/templates/tests/test_health.py.j2 +0 -0
  69. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/fastapi_spawn/utils.py +0 -0
  70. {fastapi_spawn-0.4.6 → fastapi_spawn-0.4.9}/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.6
3
+ Version: 0.4.9
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
@@ -1,6 +1,6 @@
1
1
  """fastapi-spawn — Production-ready FastAPI project scaffolding CLI."""
2
2
 
3
- __version__ = "0.4.6"
3
+ __version__ = "0.4.9"
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)
@@ -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,71 @@
1
+ from fastapi import APIRouter, Request, HTTPException
2
+ from fastapi_sso.sso.google import GoogleSSO
3
+ from fastapi_sso.sso.github import GithubSSO
4
+ from app.core.config import settings
5
+
6
+ router = APIRouter(prefix="/sso", tags=["auth"])
7
+
8
+ # Requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env
9
+ google_sso = GoogleSSO(
10
+ client_id=getattr(settings, "GOOGLE_CLIENT_ID", "placeholder"),
11
+ client_secret=getattr(settings, "GOOGLE_CLIENT_SECRET", "placeholder"),
12
+ redirect_uri="http://localhost:8000/api/v1/auth/sso/google/callback",
13
+ allow_insecure_http=True, # Set to False in production
14
+ )
15
+
16
+ @router.get("/google/login")
17
+ async def google_login():
18
+ """Redirects the user to the Google login page."""
19
+ with google_sso:
20
+ return await google_sso.get_login_redirect()
21
+
22
+ @router.get("/google/callback")
23
+ async def google_callback(request: Request):
24
+ """Process login response from Google and return user info."""
25
+ with google_sso:
26
+ user = await google_sso.verify_and_process(request)
27
+ if not user:
28
+ raise HTTPException(status_code=400, detail="Failed to login via Google")
29
+
30
+ # TODO: Create or update user in database, then generate and return JWT
31
+ return {
32
+ "id": user.id,
33
+ "email": user.email,
34
+ "first_name": user.first_name,
35
+ "last_name": user.last_name,
36
+ "picture": user.picture,
37
+ "provider": "google"
38
+ }
39
+
40
+ # --- GitHub SSO ---
41
+
42
+ # Requires GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET in .env
43
+ github_sso = GithubSSO(
44
+ client_id=getattr(settings, "GITHUB_CLIENT_ID", "placeholder"),
45
+ client_secret=getattr(settings, "GITHUB_CLIENT_SECRET", "placeholder"),
46
+ redirect_uri="http://localhost:8000/api/v1/auth/sso/github/callback",
47
+ allow_insecure_http=True, # Set to False in production
48
+ )
49
+
50
+ @router.get("/github/login")
51
+ async def github_login():
52
+ """Redirects the user to the GitHub login page."""
53
+ with github_sso:
54
+ return await github_sso.get_login_redirect()
55
+
56
+ @router.get("/github/callback")
57
+ async def github_callback(request: Request):
58
+ """Process login response from GitHub and return user info."""
59
+ with github_sso:
60
+ user = await github_sso.verify_and_process(request)
61
+ if not user:
62
+ raise HTTPException(status_code=400, detail="Failed to login via GitHub")
63
+
64
+ return {
65
+ "id": user.id,
66
+ "email": user.email,
67
+ "first_name": getattr(user, "first_name", None),
68
+ "last_name": getattr(user, "last_name", None),
69
+ "picture": getattr(user, "picture", None),
70
+ "provider": "github"
71
+ }
@@ -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.6"
7
+ version = "0.4.9"
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