appgenerator-cli 1.0.0__py3-none-any.whl

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 (48) hide show
  1. appgenerator_cli-1.0.0.dist-info/METADATA +213 -0
  2. appgenerator_cli-1.0.0.dist-info/RECORD +48 -0
  3. appgenerator_cli-1.0.0.dist-info/WHEEL +4 -0
  4. appgenerator_cli-1.0.0.dist-info/entry_points.txt +4 -0
  5. pyforge/__init__.py +10 -0
  6. pyforge/commands/__init__.py +1 -0
  7. pyforge/commands/create.py +60 -0
  8. pyforge/generator.py +248 -0
  9. pyforge/main.py +32 -0
  10. pyforge/templates/ai/.env.example +29 -0
  11. pyforge/templates/ai/.gitignore +47 -0
  12. pyforge/templates/ai/Dockerfile +22 -0
  13. pyforge/templates/ai/README.md +97 -0
  14. pyforge/templates/ai/app/__init__.py +1 -0
  15. pyforge/templates/ai/app/agents/__init__.py +1 -0
  16. pyforge/templates/ai/app/agents/assistant.py +100 -0
  17. pyforge/templates/ai/app/chains/__init__.py +1 -0
  18. pyforge/templates/ai/app/chains/rag.py +50 -0
  19. pyforge/templates/ai/app/config.py +47 -0
  20. pyforge/templates/ai/app/tools/__init__.py +1 -0
  21. pyforge/templates/ai/app/tools/registry.py +19 -0
  22. pyforge/templates/ai/app/tools/search.py +34 -0
  23. pyforge/templates/ai/docker-compose.yml +39 -0
  24. pyforge/templates/ai/main.py +40 -0
  25. pyforge/templates/ai/pyproject.toml +28 -0
  26. pyforge/templates/ai/tests/__init__.py +1 -0
  27. pyforge/templates/ai/tests/conftest.py +21 -0
  28. pyforge/templates/ai/tests/test_agent.py +53 -0
  29. pyforge/templates/fastapi/.env.example +17 -0
  30. pyforge/templates/fastapi/.gitignore +42 -0
  31. pyforge/templates/fastapi/Dockerfile +27 -0
  32. pyforge/templates/fastapi/README.md +68 -0
  33. pyforge/templates/fastapi/app/__init__.py +1 -0
  34. pyforge/templates/fastapi/app/api/__init__.py +1 -0
  35. pyforge/templates/fastapi/app/api/v1/__init__.py +1 -0
  36. pyforge/templates/fastapi/app/api/v1/health.py +25 -0
  37. pyforge/templates/fastapi/app/config.py +45 -0
  38. pyforge/templates/fastapi/app/db/__init__.py +1 -0
  39. pyforge/templates/fastapi/app/db/session.py +35 -0
  40. pyforge/templates/fastapi/app/dependencies.py +12 -0
  41. pyforge/templates/fastapi/app/main.py +58 -0
  42. pyforge/templates/fastapi/app/models/__init__.py +4 -0
  43. pyforge/templates/fastapi/app/models/base.py +23 -0
  44. pyforge/templates/fastapi/docker-compose.yml +39 -0
  45. pyforge/templates/fastapi/pyproject.toml +29 -0
  46. pyforge/templates/fastapi/tests/__init__.py +1 -0
  47. pyforge/templates/fastapi/tests/conftest.py +46 -0
  48. pyforge/templates/fastapi/tests/test_health.py +13 -0
@@ -0,0 +1,27 @@
1
+ # ── Build stage ────────────────────────────────────────────────────────────────
2
+ FROM python:3.12-slim AS builder
3
+
4
+ WORKDIR /app
5
+
6
+ # Install uv
7
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
8
+
9
+ # Install dependencies (cached layer)
10
+ COPY pyproject.toml uv.lock* ./
11
+ RUN uv sync --frozen --no-dev
12
+
13
+ # ── Runtime stage ──────────────────────────────────────────────────────────────
14
+ FROM python:3.12-slim AS runtime
15
+
16
+ WORKDIR /app
17
+
18
+ # Copy venv from builder
19
+ COPY --from=builder /app/.venv /app/.venv
20
+ ENV PATH="/app/.venv/bin:$PATH"
21
+
22
+ # Copy application source
23
+ COPY app ./app
24
+
25
+ EXPOSE 8000
26
+
27
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
@@ -0,0 +1,68 @@
1
+ # {{ project_name }}
2
+
3
+ A production-ready FastAPI backend, scaffolded by [AppGenerator](https://github.com/yourname/appgenerator-cli).
4
+
5
+ ## Tech Stack
6
+
7
+ - **[FastAPI](https://fastapi.tiangolo.com/)** — high-performance async web framework
8
+ - **[SQLModel](https://sqlmodel.tiangolo.com/)** — type-safe ORM (built on SQLAlchemy + Pydantic)
9
+ - **[Pydantic Settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/)** — typed config from env
10
+ - **[Alembic](https://alembic.sqlalchemy.org/)** — database migrations
11
+ - **[uv](https://docs.astral.sh/uv/)** — blazing-fast package management
12
+ {% if postgres %}- **PostgreSQL** — relational database
13
+ {% endif %}{% if redis %}- **Redis** — caching layer
14
+ {% endif %}{% if docker %}- **Docker** — containerisation
15
+ {% endif %}
16
+
17
+ ## Getting Started
18
+
19
+ ```bash
20
+ # 1. Copy and fill in your environment variables
21
+ cp .env.example .env
22
+
23
+ # 2. Run the development server
24
+ uv run uvicorn app.main:app --reload
25
+
26
+ # 3. Visit the interactive docs
27
+ open http://localhost:8000/docs
28
+ ```
29
+
30
+ ## Project Structure
31
+
32
+ ```
33
+ {{ project_name }}/
34
+ ├── app/
35
+ │ ├── main.py # FastAPI application factory
36
+ │ ├── config.py # Typed settings (pydantic-settings)
37
+ │ ├── dependencies.py # Shared FastAPI dependencies
38
+ │ ├── api/
39
+ │ │ ├── __init__.py
40
+ │ │ └── v1/
41
+ │ │ ├── __init__.py
42
+ │ │ └── health.py
43
+ │ ├── models/
44
+ │ │ ├── __init__.py
45
+ │ │ └── base.py # SQLModel base + common mixins
46
+ │ └── db/
47
+ │ ├── __init__.py
48
+ │ └── session.py # Async database session
49
+ ├── tests/
50
+ │ ├── conftest.py
51
+ │ └── test_health.py
52
+ ├── .env.example
53
+ ├── .gitignore
54
+ └── pyproject.toml
55
+ ```
56
+
57
+ ## Running Tests
58
+
59
+ ```bash
60
+ uv run pytest
61
+ ```
62
+
63
+ ## Linting
64
+
65
+ ```bash
66
+ uv run ruff check .
67
+ uv run ruff format .
68
+ ```
@@ -0,0 +1 @@
1
+ """{{ project_name }} application package."""
@@ -0,0 +1 @@
1
+ """API package."""
@@ -0,0 +1 @@
1
+ """API v1 package."""
@@ -0,0 +1,25 @@
1
+ """
2
+ Health-check endpoint — always the first router to wire up.
3
+ GET /api/v1/health → 200 OK
4
+ """
5
+ from datetime import datetime, timezone
6
+
7
+ from fastapi import APIRouter
8
+ from pydantic import BaseModel
9
+
10
+ router = APIRouter()
11
+
12
+
13
+ class HealthResponse(BaseModel):
14
+ status: str
15
+ timestamp: datetime
16
+ version: str
17
+
18
+
19
+ @router.get("/health", response_model=HealthResponse, summary="Service health check")
20
+ async def health_check() -> HealthResponse:
21
+ return HealthResponse(
22
+ status="ok",
23
+ timestamp=datetime.now(timezone.utc),
24
+ version="0.1.0",
25
+ )
@@ -0,0 +1,45 @@
1
+ """
2
+ Typed application settings loaded from environment variables / .env file.
3
+ """
4
+ from functools import lru_cache
5
+ from typing import Annotated
6
+
7
+ from pydantic import field_validator
8
+ from pydantic_settings import BaseSettings, SettingsConfigDict
9
+
10
+
11
+ class Settings(BaseSettings):
12
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
13
+
14
+ # App
15
+ app_name: str = "{{ project_name }}"
16
+ app_env: str = "development"
17
+ debug: bool = True
18
+ secret_key: str = "change-me"
19
+
20
+ # Server
21
+ host: str = "0.0.0.0"
22
+ port: int = 8000
23
+ cors_origins: list[str] = ["*"]
24
+
25
+ # Database
26
+ database_url: str = "sqlite+aiosqlite:///./{{ package_name }}.db"
27
+ {% if redis %}
28
+ # Redis
29
+ redis_url: str = "redis://localhost:6379/0"
30
+ {% endif %}
31
+
32
+ @field_validator("cors_origins", mode="before")
33
+ @classmethod
34
+ def parse_cors(cls, v: str | list[str]) -> list[str]:
35
+ if isinstance(v, str):
36
+ return [origin.strip() for origin in v.split(",")]
37
+ return v
38
+
39
+
40
+ @lru_cache
41
+ def get_settings() -> Settings:
42
+ return Settings()
43
+
44
+
45
+ settings = get_settings()
@@ -0,0 +1 @@
1
+ """Database package."""
@@ -0,0 +1,35 @@
1
+ """
2
+ Async database session factory using SQLModel + SQLAlchemy.
3
+ """
4
+ from collections.abc import AsyncGenerator
5
+
6
+ from sqlalchemy.ext.asyncio import create_async_engine
7
+ from sqlalchemy.orm import sessionmaker
8
+ from sqlmodel import SQLModel
9
+ from sqlmodel.ext.asyncio.session import AsyncSession
10
+
11
+ from app.config import settings
12
+
13
+ engine = create_async_engine(
14
+ settings.database_url,
15
+ echo=settings.debug,
16
+ future=True,
17
+ )
18
+
19
+ AsyncSessionLocal = sessionmaker( # type: ignore[call-overload]
20
+ bind=engine,
21
+ class_=AsyncSession,
22
+ expire_on_commit=False,
23
+ )
24
+
25
+
26
+ async def init_db() -> None:
27
+ """Create all tables on startup (use Alembic for production migrations)."""
28
+ async with engine.begin() as conn:
29
+ await conn.run_sync(SQLModel.metadata.create_all)
30
+
31
+
32
+ async def get_session() -> AsyncGenerator[AsyncSession, None]:
33
+ """FastAPI dependency that yields an async DB session."""
34
+ async with AsyncSessionLocal() as session:
35
+ yield session
@@ -0,0 +1,12 @@
1
+ """
2
+ Shared FastAPI dependency functions (DB sessions, auth, pagination, etc.).
3
+ """
4
+ from typing import Annotated
5
+
6
+ from fastapi import Depends
7
+ from sqlmodel.ext.asyncio.session import AsyncSession
8
+
9
+ from app.db.session import get_session
10
+
11
+ # Re-export as typed dependency alias for cleaner route signatures
12
+ SessionDep = Annotated[AsyncSession, Depends(get_session)]
@@ -0,0 +1,58 @@
1
+ """
2
+ {{ project_name }} — FastAPI application entry point.
3
+ """
4
+ from collections.abc import AsyncGenerator
5
+ from contextlib import asynccontextmanager
6
+
7
+ from fastapi import FastAPI
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+
10
+ from app.api.v1 import health
11
+ from app.config import settings
12
+ from app.db.session import init_db
13
+
14
+
15
+ @asynccontextmanager
16
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
17
+ """Run startup/shutdown tasks."""
18
+ await init_db()
19
+ yield
20
+ # teardown (close connections, flush caches, etc.)
21
+
22
+
23
+ def create_app() -> FastAPI:
24
+ application = FastAPI(
25
+ title=settings.app_name,
26
+ version="0.1.0",
27
+ description="Generated by AppGenerator",
28
+ docs_url="/docs" if settings.debug else None,
29
+ redoc_url="/redoc" if settings.debug else None,
30
+ lifespan=lifespan,
31
+ )
32
+
33
+ # CORS
34
+ application.add_middleware(
35
+ CORSMiddleware,
36
+ allow_origins=settings.cors_origins,
37
+ allow_credentials=True,
38
+ allow_methods=["*"],
39
+ allow_headers=["*"],
40
+ )
41
+
42
+ # Routers
43
+ application.include_router(health.router, prefix="/api/v1", tags=["health"])
44
+
45
+ return application
46
+
47
+
48
+ app = create_app()
49
+
50
+
51
+ if __name__ == "__main__":
52
+ import uvicorn
53
+ uvicorn.run(
54
+ "app.main:app",
55
+ host=settings.host,
56
+ port=settings.port,
57
+ reload=settings.debug,
58
+ )
@@ -0,0 +1,4 @@
1
+ """SQLModel domain models."""
2
+ from app.models.base import BaseModel, TimestampMixin
3
+
4
+ __all__ = ["BaseModel", "TimestampMixin"]
@@ -0,0 +1,23 @@
1
+ """
2
+ Base SQLModel classes with common fields (id, created_at, updated_at).
3
+ """
4
+ from datetime import datetime, timezone
5
+
6
+ from sqlmodel import Field, SQLModel
7
+
8
+
9
+ def utcnow() -> datetime:
10
+ return datetime.now(timezone.utc)
11
+
12
+
13
+ class TimestampMixin(SQLModel):
14
+ """Adds created_at and updated_at to any model."""
15
+
16
+ created_at: datetime = Field(default_factory=utcnow, nullable=False)
17
+ updated_at: datetime = Field(default_factory=utcnow, nullable=False)
18
+
19
+
20
+ class BaseModel(TimestampMixin, table=False):
21
+ """Abstract base — inherit from this for all your DB models."""
22
+
23
+ id: int | None = Field(default=None, primary_key=True)
@@ -0,0 +1,39 @@
1
+ version: "3.9"
2
+
3
+ services:
4
+ api:
5
+ build: .
6
+ ports:
7
+ - "8000:8000"
8
+ env_file: .env
9
+ depends_on:
10
+ {% if postgres %}- postgres{% endif %}
11
+ {% if redis %}- redis{% endif %}
12
+ restart: unless-stopped
13
+
14
+ {% if postgres %}
15
+ postgres:
16
+ image: postgres:16-alpine
17
+ environment:
18
+ POSTGRES_DB: {{ package_name }}_db
19
+ POSTGRES_USER: postgres
20
+ POSTGRES_PASSWORD: postgres
21
+ ports:
22
+ - "5432:5432"
23
+ volumes:
24
+ - pg_data:/var/lib/postgresql/data
25
+ restart: unless-stopped
26
+ {% endif %}
27
+
28
+ {% if redis %}
29
+ redis:
30
+ image: redis:7-alpine
31
+ ports:
32
+ - "6379:6379"
33
+ restart: unless-stopped
34
+ {% endif %}
35
+
36
+ {% if postgres %}
37
+ volumes:
38
+ pg_data:
39
+ {% endif %}
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "{{ project_name }}"
3
+ version = "0.1.0"
4
+ description = "A FastAPI backend project"
5
+ requires-python = ">=3.10"
6
+ dependencies = []
7
+
8
+ [build-system]
9
+ requires = ["uv_build>=0.11.2,<0.12"]
10
+ build-backend = "uv_build"
11
+
12
+ [tool.uv.build-backend]
13
+ module-name = "app"
14
+ module-root = ""
15
+
16
+ [tool.ruff]
17
+ line-length = 100
18
+ target-version = "py310"
19
+ select = ["E", "F", "I", "N", "UP"]
20
+
21
+ [tool.mypy]
22
+ python_version = "3.10"
23
+ strict = true
24
+ ignore_missing_imports = true
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+ asyncio_mode = "auto"
29
+
@@ -0,0 +1 @@
1
+ """Test suite."""
@@ -0,0 +1,46 @@
1
+ """
2
+ Pytest configuration and shared fixtures.
3
+ """
4
+ import pytest_asyncio
5
+ from httpx import ASGITransport, AsyncClient
6
+ from sqlalchemy.ext.asyncio import create_async_engine
7
+ from sqlalchemy.orm import sessionmaker
8
+ from sqlmodel import SQLModel
9
+ from sqlmodel.ext.asyncio.session import AsyncSession
10
+
11
+ from app.db.session import get_session
12
+ from app.main import app
13
+
14
+ # Use an in-memory SQLite DB for tests
15
+ TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
16
+
17
+
18
+ @pytest_asyncio.fixture(scope="session")
19
+ async def test_engine():
20
+ engine = create_async_engine(TEST_DATABASE_URL, echo=False)
21
+ async with engine.begin() as conn:
22
+ await conn.run_sync(SQLModel.metadata.create_all)
23
+ yield engine
24
+ await engine.dispose()
25
+
26
+
27
+ @pytest_asyncio.fixture
28
+ async def db_session(test_engine):
29
+ AsyncTestSession = sessionmaker(
30
+ bind=test_engine, class_=AsyncSession, expire_on_commit=False
31
+ )
32
+ async with AsyncTestSession() as session:
33
+ yield session
34
+
35
+
36
+ @pytest_asyncio.fixture
37
+ async def client(db_session):
38
+ async def override_get_session():
39
+ yield db_session
40
+
41
+ app.dependency_overrides[get_session] = override_get_session
42
+ async with AsyncClient(
43
+ transport=ASGITransport(app=app), base_url="http://test"
44
+ ) as ac:
45
+ yield ac
46
+ app.dependency_overrides.clear()
@@ -0,0 +1,13 @@
1
+ """Tests for the health-check endpoint."""
2
+ import pytest
3
+ from httpx import AsyncClient
4
+
5
+
6
+ @pytest.mark.asyncio
7
+ async def test_health_ok(client: AsyncClient) -> None:
8
+ response = await client.get("/api/v1/health")
9
+ assert response.status_code == 200
10
+ data = response.json()
11
+ assert data["status"] == "ok"
12
+ assert "timestamp" in data
13
+ assert "version" in data