[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70FWMYRWJGC3CH93QY2T16Q":3,"skill-same:01M70FWMYRWJGC3CH93QY2T16Q":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":51,"commitSha":52,"skillDirectory":53,"forksCount":54,"repositoryHtmlUrl":55,"repositoryHomepageUrl":6,"ownerHtmlUrl":56,"ownerBlogUrl":6,"ownerDescription":57,"updatedAt":58,"install":59,"resources":72},"01M70FWMYRWJGC3CH93QY2T16Q","fastapi-patterns",null,"FastAPI best practices covering project structure, Pydantic v2 schemas, dependency injection, async handlers, authentication, authorization, transactional service layers, and testing with httpx and pytest. Use when building or reviewing FastAPI apps — Pydantic schemas, dependencies, async handlers, auth, or tests.","mdnaimul22\u002Fhuman-skills","mdnaimul22","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F64520358?v=4","01M70FH95MH2H796Q01TYSFZ03","Naimul","01M70FH96798CQR4YHA13F17GA",3,"MIT","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Ffastapi-patterns\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"patterns","fastapi patterns","fastapi","async","layers","dependency injection","httpx","pytest","injection","transactional","dependency","Pydantic","project structure","structure","when","apps","testing","authentication","project","auth","2026-11-10 10:12:30",false,"---\nname: fastapi-patterns\ndescription: FastAPI best practices covering project structure, Pydantic v2 schemas, dependency injection, async handlers, authentication, authorization, transactional service layers, and testing with httpx and pytest. Use when building or reviewing FastAPI apps — Pydantic schemas, dependencies, async handlers, auth, or tests.\nmetadata:\n  origin: ECC\n---\n\n# FastAPI Patterns\n\nModern, production-grade FastAPI development: project layout, Pydantic v2 schemas, dependency injection, async patterns, auth, transactional service methods, and testing.\n\n## Project Structure\n\n```text\nmy_app\u002F\n|-- app\u002F\n|   |-- main.py               # App factory, lifespan, middleware\n|   |-- config.py             # Settings via pydantic-settings\n|   |-- dependencies.py       # Shared FastAPI dependencies\n|   |-- database.py           # SQLAlchemy engine + session\n|   |-- routers\u002F\n|   |   `-- users.py\n|   |-- models\u002F               # SQLAlchemy ORM models\n|   |   `-- user.py\n|   |-- schemas\u002F              # Pydantic request\u002Fresponse schemas\n|   |   `-- user.py\n|   `-- services\u002F             # Business logic layer\n|       `-- user_service.py\n|-- tests\u002F\n|   |-- conftest.py\n|   `-- test_users.py\n|-- pyproject.toml\n`-- .env\n```\n\n---\n\n## App Factory and Lifespan\n\n```python\n# app\u002Fmain.py\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\n\nfrom app.config import settings\nfrom app.database import engine, Base\nfrom app.routers import users\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Automatically create tables on startup for ease of use in dev\u002Fdemo environments.\n    # For strict production applications, manage schemas via Alembic migrations instead.\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield\n    # Shutdown: close pooled resources.\n    await engine.dispose()\n\n\ndef create_app() -> FastAPI:\n    app = FastAPI(\n        title=settings.app_name,\n        version=settings.app_version,\n        lifespan=lifespan,\n    )\n\n    app.add_middleware(\n        CORSMiddleware,\n        allow_origins=settings.allowed_origins,\n        allow_credentials=settings.allow_credentials,\n        allow_methods=settings.allowed_methods,\n        allow_headers=settings.allowed_headers,\n    )\n\n    app.include_router(users.router, prefix=\"\u002Fusers\", tags=[\"users\"])\n\n    return app\n\n\napp = create_app()\n```\n\n---\n\n## Configuration with pydantic-settings\n\n```python\n# app\u002Fconfig.py\nfrom pydantic_settings import BaseSettings, SettingsConfigDict\n\n\nclass Settings(BaseSettings):\n    model_config = SettingsConfigDict(env_file=\".env\", env_file_encoding=\"utf-8\")\n\n    app_name: str = \"My App\"\n    app_version: str = \"0.1.0\"\n    debug: bool = False\n\n    database_url: str\n    secret_key: str\n    algorithm: str = \"HS256\"\n    access_token_expire_minutes: int = 30\n\n    # Pydantic-settings v2 safely evaluates mutable list literals directly\n    allowed_origins: list[str] = [\"http:\u002F\u002Flocalhost:3000\"]\n    allowed_methods: list[str] = [\"GET\", \"POST\", \"PATCH\", \"DELETE\", \"OPTIONS\"]\n    allowed_headers: list[str] = [\"Authorization\", \"Content-Type\"]\n    allow_credentials: bool = True\n\n\nsettings = Settings()\n```\n\n---\n\n## Pydantic Schemas (v2)\n\n```python\n# app\u002Fschemas\u002Fuser.py\nfrom datetime import datetime\nfrom pydantic import BaseModel, EmailStr, Field, model_validator\n\n\nclass UserBase(BaseModel):\n    email: EmailStr\n    username: str = Field(min_length=3, max_length=50)\n\n\nclass UserCreate(UserBase):\n    password: str = Field(min_length=8)\n    password_confirm: str\n\n    @model_validator(mode=\"after\")\n    def passwords_match(self) -> \"UserCreate\":\n        if self.password != self.password_confirm:\n            raise ValueError(\"Passwords do not match\")\n        return self\n\n\nclass UserUpdate(BaseModel):\n    username: str | None = Field(default=None, min_length=3, max_length=50)\n    email: EmailStr | None = None\n\n\nclass UserResponse(UserBase):\n    id: int\n    is_active: bool\n    created_at: datetime\n\n    model_config = {\"from_attributes\": True}\n\n\nclass UserListResponse(BaseModel):\n    total: int\n    items: list[UserResponse]\n```\n\n---\n\n## Dependency Injection\n\n```python\n# app\u002Fdependencies.py\nfrom typing import Annotated, AsyncGenerator\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordBearer\nfrom jose import JWTError, jwt\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.config import settings\nfrom app.database import AsyncSessionLocal\nfrom app.models.user import User\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Fusers\u002Ftoken\")\n\n\nasync def get_db() -> AsyncGenerator[AsyncSession, None]:\n    async with AsyncSessionLocal() as session:\n        try:\n            yield session\n        except Exception:\n            await session.rollback()\n            raise\n\n\nasync def get_current_user(\n    token: Annotated[str, Depends(oauth2_scheme)],\n    db: Annotated[AsyncSession, Depends(get_db)],\n) -> User:\n    credentials_exception = HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=\"Could not validate credentials\",\n        headers={\"WWW-Authenticate\": \"Bearer\"},\n    )\n    try:\n        payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])\n        subject = payload.get(\"sub\")\n        if subject is None:\n            raise credentials_exception\n        user_id = int(subject)\n    except (JWTError, TypeError, ValueError):\n        raise credentials_exception\n\n    user = await db.get(User, user_id)\n    if user is None:\n        raise credentials_exception\n    return user\n\n\nasync def get_current_active_user(\n    current_user: Annotated[User, Depends(get_current_user)],\n) -> User:\n    if not current_user.is_active:\n        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=\"Inactive user\")\n    return current_user\n\n\nDbDep = Annotated[AsyncSession, Depends(get_db)]\nCurrentUserDep = Annotated[User, Depends(get_current_user)]\nActiveUserDep = Annotated[User, Depends(get_current_active_user)]\n```\n\n---\n\n## Router and Endpoint Design\n\n```python\n# app\u002Frouters\u002Fusers.py\nfrom typing import Annotated\nfrom fastapi import APIRouter, Depends, HTTPException, Query, status\nfrom fastapi.security import OAuth2PasswordRequestForm\n\nfrom app.dependencies import ActiveUserDep, DbDep\nfrom app.schemas.user import UserCreate, UserResponse, UserUpdate, UserListResponse\nfrom app.services.user_service import DuplicateUserError, UserService\n\nrouter = APIRouter()\n\n\n@router.post(\"\u002F\", response_model=UserResponse, status_code=status.HTTP_201_CREATED)\nasync def create_user(payload: UserCreate, db: DbDep) -> UserResponse:\n    service = UserService(db)\n    try:\n        return await service.create(payload)\n    except DuplicateUserError:\n        raise HTTPException(status_code=400, detail=\"Email already registered\")\n\n\n@router.get(\"\u002Fme\", response_model=UserResponse)\nasync def get_me(current_user: ActiveUserDep) -> UserResponse:\n    return current_user\n\n\n@router.get(\"\u002F\", response_model=UserListResponse)\nasync def list_users(\n    db: DbDep,\n    current_user: ActiveUserDep,\n    skip: Annotated[int, Query(ge=0)] = 0,\n    limit: Annotated[int, Query(ge=1, le=100)] = 20,\n) -> UserListResponse:\n    service = UserService(db)\n    users, total = await service.list(skip=skip, limit=limit)\n    return UserListResponse(total=total, items=users)\n\n\n@router.patch(\"\u002F{user_id}\", response_model=UserResponse)\nasync def update_user(\n    user_id: int,\n    payload: UserUpdate,\n    db: DbDep,\n    current_user: ActiveUserDep,\n) -> UserResponse:\n    if current_user.id != user_id:\n        raise HTTPException(status_code=403, detail=\"Not authorized\")\n    service = UserService(db)\n    try:\n        user = await service.update(user_id, payload)\n    except DuplicateUserError:\n        raise HTTPException(status_code=400, detail=\"Email already registered\")\n    if user is None:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    return user\n\n\n@router.post(\"\u002Ftoken\")\nasync def login(\n    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],\n    db: DbDep,\n) -> dict[str, str]:\n    service = UserService(db)\n    token = await service.authenticate(form_data.username, form_data.password)\n    if token is None:\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Incorrect username or password\",\n            headers={\"WWW-Authenticate\": \"Bearer\"},\n        )\n    return {\"access_token\": token, \"token_type\": \"bearer\"}\n```\n\n---\n\n## Service Layer\n\n```python\n# app\u002Fservices\u002Fuser_service.py\nfrom datetime import datetime, timedelta, timezone\n\nfrom jose import jwt\nfrom passlib.context import CryptContext\nfrom sqlalchemy import func, select\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.config import settings\nfrom app.models.user import User\nfrom app.schemas.user import UserCreate, UserUpdate\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass DuplicateUserError(Exception):\n    \"\"\"Raised when a unique user field conflicts with an existing row.\"\"\"\n\n\nclass UserService:\n    def __init__(self, db: AsyncSession) -> None:\n        self.db = db\n\n    async def get_by_email(self, email: str) -> User | None:\n        result = await self.db.execute(select(User).where(User.email == email))\n        return result.scalar_one_or_none()\n\n    async def create(self, payload: UserCreate) -> User:\n        user = User(\n            email=payload.email,\n            username=payload.username,\n            hashed_password=pwd_context.hash(payload.password),\n        )\n        self.db.add(user)\n        try:\n            # Rely on atomic DB constraints rather than race-prone application-level prechecks\n            await self.db.commit()\n        except IntegrityError as exc:\n            await self.db.rollback()\n            raise DuplicateUserError from exc\n        await self.db.refresh(user)\n        return user\n\n    async def list(self, skip: int = 0, limit: int = 20) -> tuple[list[User], int]:\n        total_result = await self.db.execute(select(func.count(User.id)))\n        total = total_result.scalar_one()\n        # Enforce explicit deterministic ordering to ensure reliable pagination\n        result = await self.db.execute(\n            select(User).order_by(User.id).offset(skip).limit(limit)\n        )\n        return list(result.scalars()), total\n\n    async def update(self, user_id: int, payload: UserUpdate) -> User | None:\n        user = await self.db.get(User, user_id)\n        if user is None:\n            return None\n        for field, value in payload.model_dump(exclude_unset=True).items():\n            setattr(user, field, value)\n        try:\n            await self.db.commit()\n        except IntegrityError as exc:\n            await self.db.rollback()\n            raise DuplicateUserError from exc\n        await self.db.refresh(user)\n        return user\n\n    async def authenticate(self, email: str, password: str) -> str | None:\n        user = await self.get_by_email(email)\n        if user is None or not pwd_context.verify(password, user.hashed_password):\n            return None\n        expire = datetime.now(timezone.utc) + timedelta(\n            minutes=settings.access_token_expire_minutes\n        )\n        return jwt.encode(\n            {\"sub\": str(user.id), \"exp\": expire},\n            settings.secret_key,\n            algorithm=settings.algorithm,\n        )\n```\n\n> **Note on Database Design:** Application-level unique handling requires an underlying unique database index (e.g., `unique=True` on your SQLAlchemy mapping attributes). Without underlying constraints, application layer error-catching cannot safely prevent concurrent race conditions.\n\n---\n\n## Testing with httpx and pytest\n\n```python\n# tests\u002Fconftest.py\nimport pytest_asyncio\nfrom httpx import ASGITransport, AsyncClient\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\n\nfrom app.database import Base\nfrom app.dependencies import get_db\nfrom app.main import create_app\n\nTEST_DATABASE_URL = \"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\"\n\nengine = create_async_engine(TEST_DATABASE_URL)\nTestingSessionLocal = async_sessionmaker(engine, expire_on_commit=False)\n\n\n@pytest_asyncio.fixture(autouse=True)\nasync def setup_db():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.drop_all)\n\n\n@pytest_asyncio.fixture\nasync def db_session():\n    async with TestingSessionLocal() as session:\n        yield session\n        await session.rollback()\n\n\n@pytest_asyncio.fixture\nasync def client(db_session: AsyncSession):\n    app = create_app()\n\n    async def override_get_db():\n        yield db_session\n\n    app.dependency_overrides[get_db] = override_get_db\n\n    async with AsyncClient(\n        transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\"\n    ) as ac:\n        yield ac\n\n\n@pytest_asyncio.fixture\nasync def registered_user(client: AsyncClient) -> dict:\n    resp = await client.post(\"\u002Fusers\u002F\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"securepass1\",\n        \"password_confirm\": \"securepass1\",\n    })\n    assert resp.status_code == 201\n    return resp.json()\n\n\n@pytest_asyncio.fixture\nasync def auth_token(client: AsyncClient, registered_user: dict) -> str:\n    resp = await client.post(\"\u002Fusers\u002Ftoken\", data={\n        \"username\": \"test@example.com\",\n        \"password\": \"securepass1\",\n    })\n    assert resp.status_code == 200\n    return resp.json()[\"access_token\"]\n\n\n@pytest_asyncio.fixture\nasync def auth_client(client: AsyncClient, auth_token: str) -> AsyncClient:\n    client.headers.update({\"Authorization\": f\"Bearer {auth_token}\"})\n    return client\n```\n\n---\n\n## Anti-Patterns\n\n```python\n# Bad: business logic inside route handlers.\n@router.post(\"\u002Fusers\u002F\")\nasync def create_user(payload: UserCreate, db: DbDep):\n    hashed = bcrypt.hash(payload.password)\n    user = User(email=payload.email, hashed_password=hashed)\n    db.add(user)\n    await db.commit()\n    return user\n\n# Good: thin route, transactional service handling.\n@router.post(\"\u002Fusers\u002F\", response_model=UserResponse, status_code=201)\nasync def create_user(payload: UserCreate, db: DbDep):\n    try:\n        return await UserService(db).create(payload)\n    except DuplicateUserError:\n        raise HTTPException(status_code=400, detail=\"Email already registered\")\n\n\n# Bad: sync DB calls in async routes block the event loop.\n@router.get(\"\u002Fitems\u002F\")\nasync def list_items(db: Session = Depends(get_db)):\n    return db.query(Item).all()\n\n# Good: use async SQLAlchemy executions.\n@router.get(\"\u002Fitems\u002F\")\nasync def list_items(db: AsyncSession = Depends(get_db)):\n    result = await db.execute(select(Item))\n    return result.scalars().all()\n```\n\n---\n\n## Best Practices\n\n- Always declare a typed `response_model` to prevent accidental PII\u002Fdata leaks and output clean OpenAPI schemas.\n- Consolidate standard middleware dependency injections via type-aliasing: `DbDep = Annotated[AsyncSession, Depends(get_db)]`.\n- Wrap database mutation boundaries gracefully within transactions inside your service layer, catching structural database errors directly.\n- Parse JWT parameters defensively, expecting potential string\u002Finteger cast mismatches from modern payload variations.\n- Enforce deterministic sorting (e.g., `.order_by(Model.id)`) on all offset\u002Flimit paginated endpoints to avoid data skips.\n- Isolate authorization checks from core authentication dependencies to provide precise REST status signals (`401` vs `403`).\n","# FastAPI Patterns\n\nModern, production-grade FastAPI development: project layout, Pydantic v2 schemas, dependency injection, async patterns, auth, transactional service methods, and testing.\n\n## Project Structure\n\n```text\nmy_app\u002F\n|-- app\u002F\n|   |-- main.py               # App factory, lifespan, middleware\n|   |-- config.py             # Settings via pydantic-settings\n|   |-- dependencies.py       # Shared FastAPI dependencies\n|   |-- database.py           # SQLAlchemy engine + session\n|   |-- routers\u002F\n|   |   `-- users.py\n|   |-- models\u002F               # SQLAlchemy ORM models\n|   |   `-- user.py\n|   |-- schemas\u002F              # Pydantic request\u002Fresponse schemas\n|   |   `-- user.py\n|   `-- services\u002F             # Business logic layer\n|       `-- user_service.py\n|-- tests\u002F\n|   |-- conftest.py\n|   `-- test_users.py\n|-- pyproject.toml\n`-- .env\n```\n\n---\n\n## App Factory and Lifespan\n\n```python\n# app\u002Fmain.py\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\n\nfrom app.config import settings\nfrom app.database import engine, Base\nfrom app.routers import users\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Automatically create tables on startup for ease of use in dev\u002Fdemo environments.\n    # For strict production applications, manage schemas via Alembic migrations instead.\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield\n    # Shutdown: close pooled resources.\n    await engine.dispose()\n\n\ndef create_app() -> FastAPI:\n    app = FastAPI(\n        title=settings.app_name,\n        version=settings.app_version,\n        lifespan=lifespan,\n    )\n\n    app.add_middleware(\n        CORSMiddleware,\n        allow_origins=settings.allowed_origins,\n        allow_credentials=settings.allow_credentials,\n        allow_methods=settings.allowed_methods,\n        allow_headers=settings.allowed_headers,\n    )\n\n    app.include_router(users.router, prefix=\"\u002Fusers\", tags=[\"users\"])\n\n    return app\n\n\napp = create_app()\n```\n\n---\n\n## Configuration with pydantic-settings\n\n```python\n# app\u002Fconfig.py\nfrom pydantic_settings import BaseSettings, SettingsConfigDict\n\n\nclass Settings(BaseSettings):\n    model_config = SettingsConfigDict(env_file=\".env\", env_file_encoding=\"utf-8\")\n\n    app_name: str = \"My App\"\n    app_version: str = \"0.1.0\"\n    debug: bool = False\n\n    database_url: str\n    secret_key: str\n    algorithm: str = \"HS256\"\n    access_token_expire_minutes: int = 30\n\n    # Pydantic-settings v2 safely evaluates mutable list literals directly\n    allowed_origins: list[str] = [\"http:\u002F\u002Flocalhost:3000\"]\n    allowed_methods: list[str] = [\"GET\", \"POST\", \"PATCH\", \"DELETE\", \"OPTIONS\"]\n    allowed_headers: list[str] = [\"Authorization\", \"Content-Type\"]\n    allow_credentials: bool = True\n\n\nsettings = Settings()\n```\n\n---\n\n## Pydantic Schemas (v2)\n\n```python\n# app\u002Fschemas\u002Fuser.py\nfrom datetime import datetime\nfrom pydantic import BaseModel, EmailStr, Field, model_validator\n\n\nclass UserBase(BaseModel):\n    email: EmailStr\n    username: str = Field(min_length=3, max_length=50)\n\n\nclass UserCreate(UserBase):\n    password: str = Field(min_length=8)\n    password_confirm: str\n\n    @model_validator(mode=\"after\")\n    def passwords_match(self) -> \"UserCreate\":\n        if self.password != self.password_confirm:\n            raise ValueError(\"Passwords do not match\")\n        return self\n\n\nclass UserUpdate(BaseModel):\n    username: str | None = Field(default=None, min_length=3, max_length=50)\n    email: EmailStr | None = None\n\n\nclass UserResponse(UserBase):\n    id: int\n    is_active: bool\n    created_at: datetime\n\n    model_config = {\"from_attributes\": True}\n\n\nclass UserListResponse(BaseModel):\n    total: int\n    items: list[UserResponse]\n```\n\n---\n\n## Dependency Injection\n\n```python\n# app\u002Fdependencies.py\nfrom typing import Annotated, AsyncGenerator\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordBearer\nfrom jose import JWTError, jwt\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.config import settings\nfrom app.database import AsyncSessionLocal\nfrom app.models.user import User\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Fusers\u002Ftoken\")\n\n\nasync def get_db() -> AsyncGenerator[AsyncSession, None]:\n    async with AsyncSessionLocal() as session:\n        try:\n            yield session\n        except Exception:\n            await session.rollback()\n            raise\n\n\nasync def get_current_user(\n    token: Annotated[str, Depends(oauth2_scheme)],\n    db: Annotated[AsyncSession, Depends(get_db)],\n) -> User:\n    credentials_exception = HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=\"Could not validate credentials\",\n        headers={\"WWW-Authenticate\": \"Bearer\"},\n    )\n    try:\n        payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])\n        subject = payload.get(\"sub\")\n        if subject is None:\n            raise credentials_exception\n        user_id = int(subject)\n    except (JWTError, TypeError, ValueError):\n        raise credentials_exception\n\n    user = await db.get(User, user_id)\n    if user is None:\n        raise credentials_exception\n    return user\n\n\nasync def get_current_active_user(\n    current_user: Annotated[User, Depends(get_current_user)],\n) -> User:\n    if not current_user.is_active:\n        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=\"Inactive user\")\n    return current_user\n\n\nDbDep = Annotated[AsyncSession, Depends(get_db)]\nCurrentUserDep = Annotated[User, Depends(get_current_user)]\nActiveUserDep = Annotated[User, Depends(get_current_active_user)]\n```\n\n---\n\n## Router and Endpoint Design\n\n```python\n# app\u002Frouters\u002Fusers.py\nfrom typing import Annotated\nfrom fastapi import APIRouter, Depends, HTTPException, Query, status\nfrom fastapi.security import OAuth2PasswordRequestForm\n\nfrom app.dependencies import ActiveUserDep, DbDep\nfrom app.schemas.user import UserCreate, UserResponse, UserUpdate, UserListResponse\nfrom app.services.user_service import DuplicateUserError, UserService\n\nrouter = APIRouter()\n\n\n@router.post(\"\u002F\", response_model=UserResponse, status_code=status.HTTP_201_CREATED)\nasync def create_user(payload: UserCreate, db: DbDep) -> UserResponse:\n    service = UserService(db)\n    try:\n        return await service.create(payload)\n    except DuplicateUserError:\n        raise HTTPException(status_code=400, detail=\"Email already registered\")\n\n\n@router.get(\"\u002Fme\", response_model=UserResponse)\nasync def get_me(current_user: ActiveUserDep) -> UserResponse:\n    return current_user\n\n\n@router.get(\"\u002F\", response_model=UserListResponse)\nasync def list_users(\n    db: DbDep,\n    current_user: ActiveUserDep,\n    skip: Annotated[int, Query(ge=0)] = 0,\n    limit: Annotated[int, Query(ge=1, le=100)] = 20,\n) -> UserListResponse:\n    service = UserService(db)\n    users, total = await service.list(skip=skip, limit=limit)\n    return UserListResponse(total=total, items=users)\n\n\n@router.patch(\"\u002F{user_id}\", response_model=UserResponse)\nasync def update_user(\n    user_id: int,\n    payload: UserUpdate,\n    db: DbDep,\n    current_user: ActiveUserDep,\n) -> UserResponse:\n    if current_user.id != user_id:\n        raise HTTPException(status_code=403, detail=\"Not authorized\")\n    service = UserService(db)\n    try:\n        user = await service.update(user_id, payload)\n    except DuplicateUserError:\n        raise HTTPException(status_code=400, detail=\"Email already registered\")\n    if user is None:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    return user\n\n\n@router.post(\"\u002Ftoken\")\nasync def login(\n    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],\n    db: DbDep,\n) -> dict[str, str]:\n    service = UserService(db)\n    token = await service.authenticate(form_data.username, form_data.password)\n    if token is None:\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Incorrect username or password\",\n            headers={\"WWW-Authenticate\": \"Bearer\"},\n        )\n    return {\"access_token\": token, \"token_type\": \"bearer\"}\n```\n\n---\n\n## Service Layer\n\n```python\n# app\u002Fservices\u002Fuser_service.py\nfrom datetime import datetime, timedelta, timezone\n\nfrom jose import jwt\nfrom passlib.context import CryptContext\nfrom sqlalchemy import func, select\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.config import settings\nfrom app.models.user import User\nfrom app.schemas.user import UserCreate, UserUpdate\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass DuplicateUserError(Exception):\n    \"\"\"Raised when a unique user field conflicts with an existing row.\"\"\"\n\n\nclass UserService:\n    def __init__(self, db: AsyncSession) -> None:\n        self.db = db\n\n    async def get_by_email(self, email: str) -> User | None:\n        result = await self.db.execute(select(User).where(User.email == email))\n        return result.scalar_one_or_none()\n\n    async def create(self, payload: UserCreate) -> User:\n        user = User(\n            email=payload.email,\n            username=payload.username,\n            hashed_password=pwd_context.hash(payload.password),\n        )\n        self.db.add(user)\n        try:\n            # Rely on atomic DB constraints rather than race-prone application-level prechecks\n            await self.db.commit()\n        except IntegrityError as exc:\n            await self.db.rollback()\n            raise DuplicateUserError from exc\n        await self.db.refresh(user)\n        return user\n\n    async def list(self, skip: int = 0, limit: int = 20) -> tuple[list[User], int]:\n        total_result = await self.db.execute(select(func.count(User.id)))\n        total = total_result.scalar_one()\n        # Enforce explicit deterministic ordering to ensure reliable pagination\n        result = await self.db.execute(\n            select(User).order_by(User.id).offset(skip).limit(limit)\n        )\n        return list(result.scalars()), total\n\n    async def update(self, user_id: int, payload: UserUpdate) -> User | None:\n        user = await self.db.get(User, user_id)\n        if user is None:\n            return None\n        for field, value in payload.model_dump(exclude_unset=True).items():\n            setattr(user, field, value)\n        try:\n            await self.db.commit()\n        except IntegrityError as exc:\n            await self.db.rollback()\n            raise DuplicateUserError from exc\n        await self.db.refresh(user)\n        return user\n\n    async def authenticate(self, email: str, password: str) -> str | None:\n        user = await self.get_by_email(email)\n        if user is None or not pwd_context.verify(password, user.hashed_password):\n            return None\n        expire = datetime.now(timezone.utc) + timedelta(\n            minutes=settings.access_token_expire_minutes\n        )\n        return jwt.encode(\n            {\"sub\": str(user.id), \"exp\": expire},\n            settings.secret_key,\n            algorithm=settings.algorithm,\n        )\n```\n\n> **Note on Database Design:** Application-level unique handling requires an underlying unique database index (e.g., `unique=True` on your SQLAlchemy mapping attributes). Without underlying constraints, application layer error-catching cannot safely prevent concurrent race conditions.\n\n---\n\n## Testing with httpx and pytest\n\n```python\n# tests\u002Fconftest.py\nimport pytest_asyncio\nfrom httpx import ASGITransport, AsyncClient\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\n\nfrom app.database import Base\nfrom app.dependencies import get_db\nfrom app.main import create_app\n\nTEST_DATABASE_URL = \"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\"\n\nengine = create_async_engine(TEST_DATABASE_URL)\nTestingSessionLocal = async_sessionmaker(engine, expire_on_commit=False)\n\n\n@pytest_asyncio.fixture(autouse=True)\nasync def setup_db():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.drop_all)\n\n\n@pytest_asyncio.fixture\nasync def db_session():\n    async with TestingSessionLocal() as session:\n        yield session\n        await session.rollback()\n\n\n@pytest_asyncio.fixture\nasync def client(db_session: AsyncSession):\n    app = create_app()\n\n    async def override_get_db():\n        yield db_session\n\n    app.dependency_overrides[get_db] = override_get_db\n\n    async with AsyncClient(\n        transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\"\n    ) as ac:\n        yield ac\n\n\n@pytest_asyncio.fixture\nasync def registered_user(client: AsyncClient) -> dict:\n    resp = await client.post(\"\u002Fusers\u002F\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"securepass1\",\n        \"password_confirm\": \"securepass1\",\n    })\n    assert resp.status_code == 201\n    return resp.json()\n\n\n@pytest_asyncio.fixture\nasync def auth_token(client: AsyncClient, registered_user: dict) -> str:\n    resp = await client.post(\"\u002Fusers\u002Ftoken\", data={\n        \"username\": \"test@example.com\",\n        \"password\": \"securepass1\",\n    })\n    assert resp.status_code == 200\n    return resp.json()[\"access_token\"]\n\n\n@pytest_asyncio.fixture\nasync def auth_client(client: AsyncClient, auth_token: str) -> AsyncClient:\n    client.headers.update({\"Authorization\": f\"Bearer {auth_token}\"})\n    return client\n```\n\n---\n\n## Anti-Patterns\n\n```python\n# Bad: business logic inside route handlers.\n@router.post(\"\u002Fusers\u002F\")\nasync def create_user(payload: UserCreate, db: DbDep):\n    hashed = bcrypt.hash(payload.password)\n    user = User(email=payload.email, hashed_password=hashed)\n    db.add(user)\n    await db.commit()\n    return user\n\n# Good: thin route, transactional service handling.\n@router.post(\"\u002Fusers\u002F\", response_model=UserResponse, status_code=201)\nasync def create_user(payload: UserCreate, db: DbDep):\n    try:\n        return await UserService(db).create(payload)\n    except DuplicateUserError:\n        raise HTTPException(status_code=400, detail=\"Email already registered\")\n\n\n# Bad: sync DB calls in async routes block the event loop.\n@router.get(\"\u002Fitems\u002F\")\nasync def list_items(db: Session = Depends(get_db)):\n    return db.query(Item).all()\n\n# Good: use async SQLAlchemy executions.\n@router.get(\"\u002Fitems\u002F\")\nasync def list_items(db: AsyncSession = Depends(get_db)):\n    result = await db.execute(select(Item))\n    return result.scalars().all()\n```\n\n---\n\n## Best Practices\n\n- Always declare a typed `response_model` to prevent accidental PII\u002Fdata leaks and output clean OpenAPI schemas.\n- Consolidate standard middleware dependency injections via type-aliasing: `DbDep = Annotated[AsyncSession, Depends(get_db)]`.\n- Wrap database mutation boundaries gracefully within transactions inside your service layer, catching structural database errors directly.\n- Parse JWT parameters defensively, expecting potential string\u002Finteger cast mismatches from modern payload variations.\n- Enforce deterministic sorting (e.g., `.order_by(Model.id)`) on all offset\u002Flimit paginated endpoints to avoid data skips.\n- Isolate authorization checks from core authentication dependencies to provide precise REST status signals (`401` vs `403`).\n",{"name":5,"metadata":49,"description":7},{"origin":50},"ECC","main","7092b664627bf326748a6fcbf27fee6a34c4da1d","skills\u002Fstorage\u002Faffaan-m_ECC\u002Ffastapi-patterns",0,"https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills","https:\u002F\u002Fgithub.com\u002Fmdnaimul22","AI Agents Workflows Automation | Specializing in Python, FastAPI, MCP Agents & Custom ERP.","2026-11-11 21:16:57",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add mdnaimul22\u002Fhuman-skills --skill fastapi-patterns",{"tool":65,"command":66},"bunx","bunx skills add mdnaimul22\u002Fhuman-skills --skill fastapi-patterns",{"tool":68,"command":69},"pnpm","pnpm dlx skills add mdnaimul22\u002Fhuman-skills --skill fastapi-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Ffastapi-patterns\u002FSKILL.md",true,[],[74,82,109,131,150,165],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":81},"01M72JT3YP1SQHD0MANGDXQ4RT","higgsfield-websites","Build, edit, and deploy full-stack websites, apps and games via the Higgsfield CLI (`higgsfield website …`). Each is a React 19 + TanStack Start SSR app in one Cloudflare Worker (D1\u002FR2\u002FKV\u002FDO\u002FContainers). THREE product types, picked via `--type` on create: `website` (standalone, no Higgsfield integration — references\u002Fwebsite-flow.md), `app` (Sign in with Higgsfield + fnf SDK, Quanta — references\u002Fapp-flow.md), `game` (realtime multiplayer rooms — references\u002Fgame-flow.md). Routes to the right flow; each carries its own rules and deploy\u002Fpublish gates.\nUse when: \"build me a website\", \"make a landing page\", \"create a web app\", \"build a SaaS dashboard \u002F portfolio\", \"make me a game\", \"deploy this site\", \"publish\". Also owns GAME ART: \"make a spritesheet\", \"tileable texture\", \"animate a 3D character\", game music\u002FSFX — see the game-* references.\nNOT for: single image\u002Fvideo\u002Faudio generation (higgsfield-generate), product photos (higgsfield-product-photoshoot), marketplace cards (higgsfield-marketplace-cards).","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fhiggsfield-ai_skills\u002Fhiggsfield-websites\u002FSKILL.md",[],[],"2026-11-10 22:39:42",{"skillNo":83,"name":84,"nameZh":6,"description":85,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":86,"categories":87,"keywords":100,"publishedAt":44},"01M70JPK8HAXXA85P7Q8NKZZYG","writing-skills","Use when creating new skills, editing existing skills, or verifying skills work before deployment","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-skills\u002FSKILL.md",[88,91,96,99],{"categoryNo":89,"categoryName":90,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":92,"categoryName":93,"taxonomyType":94,"taxonomyTypeDesc":95},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[101,102,103,104,105,106,107,38,108],"writing","writing skills","editing","deployment","use","Existing","new","work",{"skillNo":110,"name":111,"nameZh":6,"description":112,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":120,"publishedAt":44},"01M70JP4PHBH1CQVVDWZ94W159","writing-plans","Use when you have a spec or requirements for a multi-step task, before touching code","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-plans\u002FSKILL.md",[115,116,117],{"categoryNo":89,"categoryName":90,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":92,"categoryName":93,"taxonomyType":94,"taxonomyTypeDesc":95},{"categoryNo":118,"categoryName":119,"taxonomyType":94,"taxonomyTypeDesc":95},"01JBSEEDCAT000000000000030","开发工程师",[101,121,122,123,38,124,125,105,126,127,128,129,130],"writing plans","plans","you","code","for","requirements","task","multi","spec","multi-step task",{"skillNo":132,"name":133,"nameZh":6,"description":134,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":135,"categories":136,"keywords":137,"publishedAt":44},"01M70JNTYVGXFZT8PASZ844TH8","verification-before-completion","Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fverification-before-completion\u002FSKILL.md",[],[138,139,140,141,142,143,105,144,145,146,38,147,108,148,149],"verification","verification before completion","complete","about","output","assertions","prs","evidence","claim","claims","commands","running",{"skillNo":151,"name":152,"nameZh":6,"description":153,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":154,"categories":155,"keywords":157,"publishedAt":44},"01M70JNGVSQDESWN0Q5BPJ875V","using-superpowers","Use when starting any conversation - establishes how to find and use skills, requiring skill invocation before ANY response including clarifying questions","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-superpowers\u002FSKILL.md",[156],{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},[158,159,160,161,105,38,162,163,164],"using superpowers","superpowers","conversation","how to","how","find","questions",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":44},"01M70JN57CE588NPYPYTWH1DWG","using-git-worktrees","Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-git-worktrees\u002FSKILL.md",[],[172,173,174,175,176,177,38,108,178,179,180,181,182,183,184,122,105,185,186],"using git worktrees","Using git","git","worktrees","git worktrees","implementation","git worktree","worktree","native","Tools","isolation","fallback","feature","via","workspace"]