[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6X1THFDPHR196HMX6EPBVPF":3,"skill-same:01M6X1THFDPHR196HMX6EPBVPF":84},{"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":57,"commitSha":58,"skillDirectory":59,"forksCount":14,"repositoryHtmlUrl":60,"repositoryHomepageUrl":61,"ownerHtmlUrl":62,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":63,"install":64,"resources":77},"01M6X1THFDPHR196HMX6EPBVPF","fastapi-patterns",null,"FastAPI patterns — dependency injection, Pydantic models, async endpoints, middleware, and testing. Use when building FastAPI applications, designing API endpoints, adding authentication, or reviewing FastAPI code.","projectious-work\u002Fprocesskit","projectious-work","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F263889726?v=4","01M6N97TJJ118WQ35CHRV8RD0E","projectious.work","01M6N97TPWJPF0BMKYQ4PBY5W8",0,"MIT","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Farchive\u002Fprocesskit-install-20261101\u002Fcontext\u002Ftemplates\u002Fprocesskit\u002Fv0.23.0\u002Fcontext\u002Fskills\u002Fengineering\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],"fastapi patterns","patterns","fastapi","dependency","when","v1","middleware","dependency injection","endpoints","models","async","testing","use","work","api","applications","injection","Pydantic","authentication","code","2026-11-10 10:12:30",false,"---\nname: fastapi-patterns\ndescription: |\n  FastAPI patterns — dependency injection, Pydantic models, async endpoints, middleware, and testing. Use when building FastAPI applications, designing API endpoints, adding authentication, or reviewing FastAPI code.\nmetadata:\n  processkit:\n    apiVersion: processkit.projectious.work\u002Fv1\n    id: SKILL-fastapi-patterns\n    version: \"1.1.0\"\n    created: 2026-04-06T00:00:00Z\n    category: engineering\n---\n\n# FastAPI Patterns\n\n## Intro\n\nFastAPI leans on type hints, Pydantic models, and `Depends()` to make\nendpoints declarative. Define schemas as `BaseModel` subclasses, inject\ncollaborators with `Depends`, and use `async def` only when the work is\ngenuinely I\u002FO-bound.\n\n## Overview\n\n### Routes and routers\n\nUse HTTP method decorators (`@app.get`, `@app.post`, `@app.put`,\n`@app.delete`, `@app.patch`). Path parameters are typed function\narguments (`user_id: int`); query parameters are arguments with\ndefaults (`skip: int = 0, limit: int = 100`). Group related endpoints\nwith `APIRouter(prefix=\"\u002Fusers\", tags=[\"users\"])` and set explicit\nstatus codes via `status_code=201`.\n\n### Pydantic models\n\nDefine request bodies and responses as `BaseModel` subclasses. Keep\n`Create`, `Update`, and `Response` schemas distinct so PATCH semantics\nand ORM round-trips don't bleed together. Set\n`model_config = ConfigDict(from_attributes=True)` on response schemas\nto allow construction from ORM instances. Use `response_model=...` on\nthe route to filter what leaks out, and `Field(min_length=...,\ngt=...)` for inline validation.\n\n### Dependency injection\n\nDependencies are plain functions or callables resolved by `Depends()`.\nYield-based dependencies (`yield session; session.close()`) handle\ncleanup. Reuse them with `Annotated`:\n`DbDep = Annotated[Session, Depends(get_db)]`. Apply app-wide\ndependencies via `FastAPI(dependencies=[Depends(verify_api_key)])`.\nDependencies chain naturally — `get_current_user` depends on\n`oauth2_scheme`, which extracts the header.\n\n### Async vs sync handlers\n\nUse `async def` for I\u002FO-bound work (async DB drivers, `httpx`,\n`aiofiles`). Use plain `def` for CPU-bound or sync-only libraries —\nFastAPI runs them in a thread pool automatically. Never put blocking\ncalls inside an `async def` handler; they stall the event loop. Use\n`asyncio.gather()` to fan out concurrent async calls inside a single\nhandler.\n\n### Middleware and CORS\n\nRegister middleware with `app.add_middleware(...)`. Common ones:\n`CORSMiddleware`, `TrustedHostMiddleware`, `GZipMiddleware`. Custom\nmiddleware can be a function decorated with `@app.middleware(\"http\")`\nor a `BaseHTTPMiddleware` subclass. Order matters: the last\nmiddleware added runs first (outermost).\n\n### Error handling\n\nRaise `HTTPException(status_code=..., detail=...)` for client errors.\nValidation errors return 422 automatically with field-level detail.\nCustomize via `@app.exception_handler(...)` or by overriding\n`RequestValidationError`. Aim for a consistent error envelope:\n`{\"detail\": \"...\", \"code\": \"...\"}`.\n\n### Testing\n\nUse Starlette's `TestClient(app)` for sync tests and\n`httpx.AsyncClient` for async ones. Override dependencies with\n`app.dependency_overrides[get_db] = mock_db` and clear the overrides\nin teardown. WebSocket tests use the `client.websocket_connect(\"\u002Fws\")`\ncontext manager.\n\n## Gotchas\n\nAgent-specific failure modes — provider-neutral pause-and-self-check items:\n\n- **Calling blocking I\u002FO from an `async def` handler.** A `requests.get(...)` or synchronous database call inside `async def` stalls the entire event loop and serializes all concurrent requests behind it. Use `async def` only with async-native libraries (`httpx`, async SQLAlchemy drivers, `aiofiles`); use plain `def` for synchronous work and let FastAPI run it in a thread pool.\n- **Sharing one schema model for create, update, and response.** A single model leaks server-generated fields (`id`, `created_at`) into create payloads and loses PATCH semantics (all fields required vs. all optional). Define `XxxCreate`, `XxxUpdate`, and `XxxResponse` as separate Pydantic models.\n- **Sharing a single database `Session` across requests.** A session shared between requests bleeds state from one request into another and causes race conditions in concurrent usage. Always inject a per-request session via a `Depends(get_db)` yield dependency.\n- **Not setting `response_model=` on routes.** Without `response_model`, FastAPI returns the raw object including internal fields (passwords, internal IDs, server-only metadata) that were never meant to be serialized. Always set `response_model=` explicitly.\n- **Forgetting to clear `app.dependency_overrides` between tests.** A dependency override set in one test leaks into subsequent tests, causing mysterious failures that depend on test execution order. Clear overrides in teardown.\n- **Returning errors with status 200 and an error payload.** Embedding error information in a 200 response breaks client error handling, monitoring, and the framework's automatic error envelope. Raise `HTTPException` for client errors; let FastAPI format the response.\n- **Not setting `model_config = ConfigDict(from_attributes=True)` on response schemas.** Without this, Pydantic V2 cannot construct a response model from a SQLAlchemy ORM instance, causing a `ValidationError` at runtime rather than a type error at authoring time.\n\n## Full reference\n\n### CRUD endpoint pattern\n\n```python\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom sqlalchemy.orm import Session\nfrom typing import Annotated\n\nrouter = APIRouter(prefix=\"\u002Fitems\", tags=[\"items\"])\nDbDep = Annotated[Session, Depends(get_db)]\n\n@router.post(\"\u002F\", status_code=status.HTTP_201_CREATED, response_model=ItemResponse)\ndef create_item(item: ItemCreate, db: DbDep):\n    db_item = Item(**item.model_dump())\n    db.add(db_item)\n    db.commit()\n    db.refresh(db_item)\n    return db_item\n\n@router.get(\"\u002F\", response_model=list[ItemResponse])\ndef list_items(db: DbDep, skip: int = 0, limit: int = 20):\n    return db.query(Item).offset(skip).limit(limit).all()\n\n@router.get(\"\u002F{item_id}\", response_model=ItemResponse)\ndef get_item(item_id: int, db: DbDep):\n    item = db.get(Item, item_id)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n    return item\n\n@router.put(\"\u002F{item_id}\", response_model=ItemResponse)\ndef update_item(item_id: int, data: ItemUpdate, db: DbDep):\n    item = db.get(Item, item_id)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n    for key, val in data.model_dump(exclude_unset=True).items():\n        setattr(item, key, val)\n    db.commit()\n    db.refresh(item)\n    return item\n\n@router.delete(\"\u002F{item_id}\", status_code=status.HTTP_204_NO_CONTENT)\ndef delete_item(item_id: int, db: DbDep):\n    item = db.get(Item, item_id)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n    db.delete(item)\n    db.commit()\n```\n\n### Schema separation\n\n```python\nfrom pydantic import BaseModel, ConfigDict, Field\nfrom datetime import datetime\n\nclass ItemCreate(BaseModel):\n    name: str = Field(min_length=1, max_length=200)\n    description: str | None = None\n    price: float = Field(gt=0)\n    tags: list[str] = []\n\nclass ItemUpdate(BaseModel):\n    name: str | None = None\n    description: str | None = None\n    price: float | None = Field(default=None, gt=0)\n\nclass ItemResponse(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    name: str\n    description: str | None\n    price: float\n    created_at: datetime\n```\n\n### Cursor-based pagination\n\n```python\nclass PaginatedResponse(BaseModel):\n    items: list[ItemResponse]\n    next_cursor: str | None\n    has_more: bool\n\n@router.get(\"\u002F\", response_model=PaginatedResponse)\ndef list_items(db: DbDep, cursor: str | None = None, limit: int = 20):\n    query = db.query(Item).order_by(Item.id)\n    if cursor:\n        query = query.filter(Item.id > int(cursor))\n    items = query.limit(limit + 1).all()\n    has_more = len(items) > limit\n    items = items[:limit]\n    return PaginatedResponse(\n        items=items,\n        next_cursor=str(items[-1].id) if has_more else None,\n        has_more=has_more,\n    )\n```\n\n### File upload\n\n```python\nfrom fastapi import UploadFile, File\n\n@router.post(\"\u002Fupload\")\nasync def upload_file(file: UploadFile = File(...)):\n    if file.size > 10 * 1024 * 1024:  # 10 MB limit\n        raise HTTPException(413, \"File too large\")\n    contents = await file.read()\n    return {\"filename\": file.filename, \"size\": len(contents)}\n```\n\n### JWT authentication\n\n```python\nfrom fastapi import Security\nfrom fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm\nfrom jose import jwt, JWTError\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"token\")\n\nasync def get_current_user(token: str = Depends(oauth2_scheme), db: DbDep):\n    try:\n        payload = jwt.decode(token, SECRET_KEY, algorithms=[\"HS256\"])\n        user_id: int = payload.get(\"sub\")\n    except JWTError:\n        raise HTTPException(status_code=401, detail=\"Invalid token\")\n    user = db.get(User, user_id)\n    if not user:\n        raise HTTPException(status_code=401, detail=\"User not found\")\n    return user\n\nCurrentUser = Annotated[User, Depends(get_current_user)]\n\n@app.post(\"\u002Ftoken\")\ndef login(form: OAuth2PasswordRequestForm = Depends(), db: DbDep):\n    user = authenticate(db, form.username, form.password)\n    if not user:\n        raise HTTPException(400, \"Incorrect credentials\")\n    token = jwt.encode({\"sub\": user.id}, SECRET_KEY, algorithm=\"HS256\")\n    return {\"access_token\": token, \"token_type\": \"bearer\"}\n```\n\nAlways hash passwords with bcrypt; never store plaintext. Use\n`Security(get_current_user, scopes=[\"admin\"])` for scope-gated routes.\n\n### WebSockets\n\n```python\nfrom fastapi import WebSocket, WebSocketDisconnect\n\nclass ConnectionManager:\n    def __init__(self):\n        self.connections: list[WebSocket] = []\n\n    async def connect(self, ws: WebSocket):\n        await ws.accept()\n        self.connections.append(ws)\n\n    def disconnect(self, ws: WebSocket):\n        self.connections.remove(ws)\n\n    async def broadcast(self, message: str):\n        for conn in self.connections:\n            await conn.send_text(message)\n\nmanager = ConnectionManager()\n\n@app.websocket(\"\u002Fws\")\nasync def websocket_endpoint(ws: WebSocket):\n    await manager.connect(ws)\n    try:\n        while True:\n            data = await ws.receive_text()\n            await manager.broadcast(f\"Message: {data}\")\n    except WebSocketDisconnect:\n        manager.disconnect(ws)\n```\n\n### Background tasks\n\nInject `BackgroundTasks` and call\n`background_tasks.add_task(send_email, to=email)`. Tasks run after the\nresponse is flushed, so they cannot block the user. They share the\nsame process as the request, so failures are isolated to the task.\nFor heavy or long-running work, hand off to Celery or ARQ instead.\n\n### Streaming responses\n\n```python\nfrom fastapi.responses import StreamingResponse\nimport asyncio\n\n@router.get(\"\u002Fstream\")\nasync def stream_data():\n    async def generate():\n        for i in range(100):\n            yield f\"data: {i}\\n\\n\"\n            await asyncio.sleep(0.1)\n    return StreamingResponse(generate(), media_type=\"text\u002Fevent-stream\")\n```\n\n### Test patterns\n\n```python\nfrom fastapi.testclient import TestClient\n\nclient = TestClient(app)\napp.dependency_overrides[get_db] = lambda: test_session\n\ndef test_create_item():\n    resp = client.post(\"\u002Fitems\u002F\", json={\"name\": \"Widget\", \"price\": 9.99})\n    assert resp.status_code == 201\n    assert resp.json()[\"name\"] == \"Widget\"\n\ndef test_item_not_found():\n    resp = client.get(\"\u002Fitems\u002F9999\")\n    assert resp.status_code == 404\n\ndef test_validation_error():\n    resp = client.post(\"\u002Fitems\u002F\", json={\"name\": \"\", \"price\": -1})\n    assert resp.status_code == 422\n```\n\nAlways clear `app.dependency_overrides` between tests so state from\none test cannot bleed into another.\n\n### Anti-patterns\n\n- Calling blocking I\u002FO from `async def` — stalls the event loop\n- Mixing request and response schemas in one model — leaks server\n  fields like `id` and `created_at` into create payloads\n- Catching `Exception` in handlers and returning 500 manually —\n  defeats FastAPI's error envelope\n- Reading the request body twice — wrap with a custom middleware if\n  you must\n- Sharing a single `Session` across requests — always inject per\n  request via `Depends`\n\n### Further reading\n\n- [FastAPI Documentation](https:\u002F\u002Ffastapi.tiangolo.com\u002F)\n- [Pydantic V2 Documentation](https:\u002F\u002Fdocs.pydantic.dev\u002F)\n","# FastAPI Patterns\n\n## Intro\n\nFastAPI leans on type hints, Pydantic models, and `Depends()` to make\nendpoints declarative. Define schemas as `BaseModel` subclasses, inject\ncollaborators with `Depends`, and use `async def` only when the work is\ngenuinely I\u002FO-bound.\n\n## Overview\n\n### Routes and routers\n\nUse HTTP method decorators (`@app.get`, `@app.post`, `@app.put`,\n`@app.delete`, `@app.patch`). Path parameters are typed function\narguments (`user_id: int`); query parameters are arguments with\ndefaults (`skip: int = 0, limit: int = 100`). Group related endpoints\nwith `APIRouter(prefix=\"\u002Fusers\", tags=[\"users\"])` and set explicit\nstatus codes via `status_code=201`.\n\n### Pydantic models\n\nDefine request bodies and responses as `BaseModel` subclasses. Keep\n`Create`, `Update`, and `Response` schemas distinct so PATCH semantics\nand ORM round-trips don't bleed together. Set\n`model_config = ConfigDict(from_attributes=True)` on response schemas\nto allow construction from ORM instances. Use `response_model=...` on\nthe route to filter what leaks out, and `Field(min_length=...,\ngt=...)` for inline validation.\n\n### Dependency injection\n\nDependencies are plain functions or callables resolved by `Depends()`.\nYield-based dependencies (`yield session; session.close()`) handle\ncleanup. Reuse them with `Annotated`:\n`DbDep = Annotated[Session, Depends(get_db)]`. Apply app-wide\ndependencies via `FastAPI(dependencies=[Depends(verify_api_key)])`.\nDependencies chain naturally — `get_current_user` depends on\n`oauth2_scheme`, which extracts the header.\n\n### Async vs sync handlers\n\nUse `async def` for I\u002FO-bound work (async DB drivers, `httpx`,\n`aiofiles`). Use plain `def` for CPU-bound or sync-only libraries —\nFastAPI runs them in a thread pool automatically. Never put blocking\ncalls inside an `async def` handler; they stall the event loop. Use\n`asyncio.gather()` to fan out concurrent async calls inside a single\nhandler.\n\n### Middleware and CORS\n\nRegister middleware with `app.add_middleware(...)`. Common ones:\n`CORSMiddleware`, `TrustedHostMiddleware`, `GZipMiddleware`. Custom\nmiddleware can be a function decorated with `@app.middleware(\"http\")`\nor a `BaseHTTPMiddleware` subclass. Order matters: the last\nmiddleware added runs first (outermost).\n\n### Error handling\n\nRaise `HTTPException(status_code=..., detail=...)` for client errors.\nValidation errors return 422 automatically with field-level detail.\nCustomize via `@app.exception_handler(...)` or by overriding\n`RequestValidationError`. Aim for a consistent error envelope:\n`{\"detail\": \"...\", \"code\": \"...\"}`.\n\n### Testing\n\nUse Starlette's `TestClient(app)` for sync tests and\n`httpx.AsyncClient` for async ones. Override dependencies with\n`app.dependency_overrides[get_db] = mock_db` and clear the overrides\nin teardown. WebSocket tests use the `client.websocket_connect(\"\u002Fws\")`\ncontext manager.\n\n## Gotchas\n\nAgent-specific failure modes — provider-neutral pause-and-self-check items:\n\n- **Calling blocking I\u002FO from an `async def` handler.** A `requests.get(...)` or synchronous database call inside `async def` stalls the entire event loop and serializes all concurrent requests behind it. Use `async def` only with async-native libraries (`httpx`, async SQLAlchemy drivers, `aiofiles`); use plain `def` for synchronous work and let FastAPI run it in a thread pool.\n- **Sharing one schema model for create, update, and response.** A single model leaks server-generated fields (`id`, `created_at`) into create payloads and loses PATCH semantics (all fields required vs. all optional). Define `XxxCreate`, `XxxUpdate`, and `XxxResponse` as separate Pydantic models.\n- **Sharing a single database `Session` across requests.** A session shared between requests bleeds state from one request into another and causes race conditions in concurrent usage. Always inject a per-request session via a `Depends(get_db)` yield dependency.\n- **Not setting `response_model=` on routes.** Without `response_model`, FastAPI returns the raw object including internal fields (passwords, internal IDs, server-only metadata) that were never meant to be serialized. Always set `response_model=` explicitly.\n- **Forgetting to clear `app.dependency_overrides` between tests.** A dependency override set in one test leaks into subsequent tests, causing mysterious failures that depend on test execution order. Clear overrides in teardown.\n- **Returning errors with status 200 and an error payload.** Embedding error information in a 200 response breaks client error handling, monitoring, and the framework's automatic error envelope. Raise `HTTPException` for client errors; let FastAPI format the response.\n- **Not setting `model_config = ConfigDict(from_attributes=True)` on response schemas.** Without this, Pydantic V2 cannot construct a response model from a SQLAlchemy ORM instance, causing a `ValidationError` at runtime rather than a type error at authoring time.\n\n## Full reference\n\n### CRUD endpoint pattern\n\n```python\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom sqlalchemy.orm import Session\nfrom typing import Annotated\n\nrouter = APIRouter(prefix=\"\u002Fitems\", tags=[\"items\"])\nDbDep = Annotated[Session, Depends(get_db)]\n\n@router.post(\"\u002F\", status_code=status.HTTP_201_CREATED, response_model=ItemResponse)\ndef create_item(item: ItemCreate, db: DbDep):\n    db_item = Item(**item.model_dump())\n    db.add(db_item)\n    db.commit()\n    db.refresh(db_item)\n    return db_item\n\n@router.get(\"\u002F\", response_model=list[ItemResponse])\ndef list_items(db: DbDep, skip: int = 0, limit: int = 20):\n    return db.query(Item).offset(skip).limit(limit).all()\n\n@router.get(\"\u002F{item_id}\", response_model=ItemResponse)\ndef get_item(item_id: int, db: DbDep):\n    item = db.get(Item, item_id)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n    return item\n\n@router.put(\"\u002F{item_id}\", response_model=ItemResponse)\ndef update_item(item_id: int, data: ItemUpdate, db: DbDep):\n    item = db.get(Item, item_id)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n    for key, val in data.model_dump(exclude_unset=True).items():\n        setattr(item, key, val)\n    db.commit()\n    db.refresh(item)\n    return item\n\n@router.delete(\"\u002F{item_id}\", status_code=status.HTTP_204_NO_CONTENT)\ndef delete_item(item_id: int, db: DbDep):\n    item = db.get(Item, item_id)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n    db.delete(item)\n    db.commit()\n```\n\n### Schema separation\n\n```python\nfrom pydantic import BaseModel, ConfigDict, Field\nfrom datetime import datetime\n\nclass ItemCreate(BaseModel):\n    name: str = Field(min_length=1, max_length=200)\n    description: str | None = None\n    price: float = Field(gt=0)\n    tags: list[str] = []\n\nclass ItemUpdate(BaseModel):\n    name: str | None = None\n    description: str | None = None\n    price: float | None = Field(default=None, gt=0)\n\nclass ItemResponse(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    name: str\n    description: str | None\n    price: float\n    created_at: datetime\n```\n\n### Cursor-based pagination\n\n```python\nclass PaginatedResponse(BaseModel):\n    items: list[ItemResponse]\n    next_cursor: str | None\n    has_more: bool\n\n@router.get(\"\u002F\", response_model=PaginatedResponse)\ndef list_items(db: DbDep, cursor: str | None = None, limit: int = 20):\n    query = db.query(Item).order_by(Item.id)\n    if cursor:\n        query = query.filter(Item.id > int(cursor))\n    items = query.limit(limit + 1).all()\n    has_more = len(items) > limit\n    items = items[:limit]\n    return PaginatedResponse(\n        items=items,\n        next_cursor=str(items[-1].id) if has_more else None,\n        has_more=has_more,\n    )\n```\n\n### File upload\n\n```python\nfrom fastapi import UploadFile, File\n\n@router.post(\"\u002Fupload\")\nasync def upload_file(file: UploadFile = File(...)):\n    if file.size > 10 * 1024 * 1024:  # 10 MB limit\n        raise HTTPException(413, \"File too large\")\n    contents = await file.read()\n    return {\"filename\": file.filename, \"size\": len(contents)}\n```\n\n### JWT authentication\n\n```python\nfrom fastapi import Security\nfrom fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm\nfrom jose import jwt, JWTError\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"token\")\n\nasync def get_current_user(token: str = Depends(oauth2_scheme), db: DbDep):\n    try:\n        payload = jwt.decode(token, SECRET_KEY, algorithms=[\"HS256\"])\n        user_id: int = payload.get(\"sub\")\n    except JWTError:\n        raise HTTPException(status_code=401, detail=\"Invalid token\")\n    user = db.get(User, user_id)\n    if not user:\n        raise HTTPException(status_code=401, detail=\"User not found\")\n    return user\n\nCurrentUser = Annotated[User, Depends(get_current_user)]\n\n@app.post(\"\u002Ftoken\")\ndef login(form: OAuth2PasswordRequestForm = Depends(), db: DbDep):\n    user = authenticate(db, form.username, form.password)\n    if not user:\n        raise HTTPException(400, \"Incorrect credentials\")\n    token = jwt.encode({\"sub\": user.id}, SECRET_KEY, algorithm=\"HS256\")\n    return {\"access_token\": token, \"token_type\": \"bearer\"}\n```\n\nAlways hash passwords with bcrypt; never store plaintext. Use\n`Security(get_current_user, scopes=[\"admin\"])` for scope-gated routes.\n\n### WebSockets\n\n```python\nfrom fastapi import WebSocket, WebSocketDisconnect\n\nclass ConnectionManager:\n    def __init__(self):\n        self.connections: list[WebSocket] = []\n\n    async def connect(self, ws: WebSocket):\n        await ws.accept()\n        self.connections.append(ws)\n\n    def disconnect(self, ws: WebSocket):\n        self.connections.remove(ws)\n\n    async def broadcast(self, message: str):\n        for conn in self.connections:\n            await conn.send_text(message)\n\nmanager = ConnectionManager()\n\n@app.websocket(\"\u002Fws\")\nasync def websocket_endpoint(ws: WebSocket):\n    await manager.connect(ws)\n    try:\n        while True:\n            data = await ws.receive_text()\n            await manager.broadcast(f\"Message: {data}\")\n    except WebSocketDisconnect:\n        manager.disconnect(ws)\n```\n\n### Background tasks\n\nInject `BackgroundTasks` and call\n`background_tasks.add_task(send_email, to=email)`. Tasks run after the\nresponse is flushed, so they cannot block the user. They share the\nsame process as the request, so failures are isolated to the task.\nFor heavy or long-running work, hand off to Celery or ARQ instead.\n\n### Streaming responses\n\n```python\nfrom fastapi.responses import StreamingResponse\nimport asyncio\n\n@router.get(\"\u002Fstream\")\nasync def stream_data():\n    async def generate():\n        for i in range(100):\n            yield f\"data: {i}\\n\\n\"\n            await asyncio.sleep(0.1)\n    return StreamingResponse(generate(), media_type=\"text\u002Fevent-stream\")\n```\n\n### Test patterns\n\n```python\nfrom fastapi.testclient import TestClient\n\nclient = TestClient(app)\napp.dependency_overrides[get_db] = lambda: test_session\n\ndef test_create_item():\n    resp = client.post(\"\u002Fitems\u002F\", json={\"name\": \"Widget\", \"price\": 9.99})\n    assert resp.status_code == 201\n    assert resp.json()[\"name\"] == \"Widget\"\n\ndef test_item_not_found():\n    resp = client.get(\"\u002Fitems\u002F9999\")\n    assert resp.status_code == 404\n\ndef test_validation_error():\n    resp = client.post(\"\u002Fitems\u002F\", json={\"name\": \"\", \"price\": -1})\n    assert resp.status_code == 422\n```\n\nAlways clear `app.dependency_overrides` between tests so state from\none test cannot bleed into another.\n\n### Anti-patterns\n\n- Calling blocking I\u002FO from `async def` — stalls the event loop\n- Mixing request and response schemas in one model — leaks server\n  fields like `id` and `created_at` into create payloads\n- Catching `Exception` in handlers and returning 500 manually —\n  defeats FastAPI's error envelope\n- Reading the request body twice — wrap with a custom middleware if\n  you must\n- Sharing a single `Session` across requests — always inject per\n  request via `Depends`\n\n### Further reading\n\n- [FastAPI Documentation](https:\u002F\u002Ffastapi.tiangolo.com\u002F)\n- [Pydantic V2 Documentation](https:\u002F\u002Fdocs.pydantic.dev\u002F)\n",{"name":5,"metadata":49,"description":56},{"processkit":50},{"id":51,"created":52,"version":53,"category":54,"apiVersion":55},"SKILL-fastapi-patterns",1775433600000,"1.1.0","engineering","processkit.projectious.work\u002Fv1","FastAPI patterns — dependency injection, Pydantic models, async endpoints, middleware, and testing. Use when building FastAPI applications, designing API endpoints, adding authentication, or reviewing FastAPI code.\n","main","4876a92620ee792aac2ac9a1a806322d8a572367","archive\u002Fprocesskit-install-20261101\u002Fcontext\u002Ftemplates\u002Fprocesskit\u002Fv0.23.0\u002Fcontext\u002Fskills\u002Fengineering\u002Ffastapi-patterns","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit","https:\u002F\u002Fprojectious-work.github.io\u002Fprocesskit\u002F","https:\u002F\u002Fgithub.com\u002Fprojectious-work","2026-11-09 00:53:29",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add projectious-work\u002Fprocesskit --skill fastapi-patterns",{"tool":70,"command":71},"bunx","bunx skills add projectious-work\u002Fprocesskit --skill fastapi-patterns",{"tool":73,"command":74},"pnpm","pnpm dlx skills add projectious-work\u002Fprocesskit --skill fastapi-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Farchive\u002Fprocesskit-install-20261101\u002Fcontext\u002Ftemplates\u002Fprocesskit\u002Fv0.23.0\u002Fcontext\u002Fskills\u002Fengineering\u002Ffastapi-patterns\u002FSKILL.md",true,[78],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"references\u002Fendpoint-patterns.md","REFERENCE","参考文档",6296,"https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Farchive\u002Fprocesskit-install-20261101\u002Fcontext\u002Ftemplates\u002Fprocesskit\u002Fv0.23.0\u002Fcontext\u002Fskills\u002Fengineering\u002Ffastapi-patterns\u002Freferences\u002Fendpoint-patterns.md",[85,109,129,150,167,185],{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":94,"publishedAt":44},"01M6X5MTC89B4ZGXW3JVSQCPF8","skill-management","Manage Skill package manifests as v1 ontology entities.","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Fsrc\u002Fcontext\u002Fskills\u002Fprocesskit\u002Fskill-management\u002FSKILL.md",[91],{"categoryNo":92,"categoryName":93,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[95,96,97,98,99,100,37,101,102,103,87,104,105,106,29,107,108],"management","skill management","create","update","package","alpha","list","manage","ontology","get","as","manifests","skill package","transition",{"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":116,"publishedAt":44},"01M6X5MCV74NHW5MEDNHZRF2C8","schema-management","Manage generated processkit schema contracts through deterministic MCP rebuild and inspection workflows.","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Fsrc\u002Fcontext\u002Fskills\u002Fprocesskit\u002Fschema-management\u002FSKILL.md",[115],{"categoryNo":92,"categoryName":93,"taxonomyType":21,"taxonomyTypeDesc":22},[95,117,118,119,120,121,100,37,122,123,102,124,125,126,104,127,128],"schema","schema management","MCP","workflows","validation","generated","rebuild","contracts","wed","deterministic","contract","mode",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":135,"publishedAt":44},"01M6X5M5G6YJ96AMZGSCZJ1D13","proposition-management","Record claims, risks, beliefs, world facts, WSJF estimates, and assumptions through the Proposition interface.","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Fsrc\u002Fcontext\u002Fskills\u002Fprocesskit\u002Fproposition-management\u002FSKILL.md",[],[95,136,137,97,98,138,100,37,139,140,141,142,104,143,144,145,146,147,148,149],"proposition","proposition management","index","log","world","record","query","creation","event","interface","updates","facts","claims","WSJF",{"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":156,"publishedAt":44},"01M6X5KMQ83QN9M6MPWZM7344F","ontology-management","Manage generated v1 ontology entities that do not require a dedicated domain server.","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Fsrc\u002Fcontext\u002Fskills\u002Fprocesskit\u002Fontology-management\u002FSKILL.md",[],[95,103,157,97,100,158,159,122,160,139,102,161,162,104,163,164,144,165,29,166],"ontology management","lifecycle","archive","roadmap","do","domain","iteration","keep","measurement","generic",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":173,"publishedAt":44},"01M6X5KCY2DN3SM21RERSFNEBT","okf-compatibility","Import, export, and validate conformant Open Knowledge Format bundles.","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Fsrc\u002Fcontext\u002Fskills\u002Fprocesskit\u002Fokf-compatibility\u002FSKILL.md",[],[174,175,176,177,100,178,179,180,37,181,182,183,184],"okf","compatibility","okf compatibility","validate","open","knowledge","format","export","bundles","bundle","import",{"skillNo":186,"name":187,"nameZh":6,"description":188,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":189,"categories":190,"keywords":191,"publishedAt":44},"01M6X5JA8S5PNGJVFW4EQY8T83","capability-management","Manage versioned Capability entities and their governed lifecycle.","https:\u002F\u002Fgithub.com\u002Fprojectious-work\u002Fprocesskit\u002Fblob\u002Fmain\u002Fsrc\u002Fcontext\u002Fskills\u002Fprocesskit\u002Fcapability-management\u002FSKILL.md",[],[95,192,193,97,98,138,100,158,37,101,139,194,102,141,104,164,144,145,195,108],"capability","capability management","queries","changes"]