{
  "schemaVersion": "1.0",
  "item": {
    "slug": "afrexai-fastapi-production",
    "name": "FastAPI Production Engineering",
    "source": "tencent",
    "type": "skill",
    "category": "效率提升",
    "sourceUrl": "https://clawhub.ai/1kalin/afrexai-fastapi-production",
    "canonicalUrl": "https://clawhub.ai/1kalin/afrexai-fastapi-production",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/afrexai-fastapi-production",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-fastapi-production",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "README.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-23T16:43:11.935Z",
      "expiresAt": "2026-04-30T16:43:11.935Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=4claw-imageboard",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=4claw-imageboard",
        "contentDisposition": "attachment; filename=\"4claw-imageboard-1.0.1.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/afrexai-fastapi-production"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/afrexai-fastapi-production",
    "agentPageUrl": "https://openagent3.xyz/skills/afrexai-fastapi-production/agent",
    "manifestUrl": "https://openagent3.xyz/skills/afrexai-fastapi-production/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/afrexai-fastapi-production/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "FastAPI Production Engineering",
        "body": "Complete methodology for building, deploying, and scaling production FastAPI applications. Not a tutorial — a production operating system."
      },
      {
        "title": "Quick Health Check (/16)",
        "body": "Score 2 points each. Total < 8 = critical work needed.\n\nSignalHealthyUnhealthyType safetyPydantic v2 models everywheredict returns, no validationError handlingStructured error hierarchyBare HTTPException stringsAuthJWT + dependency injectionManual token parsingTesting80%+ coverage, async testsNo tests or sync-onlyDatabaseAsync ORM, migrationsRaw SQL, no migrationsObservabilityStructured logging + tracingprint() debuggingDeploymentMulti-stage Docker, health checksuvicorn main:app on bare metalDocumentationAuto-generated, accurate OpenAPIDefault /docs untouched"
      },
      {
        "title": "Recommended Structure",
        "body": "src/\n├── app/\n│   ├── __init__.py\n│   ├── main.py              # App factory\n│   ├── config.py             # Pydantic Settings\n│   ├── dependencies.py       # Shared DI\n│   ├── middleware.py          # Custom middleware\n│   ├── features/\n│   │   ├── users/\n│   │   │   ├── __init__.py\n│   │   │   ├── router.py     # Endpoints\n│   │   │   ├── schemas.py    # Pydantic models\n│   │   │   ├── service.py    # Business logic\n│   │   │   ├── repository.py # Data access\n│   │   │   ├── models.py     # SQLAlchemy/SQLModel\n│   │   │   ├── dependencies.py\n│   │   │   └── exceptions.py\n│   │   ├── auth/\n│   │   ├── orders/\n│   │   └── ...\n│   ├── core/\n│   │   ├── database.py       # Engine, session factory\n│   │   ├── security.py       # JWT, hashing\n│   │   ├── errors.py         # Error hierarchy\n│   │   └── logging.py        # Structlog config\n│   └── shared/\n│       ├── pagination.py\n│       ├── filters.py\n│       └── responses.py\n├── migrations/               # Alembic\n├── tests/\n│   ├── conftest.py\n│   ├── unit/\n│   ├── integration/\n│   └── e2e/\n├── pyproject.toml\n├── Dockerfile\n└── docker-compose.yml"
      },
      {
        "title": "7 Architecture Rules",
        "body": "Feature-based modules — group by domain, not by layer\nRouter → Service → Repository — strict layering, no skipping\nDependency injection everywhere — use Depends() for testability\nPydantic models at boundaries — validate all input AND output\nNo business logic in routers — routers are thin, services are thick\nConfig via environment — Pydantic Settings with .env support\nAsync by default — use async def for all I/O-bound operations"
      },
      {
        "title": "Framework Selection Context",
        "body": "# When to choose FastAPI over alternatives\nfastapi_is_best_when:\n  - \"You need auto-generated OpenAPI docs\"\n  - \"Team knows Python type hints\"\n  - \"API-first (no server-rendered HTML as primary)\"\n  - \"High concurrency with async I/O\"\n  - \"Microservice or API gateway\"\n\nconsider_alternatives:\n  django: \"Full-featured web app with admin, ORM, auth batteries\"\n  flask: \"Simple app, team prefers explicit over magic\"\n  litestar: \"Need WebSocket-heavy or more opinionated framework\"\n  hono_or_express: \"Team prefers TypeScript\""
      },
      {
        "title": "Pydantic Settings Pattern",
        "body": "from pydantic_settings import BaseSettings\nfrom pydantic import SecretStr, field_validator\nfrom functools import lru_cache\n\nclass Settings(BaseSettings):\n    # App\n    app_name: str = \"MyAPI\"\n    debug: bool = False\n    environment: str = \"production\"  # development | staging | production\n    \n    # Server\n    host: str = \"0.0.0.0\"\n    port: int = 8000\n    workers: int = 4\n    \n    # Database\n    database_url: SecretStr  # Required — no default\n    db_pool_size: int = 20\n    db_max_overflow: int = 10\n    db_pool_timeout: int = 30\n    \n    # Auth\n    jwt_secret: SecretStr  # Required\n    jwt_algorithm: str = \"HS256\"\n    jwt_expire_minutes: int = 30\n    \n    # Redis\n    redis_url: str = \"redis://localhost:6379/0\"\n    \n    # CORS\n    cors_origins: list[str] = [\"http://localhost:3000\"]\n    \n    @field_validator(\"environment\")\n    @classmethod\n    def validate_environment(cls, v: str) -> str:\n        allowed = {\"development\", \"staging\", \"production\"}\n        if v not in allowed:\n            raise ValueError(f\"environment must be one of {allowed}\")\n        return v\n    \n    model_config = {\"env_file\": \".env\", \"env_file_encoding\": \"utf-8\"}\n\n@lru_cache\ndef get_settings() -> Settings:\n    return Settings()"
      },
      {
        "title": "5 Configuration Rules",
        "body": "Never hardcode secrets — use SecretStr for sensitive values\nFail fast — required fields have no defaults; app won't start without them\nValidate at startup — use @field_validator for constraint checking\nCache settings — @lru_cache ensures single parse\nType everything — no str for structured values; use enums, Literal types"
      },
      {
        "title": "Schema Design Patterns",
        "body": "from pydantic import BaseModel, Field, ConfigDict\nfrom datetime import datetime\nfrom uuid import UUID\n\n# Base with common config\nclass AppSchema(BaseModel):\n    model_config = ConfigDict(\n        from_attributes=True,      # ORM mode\n        str_strip_whitespace=True,  # Auto-strip\n        validate_default=True,      # Validate defaults too\n    )\n\n# Input schemas (what the API accepts)\nclass UserCreate(AppSchema):\n    email: str = Field(..., pattern=r\"^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$\")\n    name: str = Field(..., min_length=1, max_length=100)\n    password: str = Field(..., min_length=8, max_length=128)\n\nclass UserUpdate(AppSchema):\n    name: str | None = Field(None, min_length=1, max_length=100)\n    email: str | None = Field(None, pattern=r\"^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$\")\n\n# Output schemas (what the API returns)\nclass UserResponse(AppSchema):\n    id: UUID\n    email: str\n    name: str\n    created_at: datetime\n    # Note: password is NEVER in response schema\n\n# List response with pagination\nclass PaginatedResponse[T](AppSchema):\n    items: list[T]\n    total: int\n    page: int\n    page_size: int\n    has_next: bool"
      },
      {
        "title": "8 Pydantic Rules",
        "body": "Separate Create/Update/Response schemas — never reuse input as output\nNever expose internal fields — no passwords, internal IDs, or debug info in responses\nUse Field() for constraints — min/max length, regex patterns, gt/lt for numbers\nEnable from_attributes=True — for ORM model → schema conversion\nUse generics for wrappers — PaginatedResponse[T], ApiResponse[T]\nValidate at boundaries — request body, query params, path params, headers\nUse computed fields — @computed_field for derived values\nDocument with examples — model_config = {\"json_schema_extra\": {\"examples\": [...]}}"
      },
      {
        "title": "Structured Error Hierarchy",
        "body": "from fastapi import Request\nfrom fastapi.responses import JSONResponse\nfrom starlette.status import (\n    HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED,\n    HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND,\n    HTTP_409_CONFLICT, HTTP_422_UNPROCESSABLE_ENTITY,\n    HTTP_429_TOO_MANY_REQUESTS, HTTP_500_INTERNAL_SERVER_ERROR,\n)\n\nclass AppError(Exception):\n    \"\"\"Base application error.\"\"\"\n    def __init__(\n        self,\n        message: str,\n        code: str,\n        status_code: int = HTTP_500_INTERNAL_SERVER_ERROR,\n        details: dict | None = None,\n    ):\n        self.message = message\n        self.code = code\n        self.status_code = status_code\n        self.details = details or {}\n        super().__init__(message)\n\nclass NotFoundError(AppError):\n    def __init__(self, resource: str, identifier: str | int):\n        super().__init__(\n            message=f\"{resource} not found: {identifier}\",\n            code=\"NOT_FOUND\",\n            status_code=HTTP_404_NOT_FOUND,\n            details={\"resource\": resource, \"identifier\": str(identifier)},\n        )\n\nclass ConflictError(AppError):\n    def __init__(self, message: str, field: str | None = None):\n        super().__init__(\n            message=message, code=\"CONFLICT\",\n            status_code=HTTP_409_CONFLICT,\n            details={\"field\": field} if field else {},\n        )\n\nclass AuthenticationError(AppError):\n    def __init__(self, message: str = \"Invalid credentials\"):\n        super().__init__(message=message, code=\"UNAUTHORIZED\", status_code=HTTP_401_UNAUTHORIZED)\n\nclass AuthorizationError(AppError):\n    def __init__(self, message: str = \"Insufficient permissions\"):\n        super().__init__(message=message, code=\"FORBIDDEN\", status_code=HTTP_403_FORBIDDEN)\n\nclass ValidationError(AppError):\n    def __init__(self, message: str, errors: list[dict] | None = None):\n        super().__init__(\n            message=message, code=\"VALIDATION_ERROR\",\n            status_code=HTTP_422_UNPROCESSABLE_ENTITY,\n            details={\"errors\": errors or []},\n        )\n\nclass RateLimitError(AppError):\n    def __init__(self, retry_after: int = 60):\n        super().__init__(\n            message=\"Rate limit exceeded\", code=\"RATE_LIMITED\",\n            status_code=HTTP_429_TOO_MANY_REQUESTS,\n            details={\"retry_after\": retry_after},\n        )\n\n# Global error handler\nasync def app_error_handler(request: Request, exc: AppError) -> JSONResponse:\n    return JSONResponse(\n        status_code=exc.status_code,\n        content={\n            \"error\": {\n                \"code\": exc.code,\n                \"message\": exc.message,\n                \"details\": exc.details,\n            }\n        },\n    )\n\n# Register in app factory\n# app.add_exception_handler(AppError, app_error_handler)"
      },
      {
        "title": "6 Error Handling Rules",
        "body": "Never return bare strings — always structured {\"error\": {\"code\", \"message\", \"details\"}}\nUse domain-specific errors — NotFoundError(\"User\", user_id) not HTTPException(404)\nGlobal handler catches all — register AppError handler in app factory\nLog server errors, don't expose — 5xx returns generic message, logs full traceback\nInclude actionable details — which field failed, what's allowed, retry-after for rate limits\nNever leak internals — no stack traces, SQL queries, or file paths in responses"
      },
      {
        "title": "JWT + Dependency Injection Pattern",
        "body": "from fastapi import Depends, Security\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials\nfrom jose import jwt, JWTError\nfrom datetime import datetime, timedelta, timezone\n\nsecurity = HTTPBearer()\n\ndef create_access_token(user_id: str, roles: list[str], settings: Settings) -> str:\n    expire = datetime.now(timezone.utc) + timedelta(minutes=settings.jwt_expire_minutes)\n    payload = {\n        \"sub\": user_id,\n        \"roles\": roles,\n        \"exp\": expire,\n        \"iat\": datetime.now(timezone.utc),\n    }\n    return jwt.encode(payload, settings.jwt_secret.get_secret_value(), algorithm=settings.jwt_algorithm)\n\nasync def get_current_user(\n    credentials: HTTPAuthorizationCredentials = Security(security),\n    settings: Settings = Depends(get_settings),\n    db: AsyncSession = Depends(get_db),\n) -> User:\n    try:\n        payload = jwt.decode(\n            credentials.credentials,\n            settings.jwt_secret.get_secret_value(),\n            algorithms=[settings.jwt_algorithm],\n        )\n        user_id = payload.get(\"sub\")\n        if not user_id:\n            raise AuthenticationError(\"Invalid token payload\")\n    except JWTError:\n        raise AuthenticationError(\"Invalid or expired token\")\n    \n    user = await db.get(User, user_id)\n    if not user:\n        raise AuthenticationError(\"User not found\")\n    return user\n\n# Role-based authorization\ndef require_role(*roles: str):\n    async def checker(user: User = Depends(get_current_user)) -> User:\n        if not any(r in user.roles for r in roles):\n            raise AuthorizationError(f\"Requires one of: {', '.join(roles)}\")\n        return user\n    return checker\n\n# Usage in router\n@router.get(\"/admin/users\")\nasync def list_users(\n    admin: User = Depends(require_role(\"admin\", \"superadmin\")),\n    service: UserService = Depends(get_user_service),\n):\n    return await service.list_all()"
      },
      {
        "title": "10-Point Security Checklist",
        "body": "#CheckPriority1JWT secret ≥ 256 bits, from envP02Token expiry ≤ 30 min for access, ≤ 7 days refreshP03Password hashed with bcrypt/argon2P04CORS configured per environmentP05Rate limiting on auth endpointsP06HTTPS enforced (redirect HTTP)P07Security headers (HSTS, CSP, X-Frame)P18Input validation on ALL endpointsP19SQL injection prevented (parameterized queries)P010Dependency scanning (safety/pip-audit)P1"
      },
      {
        "title": "Async SQLAlchemy + Repository Pattern",
        "body": "from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\nfrom sqlalchemy import select, func\nfrom uuid import uuid4, UUID\nfrom datetime import datetime, timezone\n\n# Engine setup\nengine = create_async_engine(\n    settings.database_url.get_secret_value(),\n    pool_size=settings.db_pool_size,\n    max_overflow=settings.db_max_overflow,\n    pool_timeout=settings.db_pool_timeout,\n    pool_pre_ping=True,  # Check connection health\n    echo=settings.debug,\n)\n\nSessionFactory = async_sessionmaker(engine, expire_on_commit=False)\n\nasync def get_db() -> AsyncGenerator[AsyncSession, None]:\n    async with SessionFactory() as session:\n        try:\n            yield session\n            await session.commit()\n        except Exception:\n            await session.rollback()\n            raise\n\n# Base model with common fields\nclass Base(DeclarativeBase):\n    pass\n\nclass TimestampMixin:\n    created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc))\n    updated_at: Mapped[datetime] = mapped_column(\n        default=lambda: datetime.now(timezone.utc),\n        onupdate=lambda: datetime.now(timezone.utc),\n    )\n\n# Repository pattern\nclass BaseRepository[T]:\n    def __init__(self, session: AsyncSession, model: type[T]):\n        self.session = session\n        self.model = model\n    \n    async def get_by_id(self, id: UUID) -> T | None:\n        return await self.session.get(self.model, id)\n    \n    async def get_or_raise(self, id: UUID) -> T:\n        entity = await self.get_by_id(id)\n        if not entity:\n            raise NotFoundError(self.model.__name__, str(id))\n        return entity\n    \n    async def list(\n        self, *, offset: int = 0, limit: int = 20, **filters\n    ) -> tuple[list[T], int]:\n        query = select(self.model)\n        count_query = select(func.count()).select_from(self.model)\n        \n        for field, value in filters.items():\n            if value is not None:\n                query = query.where(getattr(self.model, field) == value)\n                count_query = count_query.where(getattr(self.model, field) == value)\n        \n        total = await self.session.scalar(count_query) or 0\n        result = await self.session.execute(\n            query.offset(offset).limit(limit).order_by(self.model.created_at.desc())\n        )\n        return list(result.scalars().all()), total\n    \n    async def create(self, entity: T) -> T:\n        self.session.add(entity)\n        await self.session.flush()\n        return entity\n    \n    async def delete(self, entity: T) -> None:\n        await self.session.delete(entity)"
      },
      {
        "title": "ORM Selection Guide",
        "body": "ORMBest ForAsyncType SafetyLearning CurveSQLAlchemy 2.0Complex queries, enterprise✅✅ Mapped[]MediumSQLModelSimple CRUD, Pydantic sync✅✅LowTortoiseDjango-like feel✅PartialLowPiccoloModern, migrations built-in✅✅Low\n\nRecommendation: SQLAlchemy 2.0 for production. SQLModel for prototypes."
      },
      {
        "title": "Migration Strategy (Alembic)",
        "body": "# Setup\nalembic init migrations\n# Edit alembic.ini: sqlalchemy.url = from env\n\n# Generate migration\nalembic revision --autogenerate -m \"add users table\"\n\n# Apply\nalembic upgrade head\n\n# Rollback\nalembic downgrade -1\n\nMigration Rules:\n\nAlways review autogenerated migrations before applying\nNever edit applied migrations — create new ones\nTest migrations in staging before production\nInclude downgrade() for every upgrade()\nUse batch_alter_table for SQLite compatibility"
      },
      {
        "title": "Test Pyramid",
        "body": "LevelCoverage TargetToolsFocusUnit80%+pytest, unittest.mockService logic, validatorsIntegrationKey pathspytest-asyncio, testcontainersDB queries, external APIsE2ECritical flowshttpx.AsyncClientFull request→responseContractAPI boundariesschemathesisOpenAPI compliance"
      },
      {
        "title": "Test Patterns",
        "body": "import pytest\nfrom httpx import AsyncClient, ASGITransport\nfrom app.main import create_app\n\n@pytest.fixture\nasync def app():\n    app = create_app()\n    yield app\n\n@pytest.fixture\nasync def client(app):\n    transport = ASGITransport(app=app)\n    async with AsyncClient(transport=transport, base_url=\"http://test\") as ac:\n        yield ac\n\n@pytest.fixture\nasync def auth_client(client, test_user):\n    token = create_access_token(test_user.id, test_user.roles)\n    client.headers[\"Authorization\"] = f\"Bearer {token}\"\n    return client\n\n# E2E test\n@pytest.mark.asyncio\nasync def test_create_user(client: AsyncClient):\n    response = await client.post(\"/api/users\", json={\n        \"email\": \"test@example.com\",\n        \"name\": \"Test User\",\n        \"password\": \"securepass123\",\n    })\n    assert response.status_code == 201\n    data = response.json()\n    assert data[\"email\"] == \"test@example.com\"\n    assert \"password\" not in data  # Never expose\n\n# Unit test (service layer)\n@pytest.mark.asyncio\nasync def test_user_service_duplicate_email(user_service, mock_repo):\n    mock_repo.get_by_email.return_value = existing_user\n    with pytest.raises(ConflictError, match=\"Email already registered\"):\n        await user_service.create(UserCreate(email=\"taken@example.com\", ...))\n\n# Parametrized validation\n@pytest.mark.parametrize(\"email,expected\", [\n    (\"valid@example.com\", True),\n    (\"invalid\", False),\n    (\"\", False),\n    (\"a@b.c\", True),\n])\ndef test_email_validation(email, expected):\n    if expected:\n        UserCreate(email=email, name=\"Test\", password=\"12345678\")\n    else:\n        with pytest.raises(ValidationError):\n            UserCreate(email=email, name=\"Test\", password=\"12345678\")"
      },
      {
        "title": "7 Testing Rules",
        "body": "Test services, not routers — business logic lives in services\nUse fixtures for DI override — swap real DB with test DB via app.dependency_overrides\nOne assertion per test — clear what broke when it fails\nTest error paths — 40% of tests should be sad-path\nUse factories for test data — UserFactory.create() not manual dict construction\nAsync tests need @pytest.mark.asyncio — or set asyncio_mode = \"auto\" in config\nRun tests in CI — block merge if tests fail"
      },
      {
        "title": "Structlog Setup",
        "body": "import structlog\nfrom uuid import uuid4\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nstructlog.configure(\n    processors=[\n        structlog.contextvars.merge_contextvars,\n        structlog.stdlib.add_log_level,\n        structlog.stdlib.add_logger_name,\n        structlog.processors.TimeStamper(fmt=\"iso\"),\n        structlog.processors.StackInfoRenderer(),\n        structlog.processors.format_exc_info,\n        structlog.processors.JSONRenderer(),\n    ],\n    logger_factory=structlog.stdlib.LoggerFactory(),\n)\n\nlogger = structlog.get_logger()\n\n# Request ID middleware\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        request_id = request.headers.get(\"X-Request-ID\", str(uuid4()))\n        structlog.contextvars.clear_contextvars()\n        structlog.contextvars.bind_contextvars(\n            request_id=request_id,\n            method=request.method,\n            path=request.url.path,\n        )\n        \n        response = await call_next(request)\n        response.headers[\"X-Request-ID\"] = request_id\n        \n        logger.info(\n            \"request_completed\",\n            status_code=response.status_code,\n        )\n        return response"
      },
      {
        "title": "Health Check Endpoints",
        "body": "@router.get(\"/health\")\nasync def health():\n    \"\"\"Liveness probe — is the process running?\"\"\"\n    return {\"status\": \"ok\"}\n\n@router.get(\"/ready\")\nasync def ready(db: AsyncSession = Depends(get_db)):\n    \"\"\"Readiness probe — can we serve traffic?\"\"\"\n    checks = {}\n    try:\n        await db.execute(text(\"SELECT 1\"))\n        checks[\"database\"] = \"ok\"\n    except Exception:\n        checks[\"database\"] = \"error\"\n    \n    all_ok = all(v == \"ok\" for v in checks.values())\n    return JSONResponse(\n        status_code=200 if all_ok else 503,\n        content={\"status\": \"ok\" if all_ok else \"degraded\", \"checks\": checks},\n    )"
      },
      {
        "title": "Priority Stack",
        "body": "#TechniqueImpactEffort1Async database queriesHighLow2Connection pooling (tuned)HighLow3Response caching (Redis)HighMedium4Background tasks for heavy workHighLow5Pagination on all list endpointsMediumLow6Select only needed columnsMediumLow7Eager loading (joinedload)MediumMedium8Rate limitingMediumLow"
      },
      {
        "title": "Background Tasks",
        "body": "from fastapi import BackgroundTasks\n\n@router.post(\"/users\", status_code=201)\nasync def create_user(\n    user_in: UserCreate,\n    background_tasks: BackgroundTasks,\n    service: UserService = Depends(get_user_service),\n):\n    user = await service.create(user_in)\n    background_tasks.add_task(send_welcome_email, user.email, user.name)\n    return user"
      },
      {
        "title": "Caching Pattern",
        "body": "from redis.asyncio import Redis\nimport json\n\nclass CacheService:\n    def __init__(self, redis: Redis):\n        self.redis = redis\n    \n    async def get_or_set(self, key: str, factory, ttl: int = 300):\n        cached = await self.redis.get(key)\n        if cached:\n            return json.loads(cached)\n        result = await factory()\n        await self.redis.setex(key, ttl, json.dumps(result, default=str))\n        return result\n    \n    async def invalidate(self, pattern: str):\n        keys = await self.redis.keys(pattern)\n        if keys:\n            await self.redis.delete(*keys)"
      },
      {
        "title": "Multi-Stage Dockerfile",
        "body": "# Build stage\nFROM python:3.12-slim AS builder\nWORKDIR /app\n\nRUN pip install --no-cache-dir uv\nCOPY pyproject.toml uv.lock ./\nRUN uv sync --frozen --no-dev --no-editable\n\n# Production stage\nFROM python:3.12-slim\nWORKDIR /app\n\nRUN adduser --disabled-password --no-create-home appuser\n\nCOPY --from=builder /app/.venv /app/.venv\nCOPY src/ ./src/\nCOPY migrations/ ./migrations/\nCOPY alembic.ini ./\n\nENV PATH=\"/app/.venv/bin:$PATH\"\nENV PYTHONUNBUFFERED=1\nENV PYTHONDONTWRITEBYTECODE=1\n\nUSER appuser\nEXPOSE 8000\n\nHEALTHCHECK --interval=30s --timeout=5s --retries=3 \\\n    CMD [\"python\", \"-c\", \"import httpx; httpx.get('http://localhost:8000/health').raise_for_status()\"]\n\nCMD [\"uvicorn\", \"src.app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--workers\", \"4\"]"
      },
      {
        "title": "App Factory Pattern",
        "body": "from fastapi import FastAPI\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Startup\n    logger.info(\"starting_up\", environment=settings.environment)\n    await init_db()\n    yield\n    # Shutdown\n    logger.info(\"shutting_down\")\n    await engine.dispose()\n\ndef create_app() -> FastAPI:\n    settings = get_settings()\n    \n    app = FastAPI(\n        title=settings.app_name,\n        lifespan=lifespan,\n        docs_url=\"/docs\" if settings.debug else None,\n        redoc_url=None,\n    )\n    \n    # Middleware (order matters — last added = first executed)\n    app.add_middleware(\n        CORSMiddleware,\n        allow_origins=settings.cors_origins,\n        allow_credentials=True,\n        allow_methods=[\"*\"],\n        allow_headers=[\"*\"],\n    )\n    app.add_middleware(RequestIDMiddleware)\n    \n    # Error handlers\n    app.add_exception_handler(AppError, app_error_handler)\n    \n    # Routers\n    app.include_router(auth_router, prefix=\"/api/auth\", tags=[\"auth\"])\n    app.include_router(users_router, prefix=\"/api/users\", tags=[\"users\"])\n    app.include_router(health_router, tags=[\"health\"])\n    \n    return app\n\napp = create_app()"
      },
      {
        "title": "GitHub Actions CI",
        "body": "name: CI\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_PASSWORD: test\n          POSTGRES_DB: testdb\n        ports: [\"5432:5432\"]\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n    \n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with: { python-version: \"3.12\" }\n      - run: pip install uv && uv sync\n      - run: uv run ruff check .\n      - run: uv run mypy src/\n      - run: uv run pytest --cov=src --cov-report=xml -x\n        env:\n          DATABASE_URL: postgresql+asyncpg://postgres:test@localhost:5432/testdb\n          JWT_SECRET: test-secret-key-at-least-32-chars"
      },
      {
        "title": "Production Checklist",
        "body": "P0 — Mandatory:\n\nAll secrets from environment variables (SecretStr)\n HTTPS enforced\n CORS configured per environment\n Rate limiting on auth endpoints\n Input validation on all endpoints\n Structured error responses (no stack traces)\n Health + readiness endpoints\n Database connection pooling\n Migrations run before deploy\n Structured logging (JSON)\n Tests passing in CI\n\nP1 — Recommended:\n\nOpenTelemetry tracing\n Prometheus metrics endpoint\n Background task queue (Celery/ARQ)\n Redis caching layer\n API versioning strategy\n Request/response logging\n Dependency security scanning\n Performance benchmarks established"
      },
      {
        "title": "Middleware Stack Order",
        "body": "# Applied bottom-to-top (last added = first executed)\napp.add_middleware(GZipMiddleware, minimum_size=1000)    # 5. Compress\napp.add_middleware(CORSMiddleware, ...)                  # 4. CORS\napp.add_middleware(RequestIDMiddleware)                   # 3. Request ID\napp.add_middleware(RateLimitMiddleware)                   # 2. Rate limit\napp.add_middleware(TrustedHostMiddleware, allowed=[\"*\"])  # 1. Host check"
      },
      {
        "title": "Pagination with Cursor-Based Option",
        "body": "from fastapi import Query\n\nclass PaginationParams:\n    def __init__(\n        self,\n        page: int = Query(1, ge=1, description=\"Page number\"),\n        page_size: int = Query(20, ge=1, le=100, description=\"Items per page\"),\n    ):\n        self.offset = (page - 1) * page_size\n        self.limit = page_size\n        self.page = page\n        self.page_size = page_size\n\n@router.get(\"/users\", response_model=PaginatedResponse[UserResponse])\nasync def list_users(\n    pagination: PaginationParams = Depends(),\n    service: UserService = Depends(get_user_service),\n):\n    items, total = await service.list(\n        offset=pagination.offset, limit=pagination.limit\n    )\n    return PaginatedResponse(\n        items=items, total=total,\n        page=pagination.page, page_size=pagination.page_size,\n        has_next=(pagination.offset + pagination.limit) < total,\n    )"
      },
      {
        "title": "WebSocket Pattern",
        "body": "from fastapi import WebSocket, WebSocketDisconnect\n\nclass ConnectionManager:\n    def __init__(self):\n        self.connections: dict[str, WebSocket] = {}\n    \n    async def connect(self, user_id: str, ws: WebSocket):\n        await ws.accept()\n        self.connections[user_id] = ws\n    \n    def disconnect(self, user_id: str):\n        self.connections.pop(user_id, None)\n    \n    async def send(self, user_id: str, message: dict):\n        if ws := self.connections.get(user_id):\n            await ws.send_json(message)\n\nmanager = ConnectionManager()\n\n@router.websocket(\"/ws/{user_id}\")\nasync def websocket_endpoint(websocket: WebSocket, user_id: str):\n    await manager.connect(user_id, websocket)\n    try:\n        while True:\n            data = await websocket.receive_json()\n            # Process message\n    except WebSocketDisconnect:\n        manager.disconnect(user_id)"
      },
      {
        "title": "File Upload Pattern",
        "body": "from fastapi import UploadFile, File\n\n@router.post(\"/upload\")\nasync def upload_file(\n    file: UploadFile = File(..., description=\"File to upload\"),\n    user: User = Depends(get_current_user),\n):\n    # Validate\n    if file.size and file.size > 10 * 1024 * 1024:  # 10MB\n        raise ValidationError(\"File too large (max 10MB)\")\n    \n    allowed_types = {\"image/jpeg\", \"image/png\", \"application/pdf\"}\n    if file.content_type not in allowed_types:\n        raise ValidationError(f\"File type not allowed: {file.content_type}\")\n    \n    # Save\n    contents = await file.read()\n    path = f\"uploads/{user.id}/{file.filename}\"\n    # Save to S3/local storage...\n    \n    return {\"filename\": file.filename, \"size\": len(contents)}"
      },
      {
        "title": "Phase 12: Common Mistakes",
        "body": "#MistakeFix1Sync database calls in async appUse async SQLAlchemy/databases2Business logic in route handlersMove to service layer3No input validationPydantic models on every endpoint4Returning ORM models directlyUse response schemas (from_attributes)5Hardcoded config valuesPydantic Settings + env vars6No error handling strategyCustom exception hierarchy + global handler7Missing health checks/health + /ready endpoints8print() for loggingstructlog with JSON output9No pagination on list endpointsDefault limit, max cap (100)10Testing against production DBTest fixtures with separate DB"
      },
      {
        "title": "Quality Scoring (0–100)",
        "body": "DimensionWeight0–255075100Type Safety15%No typesPartial PydanticFull schemasStrict mypy passError Handling15%Bare HTTPExceptionCustom errorsFull hierarchy+ monitoringTesting15%NoneHappy path80%+ coverage+ contract testsSecurity15%No authBasic JWT+ RBAC + rate limit+ scanning + auditPerformance10%Sync everythingAsync DB+ caching+ profilingObservability10%print()Structured logs+ tracing+ metrics + alertsDatabase10%Raw SQLORM + migrations+ repository pattern+ connection tuningDeployment10%ManualDockerfile+ CI/CD+ health + rollback\n\nScoring: Your Score = Σ (dimension score × weight). < 40 = critical, 40–60 = needs work, 60–80 = solid, 80+ = production-grade."
      },
      {
        "title": "10 Commandments of FastAPI Production",
        "body": "Pydantic models at every boundary — request, response, config\nAsync all the way down — one sync call blocks the event loop\nServices own business logic — routers are thin wrappers\nDependency injection for testability — Depends() is your best friend\nStructured errors, structured logs — JSON everything\nHealth checks are non-negotiable — liveness + readiness\nTest the sad paths — 40% of tests should be error cases\nMigrations before deployment — never modify schema manually\nSecrets in environment, never in code — SecretStr enforces this\nProfile before optimizing — measure, don't guess"
      },
      {
        "title": "Natural Language Commands",
        "body": "audit my FastAPI project → Run health check, identify gaps\nset up a new FastAPI project → Generate project structure + config\nadd authentication to my API → JWT + RBAC dependency pattern\ncreate a CRUD feature for [resource] → Full router/service/repo/schemas\noptimize my database queries → Connection pooling + async + N+1 prevention\nadd structured logging → Structlog + request ID middleware\nwrite tests for [feature] → Async test patterns + fixtures\nprepare for production deployment → Dockerfile + CI + checklist\nadd caching to my API → Redis caching pattern\nset up error handling → Custom exception hierarchy + global handler\nadd WebSocket support → Connection manager pattern\nreview my API security → 10-point security checklist audit\n\n⚡ Level up your FastAPI APIs → Get the AfrexAI SaaS Context Pack ($47) for complete SaaS architecture, pricing strategies, and go-to-market playbooks.\n\n🔗 More free skills by AfrexAI:\n\nafrexai-python-production — Python production engineering\nafrexai-api-architecture — API design methodology\nafrexai-database-engineering — Database patterns\nafrexai-test-automation-engineering — Testing strategy\nafrexai-cicd-engineering — CI/CD pipeline design\n\n🛒 Browse all packs → AfrexAI Storefront"
      }
    ],
    "body": "FastAPI Production Engineering\n\nComplete methodology for building, deploying, and scaling production FastAPI applications. Not a tutorial — a production operating system.\n\nQuick Health Check (/16)\n\nScore 2 points each. Total < 8 = critical work needed.\n\nSignal\tHealthy\tUnhealthy\nType safety\tPydantic v2 models everywhere\tdict returns, no validation\nError handling\tStructured error hierarchy\tBare HTTPException strings\nAuth\tJWT + dependency injection\tManual token parsing\nTesting\t80%+ coverage, async tests\tNo tests or sync-only\nDatabase\tAsync ORM, migrations\tRaw SQL, no migrations\nObservability\tStructured logging + tracing\tprint() debugging\nDeployment\tMulti-stage Docker, health checks\tuvicorn main:app on bare metal\nDocumentation\tAuto-generated, accurate OpenAPI\tDefault /docs untouched\nPhase 1: Project Architecture\nRecommended Structure\nsrc/\n├── app/\n│   ├── __init__.py\n│   ├── main.py              # App factory\n│   ├── config.py             # Pydantic Settings\n│   ├── dependencies.py       # Shared DI\n│   ├── middleware.py          # Custom middleware\n│   ├── features/\n│   │   ├── users/\n│   │   │   ├── __init__.py\n│   │   │   ├── router.py     # Endpoints\n│   │   │   ├── schemas.py    # Pydantic models\n│   │   │   ├── service.py    # Business logic\n│   │   │   ├── repository.py # Data access\n│   │   │   ├── models.py     # SQLAlchemy/SQLModel\n│   │   │   ├── dependencies.py\n│   │   │   └── exceptions.py\n│   │   ├── auth/\n│   │   ├── orders/\n│   │   └── ...\n│   ├── core/\n│   │   ├── database.py       # Engine, session factory\n│   │   ├── security.py       # JWT, hashing\n│   │   ├── errors.py         # Error hierarchy\n│   │   └── logging.py        # Structlog config\n│   └── shared/\n│       ├── pagination.py\n│       ├── filters.py\n│       └── responses.py\n├── migrations/               # Alembic\n├── tests/\n│   ├── conftest.py\n│   ├── unit/\n│   ├── integration/\n│   └── e2e/\n├── pyproject.toml\n├── Dockerfile\n└── docker-compose.yml\n\n7 Architecture Rules\nFeature-based modules — group by domain, not by layer\nRouter → Service → Repository — strict layering, no skipping\nDependency injection everywhere — use Depends() for testability\nPydantic models at boundaries — validate all input AND output\nNo business logic in routers — routers are thin, services are thick\nConfig via environment — Pydantic Settings with .env support\nAsync by default — use async def for all I/O-bound operations\nFramework Selection Context\n# When to choose FastAPI over alternatives\nfastapi_is_best_when:\n  - \"You need auto-generated OpenAPI docs\"\n  - \"Team knows Python type hints\"\n  - \"API-first (no server-rendered HTML as primary)\"\n  - \"High concurrency with async I/O\"\n  - \"Microservice or API gateway\"\n\nconsider_alternatives:\n  django: \"Full-featured web app with admin, ORM, auth batteries\"\n  flask: \"Simple app, team prefers explicit over magic\"\n  litestar: \"Need WebSocket-heavy or more opinionated framework\"\n  hono_or_express: \"Team prefers TypeScript\"\n\nPhase 2: Configuration & Environment\nPydantic Settings Pattern\nfrom pydantic_settings import BaseSettings\nfrom pydantic import SecretStr, field_validator\nfrom functools import lru_cache\n\nclass Settings(BaseSettings):\n    # App\n    app_name: str = \"MyAPI\"\n    debug: bool = False\n    environment: str = \"production\"  # development | staging | production\n    \n    # Server\n    host: str = \"0.0.0.0\"\n    port: int = 8000\n    workers: int = 4\n    \n    # Database\n    database_url: SecretStr  # Required — no default\n    db_pool_size: int = 20\n    db_max_overflow: int = 10\n    db_pool_timeout: int = 30\n    \n    # Auth\n    jwt_secret: SecretStr  # Required\n    jwt_algorithm: str = \"HS256\"\n    jwt_expire_minutes: int = 30\n    \n    # Redis\n    redis_url: str = \"redis://localhost:6379/0\"\n    \n    # CORS\n    cors_origins: list[str] = [\"http://localhost:3000\"]\n    \n    @field_validator(\"environment\")\n    @classmethod\n    def validate_environment(cls, v: str) -> str:\n        allowed = {\"development\", \"staging\", \"production\"}\n        if v not in allowed:\n            raise ValueError(f\"environment must be one of {allowed}\")\n        return v\n    \n    model_config = {\"env_file\": \".env\", \"env_file_encoding\": \"utf-8\"}\n\n@lru_cache\ndef get_settings() -> Settings:\n    return Settings()\n\n5 Configuration Rules\nNever hardcode secrets — use SecretStr for sensitive values\nFail fast — required fields have no defaults; app won't start without them\nValidate at startup — use @field_validator for constraint checking\nCache settings — @lru_cache ensures single parse\nType everything — no str for structured values; use enums, Literal types\nPhase 3: Pydantic v2 Mastery\nSchema Design Patterns\nfrom pydantic import BaseModel, Field, ConfigDict\nfrom datetime import datetime\nfrom uuid import UUID\n\n# Base with common config\nclass AppSchema(BaseModel):\n    model_config = ConfigDict(\n        from_attributes=True,      # ORM mode\n        str_strip_whitespace=True,  # Auto-strip\n        validate_default=True,      # Validate defaults too\n    )\n\n# Input schemas (what the API accepts)\nclass UserCreate(AppSchema):\n    email: str = Field(..., pattern=r\"^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$\")\n    name: str = Field(..., min_length=1, max_length=100)\n    password: str = Field(..., min_length=8, max_length=128)\n\nclass UserUpdate(AppSchema):\n    name: str | None = Field(None, min_length=1, max_length=100)\n    email: str | None = Field(None, pattern=r\"^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$\")\n\n# Output schemas (what the API returns)\nclass UserResponse(AppSchema):\n    id: UUID\n    email: str\n    name: str\n    created_at: datetime\n    # Note: password is NEVER in response schema\n\n# List response with pagination\nclass PaginatedResponse[T](AppSchema):\n    items: list[T]\n    total: int\n    page: int\n    page_size: int\n    has_next: bool\n\n8 Pydantic Rules\nSeparate Create/Update/Response schemas — never reuse input as output\nNever expose internal fields — no passwords, internal IDs, or debug info in responses\nUse Field() for constraints — min/max length, regex patterns, gt/lt for numbers\nEnable from_attributes=True — for ORM model → schema conversion\nUse generics for wrappers — PaginatedResponse[T], ApiResponse[T]\nValidate at boundaries — request body, query params, path params, headers\nUse computed fields — @computed_field for derived values\nDocument with examples — model_config = {\"json_schema_extra\": {\"examples\": [...]}}\nPhase 4: Error Handling Architecture\nStructured Error Hierarchy\nfrom fastapi import Request\nfrom fastapi.responses import JSONResponse\nfrom starlette.status import (\n    HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED,\n    HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND,\n    HTTP_409_CONFLICT, HTTP_422_UNPROCESSABLE_ENTITY,\n    HTTP_429_TOO_MANY_REQUESTS, HTTP_500_INTERNAL_SERVER_ERROR,\n)\n\nclass AppError(Exception):\n    \"\"\"Base application error.\"\"\"\n    def __init__(\n        self,\n        message: str,\n        code: str,\n        status_code: int = HTTP_500_INTERNAL_SERVER_ERROR,\n        details: dict | None = None,\n    ):\n        self.message = message\n        self.code = code\n        self.status_code = status_code\n        self.details = details or {}\n        super().__init__(message)\n\nclass NotFoundError(AppError):\n    def __init__(self, resource: str, identifier: str | int):\n        super().__init__(\n            message=f\"{resource} not found: {identifier}\",\n            code=\"NOT_FOUND\",\n            status_code=HTTP_404_NOT_FOUND,\n            details={\"resource\": resource, \"identifier\": str(identifier)},\n        )\n\nclass ConflictError(AppError):\n    def __init__(self, message: str, field: str | None = None):\n        super().__init__(\n            message=message, code=\"CONFLICT\",\n            status_code=HTTP_409_CONFLICT,\n            details={\"field\": field} if field else {},\n        )\n\nclass AuthenticationError(AppError):\n    def __init__(self, message: str = \"Invalid credentials\"):\n        super().__init__(message=message, code=\"UNAUTHORIZED\", status_code=HTTP_401_UNAUTHORIZED)\n\nclass AuthorizationError(AppError):\n    def __init__(self, message: str = \"Insufficient permissions\"):\n        super().__init__(message=message, code=\"FORBIDDEN\", status_code=HTTP_403_FORBIDDEN)\n\nclass ValidationError(AppError):\n    def __init__(self, message: str, errors: list[dict] | None = None):\n        super().__init__(\n            message=message, code=\"VALIDATION_ERROR\",\n            status_code=HTTP_422_UNPROCESSABLE_ENTITY,\n            details={\"errors\": errors or []},\n        )\n\nclass RateLimitError(AppError):\n    def __init__(self, retry_after: int = 60):\n        super().__init__(\n            message=\"Rate limit exceeded\", code=\"RATE_LIMITED\",\n            status_code=HTTP_429_TOO_MANY_REQUESTS,\n            details={\"retry_after\": retry_after},\n        )\n\n# Global error handler\nasync def app_error_handler(request: Request, exc: AppError) -> JSONResponse:\n    return JSONResponse(\n        status_code=exc.status_code,\n        content={\n            \"error\": {\n                \"code\": exc.code,\n                \"message\": exc.message,\n                \"details\": exc.details,\n            }\n        },\n    )\n\n# Register in app factory\n# app.add_exception_handler(AppError, app_error_handler)\n\n6 Error Handling Rules\nNever return bare strings — always structured {\"error\": {\"code\", \"message\", \"details\"}}\nUse domain-specific errors — NotFoundError(\"User\", user_id) not HTTPException(404)\nGlobal handler catches all — register AppError handler in app factory\nLog server errors, don't expose — 5xx returns generic message, logs full traceback\nInclude actionable details — which field failed, what's allowed, retry-after for rate limits\nNever leak internals — no stack traces, SQL queries, or file paths in responses\nPhase 5: Authentication & Authorization\nJWT + Dependency Injection Pattern\nfrom fastapi import Depends, Security\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials\nfrom jose import jwt, JWTError\nfrom datetime import datetime, timedelta, timezone\n\nsecurity = HTTPBearer()\n\ndef create_access_token(user_id: str, roles: list[str], settings: Settings) -> str:\n    expire = datetime.now(timezone.utc) + timedelta(minutes=settings.jwt_expire_minutes)\n    payload = {\n        \"sub\": user_id,\n        \"roles\": roles,\n        \"exp\": expire,\n        \"iat\": datetime.now(timezone.utc),\n    }\n    return jwt.encode(payload, settings.jwt_secret.get_secret_value(), algorithm=settings.jwt_algorithm)\n\nasync def get_current_user(\n    credentials: HTTPAuthorizationCredentials = Security(security),\n    settings: Settings = Depends(get_settings),\n    db: AsyncSession = Depends(get_db),\n) -> User:\n    try:\n        payload = jwt.decode(\n            credentials.credentials,\n            settings.jwt_secret.get_secret_value(),\n            algorithms=[settings.jwt_algorithm],\n        )\n        user_id = payload.get(\"sub\")\n        if not user_id:\n            raise AuthenticationError(\"Invalid token payload\")\n    except JWTError:\n        raise AuthenticationError(\"Invalid or expired token\")\n    \n    user = await db.get(User, user_id)\n    if not user:\n        raise AuthenticationError(\"User not found\")\n    return user\n\n# Role-based authorization\ndef require_role(*roles: str):\n    async def checker(user: User = Depends(get_current_user)) -> User:\n        if not any(r in user.roles for r in roles):\n            raise AuthorizationError(f\"Requires one of: {', '.join(roles)}\")\n        return user\n    return checker\n\n# Usage in router\n@router.get(\"/admin/users\")\nasync def list_users(\n    admin: User = Depends(require_role(\"admin\", \"superadmin\")),\n    service: UserService = Depends(get_user_service),\n):\n    return await service.list_all()\n\n10-Point Security Checklist\n#\tCheck\tPriority\n1\tJWT secret ≥ 256 bits, from env\tP0\n2\tToken expiry ≤ 30 min for access, ≤ 7 days refresh\tP0\n3\tPassword hashed with bcrypt/argon2\tP0\n4\tCORS configured per environment\tP0\n5\tRate limiting on auth endpoints\tP0\n6\tHTTPS enforced (redirect HTTP)\tP0\n7\tSecurity headers (HSTS, CSP, X-Frame)\tP1\n8\tInput validation on ALL endpoints\tP1\n9\tSQL injection prevented (parameterized queries)\tP0\n10\tDependency scanning (safety/pip-audit)\tP1\nPhase 6: Database Patterns\nAsync SQLAlchemy + Repository Pattern\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\nfrom sqlalchemy import select, func\nfrom uuid import uuid4, UUID\nfrom datetime import datetime, timezone\n\n# Engine setup\nengine = create_async_engine(\n    settings.database_url.get_secret_value(),\n    pool_size=settings.db_pool_size,\n    max_overflow=settings.db_max_overflow,\n    pool_timeout=settings.db_pool_timeout,\n    pool_pre_ping=True,  # Check connection health\n    echo=settings.debug,\n)\n\nSessionFactory = async_sessionmaker(engine, expire_on_commit=False)\n\nasync def get_db() -> AsyncGenerator[AsyncSession, None]:\n    async with SessionFactory() as session:\n        try:\n            yield session\n            await session.commit()\n        except Exception:\n            await session.rollback()\n            raise\n\n# Base model with common fields\nclass Base(DeclarativeBase):\n    pass\n\nclass TimestampMixin:\n    created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc))\n    updated_at: Mapped[datetime] = mapped_column(\n        default=lambda: datetime.now(timezone.utc),\n        onupdate=lambda: datetime.now(timezone.utc),\n    )\n\n# Repository pattern\nclass BaseRepository[T]:\n    def __init__(self, session: AsyncSession, model: type[T]):\n        self.session = session\n        self.model = model\n    \n    async def get_by_id(self, id: UUID) -> T | None:\n        return await self.session.get(self.model, id)\n    \n    async def get_or_raise(self, id: UUID) -> T:\n        entity = await self.get_by_id(id)\n        if not entity:\n            raise NotFoundError(self.model.__name__, str(id))\n        return entity\n    \n    async def list(\n        self, *, offset: int = 0, limit: int = 20, **filters\n    ) -> tuple[list[T], int]:\n        query = select(self.model)\n        count_query = select(func.count()).select_from(self.model)\n        \n        for field, value in filters.items():\n            if value is not None:\n                query = query.where(getattr(self.model, field) == value)\n                count_query = count_query.where(getattr(self.model, field) == value)\n        \n        total = await self.session.scalar(count_query) or 0\n        result = await self.session.execute(\n            query.offset(offset).limit(limit).order_by(self.model.created_at.desc())\n        )\n        return list(result.scalars().all()), total\n    \n    async def create(self, entity: T) -> T:\n        self.session.add(entity)\n        await self.session.flush()\n        return entity\n    \n    async def delete(self, entity: T) -> None:\n        await self.session.delete(entity)\n\nORM Selection Guide\nORM\tBest For\tAsync\tType Safety\tLearning Curve\nSQLAlchemy 2.0\tComplex queries, enterprise\t✅\t✅ Mapped[]\tMedium\nSQLModel\tSimple CRUD, Pydantic sync\t✅\t✅\tLow\nTortoise\tDjango-like feel\t✅\tPartial\tLow\nPiccolo\tModern, migrations built-in\t✅\t✅\tLow\n\nRecommendation: SQLAlchemy 2.0 for production. SQLModel for prototypes.\n\nMigration Strategy (Alembic)\n# Setup\nalembic init migrations\n# Edit alembic.ini: sqlalchemy.url = from env\n\n# Generate migration\nalembic revision --autogenerate -m \"add users table\"\n\n# Apply\nalembic upgrade head\n\n# Rollback\nalembic downgrade -1\n\n\nMigration Rules:\n\nAlways review autogenerated migrations before applying\nNever edit applied migrations — create new ones\nTest migrations in staging before production\nInclude downgrade() for every upgrade()\nUse batch_alter_table for SQLite compatibility\nPhase 7: Testing Strategy\nTest Pyramid\nLevel\tCoverage Target\tTools\tFocus\nUnit\t80%+\tpytest, unittest.mock\tService logic, validators\nIntegration\tKey paths\tpytest-asyncio, testcontainers\tDB queries, external APIs\nE2E\tCritical flows\thttpx.AsyncClient\tFull request→response\nContract\tAPI boundaries\tschemathesis\tOpenAPI compliance\nTest Patterns\nimport pytest\nfrom httpx import AsyncClient, ASGITransport\nfrom app.main import create_app\n\n@pytest.fixture\nasync def app():\n    app = create_app()\n    yield app\n\n@pytest.fixture\nasync def client(app):\n    transport = ASGITransport(app=app)\n    async with AsyncClient(transport=transport, base_url=\"http://test\") as ac:\n        yield ac\n\n@pytest.fixture\nasync def auth_client(client, test_user):\n    token = create_access_token(test_user.id, test_user.roles)\n    client.headers[\"Authorization\"] = f\"Bearer {token}\"\n    return client\n\n# E2E test\n@pytest.mark.asyncio\nasync def test_create_user(client: AsyncClient):\n    response = await client.post(\"/api/users\", json={\n        \"email\": \"test@example.com\",\n        \"name\": \"Test User\",\n        \"password\": \"securepass123\",\n    })\n    assert response.status_code == 201\n    data = response.json()\n    assert data[\"email\"] == \"test@example.com\"\n    assert \"password\" not in data  # Never expose\n\n# Unit test (service layer)\n@pytest.mark.asyncio\nasync def test_user_service_duplicate_email(user_service, mock_repo):\n    mock_repo.get_by_email.return_value = existing_user\n    with pytest.raises(ConflictError, match=\"Email already registered\"):\n        await user_service.create(UserCreate(email=\"taken@example.com\", ...))\n\n# Parametrized validation\n@pytest.mark.parametrize(\"email,expected\", [\n    (\"valid@example.com\", True),\n    (\"invalid\", False),\n    (\"\", False),\n    (\"a@b.c\", True),\n])\ndef test_email_validation(email, expected):\n    if expected:\n        UserCreate(email=email, name=\"Test\", password=\"12345678\")\n    else:\n        with pytest.raises(ValidationError):\n            UserCreate(email=email, name=\"Test\", password=\"12345678\")\n\n7 Testing Rules\nTest services, not routers — business logic lives in services\nUse fixtures for DI override — swap real DB with test DB via app.dependency_overrides\nOne assertion per test — clear what broke when it fails\nTest error paths — 40% of tests should be sad-path\nUse factories for test data — UserFactory.create() not manual dict construction\nAsync tests need @pytest.mark.asyncio — or set asyncio_mode = \"auto\" in config\nRun tests in CI — block merge if tests fail\nPhase 8: Structured Logging & Observability\nStructlog Setup\nimport structlog\nfrom uuid import uuid4\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nstructlog.configure(\n    processors=[\n        structlog.contextvars.merge_contextvars,\n        structlog.stdlib.add_log_level,\n        structlog.stdlib.add_logger_name,\n        structlog.processors.TimeStamper(fmt=\"iso\"),\n        structlog.processors.StackInfoRenderer(),\n        structlog.processors.format_exc_info,\n        structlog.processors.JSONRenderer(),\n    ],\n    logger_factory=structlog.stdlib.LoggerFactory(),\n)\n\nlogger = structlog.get_logger()\n\n# Request ID middleware\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        request_id = request.headers.get(\"X-Request-ID\", str(uuid4()))\n        structlog.contextvars.clear_contextvars()\n        structlog.contextvars.bind_contextvars(\n            request_id=request_id,\n            method=request.method,\n            path=request.url.path,\n        )\n        \n        response = await call_next(request)\n        response.headers[\"X-Request-ID\"] = request_id\n        \n        logger.info(\n            \"request_completed\",\n            status_code=response.status_code,\n        )\n        return response\n\nHealth Check Endpoints\n@router.get(\"/health\")\nasync def health():\n    \"\"\"Liveness probe — is the process running?\"\"\"\n    return {\"status\": \"ok\"}\n\n@router.get(\"/ready\")\nasync def ready(db: AsyncSession = Depends(get_db)):\n    \"\"\"Readiness probe — can we serve traffic?\"\"\"\n    checks = {}\n    try:\n        await db.execute(text(\"SELECT 1\"))\n        checks[\"database\"] = \"ok\"\n    except Exception:\n        checks[\"database\"] = \"error\"\n    \n    all_ok = all(v == \"ok\" for v in checks.values())\n    return JSONResponse(\n        status_code=200 if all_ok else 503,\n        content={\"status\": \"ok\" if all_ok else \"degraded\", \"checks\": checks},\n    )\n\nPhase 9: Performance Optimization\nPriority Stack\n#\tTechnique\tImpact\tEffort\n1\tAsync database queries\tHigh\tLow\n2\tConnection pooling (tuned)\tHigh\tLow\n3\tResponse caching (Redis)\tHigh\tMedium\n4\tBackground tasks for heavy work\tHigh\tLow\n5\tPagination on all list endpoints\tMedium\tLow\n6\tSelect only needed columns\tMedium\tLow\n7\tEager loading (joinedload)\tMedium\tMedium\n8\tRate limiting\tMedium\tLow\nBackground Tasks\nfrom fastapi import BackgroundTasks\n\n@router.post(\"/users\", status_code=201)\nasync def create_user(\n    user_in: UserCreate,\n    background_tasks: BackgroundTasks,\n    service: UserService = Depends(get_user_service),\n):\n    user = await service.create(user_in)\n    background_tasks.add_task(send_welcome_email, user.email, user.name)\n    return user\n\nCaching Pattern\nfrom redis.asyncio import Redis\nimport json\n\nclass CacheService:\n    def __init__(self, redis: Redis):\n        self.redis = redis\n    \n    async def get_or_set(self, key: str, factory, ttl: int = 300):\n        cached = await self.redis.get(key)\n        if cached:\n            return json.loads(cached)\n        result = await factory()\n        await self.redis.setex(key, ttl, json.dumps(result, default=str))\n        return result\n    \n    async def invalidate(self, pattern: str):\n        keys = await self.redis.keys(pattern)\n        if keys:\n            await self.redis.delete(*keys)\n\nPhase 10: Production Deployment\nMulti-Stage Dockerfile\n# Build stage\nFROM python:3.12-slim AS builder\nWORKDIR /app\n\nRUN pip install --no-cache-dir uv\nCOPY pyproject.toml uv.lock ./\nRUN uv sync --frozen --no-dev --no-editable\n\n# Production stage\nFROM python:3.12-slim\nWORKDIR /app\n\nRUN adduser --disabled-password --no-create-home appuser\n\nCOPY --from=builder /app/.venv /app/.venv\nCOPY src/ ./src/\nCOPY migrations/ ./migrations/\nCOPY alembic.ini ./\n\nENV PATH=\"/app/.venv/bin:$PATH\"\nENV PYTHONUNBUFFERED=1\nENV PYTHONDONTWRITEBYTECODE=1\n\nUSER appuser\nEXPOSE 8000\n\nHEALTHCHECK --interval=30s --timeout=5s --retries=3 \\\n    CMD [\"python\", \"-c\", \"import httpx; httpx.get('http://localhost:8000/health').raise_for_status()\"]\n\nCMD [\"uvicorn\", \"src.app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--workers\", \"4\"]\n\nApp Factory Pattern\nfrom fastapi import FastAPI\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Startup\n    logger.info(\"starting_up\", environment=settings.environment)\n    await init_db()\n    yield\n    # Shutdown\n    logger.info(\"shutting_down\")\n    await engine.dispose()\n\ndef create_app() -> FastAPI:\n    settings = get_settings()\n    \n    app = FastAPI(\n        title=settings.app_name,\n        lifespan=lifespan,\n        docs_url=\"/docs\" if settings.debug else None,\n        redoc_url=None,\n    )\n    \n    # Middleware (order matters — last added = first executed)\n    app.add_middleware(\n        CORSMiddleware,\n        allow_origins=settings.cors_origins,\n        allow_credentials=True,\n        allow_methods=[\"*\"],\n        allow_headers=[\"*\"],\n    )\n    app.add_middleware(RequestIDMiddleware)\n    \n    # Error handlers\n    app.add_exception_handler(AppError, app_error_handler)\n    \n    # Routers\n    app.include_router(auth_router, prefix=\"/api/auth\", tags=[\"auth\"])\n    app.include_router(users_router, prefix=\"/api/users\", tags=[\"users\"])\n    app.include_router(health_router, tags=[\"health\"])\n    \n    return app\n\napp = create_app()\n\nGitHub Actions CI\nname: CI\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_PASSWORD: test\n          POSTGRES_DB: testdb\n        ports: [\"5432:5432\"]\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n    \n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with: { python-version: \"3.12\" }\n      - run: pip install uv && uv sync\n      - run: uv run ruff check .\n      - run: uv run mypy src/\n      - run: uv run pytest --cov=src --cov-report=xml -x\n        env:\n          DATABASE_URL: postgresql+asyncpg://postgres:test@localhost:5432/testdb\n          JWT_SECRET: test-secret-key-at-least-32-chars\n\nProduction Checklist\n\nP0 — Mandatory:\n\n All secrets from environment variables (SecretStr)\n HTTPS enforced\n CORS configured per environment\n Rate limiting on auth endpoints\n Input validation on all endpoints\n Structured error responses (no stack traces)\n Health + readiness endpoints\n Database connection pooling\n Migrations run before deploy\n Structured logging (JSON)\n Tests passing in CI\n\nP1 — Recommended:\n\n OpenTelemetry tracing\n Prometheus metrics endpoint\n Background task queue (Celery/ARQ)\n Redis caching layer\n API versioning strategy\n Request/response logging\n Dependency security scanning\n Performance benchmarks established\nPhase 11: Advanced Patterns\nMiddleware Stack Order\n# Applied bottom-to-top (last added = first executed)\napp.add_middleware(GZipMiddleware, minimum_size=1000)    # 5. Compress\napp.add_middleware(CORSMiddleware, ...)                  # 4. CORS\napp.add_middleware(RequestIDMiddleware)                   # 3. Request ID\napp.add_middleware(RateLimitMiddleware)                   # 2. Rate limit\napp.add_middleware(TrustedHostMiddleware, allowed=[\"*\"])  # 1. Host check\n\nPagination with Cursor-Based Option\nfrom fastapi import Query\n\nclass PaginationParams:\n    def __init__(\n        self,\n        page: int = Query(1, ge=1, description=\"Page number\"),\n        page_size: int = Query(20, ge=1, le=100, description=\"Items per page\"),\n    ):\n        self.offset = (page - 1) * page_size\n        self.limit = page_size\n        self.page = page\n        self.page_size = page_size\n\n@router.get(\"/users\", response_model=PaginatedResponse[UserResponse])\nasync def list_users(\n    pagination: PaginationParams = Depends(),\n    service: UserService = Depends(get_user_service),\n):\n    items, total = await service.list(\n        offset=pagination.offset, limit=pagination.limit\n    )\n    return PaginatedResponse(\n        items=items, total=total,\n        page=pagination.page, page_size=pagination.page_size,\n        has_next=(pagination.offset + pagination.limit) < total,\n    )\n\nWebSocket Pattern\nfrom fastapi import WebSocket, WebSocketDisconnect\n\nclass ConnectionManager:\n    def __init__(self):\n        self.connections: dict[str, WebSocket] = {}\n    \n    async def connect(self, user_id: str, ws: WebSocket):\n        await ws.accept()\n        self.connections[user_id] = ws\n    \n    def disconnect(self, user_id: str):\n        self.connections.pop(user_id, None)\n    \n    async def send(self, user_id: str, message: dict):\n        if ws := self.connections.get(user_id):\n            await ws.send_json(message)\n\nmanager = ConnectionManager()\n\n@router.websocket(\"/ws/{user_id}\")\nasync def websocket_endpoint(websocket: WebSocket, user_id: str):\n    await manager.connect(user_id, websocket)\n    try:\n        while True:\n            data = await websocket.receive_json()\n            # Process message\n    except WebSocketDisconnect:\n        manager.disconnect(user_id)\n\nFile Upload Pattern\nfrom fastapi import UploadFile, File\n\n@router.post(\"/upload\")\nasync def upload_file(\n    file: UploadFile = File(..., description=\"File to upload\"),\n    user: User = Depends(get_current_user),\n):\n    # Validate\n    if file.size and file.size > 10 * 1024 * 1024:  # 10MB\n        raise ValidationError(\"File too large (max 10MB)\")\n    \n    allowed_types = {\"image/jpeg\", \"image/png\", \"application/pdf\"}\n    if file.content_type not in allowed_types:\n        raise ValidationError(f\"File type not allowed: {file.content_type}\")\n    \n    # Save\n    contents = await file.read()\n    path = f\"uploads/{user.id}/{file.filename}\"\n    # Save to S3/local storage...\n    \n    return {\"filename\": file.filename, \"size\": len(contents)}\n\nPhase 12: Common Mistakes\n#\tMistake\tFix\n1\tSync database calls in async app\tUse async SQLAlchemy/databases\n2\tBusiness logic in route handlers\tMove to service layer\n3\tNo input validation\tPydantic models on every endpoint\n4\tReturning ORM models directly\tUse response schemas (from_attributes)\n5\tHardcoded config values\tPydantic Settings + env vars\n6\tNo error handling strategy\tCustom exception hierarchy + global handler\n7\tMissing health checks\t/health + /ready endpoints\n8\tprint() for logging\tstructlog with JSON output\n9\tNo pagination on list endpoints\tDefault limit, max cap (100)\n10\tTesting against production DB\tTest fixtures with separate DB\nQuality Scoring (0–100)\nDimension\tWeight\t0–25\t50\t75\t100\nType Safety\t15%\tNo types\tPartial Pydantic\tFull schemas\tStrict mypy pass\nError Handling\t15%\tBare HTTPException\tCustom errors\tFull hierarchy\t+ monitoring\nTesting\t15%\tNone\tHappy path\t80%+ coverage\t+ contract tests\nSecurity\t15%\tNo auth\tBasic JWT\t+ RBAC + rate limit\t+ scanning + audit\nPerformance\t10%\tSync everything\tAsync DB\t+ caching\t+ profiling\nObservability\t10%\tprint()\tStructured logs\t+ tracing\t+ metrics + alerts\nDatabase\t10%\tRaw SQL\tORM + migrations\t+ repository pattern\t+ connection tuning\nDeployment\t10%\tManual\tDockerfile\t+ CI/CD\t+ health + rollback\n\nScoring: Your Score = Σ (dimension score × weight). < 40 = critical, 40–60 = needs work, 60–80 = solid, 80+ = production-grade.\n\n10 Commandments of FastAPI Production\nPydantic models at every boundary — request, response, config\nAsync all the way down — one sync call blocks the event loop\nServices own business logic — routers are thin wrappers\nDependency injection for testability — Depends() is your best friend\nStructured errors, structured logs — JSON everything\nHealth checks are non-negotiable — liveness + readiness\nTest the sad paths — 40% of tests should be error cases\nMigrations before deployment — never modify schema manually\nSecrets in environment, never in code — SecretStr enforces this\nProfile before optimizing — measure, don't guess\nNatural Language Commands\naudit my FastAPI project → Run health check, identify gaps\nset up a new FastAPI project → Generate project structure + config\nadd authentication to my API → JWT + RBAC dependency pattern\ncreate a CRUD feature for [resource] → Full router/service/repo/schemas\noptimize my database queries → Connection pooling + async + N+1 prevention\nadd structured logging → Structlog + request ID middleware\nwrite tests for [feature] → Async test patterns + fixtures\nprepare for production deployment → Dockerfile + CI + checklist\nadd caching to my API → Redis caching pattern\nset up error handling → Custom exception hierarchy + global handler\nadd WebSocket support → Connection manager pattern\nreview my API security → 10-point security checklist audit\n\n⚡ Level up your FastAPI APIs → Get the AfrexAI SaaS Context Pack ($47) for complete SaaS architecture, pricing strategies, and go-to-market playbooks.\n\n🔗 More free skills by AfrexAI:\n\nafrexai-python-production — Python production engineering\nafrexai-api-architecture — API design methodology\nafrexai-database-engineering — Database patterns\nafrexai-test-automation-engineering — Testing strategy\nafrexai-cicd-engineering — CI/CD pipeline design\n\n🛒 Browse all packs → AfrexAI Storefront"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/1kalin/afrexai-fastapi-production",
    "publisherUrl": "https://clawhub.ai/1kalin/afrexai-fastapi-production",
    "owner": "1kalin",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/afrexai-fastapi-production",
    "downloadUrl": "https://openagent3.xyz/downloads/afrexai-fastapi-production",
    "agentUrl": "https://openagent3.xyz/skills/afrexai-fastapi-production/agent",
    "manifestUrl": "https://openagent3.xyz/skills/afrexai-fastapi-production/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/afrexai-fastapi-production/agent.md"
  }
}