fastapi-spawn 0.4.40__tar.gz → 0.5.0__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.40 → fastapi_spawn-0.5.0}/PKG-INFO +1 -1
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/cli.py +154 -117
- fastapi_spawn-0.5.0/fastapi_spawn/steps.py +455 -0
- fastapi_spawn-0.5.0/fastapi_spawn/templates/base/env.j2 +209 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/base/env_example.j2 +49 -29
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/pyproject.toml +1 -1
- fastapi_spawn-0.4.40/fastapi_spawn/templates/base/env.j2 +0 -103
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/.gitignore +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/LICENSE +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/README.md +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/__init__.py +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/config.py +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/constants.py +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/generator.py +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/interactive.py +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/alembic/alembic.ini.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/alembic/env.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/__init__.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/admin/setup.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/deps.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/graphql.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/auth/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/auth/sso.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/health/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/pagination/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/payments/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/permissions/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/streaming/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/uploads/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/api/v1/ws/router.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/ai.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/cache.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/config.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/email.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/exceptions.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/logger.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/logging.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/monitoring.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/notifications.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/ocr.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/permissions.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/search.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/security.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/storage.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/vector_db.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/core/ws_manager.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/db/session.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/frontend/index.html.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/main.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/middleware/__init__.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/middleware/rate_limit.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/middleware/request_logger.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/app/middleware/response_format.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/base/Makefile.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/base/README.md.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/base/gitignore.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/base/pre_commit.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/base/pyproject.toml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/ci/github/publish.yml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/ci/github/tests.yml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/db/seed.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/docker/Dockerfile.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/docker/docker-compose.yml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/docker/dockerignore.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/infra/docker/docker-compose.prod.yml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/infra/helm/Chart.yaml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/infra/helm/values.yaml.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/infra/terraform/main.tf.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/infra/terraform/variables.tf.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/root/main.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/tasks/arq_worker.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/tasks/celery_app.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/tasks/sample_tasks.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/tests/conftest.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/templates/tests/test_health.py.j2 +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/utils.py +0 -0
- {fastapi_spawn-0.4.40 → fastapi_spawn-0.5.0}/fastapi_spawn/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-spawn
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
|
@@ -62,6 +62,14 @@ def version_callback(value: bool) -> None:
|
|
|
62
62
|
raise typer.Exit()
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
@app.callback()
|
|
66
|
+
def main(
|
|
67
|
+
version: Optional[bool] = typer.Option(
|
|
68
|
+
None, "--version", "-v", callback=version_callback, is_eager=True, help="Show version"
|
|
69
|
+
),
|
|
70
|
+
) -> None:
|
|
71
|
+
pass
|
|
72
|
+
|
|
65
73
|
@app.command("help", help="Show this help message and exit.")
|
|
66
74
|
def show_help(ctx: typer.Context) -> None:
|
|
67
75
|
"""Print the global CLI help."""
|
|
@@ -485,6 +493,8 @@ _ADDABLE_FEATURES = {
|
|
|
485
493
|
"opentelemetry": "OpenTelemetry distributed tracing",
|
|
486
494
|
"sendgrid": "SendGrid email",
|
|
487
495
|
"smtp": "SMTP email (fastapi-mail)",
|
|
496
|
+
"ses": "AWS SES email",
|
|
497
|
+
"resend": "Resend transactional email",
|
|
488
498
|
"slack": "Slack webhook notifications",
|
|
489
499
|
"discord": "Discord webhook notifications",
|
|
490
500
|
"qdrant": "Qdrant vector database",
|
|
@@ -522,18 +532,35 @@ def add_feature(
|
|
|
522
532
|
feature: Optional[str] = typer.Argument(None, help="Feature to add. Leave blank to see all available features."),
|
|
523
533
|
project_dir: Path = typer.Option(Path("."), "--dir", "-d", help="Path to the existing project"),
|
|
524
534
|
) -> None:
|
|
535
|
+
import json
|
|
536
|
+
import shlex
|
|
537
|
+
import subprocess
|
|
538
|
+
from jinja2 import Environment, PackageLoader, StrictUndefined, TemplateNotFound
|
|
539
|
+
from fastapi_spawn.steps import FEATURE_ACTIONS
|
|
540
|
+
|
|
525
541
|
_print_banner()
|
|
542
|
+
|
|
543
|
+
# Load tracking config
|
|
544
|
+
config_path = project_dir / ".fastapi-spawn.json"
|
|
545
|
+
project_status: dict = {}
|
|
546
|
+
if config_path.exists():
|
|
547
|
+
try:
|
|
548
|
+
project_status = json.loads(config_path.read_text(encoding="utf-8"))
|
|
549
|
+
except Exception:
|
|
550
|
+
console.print("[dim yellow]⚠ Could not parse .fastapi-spawn.json — starting fresh[/dim yellow]")
|
|
551
|
+
|
|
552
|
+
# Show feature table if no feature given
|
|
526
553
|
if not feature:
|
|
527
|
-
|
|
528
|
-
table = Table(box=None)
|
|
554
|
+
installed = set(project_status.get("installed_features", []))
|
|
555
|
+
table = Table(title="Available Features", box=None)
|
|
529
556
|
table.add_column("Feature", style="bold green", justify="left")
|
|
530
557
|
table.add_column("Description", style="white", justify="left")
|
|
531
|
-
|
|
558
|
+
table.add_column("Status", justify="center")
|
|
532
559
|
for k, v in _ADDABLE_FEATURES.items():
|
|
533
|
-
|
|
534
|
-
|
|
560
|
+
status = "[bold green]✓ installed[/bold green]" if k in installed else "[dim]—[/dim]"
|
|
561
|
+
table.add_row(k, v, status)
|
|
535
562
|
console.print(table)
|
|
536
|
-
console.print("\n[dim]Run
|
|
563
|
+
console.print("\n[dim]Run: fastapi-spawn add <feature>[/dim]\n")
|
|
537
564
|
raise typer.Exit(0)
|
|
538
565
|
|
|
539
566
|
if feature not in _ADDABLE_FEATURES:
|
|
@@ -547,124 +574,134 @@ def add_feature(
|
|
|
547
574
|
console.print(f"[bold red]✗ Directory not found:[/bold red] {project_dir.resolve()}")
|
|
548
575
|
raise typer.Exit(1)
|
|
549
576
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
577
|
+
installed_features: list = project_status.get("installed_features", [])
|
|
578
|
+
if feature in installed_features:
|
|
579
|
+
console.print(f"[bold yellow]⚠ '{feature}' already installed in this project.[/bold yellow]")
|
|
580
|
+
raise typer.Exit(0)
|
|
581
|
+
|
|
582
|
+
action = FEATURE_ACTIONS.get(feature)
|
|
583
|
+
if not action:
|
|
584
|
+
console.print(f"[bold yellow]⚠ No automation defined for '{feature}'.[/bold yellow]")
|
|
585
|
+
raise typer.Exit(0)
|
|
586
|
+
|
|
587
|
+
console.print(Panel.fit(
|
|
588
|
+
f"[bold cyan]→ Adding:[/bold cyan] [bold]{feature}[/bold]\n[dim]{_ADDABLE_FEATURES[feature]}[/dim]",
|
|
589
|
+
border_style="cyan", padding=(0, 2),
|
|
590
|
+
))
|
|
591
|
+
|
|
592
|
+
# [1/4] Install deps
|
|
593
|
+
deps = action.get("deps", [])
|
|
594
|
+
if deps:
|
|
595
|
+
console.print("\n[bold cyan][1/4] Installing dependencies...[/bold cyan]")
|
|
596
|
+
for d in deps:
|
|
597
|
+
console.print(f" [dim]uv add {d}[/dim]")
|
|
569
598
|
try:
|
|
570
|
-
|
|
571
|
-
|
|
599
|
+
subprocess.run(["uv", "add"] + deps, cwd=project_dir, check=True)
|
|
600
|
+
console.print(" [bold green]✓ Dependencies installed[/bold green]")
|
|
572
601
|
except subprocess.CalledProcessError:
|
|
573
|
-
console.print("[bold red]✗ Failed to
|
|
602
|
+
console.print(f" [bold red]✗ Failed to install deps[/bold red]")
|
|
574
603
|
raise typer.Exit(1)
|
|
575
|
-
|
|
576
|
-
|
|
604
|
+
else:
|
|
605
|
+
console.print("\n[bold cyan][1/4] No extra dependencies needed.[/bold cyan]")
|
|
606
|
+
|
|
607
|
+
# [2/4] Generate files from templates
|
|
608
|
+
files = action.get("files", [])
|
|
609
|
+
if files:
|
|
610
|
+
console.print("\n[bold cyan][2/4] Generating files...[/bold cyan]")
|
|
611
|
+
jinja_env = Environment(
|
|
612
|
+
loader=PackageLoader("fastapi_spawn", "templates"),
|
|
613
|
+
undefined=StrictUndefined,
|
|
614
|
+
keep_trailing_newline=True,
|
|
615
|
+
trim_blocks=True,
|
|
616
|
+
lstrip_blocks=True,
|
|
617
|
+
)
|
|
618
|
+
ctx = {
|
|
619
|
+
"project_name": project_status.get("project_name", project_dir.name),
|
|
620
|
+
"db": project_status.get("db", "postgresql"),
|
|
621
|
+
"orm": project_status.get("orm", "sqlalchemy"),
|
|
622
|
+
"has_auth": feature == "auth" or "auth" in installed_features,
|
|
623
|
+
"has_alembic": feature == "alembic" or "alembic" in installed_features,
|
|
624
|
+
"has_broker": feature in ("celery", "arq") or any(f in installed_features for f in ("celery", "arq")),
|
|
625
|
+
"has_s3": feature in ("s3", "gcs", "cloudinary") or any(f in installed_features for f in ("s3", "gcs", "cloudinary")),
|
|
626
|
+
"has_ai": feature in ("openai", "anthropic", "gemini", "ollama", "langchain", "llamaindex"),
|
|
627
|
+
"ai_provider": feature if feature in ("openai", "anthropic", "gemini", "ollama", "langchain", "llamaindex") else project_status.get("ai", "none"),
|
|
628
|
+
"storage_provider": feature if feature in ("s3", "gcs", "cloudinary") else project_status.get("storage", "none"),
|
|
629
|
+
"email_provider": feature if feature in ("sendgrid", "smtp", "ses", "resend") else project_status.get("email", "none"),
|
|
630
|
+
"notify_provider": feature if feature in ("slack", "discord") else project_status.get("notify", "none"),
|
|
631
|
+
"vector_db": feature if feature in ("qdrant", "chroma", "pinecone", "elasticsearch") else project_status.get("vector_db", "none"),
|
|
632
|
+
"monitoring": feature if feature in ("sentry", "prometheus", "opentelemetry") else project_status.get("monitoring", "none"),
|
|
633
|
+
}
|
|
634
|
+
for template_path, dest_rel in files:
|
|
635
|
+
dest = project_dir / dest_rel
|
|
636
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
637
|
+
try:
|
|
638
|
+
tmpl = jinja_env.get_template(template_path)
|
|
639
|
+
dest.write_text(tmpl.render(**ctx), encoding="utf-8")
|
|
640
|
+
console.print(f" [bold green]✓[/bold green] {dest_rel}")
|
|
641
|
+
except TemplateNotFound:
|
|
642
|
+
console.print(f" [dim yellow]⚠ No template for {dest_rel} — skipping[/dim yellow]")
|
|
643
|
+
except Exception as exc:
|
|
644
|
+
console.print(f" [bold red]✗ {dest_rel}: {exc}[/bold red]")
|
|
645
|
+
else:
|
|
646
|
+
console.print("\n[bold cyan][2/4] No files to generate.[/bold cyan]")
|
|
647
|
+
|
|
648
|
+
# [3/4] Append env vars to .env
|
|
649
|
+
env_vars = action.get("env_vars", [])
|
|
650
|
+
if env_vars:
|
|
651
|
+
console.print("\n[bold cyan][3/4] Updating .env...[/bold cyan]")
|
|
652
|
+
env_file = project_dir / ".env"
|
|
653
|
+
existing_env = env_file.read_text(encoding="utf-8") if env_file.exists() else ""
|
|
654
|
+
new_lines = [v for v in env_vars if v.split("=")[0].strip() not in existing_env]
|
|
655
|
+
if new_lines:
|
|
656
|
+
with env_file.open("a", encoding="utf-8") as f:
|
|
657
|
+
f.write(f"\n# Added by fastapi-spawn add {feature}\n")
|
|
658
|
+
for line in new_lines:
|
|
659
|
+
f.write(line + "\n")
|
|
660
|
+
console.print(f" [bold green]✓[/bold green] Added {len(new_lines)} env var(s)")
|
|
661
|
+
else:
|
|
662
|
+
console.print(" [dim]All env vars already in .env[/dim]")
|
|
663
|
+
else:
|
|
664
|
+
console.print("\n[bold cyan][3/4] No env vars needed.[/bold cyan]")
|
|
665
|
+
|
|
666
|
+
# [4/4] Run init commands / special handling
|
|
667
|
+
if action.get("_special") == "alembic":
|
|
668
|
+
console.print("\n[bold cyan][4/4] Initializing Alembic...[/bold cyan]")
|
|
577
669
|
try:
|
|
578
|
-
console.print("[dim]Running: uv run alembic init migrations[/dim]")
|
|
579
670
|
subprocess.run(["uv", "run", "alembic", "init", "migrations"], cwd=project_dir, check=True)
|
|
671
|
+
console.print(" [bold green]✓ alembic init migrations[/bold green]")
|
|
672
|
+
db_val = project_status.get("db", "postgresql")
|
|
673
|
+
cfg = ProjectConfig(project_name=project_dir.name, db=Database(db_val), orm=ORM.sqlalchemy, migration=MigrationTool.alembic)
|
|
674
|
+
gen = ProjectGenerator(cfg, project_dir)
|
|
675
|
+
gen._render_to(project_dir / "migrations" / "env.py", "alembic/env.py.j2")
|
|
676
|
+
console.print(" [bold green]✓ Rendered async env.py[/bold green]")
|
|
580
677
|
except subprocess.CalledProcessError:
|
|
581
|
-
console.print("[bold red]✗
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
generator._render_to(project_dir / "migrations" / "env.py", "alembic/env.py.j2")
|
|
594
|
-
|
|
595
|
-
console.print("[bold green]✓ Alembic added successfully![/bold green]")
|
|
596
|
-
console.print("\n[bold yellow]Next Steps:[/bold yellow]")
|
|
597
|
-
console.print(" 1. Update [bold]alembic.ini[/bold] with your database URL.")
|
|
598
|
-
console.print(" 2. Run [bold]uv run alembic revision --autogenerate -m 'initial'[/bold]")
|
|
599
|
-
console.print(" 3. Run [bold]uv run alembic upgrade head[/bold]\n")
|
|
600
|
-
raise typer.Exit(0)
|
|
678
|
+
console.print(" [bold red]✗ alembic init failed[/bold red]")
|
|
679
|
+
elif action.get("run_cmds"):
|
|
680
|
+
console.print("\n[bold cyan][4/4] Running commands...[/bold cyan]")
|
|
681
|
+
for cmd_str in action["run_cmds"]:
|
|
682
|
+
console.print(f" [dim]{cmd_str}[/dim]")
|
|
683
|
+
try:
|
|
684
|
+
subprocess.run(shlex.split(cmd_str), cwd=project_dir, check=True)
|
|
685
|
+
console.print(f" [bold green]✓[/bold green] done")
|
|
686
|
+
except subprocess.CalledProcessError:
|
|
687
|
+
console.print(f" [bold red]✗ Failed: {cmd_str}[/bold red]")
|
|
688
|
+
else:
|
|
689
|
+
console.print("\n[bold cyan][4/4] No init commands needed.[/bold cyan]")
|
|
601
690
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
"gemini": ["Add dep: google-generativeai>=0.7.0", "Create app/core/ai.py (chat_completion, get_embedding)", "Add to .env: GEMINI_API_KEY, GEMINI_MODEL=gemini-1.5-pro"],
|
|
617
|
-
"ollama": ["No API key needed — run: docker run -p 11434:11434 ollama/ollama", "Create app/core/ai.py (chat via httpx)", "Add to .env: OLLAMA_HOST=localhost, OLLAMA_PORT=11434, OLLAMA_MODEL=llama3"],
|
|
618
|
-
"langchain": ["Add deps: langchain>=0.2.0, langchain-openai>=0.1.0", "Create app/core/ai.py (LangChain ChatOpenAI + embeddings)", "Add to .env: OPENAI_API_KEY, OPENAI_MODEL, OPENAI_BASE_URL"],
|
|
619
|
-
"llamaindex":["Add deps: llama-index>=0.10.0, llama-index-llms-openai, llama-index-embeddings-openai", "Create app/core/ai.py (LlamaIndex VectorStoreIndex)", "Add to .env: OPENAI_API_KEY, OPENAI_MODEL, OPENAI_EMBEDDING_MODEL"],
|
|
620
|
-
"alembic": ["Add dep: alembic>=1.13.0", "Run: alembic init migrations", "Replace migrations/env.py with async-compatible version", "Add to [tool.uv.scripts]: migrate = 'alembic upgrade head'", "Run: uv run migrate"],
|
|
621
|
-
"celery": ["Add dep: celery[redis]>=5.3.6", "Create tasks/celery_app.py + tasks/sample_tasks.py", "Add to .env: REDIS_HOST, REDIS_PORT, REDIS_DB", "Add to [tool.uv.scripts]: worker = 'celery -A tasks.celery_app worker --loglevel=info'"],
|
|
622
|
-
"arq": ["Add deps: arq>=0.25.0, redis[hiredis]>=5.0.0", "Create tasks/arq_worker.py (WorkerSettings, task defs)", "Run with: arq tasks.arq_worker.WorkerSettings"],
|
|
623
|
-
"redis": ["Add dep: redis[hiredis]>=5.0.0", "Add to .env: REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_DB", "Add redis_url @property to Settings in app/core/config.py"],
|
|
624
|
-
"kafka": ["Add dep: aiokafka>=0.10.0", "Add to .env: KAFKA_HOST=localhost, KAFKA_PORT=9092"],
|
|
625
|
-
"sentry": ["Add dep: sentry-sdk[fastapi]>=2.0.0", "Create app/core/monitoring.py (init_sentry)", "Add to .env: SENTRY_DSN=https://xxx@sentry.io/yyy", "Call init_sentry() in app/main.py on startup"],
|
|
626
|
-
"prometheus":["Add dep: prometheus-fastapi-instrumentator>=7.0.0", "Create app/core/monitoring.py (init_prometheus)", "Call init_prometheus(app) in app/main.py — exposes /metrics"],
|
|
627
|
-
"opentelemetry": ["Add dep: opentelemetry-sdk, opentelemetry-instrumentation-fastapi", "Set OTEL_EXPORTER_OTLP_ENDPOINT in .env or compose"],
|
|
628
|
-
"sendgrid": ["Add dep: sendgrid>=6.11.0", "Create app/core/email.py", "Add to .env: SENDGRID_API_KEY, SENDGRID_FROM_EMAIL"],
|
|
629
|
-
"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"],
|
|
630
|
-
"ses": ["Add dep: boto3>=1.34.0", "Create app/core/email.py", "Add to .env: AWS_* credentials, SES_FROM_EMAIL"],
|
|
631
|
-
"resend": ["Add dep: resend>=2.1.0", "Create app/core/email.py (Resend client)", "Add to .env: RESEND_API_KEY"],
|
|
632
|
-
"slack": ["No extra dep (uses httpx)", "Create app/core/notifications.py", "Add to .env: SLACK_WEBHOOK_URL=https://hooks.slack.com/services/..."],
|
|
633
|
-
"discord": ["No extra dep (uses httpx)", "Create app/core/notifications.py", "Add to .env: DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/..."],
|
|
634
|
-
"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)"],
|
|
635
|
-
"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"],
|
|
636
|
-
"pinecone": ["Add dep: pinecone-client>=3.2.0", "Create app/core/vector_db.py", "Add to .env: PINECONE_API_KEY, PINECONE_INDEX_NAME"],
|
|
637
|
-
"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"],
|
|
638
|
-
"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"],
|
|
639
|
-
"opensearch": ["Add dep: opensearch-py[async]>=2.5.0", "Create app/core/search.py (OpenSearch client)", "Add to .env: OPENSEARCH_HOST, OPENSEARCH_PORT, OPENSEARCH_USER, OPENSEARCH_PASSWORD"],
|
|
640
|
-
"vespa": ["Add dep: pyvespa>=0.40.0", "Create app/core/search.py (Vespa client)", "Add to .env: VESPA_ENDPOINT"],
|
|
641
|
-
"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}"],
|
|
642
|
-
"sse": ["Add dep: sse-starlette>=2.1.0", "Create app/api/v1/streaming/router.py", "Return EventSourceResponse(async_generator)"],
|
|
643
|
-
"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')"],
|
|
644
|
-
"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"],
|
|
645
|
-
"sso": ["Add dep: fastapi-sso>=0.14.0", "Create app/api/v1/auth/sso.py (Google/Github/Microsoft SSO)", "Add to .env: GOOGLE_CLIENT_ID, GITHUB_CLIENT_ID, etc."],
|
|
646
|
-
"sso-google": ["1. Use 'fastapi-spawn new temp_app --extra sso-google' and copy the resulting sso.py"],
|
|
647
|
-
"sso-github": ["1. Use 'fastapi-spawn new temp_app --extra sso-github' and copy the resulting sso.py"],
|
|
648
|
-
"sso-microsoft": ["1. Use 'fastapi-spawn new temp_app --extra sso-microsoft' and copy the resulting sso.py"],
|
|
649
|
-
"seed": ["Add dep: faker>=25.0.0", "Create db/seed.py (generate 100 mock users/posts)", "Run: uv run python db/seed.py"],
|
|
650
|
-
"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"],
|
|
651
|
-
"rbac": ["Create app/core/permissions.py and app/api/v1/permissions/router.py", "Add app.include_router(permissions.router) to main.py"],
|
|
652
|
-
"caching": ["Add dep: fastapi-cache2[redis]>=0.2.1", "Create app/core/cache.py", "Initialize cache in lifespan and use @cache(expire=60) on endpoints"],
|
|
653
|
-
"response-format": ["Create app/middleware/response_format.py", "Add app.add_middleware(ResponseFormattingMiddleware) to main.py"],
|
|
654
|
-
"admin": ["Add dep: sqladmin[full]>=0.16.1", "Create app/admin/setup.py", "Call setup_admin(app, engine) in main.py"],
|
|
655
|
-
"pagination":["Add dep: fastapi-pagination>=0.12.0", "Create app/api/v1/pagination/router.py", "Call add_pagination(app) in main.py"],
|
|
656
|
-
"uploads": ["Create app/api/v1/uploads/router.py", "Include router in app/api/v1/router.py"],
|
|
657
|
-
"docker": ["Create Dockerfile (multi-stage, uv-based)", "Create docker-compose.yml with all selected services", "Create .dockerignore"],
|
|
658
|
-
"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"],
|
|
659
|
-
"helm": ["Create infra/helm/Chart.yaml", "Create infra/helm/values.yaml (replicas, image, resources)", "Run: helm install my-release ./infra/helm"],
|
|
660
|
-
"terraform": ["Create infra/terraform/main.tf (AWS ECR + ECS)", "Create infra/terraform/variables.tf", "Run: terraform -chdir=infra/terraform init && terraform apply"],
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
steps = _STEPS.get(feature, [])
|
|
664
|
-
if steps:
|
|
665
|
-
content = "\n".join(f" [dim]{i+1}.[/dim] {s}" for i, s in enumerate(steps))
|
|
666
|
-
console.print(Panel(content, title=f"[bold cyan]Steps to add '{feature}'[/bold cyan]", border_style="cyan", padding=(0, 1)))
|
|
667
|
-
console.print(f"\n[dim]Preview files:[/dim] [bold cyan]fastapi-spawn new <name> --{feature} --dry-run[/bold cyan]")
|
|
691
|
+
# Update tracking file
|
|
692
|
+
installed_features.append(feature)
|
|
693
|
+
project_status["installed_features"] = installed_features
|
|
694
|
+
try:
|
|
695
|
+
config_path.write_text(json.dumps(project_status, indent=2), encoding="utf-8")
|
|
696
|
+
console.print("\n[dim]ℹ .fastapi-spawn.json updated[/dim]")
|
|
697
|
+
except Exception:
|
|
698
|
+
pass
|
|
699
|
+
|
|
700
|
+
note = action.get("note")
|
|
701
|
+
msg = f"[bold green]✓ {feature.capitalize()} added successfully![/bold green]"
|
|
702
|
+
if note:
|
|
703
|
+
msg += f"\n[dim]{note}[/dim]"
|
|
704
|
+
console.print(Panel(msg, border_style="green", padding=(0, 2)))
|
|
668
705
|
|
|
669
706
|
|
|
670
707
|
# ── Helpers ────────────────────────────────────────────────────────────────────
|