[{"data":1,"prerenderedAt":1885},["ShallowReactive",2],{"hub-fastapi":3},{"framework":4,"topics":15,"qa":86},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Ffastapi.yml","FastAPI interview questions on async routing, Pydantic validation, dependency injection, OAuth2 security, database integration and deployment — the go-to Python framework for production APIs.","yml","fastapi",{},"FastAPI",6,"frameworks\u002Ffastapi",1,"lgr_X74wBdBYovbrlazGWPWqqi-YwNEUq44l1BmtgyE",[16,24,33,42,51,60,68,77],{"id":17,"description":18,"extension":7,"frameworkSlug":8,"meta":19,"name":20,"order":13,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Ffastapi-fundamentals.yml","Async\u002Fawait, ASGI, the request lifecycle and OpenAPI — the FastAPI foundation every interviewer expects you to understand cold.",{},"Fundamentals","fundamentals","topics\u002Ffastapi-fundamentals","L8aL7gSWL5pRFH-IJgI0dL4rZBXrNTnrnAiFK5-7hd8",{"id":25,"description":26,"extension":7,"frameworkSlug":8,"meta":27,"name":28,"order":29,"slug":30,"stem":31,"__hash__":32},"topics\u002Ftopics\u002Ffastapi-routing.yml","Path parameters, query strings, request bodies, response models and APIRouter — how FastAPI maps URLs to handlers and shapes their inputs and outputs.",{},"Routing & Parameters",2,"routing","topics\u002Ffastapi-routing","Q6gEKizgj7nZmKSg-lTB55By4eTBV1robiiWRTfhCt4",{"id":34,"description":35,"extension":7,"frameworkSlug":8,"meta":36,"name":37,"order":38,"slug":39,"stem":40,"__hash__":41},"topics\u002Ftopics\u002Ffastapi-pydantic.yml","BaseModel, field validators, serialization and BaseSettings — Pydantic v2 powers FastAPI's entire request validation and config story.",{},"Pydantic & Validation",3,"pydantic","topics\u002Ffastapi-pydantic","bQyImPYZKMyV5ma4Y1CZQ0j3Ub80FRXzGDRn6ofwBek",{"id":43,"description":44,"extension":7,"frameworkSlug":8,"meta":45,"name":46,"order":47,"slug":48,"stem":49,"__hash__":50},"topics\u002Ftopics\u002Ffastapi-dependency-injection.yml","Depends(), yield dependencies, class-based deps and lifespan — FastAPI's DI system is one of its most interview-tested features.",{},"Dependency Injection",4,"dependency-injection","topics\u002Ffastapi-dependency-injection","2VA0cVD1rLFmzQg2_OPMEFHUbgCD2k27Pu5RORd9xGY",{"id":52,"description":53,"extension":7,"frameworkSlug":8,"meta":54,"name":55,"order":56,"slug":57,"stem":58,"__hash__":59},"topics\u002Ftopics\u002Ffastapi-security.yml","OAuth2, JWT tokens and API keys — common FastAPI auth patterns that come up in both system-design and code-review interview rounds.",{},"Security & Auth",5,"security","topics\u002Ffastapi-security","1fvRyaYy_dE2_wzmizOLRbEhkO0ohphmkOIKnhHthGk",{"id":61,"description":62,"extension":7,"frameworkSlug":8,"meta":63,"name":64,"order":11,"slug":65,"stem":66,"__hash__":67},"topics\u002Ftopics\u002Ffastapi-database.yml","SQLAlchemy sync and async sessions, Alembic migrations — how to wire a database into a FastAPI app without blocking the event loop.",{},"Database Integration","database","topics\u002Ffastapi-database","SAsWXX3chvq2-fKfl79Asl_FrKzV6OFLW2k1XjQ1CKA",{"id":69,"description":70,"extension":7,"frameworkSlug":8,"meta":71,"name":72,"order":73,"slug":74,"stem":75,"__hash__":76},"topics\u002Ftopics\u002Ffastapi-testing.yml","TestClient, AsyncClient, pytest-asyncio and dependency overrides — the testing patterns FastAPI teams reach for in production codebases.",{},"Testing",7,"testing","topics\u002Ffastapi-testing","aoBJ66lxAhs9p_bkdduY2b_KOcOC_OUW2WJCgKvsSA4",{"id":78,"description":79,"extension":7,"frameworkSlug":8,"meta":80,"name":81,"order":82,"slug":83,"stem":84,"__hash__":85},"topics\u002Ftopics\u002Ffastapi-deployment.yml","Uvicorn, Gunicorn, CORS middleware and BackgroundTasks — deploying FastAPI apps and tuning them for production traffic.",{},"Deployment & Middleware",8,"deployment","topics\u002Ffastapi-deployment","Dok_4ZK-VioVVTjvSNpbt4_sKd1DLevjkz9kGwkpQUQ",[87,156,219,278,353,421,493,552,615,674,737,799,868,931,997,1056,1114,1177,1236,1294,1368,1434,1500,1559,1617,1688,1751,1818],{"id":88,"title":89,"body":90,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":98,"navigation":99,"order":13,"path":100,"questions":101,"questionsCount":148,"related":149,"seo":150,"seoDescription":151,"stem":152,"subtopic":153,"topic":64,"topicSlug":65,"updated":154,"__hash__":155},"qa\u002Ffastapi\u002Fdatabase\u002Fsqlalchemy.md","Sqlalchemy",{"type":91,"value":92,"toc":93},"minimark",[],{"title":94,"searchDepth":29,"depth":29,"links":95},"",[],"medium","md",{},true,"\u002Ffastapi\u002Fdatabase\u002Fsqlalchemy",[102,107,111,115,119,123,128,132,136,140,144],{"id":103,"difficulty":104,"q":105,"a":106},"sqlalchemy-setup","easy","How do you set up SQLAlchemy with FastAPI for synchronous use?","```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import sessionmaker, DeclarativeBase\n\nDATABASE_URL = \"postgresql:\u002F\u002Fuser:pass@localhost\u002Fmydb\"\n\nengine = create_engine(DATABASE_URL, pool_size=10, max_overflow=20)\nSessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)\n\nclass Base(DeclarativeBase):\n    pass\n```\n\nUse a yield dependency to provide a session per request:\n```python\nfrom fastapi import Depends\nfrom sqlalchemy.orm import Session\n\ndef get_db():\n    db = SessionLocal()\n    try:\n        yield db\n    finally:\n        db.close()\n\n@app.get(\"\u002Fusers\")\ndef list_users(db: Session = Depends(get_db)):\n    return db.query(User).all()\n```\n\nRule of thumb: always use a yield dep for DB sessions — the `finally` block\nguarantees `db.close()` runs even if the handler raises.\n",{"id":108,"difficulty":104,"q":109,"a":110},"orm-model","How do you define a SQLAlchemy ORM model and map it to a Pydantic response model?","```python\nfrom sqlalchemy import Column, Integer, String\nfrom sqlalchemy.orm import DeclarativeBase\nfrom pydantic import BaseModel, ConfigDict\n\nclass Base(DeclarativeBase):\n    pass\n\n# ORM model (maps to the DB table)\nclass User(Base):\n    __tablename__ = \"users\"\n    id = Column(Integer, primary_key=True)\n    name = Column(String(100), nullable=False)\n    email = Column(String(200), unique=True, nullable=False)\n\n# Pydantic output schema\nclass UserOut(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    name: str\n    email: str\n```\n\n`from_attributes=True` lets Pydantic read ORM attributes instead of dict keys.\n\nRule of thumb: keep ORM models and Pydantic schemas in separate files — they\nserve different purposes and change for different reasons.\n",{"id":112,"difficulty":96,"q":113,"a":114},"session-commit-rollback","When should you call `db.commit()` and `db.rollback()` in a FastAPI route?","Commit after all writes in the handler succeed; rollback on exception:\n\n```python\ndef get_db():\n    db = SessionLocal()\n    try:\n        yield db\n    except Exception:\n        db.rollback()\n        raise\n    finally:\n        db.close()\n\n@app.post(\"\u002Fusers\")\ndef create_user(user: UserCreate, db: Session = Depends(get_db)):\n    db_user = User(name=user.name, email=user.email)\n    db.add(db_user)\n    db.commit()           # persist to DB\n    db.refresh(db_user)   # reload auto-generated fields (id, created_at)\n    return UserOut.model_validate(db_user)\n```\n\n`autocommit=False` (the default) means writes are buffered until `commit()`.\nReads don't need a commit.\n\nRule of thumb: commit as late as possible (after all writes succeed); never\ncommit in a loop — batch all changes and commit once.\n",{"id":116,"difficulty":96,"q":117,"a":118},"querying-patterns","What are the main SQLAlchemy query patterns used in FastAPI routes?","**ORM-style queries** (SQLAlchemy 2.x preferred):\n```python\nfrom sqlalchemy import select\n\n# Single object\nuser = db.get(User, user_id)                       # by primary key\n\n# Filtered list\nstmt = select(User).where(User.is_active == True)\nusers = db.scalars(stmt).all()\n\n# Pagination\nstmt = select(User).offset(skip).limit(limit)\nusers = db.scalars(stmt).all()\n\n# Count\nfrom sqlalchemy import func\ncount = db.scalar(select(func.count()).select_from(User))\n```\n\n**Legacy query API** (still works, being phased out):\n```python\nuser = db.query(User).filter(User.id == user_id).first()\n```\n\nRule of thumb: prefer `select()` + `db.scalars()` (SQLAlchemy 2.x style) over\n`db.query()` — it's the modern API and works with both sync and async.\n",{"id":120,"difficulty":96,"q":121,"a":122},"relationships","How do you handle SQLAlchemy relationships in FastAPI responses?","Load related objects eagerly to avoid N+1 queries:\n\n```python\nfrom sqlalchemy.orm import relationship, selectinload\n\nclass User(Base):\n    __tablename__ = \"users\"\n    id = Column(Integer, primary_key=True)\n    posts = relationship(\"Post\", back_populates=\"author\")\n\n# Eager load with selectinload\nstmt = select(User).options(selectinload(User.posts)).where(User.id == user_id)\nuser = db.scalars(stmt).first()\n```\n\nPydantic schema with nested relationship:\n```python\nclass PostOut(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    title: str\n\nclass UserWithPosts(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    name: str\n    posts: list[PostOut]\n```\n\nRule of thumb: always eager-load relationships you plan to serialise —\nlazy loading in a closed session causes `DetachedInstanceError`.\n",{"id":124,"difficulty":125,"q":126,"a":127},"n-plus-one","hard","What is the N+1 query problem and how do you avoid it in FastAPI?","The **N+1 problem** occurs when you load N parent rows and then issue 1 query\nper parent to load its children — N+1 queries total.\n\n```python\n# N+1 — 1 query for users + 1 per user for posts\nusers = db.scalars(select(User)).all()\nfor user in users:\n    print(user.posts)   # lazy load triggers a new query each time!\n```\n\nFix with eager loading:\n```python\n# 2 queries total: one for users, one for all their posts\nusers = db.scalars(\n    select(User).options(selectinload(User.posts))\n).all()\n\n# Or joined load (single query with JOIN)\nusers = db.scalars(\n    select(User).options(joinedload(User.posts))\n).all()\n```\n\n`selectinload` is better for one-to-many (avoids duplicated parent rows);\n`joinedload` is better for many-to-one or when the child set is small.\n\nRule of thumb: any serialised response that includes a relationship must use\n`selectinload` or `joinedload` — never rely on lazy loading in API handlers.\n",{"id":129,"difficulty":96,"q":130,"a":131},"db-session-scope","What is the correct scope for a SQLAlchemy session in FastAPI?","**One session per request**. The standard `get_db` yield dependency provides\nexactly this — a session is created at the start of the request and closed\n(and rolled back on error) at the end.\n\n```python\n# ✅ One session per request\ndef get_db():\n    db = SessionLocal()\n    try:\n        yield db\n    finally:\n        db.close()\n```\n\nAnti-patterns:\n- **Global session**: shared across requests → race conditions.\n- **Session per DB call**: loses transaction semantics; can't roll back multiple writes together.\n- **Thread-local session outside a request context**: leaks sessions.\n\nRule of thumb: bind the session to the HTTP request lifetime via a yield dep —\none request = one transaction = one session.\n",{"id":133,"difficulty":96,"q":134,"a":135},"sqlalchemy-fastapi-pattern","What is the recommended project structure for SQLAlchemy + FastAPI?","```\napp\u002F\n├── db\u002F\n│   ├── base.py          # DeclarativeBase\n│   ├── session.py       # engine, SessionLocal, get_db\n│   └── models\u002F\n│       ├── user.py      # SQLAlchemy ORM models\n│       └── order.py\n├── schemas\u002F             # Pydantic models (input\u002Foutput DTOs)\n│   ├── user.py\n│   └── order.py\n├── crud\u002F                # DB operations (no FastAPI imports)\n│   ├── user.py\n│   └── order.py\n└── routers\u002F\n    ├── users.py         # thin — calls CRUD, returns schema\n    └── orders.py\n```\n\nCRUD functions are plain Python:\n```python\n# crud\u002Fuser.py\ndef get_user(db: Session, user_id: int) -> User | None:\n    return db.get(User, user_id)\n```\n\nRule of thumb: keep DB logic in `crud\u002F`, keep HTTP logic in `routers\u002F` —\nit makes CRUD functions unit-testable without standing up an ASGI server.\n",{"id":137,"difficulty":125,"q":138,"a":139},"connection-pool","How does SQLAlchemy's connection pool interact with a multi-worker FastAPI deployment?","Each **Uvicorn worker process** gets its own SQLAlchemy engine and connection pool.\nWith 4 workers and `pool_size=10`, you have 40 total connections to the DB.\n\n```python\nengine = create_engine(\n    DATABASE_URL,\n    pool_size=5,        # connections kept open\n    max_overflow=10,    # burst connections allowed\n    pool_timeout=30,    # wait time before \"too many connections\" error\n    pool_recycle=1800,  # recycle connections every 30 min (avoids stale)\n)\n```\n\nFor containers \u002F Kubernetes where the worker count varies, use **PgBouncer**\nas a connection pooler in front of PostgreSQL to cap total connections\nregardless of pod count.\n\nRule of thumb: set `pool_size` conservatively (2-5 per worker); use PgBouncer\nin transaction-pooling mode for high-concurrency deployments.\n",{"id":141,"difficulty":104,"q":142,"a":143},"create-tables","How do you create database tables from SQLAlchemy models in FastAPI?","Call `Base.metadata.create_all(engine)` — typically in the `lifespan` function\nor a startup script:\n\n```python\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    Base.metadata.create_all(bind=engine)   # creates tables if they don't exist\n    yield\n\napp = FastAPI(lifespan=lifespan)\n```\n\nFor production, **never** use `create_all` at startup — use **Alembic** migrations\ninstead. `create_all` in production risks schema drift and doesn't handle alterations.\n\nRule of thumb: use `create_all` only for local dev and test databases; use\nAlembic for staging and production schema changes.\n",{"id":145,"difficulty":96,"q":146,"a":147},"soft-delete","How do you implement soft deletes in a SQLAlchemy + FastAPI application?","Add a `deleted_at` timestamp column and filter it out in queries:\n\n```python\nfrom datetime import datetime\nfrom sqlalchemy import Column, DateTime\nfrom sqlalchemy.sql import expression\n\nclass User(Base):\n    __tablename__ = \"users\"\n    id = Column(Integer, primary_key=True)\n    name = Column(String)\n    deleted_at = Column(DateTime, nullable=True, default=None)\n\n    @property\n    def is_deleted(self) -> bool:\n        return self.deleted_at is not None\n\n# Query only active records\nstmt = select(User).where(User.deleted_at.is_(None))\n\n# Soft delete\nuser.deleted_at = datetime.utcnow()\ndb.commit()\n```\n\nRule of thumb: always filter `WHERE deleted_at IS NULL` in all list queries —\nadd a SQLAlchemy event listener or a custom query class to enforce this globally.\n",11,null,{"description":94},"FastAPI SQLAlchemy interview questions — sync session setup, get_db dependency, ORM models, querying, relationships and Pydantic integration.","fastapi\u002Fdatabase\u002Fsqlalchemy","SQLAlchemy (Sync)","2026-06-20","GllAMcQpIv65eU2Qj4npgIWT77-UhwQshnmrAjK85hY",{"id":157,"title":158,"body":159,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":163,"navigation":99,"order":13,"path":164,"questions":165,"questionsCount":214,"related":149,"seo":215,"seoDescription":216,"stem":217,"subtopic":158,"topic":46,"topicSlug":48,"updated":154,"__hash__":218},"qa\u002Ffastapi\u002Fdependency-injection\u002Fdepends-basics.md","Depends Basics",{"type":91,"value":160,"toc":161},[],{"title":94,"searchDepth":29,"depth":29,"links":162},[],{},"\u002Ffastapi\u002Fdependency-injection\u002Fdepends-basics",[166,170,174,178,182,186,190,194,198,202,206,210],{"id":167,"difficulty":104,"q":168,"a":169},"depends-basics","What is `Depends()` in FastAPI and why is it useful?","`Depends(fn)` tells FastAPI to call `fn`, pass any of its own parameters through\nthe same injection system, and give the **result** to the handler as the argument.\n\n```python\nfrom fastapi import Depends, FastAPI\n\napp = FastAPI()\n\ndef get_query(q: str | None = None):\n    return q\n\n@app.get(\"\u002Fitems\")\nasync def list_items(query: str | None = Depends(get_query)):\n    return {\"q\": query}\n```\n\nBenefits:\n- **Reusable logic** (auth, DB sessions, pagination) across many handlers.\n- **Testable** — swap dependencies via `app.dependency_overrides`.\n- **Recursive** — a dependency can itself declare `Depends()`.\n\nRule of thumb: extract any logic that repeats across handlers into a dependency\nfunction; if it's in more than two handlers, it probably belongs in a dep.\n",{"id":171,"difficulty":104,"q":172,"a":173},"dependency-parameters","Can a dependency function accept its own parameters (path, query, headers)?","Yes — dependency functions participate in the same parameter extraction system\nas route handlers. They can declare path params, query params, headers, cookies,\nand other `Depends()`.\n\n```python\nfrom fastapi import Depends, Query\n\ndef pagination(\n    page: int = Query(ge=1, default=1),\n    size: int = Query(ge=1, le=100, default=20),\n) -> dict:\n    return {\"skip\": (page - 1) * size, \"limit\": size}\n\n@app.get(\"\u002Fusers\")\nasync def list_users(paging: dict = Depends(pagination)):\n    return await db.get_users(**paging)\n\n@app.get(\"\u002Forders\")\nasync def list_orders(paging: dict = Depends(pagination)):\n    return await db.get_orders(**paging)\n```\n\nRule of thumb: put shared query parameters (pagination, sorting, filtering) into\na dependency class or function — define once, reuse everywhere.\n",{"id":175,"difficulty":96,"q":176,"a":177},"sub-dependencies","Can a dependency depend on another dependency (sub-dependencies)?","Yes — FastAPI resolves the full dependency graph recursively. Sub-dependencies\nare resolved before the dependencies that need them.\n\n```python\ndef get_db():\n    ...\n    yield session\n\ndef get_current_user(db = Depends(get_db)):\n    user = db.query(User).first()\n    return user\n\ndef require_admin(user = Depends(get_current_user)):\n    if not user.is_admin:\n        raise HTTPException(403, \"Admin only\")\n    return user\n\n@app.delete(\"\u002Fusers\u002F{id}\")\nasync def delete_user(id: int, admin = Depends(require_admin)):\n    ...\n```\n\nFastAPI calls `get_db → get_current_user → require_admin` in order, caching each\nresult within the request.\n\nRule of thumb: chain dependencies to encode privilege escalation cleanly —\n`require_admin` composes `get_current_user` which composes `get_db`.\n",{"id":179,"difficulty":96,"q":180,"a":181},"dependency-caching","If two dependencies declare `Depends(get_db)`, how many times is `get_db` called?","**Once per request** (by default). FastAPI caches dependency results within a\nsingle request — the same object is passed to both dependants.\n\n```python\ndef get_db():\n    print(\"get_db called\")\n    yield session\n\ndef dep_a(db = Depends(get_db)): ...\ndef dep_b(db = Depends(get_db)): ...\n\n@app.get(\"\u002F\")\nasync def handler(a = Depends(dep_a), b = Depends(dep_b)):\n    # \"get_db called\" prints once, not twice\n    ...\n```\n\nTo force a fresh call (bypass cache):\n```python\nDepends(get_db, use_cache=False)\n```\n\nRule of thumb: rely on caching to share a single DB session across all\ndependencies in a request — it prevents phantom double-writes or double-reads.\n",{"id":183,"difficulty":96,"q":184,"a":185},"class-based-dep","How do you create a class-based dependency in FastAPI?","Define a class with `__init__` taking FastAPI-injectable parameters, then\nuse the class itself (not an instance) as the argument to `Depends`:\n\n```python\nfrom fastapi import Depends, Query\n\nclass PaginationDep:\n    def __init__(\n        self,\n        page: int = Query(ge=1, default=1),\n        size: int = Query(ge=1, le=100, default=20),\n    ):\n        self.skip = (page - 1) * size\n        self.limit = size\n\n@app.get(\"\u002Fitems\")\nasync def list_items(paging: PaginationDep = Depends()):\n    return await db.get(skip=paging.skip, limit=paging.limit)\n```\n\n`Depends()` (with no argument) is shorthand for `Depends(PaginationDep)` when\nthe type annotation is already the class.\n\nRule of thumb: use class-based dependencies for multi-parameter groups (pagination,\nfiltering) — they're more readable than a function returning a tuple of values.\n",{"id":187,"difficulty":96,"q":188,"a":189},"callable-instance-dep","Can a callable object instance be used as a FastAPI dependency?","Yes — any callable can be a dependency, including instances with `__call__`:\n\n```python\nclass VerifyToken:\n    def __init__(self, required_scope: str):\n        self.required_scope = required_scope\n\n    def __call__(self, token: str = Depends(oauth2_scheme)):\n        payload = decode_jwt(token)\n        if self.required_scope not in payload.get(\"scopes\", []):\n            raise HTTPException(403, \"Insufficient scope\")\n        return payload\n\nverify_read  = VerifyToken(\"read\")\nverify_write = VerifyToken(\"write\")\n\n@app.get(\"\u002Fitems\", dependencies=[Depends(verify_read)])\nasync def list_items(): ...\n\n@app.post(\"\u002Fitems\", dependencies=[Depends(verify_write)])\nasync def create_item(): ...\n```\n\nRule of thumb: use callable instances when a dependency needs to be parameterised\nat definition time (scope, role, feature flag) — it avoids writing multiple\nnear-identical dependency functions.\n",{"id":191,"difficulty":104,"q":192,"a":193},"depends-shorthand","What is the `Depends()` shorthand and when can you use it?","When the type annotation already names the dependency class, you can write\n`Depends()` (no argument) and FastAPI infers the class from the annotation:\n\n```python\nclass CommonQueryParams:\n    def __init__(self, q: str | None = None, skip: int = 0):\n        self.q = q\n        self.skip = skip\n\n@app.get(\"\u002Fitems\")\nasync def list_items(commons: CommonQueryParams = Depends()):\n    #                                                  ^^^ shorthand\n    return commons.__dict__\n```\n\nThis only works in Python 3.10+ (or with `from __future__ import annotations`)\nand requires the annotation to be a class directly, not a string or `Optional`.\n\nRule of thumb: use the shorthand for class-based deps to reduce repetition;\nuse the explicit `Depends(SomeFunction)` for function-based deps (no shorthand there).\n",{"id":195,"difficulty":96,"q":196,"a":197},"path-operation-dependencies","How do you apply a dependency to a route without using its return value?","Pass it to the `dependencies=` list in the route decorator. The dependency runs\nbut its return value is discarded.\n\n```python\nfrom fastapi import Depends\n\nasync def rate_limit():\n    if too_many_requests():\n        raise HTTPException(429, \"Too many requests\")\n\n@app.get(\"\u002Fitems\", dependencies=[Depends(rate_limit)])\nasync def list_items():\n    return []\n```\n\nThe same dependency can be applied at the router level:\n```python\nrouter = APIRouter(dependencies=[Depends(rate_limit)])\n```\n\nRule of thumb: use `dependencies=[...]` for side-effect deps (auth checks,\nrate limiting, audit logging) where the return value is not needed in the handler.\n",{"id":199,"difficulty":96,"q":200,"a":201},"global-dependencies","How do you apply a dependency globally to every route in the app?","Pass `dependencies` to the `FastAPI()` constructor:\n\n```python\nfrom fastapi import FastAPI, Depends\n\nasync def verify_api_key(x_api_key: str = Header()):\n    if x_api_key != settings.api_key:\n        raise HTTPException(401)\n\napp = FastAPI(dependencies=[Depends(verify_api_key)])\n```\n\nEvery single route in the app now runs `verify_api_key` first. To exclude\nspecific routes (e.g., the `\u002Fhealth` check), structure them under a different\nrouter that doesn't inherit the global dependency.\n\nRule of thumb: global dependencies are powerful but inflexible — prefer\nrouter-level dependencies so you can have public and protected route groups.\n",{"id":203,"difficulty":104,"q":204,"a":205},"dependency-return-type","What types can a dependency function return?","Any Python value — FastAPI passes whatever the function returns to the handler\nparameter:\n\n```python\n# returns a simple value\ndef current_user_id(token: str = Depends(oauth2_scheme)) -> int:\n    return decode_jwt(token)[\"sub\"]\n\n# returns a Pydantic model\ndef current_user(token: str = Depends(oauth2_scheme)) -> User:\n    return User.model_validate(decode_jwt(token))\n\n# returns None (side-effect dep)\ndef check_rate_limit():\n    if over_limit():\n        raise HTTPException(429)\n\n@app.get(\"\u002Fme\")\nasync def me(user: User = Depends(current_user)):\n    return user\n```\n\nRule of thumb: annotate the return type of dependency functions — it documents\nwhat the handler receives and helps type checkers catch mismatches.\n",{"id":207,"difficulty":96,"q":208,"a":209},"dependency-exception","What happens if a dependency raises an `HTTPException`?","The exception propagates up through the dependency chain and FastAPI returns the\nerror response to the client — the handler never runs.\n\n```python\nasync def get_current_user(token: str = Depends(oauth2_scheme)):\n    try:\n        payload = jwt.decode(token, SECRET_KEY, algorithms=[\"HS256\"])\n    except JWTError:\n        raise HTTPException(\n            status_code=401,\n            detail=\"Invalid token\",\n            headers={\"WWW-Authenticate\": \"Bearer\"},\n        )\n    return payload\n\n@app.get(\"\u002Fme\")\nasync def me(user: dict = Depends(get_current_user)):\n    return user   # only reached if get_current_user doesn't raise\n```\n\nRule of thumb: raise `HTTPException` in dependencies rather than returning\nsentinel values — the handler should never need to check \"did the dep succeed?\".\n",{"id":211,"difficulty":96,"q":212,"a":213},"testing-dependency-override","How do you override a dependency in tests?","Use `app.dependency_overrides` dict. The key is the original function; the value\nis the replacement callable:\n\n```python\nfrom fastapi.testclient import TestClient\nfrom app.main import app\nfrom app.dependencies import get_current_user\n\ndef mock_user():\n    return {\"sub\": 1, \"role\": \"admin\"}\n\napp.dependency_overrides[get_current_user] = mock_user\n\nclient = TestClient(app)\n\ndef test_admin_endpoint():\n    resp = client.get(\"\u002Fadmin\u002Fstats\")\n    assert resp.status_code == 200\n\n# Clean up after the test\napp.dependency_overrides.clear()\n```\n\nRule of thumb: always clear `dependency_overrides` after each test — a stale\noverride in one test silently affects subsequent tests.\n",12,{"description":94},"FastAPI dependency injection interview questions — Depends(), shared sub-dependencies, caching, class-based dependencies and path operation dependencies.","fastapi\u002Fdependency-injection\u002Fdepends-basics","mPp69gCF-1heEe8Jgp4KWGXtAoVklZZTpM7ZiXN4AS8",{"id":220,"title":221,"body":222,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":226,"navigation":99,"order":13,"path":227,"questions":228,"questionsCount":148,"related":149,"seo":273,"seoDescription":274,"stem":275,"subtopic":276,"topic":81,"topicSlug":83,"updated":154,"__hash__":277},"qa\u002Ffastapi\u002Fdeployment\u002Fuvicorn-gunicorn.md","Uvicorn Gunicorn",{"type":91,"value":223,"toc":224},[],{"title":94,"searchDepth":29,"depth":29,"links":225},[],{},"\u002Ffastapi\u002Fdeployment\u002Fuvicorn-gunicorn",[229,233,237,241,245,249,253,257,261,265,269],{"id":230,"difficulty":104,"q":231,"a":232},"uvicorn-basics","What is Uvicorn and why is it the recommended server for FastAPI?","**Uvicorn** is a lightning-fast ASGI server built on `uvloop` (a C-accelerated\nasyncio event loop) and `httptools`. FastAPI requires an ASGI server because\nit's built on Starlette, which uses the ASGI protocol.\n\n```bash\npip install uvicorn[standard]   # includes uvloop and httptools\nuvicorn app.main:app --host 0.0.0.0 --port 8000\n```\n\nKey flags:\n- `--reload` — hot reload for development\n- `--workers N` — multiple processes (single worker = one event loop)\n- `--host 0.0.0.0` — listen on all interfaces\n- `--port 8000`\n\nRule of thumb: use `uvicorn[standard]` in production for the `uvloop` speedup;\nuse plain `uvicorn` in Docker to keep the image small (uvloop has C deps).\n",{"id":234,"difficulty":96,"q":235,"a":236},"gunicorn-uvicorn-workers","Why combine Gunicorn with Uvicorn workers and how do you configure it?","Uvicorn alone handles one event loop per process. **Gunicorn** is a battle-tested\nprocess manager that handles worker lifecycle (respawning crashed workers,\ngraceful restarts, signal handling). Together they give you:\n- Gunicorn's robust process management.\n- Uvicorn's async event loop per worker.\n\n```bash\npip install gunicorn\ngunicorn app.main:app \\\n    -k uvicorn.workers.UvicornWorker \\\n    --workers 4 \\\n    --bind 0.0.0.0:8000 \\\n    --timeout 120\n```\n\n`UvicornWorker` replaces Gunicorn's default sync worker with an async one.\n\nRule of thumb: use Gunicorn + `UvicornWorker` for traditional deployments on\nVMs\u002Fbare metal; use Uvicorn directly in Kubernetes where the orchestrator handles\npod restarts.\n",{"id":238,"difficulty":96,"q":239,"a":240},"worker-count","How many workers should you run per server for a FastAPI app?","The classic formula: **`workers = 2 × CPU_cores + 1`**.\n\n```bash\n# 4-core machine → 9 workers\ngunicorn app.main:app -k uvicorn.workers.UvicornWorker --workers 9\n```\n\nHowever, FastAPI is async — a single worker handles many concurrent requests\nthrough the event loop. For I\u002FO-bound apps (most web APIs), 2-4 workers per\nmachine is often sufficient:\n\n| App type | Worker count |\n|----------|-------------|\n| Pure async I\u002FO | 2–4 per machine |\n| Mixed sync\u002Fasync | 2 × cores |\n| CPU-bound | 1 per core (use multiprocessing separately) |\n\nRule of thumb: start with `2 × cores + 1`; profile under load and reduce if\nworkers share limited resources (DB connections, RAM).\n",{"id":242,"difficulty":96,"q":243,"a":244},"concurrency-vs-parallelism","What is the difference between concurrency and parallelism in the context of FastAPI?","- **Concurrency**: multiple tasks make progress by interleaving on a single CPU\n  (one event loop thread handles thousands of waiting I\u002FO operations).\n- **Parallelism**: multiple tasks run simultaneously on multiple CPUs (multiple\n  Uvicorn worker processes, each with their own event loop).\n\nFastAPI gives you **concurrency** within a single worker via `async def` handlers.\nYou get **parallelism** by running multiple workers.\n\n```\nWorker 1 (CPU core 1): event loop handles 1000 concurrent requests\nWorker 2 (CPU core 2): event loop handles 1000 concurrent requests\n```\n\nCPU-bound code (heavy computation) blocks a core — neither concurrency nor more\n`async def` helps. Use a thread\u002Fprocess pool or a task queue.\n\nRule of thumb: `async def` handlers add concurrency (better I\u002FO throughput per\nworker); more workers add parallelism (better CPU utilisation).\n",{"id":246,"difficulty":104,"q":247,"a":248},"reload-dev","How do you enable hot reload in Uvicorn for development?","Pass `--reload` flag:\n\n```bash\nuvicorn app.main:app --reload --reload-dir app\n```\n\nOr use the Python API (better for IDEs):\n```python\n# run.py\nimport uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.main:app\", host=\"0.0.0.0\", port=8000, reload=True)\n```\n\n`--reload-dir app` restricts watching to the `app\u002F` directory, avoiding\nfalse reloads when `.pyc` files or test outputs change.\n\nRule of thumb: never use `--reload` in production — it adds overhead and\nrestarts the process on any file change, including logs and temp files.\n",{"id":250,"difficulty":96,"q":251,"a":252},"docker-fastapi","What is the recommended Dockerfile structure for a FastAPI application?","```dockerfile\nFROM python:3.12-slim\n\nWORKDIR \u002Fapp\n\n# Install dependencies first (layer cached until requirements change)\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\n# Copy application code\nCOPY .\u002Fapp .\u002Fapp\n\nEXPOSE 8000\n\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--workers\", \"4\"]\n```\n\nMulti-stage build for smaller images:\n```dockerfile\nFROM python:3.12-slim AS builder\nRUN pip install --no-cache-dir -r requirements.txt --target \u002Finstall\n\nFROM python:3.12-slim\nCOPY --from=builder \u002Finstall \u002Fusr\u002Flocal\u002Flib\u002Fpython3.12\u002Fsite-packages\nCOPY .\u002Fapp .\u002Fapp\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n```\n\nRule of thumb: use `--no-cache-dir` in pip installs to keep image size down;\ncopy `requirements.txt` before source code for layer caching.\n",{"id":254,"difficulty":104,"q":255,"a":256},"health-check","How do you add a health check endpoint in FastAPI for container orchestration?","```python\nfrom fastapi import FastAPI\nfrom fastapi.responses import JSONResponse\n\napp = FastAPI()\n\n@app.get(\"\u002Fhealth\", include_in_schema=False)\nasync def health():\n    return {\"status\": \"ok\"}\n\n# Liveness (is the process alive?)\n@app.get(\"\u002Fhealth\u002Flive\", include_in_schema=False)\nasync def liveness():\n    return {\"status\": \"alive\"}\n\n# Readiness (is the app ready to serve traffic?)\n@app.get(\"\u002Fhealth\u002Fready\", include_in_schema=False)\nasync def readiness():\n    try:\n        await db.execute(\"SELECT 1\")\n    except Exception:\n        return JSONResponse({\"status\": \"not ready\"}, status_code=503)\n    return {\"status\": \"ready\"}\n```\n\nKubernetes `livenessProbe` uses `\u002Fhealth\u002Flive`; `readinessProbe` uses `\u002Fhealth\u002Fready`.\n\nRule of thumb: readiness should check actual dependencies (DB, cache);\nliveness should only check the process is alive — failing liveness kills the pod.\n",{"id":258,"difficulty":125,"q":259,"a":260},"graceful-shutdown","How does FastAPI\u002FUvicorn handle graceful shutdown?","When Uvicorn receives **SIGTERM** (sent by Kubernetes, Docker, or Gunicorn):\n1. It stops accepting new connections.\n2. Waits for in-flight requests to complete (up to `--timeout-graceful-shutdown` seconds).\n3. Calls the `lifespan` shutdown code (after `yield`).\n4. Closes the event loop and exits.\n\n```bash\nuvicorn app.main:app --timeout-graceful-shutdown 30\n```\n\nIn Kubernetes, set a `preStop` hook to delay pod termination so the load\nbalancer routes traffic away before the pod stops accepting connections:\n\n```yaml\nlifecycle:\n    preStop:\n        exec:\n            command: [\"sleep\", \"5\"]\n```\n\nRule of thumb: always set `--timeout-graceful-shutdown` to slightly less than\nKubernetes' `terminationGracePeriodSeconds` to give in-flight requests time to finish.\n",{"id":262,"difficulty":104,"q":263,"a":264},"environment-variables-uvicorn","How do you pass environment variables to a FastAPI app running in Docker?","Pass them at container run time (don't bake secrets into the image):\n\n```bash\ndocker run -e DATABASE_URL=postgresql:\u002F\u002F... -e SECRET_KEY=... myapp\n```\n\nOr use a `.env` file:\n```bash\ndocker run --env-file .env.production myapp\n```\n\nIn Docker Compose:\n```yaml\nservices:\n    api:\n        image: myapp\n        environment:\n            - DATABASE_URL=postgresql:\u002F\u002Fdb\u002Fmydb\n            - SECRET_KEY=${SECRET_KEY}\n```\n\nIn Kubernetes, use `Secrets` (base64-encoded) for sensitive values:\n```yaml\nenvFrom:\n    - secretRef:\n        name: myapp-secrets\n```\n\nRule of thumb: never embed production secrets in the Docker image or\n`docker-compose.yml` — always inject at runtime from secrets management.\n",{"id":266,"difficulty":125,"q":267,"a":268},"multiple-apps-mount","How do you serve multiple FastAPI apps from a single Uvicorn process?","Mount sub-applications using Starlette's `Mount`:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import Mount\nfrom fastapi import FastAPI\n\nv1_app = FastAPI(title=\"API v1\")\nv2_app = FastAPI(title=\"API v2\")\n\n@v1_app.get(\"\u002Fitems\")\nasync def v1_items(): return [{\"version\": \"v1\"}]\n\n@v2_app.get(\"\u002Fitems\")\nasync def v2_items(): return [{\"version\": \"v2\", \"new_field\": True}]\n\n# Root app that routes between them\nroot_app = Starlette(routes=[\n    Mount(\"\u002Fv1\", app=v1_app),\n    Mount(\"\u002Fv2\", app=v2_app),\n])\n```\n\n```bash\nuvicorn main:root_app --port 8000\n# \u002Fv1\u002Fitems → v1_app; \u002Fv2\u002Fitems → v2_app\n# \u002Fv1\u002Fdocs and \u002Fv2\u002Fdocs each work independently\n```\n\nRule of thumb: mount versioned sub-apps when versions differ so significantly\nthat sharing middleware or the OpenAPI schema would be confusing.\n",{"id":270,"difficulty":96,"q":271,"a":272},"ssl-tls-uvicorn","How do you run Uvicorn with SSL\u002FTLS in development?","Pass `--ssl-keyfile` and `--ssl-certfile`:\n\n```bash\n# Generate self-signed cert for dev\nopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes\n\nuvicorn app.main:app --ssl-keyfile key.pem --ssl-certfile cert.pem --port 8443\n```\n\nIn production, **don't terminate TLS in Uvicorn** — use Nginx\u002FCaddy\u002FALB in front.\nTLS termination in the reverse proxy lets you use `certbot` for Let's Encrypt,\nhandle certificate rotation without restarting Uvicorn, and offload TLS overhead.\n\nRule of thumb: Uvicorn TLS is fine for internal service-to-service encryption\nor local dev HTTPS; for public-facing production use a reverse proxy for TLS.\n",{"description":94},"FastAPI Uvicorn and Gunicorn interview questions — workers, concurrency model, process management, Docker deployment and performance tuning.","fastapi\u002Fdeployment\u002Fuvicorn-gunicorn","Uvicorn & Gunicorn","NB_fUy4qlVwanwlzoDBtcJM_FUAdfFUHj-XXR8yQdyc",{"id":279,"title":280,"body":281,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":285,"navigation":99,"order":13,"path":286,"questions":287,"questionsCount":348,"related":149,"seo":349,"seoDescription":350,"stem":351,"subtopic":280,"topic":20,"topicSlug":21,"updated":154,"__hash__":352},"qa\u002Ffastapi\u002Ffundamentals\u002Fasync-basics.md","Async Basics",{"type":91,"value":282,"toc":283},[],{"title":94,"searchDepth":29,"depth":29,"links":284},[],{},"\u002Ffastapi\u002Ffundamentals\u002Fasync-basics",[288,292,296,300,304,308,312,316,320,324,328,332,336,340,344],{"id":289,"difficulty":104,"q":290,"a":291},"what-is-async-await","What is async\u002Fawait in Python and why does FastAPI use it?","`async`\u002F`await` is Python's syntax for **cooperative concurrency**. An `async def`\nfunction returns a **coroutine** object; `await` suspends that coroutine until the\nawaited operation completes, letting the event loop run other coroutines in the\nmeantime. No thread switch happens — the same OS thread handles many requests.\n\n```python\nimport asyncio\n\nasync def fetch_user(user_id: int) -> dict:\n    await asyncio.sleep(0.1)   # yield control while \"waiting\"\n    return {\"id\": user_id}\n```\n\nFastAPI is built on top of **Starlette** and **asyncio**. When you declare a route\nhandler as `async def`, FastAPI runs it directly on the event loop, giving you\nhigh concurrency without spawning new threads for each request.\n\nRule of thumb: use `async def` handlers whenever you `await` something (DB call,\nHTTP request, cache); use `def` for pure CPU work that has no async alternative.\n",{"id":293,"difficulty":96,"q":294,"a":295},"asgi-vs-wsgi","What is the difference between ASGI and WSGI?","**WSGI** (Web Server Gateway Interface, PEP 3333) is the classic Python web\nserver standard. It assumes a **synchronous**, request-per-thread model —\na server calls `app(environ, start_response)` and blocks until the response is\nreturned. Frameworks like Flask and Django (pre-4.x) use WSGI.\n\n**ASGI** (Asynchronous Server Gateway Interface) extends WSGI to support\n**async handlers, WebSockets and long-polling** in the same interface. A server\ncalls `await app(scope, receive, send)` and the app may `await` I\u002FO freely.\n\n```python\n# Minimal ASGI app (what Starlette\u002FFastAPI is under the hood)\nasync def app(scope, receive, send):\n    if scope[\"type\"] == \"http\":\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n        await send({\"type\": \"http.response.body\", \"body\": b\"Hello\"})\n```\n\nFastAPI is an ASGI framework served by **Uvicorn** (or Hypercorn). ASGI lets a\nsingle process handle thousands of simultaneous connections without one thread\nper connection.\n\nRule of thumb: WSGI = sync\u002Fthreads; ASGI = async\u002Fevent-loop; FastAPI requires ASGI.\n",{"id":297,"difficulty":96,"q":298,"a":299},"event-loop","What is the Python event loop and how does FastAPI interact with it?","The **event loop** is a scheduler that runs coroutines, handles I\u002FO callbacks\nand timers — all in a single thread. When a coroutine `await`s an I\u002FO operation,\nthe loop suspends it and runs the next ready coroutine.\n\n```python\nimport asyncio\n\nasync def task_a():\n    print(\"A start\")\n    await asyncio.sleep(1)   # loop runs task_b here\n    print(\"A done\")\n\nasync def task_b():\n    print(\"B start\")\n    await asyncio.sleep(0.5)\n    print(\"B done\")\n\nasyncio.run(asyncio.gather(task_a(), task_b()))\n# prints: A start, B start, B done, A done\n```\n\nUvicorn creates one event loop per worker process and passes every incoming HTTP\nrequest into it. FastAPI schedules your `async def` handler on that loop. If you\nblock the loop (e.g., call `time.sleep()` or a synchronous DB driver), *all*\nrequests stall until the block clears.\n\nRule of thumb: never call blocking code directly from an `async def` handler —\nuse `await asyncio.to_thread()` or a thread pool executor instead.\n",{"id":301,"difficulty":104,"q":302,"a":303},"coroutine-vs-function","What is a coroutine and how is it different from a regular function?","A **coroutine** is created by an `async def` function. Calling it does **not**\nexecute the body — it returns a coroutine object. The body only runs when you\n`await` it (or pass it to `asyncio.run()`).\n\n```python\ndef regular():\n    return 42          # runs immediately on call\n\nasync def coro():\n    return 42          # returns \u003Ccoroutine object> on call\n\nresult = regular()     # 42\nobj    = coro()        # \u003Ccoroutine object coro at 0x...> — nothing ran yet\nresult = await coro()  # 42 — body now runs\n```\n\nFastAPI recognises `async def` route handlers and schedules them with `await`\ninternally. If you declare a route as `def`, FastAPI runs it in a thread pool\nto avoid blocking the event loop.\n\nRule of thumb: a coroutine is a pauseable function; without `await` (or\n`asyncio.run`), its body never executes.\n",{"id":305,"difficulty":96,"q":306,"a":307},"sync-vs-async-handler","When should you use `def` vs `async def` for a FastAPI route handler?","| Handler | When to use | FastAPI runs it in |\n|---------|-------------|--------------------|\n| `async def` | calls `await`-able I\u002FO (DB, HTTP, cache) | event loop directly |\n| `def` | CPU work or sync libraries (Pandas, Pillow) | thread-pool executor |\n\n```python\n# async — awaits the DB; loop stays free\n@app.get(\"\u002Fusers\u002F{id}\")\nasync def get_user(id: int, db: AsyncSession = Depends(get_db)):\n    return await db.get(User, id)\n\n# def — pandas is sync-only; FastAPI offloads to a thread\n@app.post(\"\u002Freport\")\ndef generate_report(payload: ReportRequest):\n    df = pd.read_csv(payload.path)   # blocking I\u002FO, but in a thread\n    return df.describe().to_dict()\n```\n\nThe wrong choice either wastes threads (`async def` + sync library that blocks)\nor adds unnecessary thread overhead (`def` + code that could have been `await`ed).\n\nRule of thumb: if you `await` anything inside the handler, declare it `async def`;\nif the handler is pure CPU\u002Fsync, use `def`.\n",{"id":309,"difficulty":125,"q":310,"a":311},"blocking-event-loop","What happens if you block the event loop in FastAPI, and how do you fix it?","Blocking the event loop means calling any operation that occupies the thread\nwithout yielding: `time.sleep()`, synchronous file reads, `requests.get()`, heavy\ncomputation. While blocked, **no other coroutine runs** — all concurrent requests\nstall until the block clears.\n\n```python\n# BAD — blocks the event loop for every request\n@app.get(\"\u002Fslow\")\nasync def slow():\n    time.sleep(2)          # freezes ALL requests for 2 s\n    return {\"ok\": True}\n\n# GOOD — moves the blocking call to a thread\n@app.get(\"\u002Fslow\")\nasync def slow():\n    await asyncio.to_thread(time.sleep, 2)   # only this coroutine pauses\n    return {\"ok\": True}\n```\n\nOther fixes:\n- Switch to an async library (`httpx` instead of `requests`, `asyncpg` instead of `psycopg2`).\n- Use `loop.run_in_executor(None, blocking_fn)` (older equivalent of `asyncio.to_thread`).\n- Declare the handler as plain `def` — FastAPI automatically runs it in a thread pool.\n\nRule of thumb: if you can't avoid a blocking call inside `async def`, use\n`asyncio.to_thread()` to push it off the event loop.\n",{"id":313,"difficulty":96,"q":314,"a":315},"asyncio-gather","What does `asyncio.gather()` do and when would you use it in FastAPI?","`asyncio.gather(*coros)` runs multiple coroutines **concurrently on the same\nevent loop** and returns their results as a list in the same order as the inputs.\nIt's the idiomatic way to fan-out I\u002FO work within a single handler.\n\n```python\n@app.get(\"\u002Fdashboard\u002F{user_id}\")\nasync def dashboard(user_id: int):\n    # fire both DB queries at the same time instead of sequentially\n    user, orders = await asyncio.gather(\n        fetch_user(user_id),\n        fetch_orders(user_id),\n    )\n    return {\"user\": user, \"orders\": orders}\n```\n\nSequential awaits would take `t_user + t_orders`; `gather` takes `max(t_user, t_orders)`.\n\nRule of thumb: use `asyncio.gather()` whenever two or more I\u002FO calls are\nindependent — it cuts latency to the slowest one instead of the sum.\n",{"id":317,"difficulty":125,"q":318,"a":319},"anyio-trio","What is anyio and how does it relate to FastAPI?","**anyio** is a compatibility shim that lets library authors write async code once\nand run it on **asyncio** *or* **Trio** without changes. Starlette (FastAPI's\nfoundation) adopted anyio as its async backend in v0.20+.\n\n```python\nimport anyio\n\nasync def run_parallel():\n    async with anyio.create_task_group() as tg:\n        tg.start_soon(task_a)\n        tg.start_soon(task_b)\n```\n\nIn practice for FastAPI users:\n- You still write plain `asyncio` code — anyio is transparent.\n- `pytest-anyio` \u002F `anyio.from_thread.run_sync` become relevant when testing.\n- Starlette's `BackgroundTask` and `lifespan` use anyio task groups internally.\n\nRule of thumb: you rarely call anyio directly in application code; knowing it\nexists explains why `pytest-anyio` is the recommended test runner for Starlette apps.\n",{"id":321,"difficulty":96,"q":322,"a":323},"run-sync-in-async","How do you call a synchronous function from an async context without blocking?","Use `asyncio.to_thread()` (Python 3.9+) to run a sync function in the default\nthread-pool executor while keeping the event loop free.\n\n```python\nimport asyncio\n\ndef sync_heavy(n: int) -> int:\n    # CPU or blocking I\u002FO, e.g. reading a large file\n    return sum(range(n))\n\n@app.get(\"\u002Fcompute\")\nasync def compute(n: int):\n    result = await asyncio.to_thread(sync_heavy, n)\n    return {\"result\": result}\n```\n\nFor older Python (3.7–3.8) use `loop.run_in_executor(None, fn, *args)`.\nFor FastAPI specifically, declaring the route as plain `def` achieves the same\nthing automatically — FastAPI calls `run_in_executor` for you.\n\nRule of thumb: prefer `def` route handlers for wholly-sync work; use\n`asyncio.to_thread()` only when you're already in an `async def` and need\none sync call inside otherwise-async logic.\n",{"id":325,"difficulty":96,"q":326,"a":327},"httpx-vs-requests","Why use httpx instead of requests in a FastAPI application?","`requests` is **synchronous** — calling `requests.get()` blocks the OS thread\nuntil the response arrives. In an `async def` handler that means the event loop\nis frozen for the duration of every outbound HTTP call.\n\n`httpx` provides an identical API *plus* an `AsyncClient` that integrates with\nasyncio:\n\n```python\nimport httpx\n\n@app.get(\"\u002Fproxy\")\nasync def proxy(url: str):\n    async with httpx.AsyncClient() as client:\n        resp = await client.get(url)   # loop stays free\n    return resp.json()\n```\n\n`httpx` also supports HTTP\u002F2, connection pooling via a shared client, and\n`httpx.AsyncClient` as a test transport for FastAPI's `TestClient`.\n\nRule of thumb: always use `httpx.AsyncClient` for outbound HTTP inside FastAPI\nasync handlers; use `requests` only in plain `def` routes or CLI scripts.\n",{"id":329,"difficulty":104,"q":330,"a":331},"what-is-starlette","What is Starlette and what does FastAPI add on top of it?","**Starlette** is a lightweight ASGI framework\u002Ftoolkit that provides routing,\nmiddleware, WebSockets, background tasks and static files. FastAPI is built\n**directly on top of Starlette** — every `FastAPI()` instance is a Starlette app.\n\nFastAPI's additions:\n- **Type-hint-driven parameter parsing** — path, query, body extracted via annotations.\n- **Pydantic v2 validation** — automatic request validation and serialization.\n- **Dependency injection** via `Depends()`.\n- **Automatic OpenAPI \u002F JSON Schema** generation (Swagger UI, ReDoc).\n- `HTTPException` helpers and response model enforcement.\n\n```python\nfrom fastapi import FastAPI\nfrom starlette.applications import Starlette   # same base class\n```\n\nRule of thumb: think of FastAPI as Starlette + Pydantic + OpenAPI; you can use\nall Starlette primitives (middleware, WebSockets, mounts) in a FastAPI app.\n",{"id":333,"difficulty":125,"q":334,"a":335},"concurrency-model","Describe FastAPI's concurrency model end-to-end.","1. **Uvicorn** (ASGI server) runs one event loop per worker process.\n2. A new HTTP connection arrives; Uvicorn's event loop accepts it and parses headers.\n3. FastAPI's router matches the URL and calls the handler.\n4. If the handler is `async def` → FastAPI `await`s it on the event loop.\n   If the handler is `def` → FastAPI submits it to `anyio`'s thread pool and\n   `await`s the future, keeping the event loop free.\n5. The handler performs I\u002FO (e.g., `await db.execute()`). The coroutine suspends\n   and the loop picks up the next ready coroutine.\n6. When I\u002FO completes, the handler resumes and returns a response dict.\n7. FastAPI serializes it (Pydantic `model_dump` → JSON) and sends bytes back\n   through Uvicorn.\n\n```\nrequest → Uvicorn → FastAPI router → async handler (event loop)\n                                   or sync handler (thread pool)\n                                   → Pydantic serialize → response\n```\n\nRule of thumb: one worker process = one event loop = many concurrent requests,\nbut only one coroutine actually executes CPU instructions at a time.\n",{"id":337,"difficulty":96,"q":338,"a":339},"asyncio-task","What is `asyncio.create_task()` and how does it differ from `await`?","`asyncio.create_task(coro)` schedules a coroutine to run **concurrently** on the\nevent loop and returns a `Task` object immediately. The task starts running as\nsoon as the current coroutine yields. `await coro` runs the coroutine\n**sequentially** — the caller suspends until it finishes.\n\n```python\nasync def handler():\n    # sequential: total time = t_a + t_b\n    result_a = await slow_query_a()\n    result_b = await slow_query_b()\n\n    # concurrent: total time = max(t_a, t_b)\n    task_a = asyncio.create_task(slow_query_a())\n    task_b = asyncio.create_task(slow_query_b())\n    result_a = await task_a\n    result_b = await task_b\n```\n\n`asyncio.gather(*coros)` is usually more convenient than manual `create_task`\nwhen you want results; `create_task` is useful when you want to start a task\nand do other work before collecting its result.\n\nRule of thumb: `create_task` is fire-and-partially-forget; use `gather` when you\nneed all results in one call.\n",{"id":341,"difficulty":125,"q":342,"a":343},"thread-pool-size","How does FastAPI\u002Fanyio determine the size of its thread pool for sync handlers?","FastAPI delegates sync (`def`) handlers to **anyio**'s default thread limiter,\nwhich caps concurrent threads at **40 by default** (per worker process). This\nprevents unbounded thread creation from swamping the OS.\n\n```python\nimport anyio\n\n# Check or raise the cap at startup\n@app.on_event(\"startup\")\nasync def configure_threads():\n    limiter = anyio.from_thread.current_default_thread_limiter()\n    limiter.total_tokens = 20   # reduce if each thread uses lots of memory\n```\n\nThe 40-thread default is conservative for most workloads. If your sync handlers\ndo fast CPU work, 40 is plenty. If they block on slow external calls, consider\nusing `async def` + `asyncio.to_thread()` instead so you control the pool.\n\nRule of thumb: for heavily sync-bound FastAPI apps, tune the anyio thread limit or,\nbetter, switch to async libraries so you don't need threads at all.\n",{"id":345,"difficulty":96,"q":346,"a":347},"lifespan-startup-shutdown","What is the difference between `@app.on_event(\"startup\")` and `lifespan`?","`@app.on_event(\"startup\u002Fshutdown\")` decorators are the **legacy** approach —\nthey work but are deprecated in recent FastAPI. The modern replacement is the\n**`lifespan` context manager**, which groups startup and shutdown in one function\nusing `yield`.\n\n```python\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # startup\n    await db_pool.connect()\n    yield\n    # shutdown\n    await db_pool.disconnect()\n\napp = FastAPI(lifespan=lifespan)\n```\n\nBenefits of `lifespan`:\n- Single function instead of two decorators.\n- Exception in startup cleanly skips the yield and shutdown block won't be reached.\n- Composable — you can call other async context managers inside.\n- Testable with `@asynccontextmanager` directly.\n\nRule of thumb: use `lifespan=` for all new FastAPI apps; avoid `@app.on_event`.\n",15,{"description":94},"FastAPI async\u002Fawait interview questions — coroutines, event loop, ASGI vs WSGI, sync vs async route handlers and avoiding blocking the event loop.","fastapi\u002Ffundamentals\u002Fasync-basics","n2u_aF57BBwUZZehz50R-u1kpVdckwYf2HwJHCctb6c",{"id":354,"title":355,"body":356,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":360,"navigation":99,"order":13,"path":361,"questions":362,"questionsCount":415,"related":149,"seo":416,"seoDescription":417,"stem":418,"subtopic":419,"topic":37,"topicSlug":39,"updated":154,"__hash__":420},"qa\u002Ffastapi\u002Fpydantic\u002Fmodels.md","Models",{"type":91,"value":357,"toc":358},[],{"title":94,"searchDepth":29,"depth":29,"links":359},[],{},"\u002Ffastapi\u002Fpydantic\u002Fmodels",[363,367,371,375,379,383,387,391,395,399,403,407,411],{"id":364,"difficulty":104,"q":365,"a":366},"basemodel-basics","What is Pydantic's `BaseModel` and why does FastAPI rely on it?","`BaseModel` is Pydantic's foundation class. Subclasses declare fields as\nclass-level annotations; Pydantic validates and coerces incoming data at\ninstantiation time.\n\n```python\nfrom pydantic import BaseModel\n\nclass Item(BaseModel):\n    name: str\n    price: float\n    quantity: int = 1    # default value\n\nitem = Item(name=\"Widget\", price=9.99)\n# Item(name='Widget', price=9.99, quantity=1)\n\nItem(name=\"Widget\", price=\"nine\")   # raises ValidationError: price must be float\n```\n\nFastAPI uses `BaseModel` to:\n1. Parse and validate incoming JSON bodies.\n2. Serialise outgoing responses.\n3. Generate OpenAPI JSON Schema.\n\nRule of thumb: use a `BaseModel` subclass for any structured data that crosses\nthe HTTP boundary — it's free validation and documentation.\n",{"id":368,"difficulty":104,"q":369,"a":370},"field-defaults","How do you add metadata (description, example, constraints) to a Pydantic field?","Use `Field()` from Pydantic:\n\n```python\nfrom pydantic import BaseModel, Field\n\nclass Product(BaseModel):\n    name: str = Field(min_length=1, max_length=100, description=\"Product name\")\n    price: float = Field(gt=0, description=\"Price in USD\", example=9.99)\n    sku: str = Field(pattern=r\"^[A-Z]{3}-\\d{4}$\", examples=[\"ABC-1234\"])\n```\n\n`Field()` parameters:\n- Constraints: `gt`, `ge`, `lt`, `le`, `min_length`, `max_length`, `pattern`, `multiple_of`\n- Metadata: `title`, `description`, `example`, `examples`\n- Behaviour: `default`, `default_factory`, `alias`, `exclude`\n\nRule of thumb: use `Field()` for any field that needs a constraint or a helpful\ndescription — it ends up in the OpenAPI schema and saves clients guessing.\n",{"id":372,"difficulty":104,"q":373,"a":374},"required-vs-optional","How do you mark a Pydantic field as required vs optional?","- **Required**: no default value — Pydantic raises `ValidationError` if omitted.\n- **Optional with None**: `field: T | None = None` — accepts `None` or the type.\n- **Optional with default**: `field: T = default_value`.\n\n```python\nfrom pydantic import BaseModel\n\nclass User(BaseModel):\n    id: int                      # required\n    name: str                    # required\n    bio: str | None = None       # optional, defaults to None\n    role: str = \"viewer\"         # optional, defaults to \"viewer\"\n```\n\nIn OpenAPI:\n- Required fields → `required: [...]` array in the schema.\n- Optional fields → absent from `required`, with `default` or `nullable`.\n\nRule of thumb: think of `None` default as \"not provided\"; use a semantic default\nlike `\"viewer\"` when the field has a meaningful fallback value.\n",{"id":376,"difficulty":96,"q":377,"a":378},"field-alias","What is a field alias in Pydantic and when would you use it?","An alias lets the JSON key differ from the Python attribute name. Useful when\nthe API uses camelCase, hyphens, or reserved Python keywords.\n\n```python\nfrom pydantic import BaseModel, Field\n\nclass Order(BaseModel):\n    order_id: int = Field(alias=\"orderId\")        # JSON: \"orderId\"\n    item_count: int = Field(alias=\"itemCount\")\n\n# Parsing from JSON (camelCase input)\norder = Order.model_validate({\"orderId\": 42, \"itemCount\": 3})\nprint(order.order_id)  # 42   (Python snake_case attribute)\n```\n\nFor global camelCase ↔ snake_case conversion use `model_config`:\n```python\nfrom pydantic import ConfigDict\n\nclass MyModel(BaseModel):\n    model_config = ConfigDict(alias_generator=lambda s: s.replace(\"_\", \"\"), populate_by_name=True)\n    order_id: int\n```\n\nRule of thumb: use `alias` for a few fields; use `alias_generator` when the\nentire API uses camelCase (common with JavaScript clients).\n",{"id":380,"difficulty":96,"q":381,"a":382},"model-inheritance","How does Pydantic model inheritance work and what are its use cases in FastAPI?","A child model inherits all fields from its parent and can add or override them.\n\n```python\nclass ItemBase(BaseModel):\n    name: str\n    price: float\n\nclass ItemCreate(ItemBase):\n    # used for POST body — no id yet\n    pass\n\nclass ItemUpdate(ItemBase):\n    # all fields optional for PATCH\n    name: str | None = None\n    price: float | None = None\n\nclass ItemOut(ItemBase):\n    id: int            # added by DB\n    created_at: datetime\n```\n\nThis pattern keeps field definitions DRY while giving each use-case the exact\nshape it needs.\n\nRule of thumb: define a `Base` model with shared fields, then derive `Create`,\n`Update`, and `Out` variants — the \"Input\u002FOutput DTO\" pattern.\n",{"id":384,"difficulty":96,"q":385,"a":386},"model-config","What is `model_config` in Pydantic v2 and what can you configure with it?","`model_config = ConfigDict(...)` replaces Pydantic v1's inner `class Config`.\nKey options:\n\n| Setting | Effect |\n|---------|--------|\n| `extra=\"forbid\"` | Reject extra fields |\n| `frozen=True` | Make instances immutable (hashable) |\n| `populate_by_name=True` | Allow both alias and name |\n| `from_attributes=True` | Parse from ORM objects (SQLAlchemy) |\n| `str_strip_whitespace=True` | Auto-strip leading\u002Ftrailing spaces |\n| `alias_generator=fn` | Auto-generate aliases for all fields |\n\n```python\nfrom pydantic import BaseModel, ConfigDict\n\nclass StrictUser(BaseModel):\n    model_config = ConfigDict(extra=\"forbid\", frozen=True)\n    id: int\n    name: str\n```\n\nRule of thumb: set `from_attributes=True` on any model that reads from SQLAlchemy\nORM objects; set `extra=\"forbid\"` on request models to catch client typos.\n",{"id":388,"difficulty":96,"q":389,"a":390},"orm-mode","What is `from_attributes=True` (formerly `orm_mode`) and why is it needed?","By default Pydantic models only parse from dicts. `from_attributes=True` allows\nparsing from any object with attributes — including SQLAlchemy ORM instances.\n\n```python\nfrom pydantic import BaseModel, ConfigDict\nfrom sqlalchemy import Column, Integer, String\nfrom sqlalchemy.orm import DeclarativeBase\n\nclass Base(DeclarativeBase): pass\n\nclass UserORM(Base):\n    __tablename__ = \"users\"\n    id = Column(Integer, primary_key=True)\n    name = Column(String)\n\nclass UserOut(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    name: str\n\nuser_orm = session.get(UserORM, 1)\nuser_out = UserOut.model_validate(user_orm)   # reads .id and .name attributes\n```\n\nWithout `from_attributes=True`, `model_validate(orm_obj)` raises a\n`ValidationError` because ORM objects aren't dicts.\n\nRule of thumb: always set `from_attributes=True` on output schemas that will\nbe constructed from SQLAlchemy models.\n",{"id":392,"difficulty":96,"q":393,"a":394},"computed-field","How do you add a computed (read-only) field to a Pydantic model?","Use `@computed_field` (Pydantic v2):\n\n```python\nfrom pydantic import BaseModel, computed_field\n\nclass Rectangle(BaseModel):\n    width: float\n    height: float\n\n    @computed_field\n    @property\n    def area(self) -> float:\n        return self.width * self.height\n\nr = Rectangle(width=3.0, height=4.0)\nprint(r.area)           # 12.0\nprint(r.model_dump())   # {\"width\": 3.0, \"height\": 4.0, \"area\": 12.0}\n```\n\n`@computed_field` fields are included in serialisation and the OpenAPI schema.\nThey are always read-only — you can't set them from input.\n\nRule of thumb: use `@computed_field` for derived values that belong in the\nresponse (full name from first + last, URL from ID); avoid heavy computation in them.\n",{"id":396,"difficulty":104,"q":397,"a":398},"model-validate","What is `model_validate()` and how does it differ from calling the constructor?","`Model.model_validate(obj)` is the explicit way to parse data in Pydantic v2.\nIt accepts a dict **or** any object (with `from_attributes=True`) and returns a\nvalidated model instance.\n\n```python\n# constructor — same as model_validate for dicts\nitem = Item(name=\"Widget\", price=9.99)\n\n# model_validate — more explicit, required for non-dict input\nitem = Item.model_validate({\"name\": \"Widget\", \"price\": 9.99})\nitem = Item.model_validate(orm_instance)   # needs from_attributes=True\n```\n\nIn FastAPI, `model_validate` is called internally when parsing request bodies.\nYou call it explicitly when converting ORM objects to Pydantic models in service\nor repository layers.\n\nRule of thumb: use the constructor for tests with literal dicts; use\n`model_validate` in production code where the source object might be an ORM row.\n",{"id":400,"difficulty":104,"q":401,"a":402},"model-dump","What does `model_dump()` return and what options does it accept?","`model_dump()` returns a Python dict of the model's fields. It's Pydantic v2's\nreplacement for `.dict()`.\n\n```python\nitem = Item(name=\"Widget\", price=9.99)\nitem.model_dump()\n# {\"name\": \"Widget\", \"price\": 9.99}\n\n# exclude fields\nitem.model_dump(exclude={\"price\"})\n# {\"name\": \"Widget\"}\n\n# only unset fields (for PATCH)\nitem.model_dump(exclude_unset=True)\n\n# JSON-safe output (datetime → str, UUID → str)\nitem.model_dump(mode=\"json\")\n```\n\nRule of thumb: use `model_dump(exclude_unset=True)` in PATCH handlers to get\nonly the fields the client explicitly sent.\n",{"id":404,"difficulty":96,"q":405,"a":406},"model-json-schema","How do you get the JSON Schema for a Pydantic model?","Call `Model.model_json_schema()`:\n\n```python\nimport json\nfrom pydantic import BaseModel, Field\n\nclass Item(BaseModel):\n    name: str = Field(min_length=1)\n    price: float = Field(gt=0)\n\nprint(json.dumps(Item.model_json_schema(), indent=2))\n# {\n#   \"title\": \"Item\",\n#   \"type\": \"object\",\n#   \"properties\": {\n#     \"name\": {\"type\": \"string\", \"minLength\": 1},\n#     \"price\": {\"type\": \"number\", \"exclusiveMinimum\": 0}\n#   },\n#   \"required\": [\"name\", \"price\"]\n# }\n```\n\nFastAPI embeds this schema in `\u002Fopenapi.json` automatically. You might call\n`model_json_schema()` directly to validate schemas in tests or export them to\nother systems.\n\nRule of thumb: write a test that calls `model_json_schema()` and asserts key\nproperties — it catches breaking schema changes before they hit production.\n",{"id":408,"difficulty":96,"q":409,"a":410},"pydantic-v1-vs-v2","What are the key differences between Pydantic v1 and v2 that affect FastAPI code?","| Feature | Pydantic v1 | Pydantic v2 |\n|---------|-------------|-------------|\n| Config | `class Config:` | `model_config = ConfigDict(...)` |\n| ORM mode | `orm_mode = True` | `from_attributes=True` |\n| Serialise | `.dict()` \u002F `.json()` | `.model_dump()` \u002F `.model_dump_json()` |\n| Parse | `MyModel(**data)` \u002F `.parse_obj()` | `.model_validate(data)` |\n| Validators | `@validator` | `@field_validator` \u002F `@model_validator` |\n| Performance | Pure Python | Rust core (10-50× faster) |\n\nFastAPI 0.100+ requires Pydantic v2. Code targeting both versions uses the\n`pydantic.v1` compatibility shim.\n\nRule of thumb: always use Pydantic v2 APIs in new code; if maintaining a\nv1 codebase, migrate validators first (they have the most breaking changes).\n",{"id":412,"difficulty":125,"q":413,"a":414},"discriminated-union","What is a discriminated union in Pydantic and when is it useful in FastAPI?","A discriminated union uses a `Literal` field as a **type tag** to unambiguously\nselect which model to use during parsing, instead of trying each model in order.\n\n```python\nfrom typing import Literal, Union, Annotated\nfrom pydantic import BaseModel, Field\n\nclass Cat(BaseModel):\n    type: Literal[\"cat\"]\n    meows: bool\n\nclass Dog(BaseModel):\n    type: Literal[\"dog\"]\n    barks: bool\n\nclass PetPayload(BaseModel):\n    pet: Annotated[Union[Cat, Dog], Field(discriminator=\"type\")]\n\npayload = PetPayload.model_validate({\"pet\": {\"type\": \"dog\", \"barks\": True}})\nprint(type(payload.pet))  # \u003Cclass 'Dog'>\n```\n\nDiscriminated unions are:\n- **Faster** — no trial-and-error parsing.\n- **Clearer errors** — \"expected type to be 'cat' or 'dog'\" vs generic failure.\n- **Better OpenAPI** — generates `oneOf` with a discriminator property.\n\nRule of thumb: whenever a body can be one of several shapes, add a `type` tag\nand use a discriminated union — it's explicit, fast, and self-documenting.\n",13,{"description":94},"FastAPI Pydantic model interview questions — BaseModel, Field, model_config, nested models, inheritance, computed fields and ORM mode.","fastapi\u002Fpydantic\u002Fmodels","Pydantic Models","Aj-1OWK5V5w5kHw0JCExAUHqEZCCOYZA0iqfVhNFxzw",{"id":422,"title":423,"body":424,"description":94,"difficulty":104,"extension":97,"framework":10,"frameworkSlug":8,"meta":428,"navigation":99,"order":13,"path":429,"questions":430,"questionsCount":487,"related":149,"seo":488,"seoDescription":489,"stem":490,"subtopic":491,"topic":28,"topicSlug":30,"updated":154,"__hash__":492},"qa\u002Ffastapi\u002Frouting\u002Fpath-query-params.md","Path Query Params",{"type":91,"value":425,"toc":426},[],{"title":94,"searchDepth":29,"depth":29,"links":427},[],{},"\u002Ffastapi\u002Frouting\u002Fpath-query-params",[431,435,439,443,447,451,455,459,463,467,471,475,479,483],{"id":432,"difficulty":104,"q":433,"a":434},"path-param-basics","How do you define and read a path parameter in FastAPI?","Declare the parameter name inside curly braces in the path string and add a\nmatching argument to the handler function. FastAPI parses and type-coerces it\nautomatically.\n\n```python\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(item_id: int):\n    return {\"item_id\": item_id}\n# GET \u002Fitems\u002F42 → {\"item_id\": 42} (integer, not string)\n```\n\nIf the value can't be coerced to the declared type, FastAPI returns 422.\n\nRule of thumb: always type-annotate path parameters so FastAPI validates the URL\nsegment before the handler runs.\n",{"id":436,"difficulty":104,"q":437,"a":438},"query-param-basics","How do you define a required query parameter in FastAPI?","Declare a function parameter with a type annotation and **no default value**.\nFastAPI treats any simple type not in the path template as a query parameter.\n\n```python\n@app.get(\"\u002Fitems\")\nasync def search_items(q: str):\n    return {\"q\": q}\n# GET \u002Fitems?q=laptop → {\"q\": \"laptop\"}\n# GET \u002Fitems        → 422 (required param missing)\n```\n\nRule of thumb: no default → required query param; add `= None` or `= value`\nto make it optional.\n",{"id":440,"difficulty":104,"q":441,"a":442},"query-param-default","How do you give a query parameter a default value?","Assign a default in the function signature. FastAPI uses it when the client\nomits the parameter.\n\n```python\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    page: int = 1,\n    size: int = 20,\n    active: bool = True,\n):\n    return {\"page\": page, \"size\": size, \"active\": active}\n# GET \u002Fitems → {\"page\": 1, \"size\": 20, \"active\": true}\n# GET \u002Fitems?page=3 → {\"page\": 3, \"size\": 20, \"active\": true}\n```\n\nRule of thumb: choose defaults that represent the most common use case so\nclients don't need to pass boilerplate on every request.\n",{"id":444,"difficulty":104,"q":445,"a":446},"bool-query-param","How does FastAPI parse boolean query parameters?","FastAPI accepts a flexible range of truthy\u002Ffalsy string values and converts them\nto Python `bool`:\n\n- Truthy: `\"1\"`, `\"true\"`, `\"on\"`, `\"yes\"` (case-insensitive)\n- Falsy: `\"0\"`, `\"false\"`, `\"off\"`, `\"no\"` (case-insensitive)\n\n```python\n@app.get(\"\u002Fitems\")\nasync def list_items(active: bool = True):\n    ...\n# GET \u002Fitems?active=false → active = False\n# GET \u002Fitems?active=0    → active = False\n# GET \u002Fitems?active=yes  → active = True\n```\n\nAny other value triggers a 422 validation error.\n\nRule of thumb: use `bool` for feature flags\u002Ffilters in query params — FastAPI's\nflexible string-to-bool coercion covers all common client conventions.\n",{"id":448,"difficulty":96,"q":449,"a":450},"path-query-same-name","What happens if a path parameter and a query parameter have the same name?","FastAPI gives priority to the **path parameter**. A parameter name that appears\nin the route template `\u002F{name}` is always a path parameter; you cannot also read\nit from the query string with the same name in the same handler.\n\n```python\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int, q: str | None = None):\n    # id → from path, q → from query\n    ...\n```\n\nIf you genuinely need both a path and query parameter with the same name, use an\nalias: `Query(alias=\"id\")` — though this is a design smell.\n\nRule of thumb: keep path and query parameter names distinct; collision means\nyou should rethink the route design.\n",{"id":452,"difficulty":96,"q":453,"a":454},"path-helper","What is `Path()` and when should you use it instead of a bare type annotation?","`Path()` is FastAPI's parameter helper for adding **metadata and validation\nconstraints** to path parameters while keeping the type annotation clean.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Path\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: Annotated[int, Path(title=\"Item ID\", ge=1, le=999_999)],\n):\n    return {\"id\": item_id}\n```\n\nConstraints (`ge`, `le`, `gt`, `lt`) are enforced at validation time and\nreflected in the OpenAPI schema. You can also pass `description`, `example`, and\n`deprecated`.\n\nRule of thumb: use `Path()` whenever a path parameter has a meaningful range,\ndescription, or example worth documenting in the schema.\n",{"id":456,"difficulty":96,"q":457,"a":458},"query-helper","What extra control does `Query()` give you over a plain query parameter annotation?","`Query()` adds string constraints (`min_length`, `max_length`, `pattern`),\nnumeric bounds, metadata (`title`, `description`, `example`), and the ability\nto collect multi-value parameters.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fsearch\")\nasync def search(\n    q: Annotated[str, Query(\n        min_length=3,\n        max_length=100,\n        description=\"Search term\",\n        example=\"fastapi\",\n    )],\n    sort: Annotated[str, Query(pattern=r\"^(asc|desc)$\")] = \"asc\",\n):\n    ...\n```\n\nRule of thumb: add `Query()` the moment you need any constraint or documentation\non a query parameter — it's zero runtime cost and improves the schema immediately.\n",{"id":460,"difficulty":96,"q":461,"a":462},"multi-value-query","How do you accept a list of values for the same query parameter key?","Annotate with `list[T]` and wrap with `Query()`:\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def filter_items(\n    tags: Annotated[list[str], Query()] = [],\n):\n    return {\"tags\": tags}\n# GET \u002Fitems?tags=python&tags=web → {\"tags\": [\"python\", \"web\"]}\n```\n\nWithout `Query()`, `list[str]` would be interpreted as a JSON body parameter.\nAn empty list default (`= []`) means the param is optional; use `= Query(min_length=1)`\non the list if at least one tag is required.\n\nRule of thumb: `list[T]` + `Query()` = multi-value param; the client repeats\nthe key multiple times in the query string.\n",{"id":464,"difficulty":96,"q":465,"a":466},"alias-parameter","How do you use a different query string key than the Python parameter name?","Pass `alias=` to `Query()` or `Path()`. FastAPI reads the value from the alias\nkey in the request but binds it to the Python name in the handler.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    item_query: Annotated[str | None, Query(alias=\"item-query\")] = None,\n):\n    return {\"query\": item_query}\n# GET \u002Fitems?item-query=foo → item_query = \"foo\"\n```\n\nThis is useful when the URL convention requires hyphens (which are invalid\nPython identifiers) or when you're preserving backward-compatible parameter names.\n\nRule of thumb: use `alias` to bridge the gap between URL naming conventions\n(hyphens) and Python naming conventions (underscores).\n",{"id":468,"difficulty":104,"q":469,"a":470},"deprecated-param","How do you mark a query parameter as deprecated in the OpenAPI schema?","Pass `deprecated=True` to `Query()` or `Path()`. The parameter still works\nat runtime — it's marked deprecated in the generated schema only.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    q: Annotated[str | None, Query()] = None,\n    search: Annotated[str | None, Query(deprecated=True)] = None,  # old alias\n):\n    effective_q = q or search\n    ...\n```\n\nSwagger UI renders deprecated parameters with a strikethrough and a warning badge.\n\nRule of thumb: mark old parameter names deprecated rather than removing them\nimmediately — gives clients a migration window while keeping the schema honest.\n",{"id":472,"difficulty":96,"q":473,"a":474},"numeric-constraints","What numeric constraints can you apply to path and query parameters in FastAPI?","| Constraint | Meaning |\n|------------|---------|\n| `ge=n` | greater than or equal (`>=`) |\n| `gt=n` | strictly greater than (`>`) |\n| `le=n` | less than or equal (`\u003C=`) |\n| `lt=n` | strictly less than (`\u003C`) |\n| `multiple_of=n` | value must be a multiple of n |\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query, Path\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: Annotated[int, Path(ge=1)],           # ID must be positive\n    page: Annotated[int, Query(ge=1, le=100)] = 1, # page 1-100\n    price_min: Annotated[float, Query(gt=0)] = 0.0,\n):\n    ...\n```\n\nConstraints are reflected in the OpenAPI JSON Schema properties so client\nvalidators can enforce them before the request is sent.\n\nRule of thumb: apply `ge=1` to all ID path parameters — negative or zero IDs\nare almost always bugs.\n",{"id":476,"difficulty":96,"q":477,"a":478},"string-constraints","What string constraints are available for query parameters?","| Constraint | Meaning |\n|------------|---------|\n| `min_length=n` | minimum string length |\n| `max_length=n` | maximum string length |\n| `pattern=r\"...\"` | regex the value must match |\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fusers\")\nasync def search_users(\n    username: Annotated[str, Query(min_length=3, max_length=50, pattern=r\"^\\w+$\")],\n):\n    ...\n# GET \u002Fusers?username=al → 422 (min_length=3)\n# GET \u002Fusers?username=al!ce → 422 (pattern)\n```\n\nRule of thumb: always set `max_length` on free-text query params to prevent\naccidental DoS from clients sending huge query strings.\n",{"id":480,"difficulty":104,"q":481,"a":482},"exclude-from-schema","How do you hide a query parameter from the OpenAPI schema?","Pass `include_in_schema=False` to `Query()`. The parameter still works at runtime\nbut won't appear in `\u002Fopenapi.json` or the docs.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    q: str | None = None,\n    _internal_trace_id: Annotated[str | None, Query(include_in_schema=False)] = None,\n):\n    ...\n```\n\nRule of thumb: use `include_in_schema=False` for internal\u002Finfra params\n(tracing IDs, A\u002FB test flags) that aren't part of the public API contract.\n",{"id":484,"difficulty":96,"q":485,"a":486},"path-vs-query-design","When should a resource identifier be a path parameter vs a query parameter?","**Path parameters** are for identifying a specific resource:\n```python\nGET \u002Fusers\u002F{user_id}       # identifies a specific user\nGET \u002Forders\u002F{order_id}\u002Fitems  # items within a specific order\n```\n\n**Query parameters** are for filtering, sorting, searching or pagination of a collection:\n```python\nGET \u002Fusers?role=admin&page=2   # filter users by role, paginate\nGET \u002Forders?status=pending     # filter orders\n```\n\nMixing them up leads to ugly URLs like `\u002Fusers?id=42` (should be `\u002Fusers\u002F42`)\nor `\u002Fusers\u002Fadmin` for a filter (should be `\u002Fusers?role=admin`).\n\nRule of thumb: if removing the identifier would leave a meaningless URL\n(`\u002Fusers\u002F` is just a list), it belongs in the path; if it's a filter, it's a query param.\n",14,{"description":94},"FastAPI path and query parameter interview questions — type coercion, optional params, validation constraints, multi-value query params and Path\u002FQuery helpers.","fastapi\u002Frouting\u002Fpath-query-params","Path & Query Parameters","X4UtReULzyfYdlFbaM_Q6J6VrwdK5UfMWRcLWh4kW0I",{"id":494,"title":495,"body":496,"description":94,"difficulty":125,"extension":97,"framework":10,"frameworkSlug":8,"meta":500,"navigation":99,"order":13,"path":501,"questions":502,"questionsCount":148,"related":149,"seo":547,"seoDescription":548,"stem":549,"subtopic":550,"topic":55,"topicSlug":57,"updated":154,"__hash__":551},"qa\u002Ffastapi\u002Fsecurity\u002Foauth2.md","Oauth2",{"type":91,"value":497,"toc":498},[],{"title":94,"searchDepth":29,"depth":29,"links":499},[],{},"\u002Ffastapi\u002Fsecurity\u002Foauth2",[503,507,511,515,519,523,527,531,535,539,543],{"id":504,"difficulty":96,"q":505,"a":506},"oauth2-password-bearer","What is `OAuth2PasswordBearer` in FastAPI and what does it do?","`OAuth2PasswordBearer` is a callable security dependency that:\n1. Extracts the Bearer token from the `Authorization: Bearer \u003Ctoken>` header.\n2. Returns the token string to your handler.\n3. Registers the OAuth2 password flow in the OpenAPI schema so Swagger UI shows\n   an \"Authorize\" dialog.\n\n```python\nfrom fastapi.security import OAuth2PasswordBearer\nfrom fastapi import Depends\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Ftoken\")\n\n@app.get(\"\u002Fme\")\nasync def current_user(token: str = Depends(oauth2_scheme)):\n    # token is the raw Bearer string — validate it yourself\n    payload = decode_jwt(token)\n    return payload\n```\n\n`OAuth2PasswordBearer` only extracts the token — it does NOT validate it.\nValidation is your responsibility.\n\nRule of thumb: use `OAuth2PasswordBearer` so Swagger UI gets the Authorize button;\ndo JWT decoding\u002Fverification in a separate `get_current_user` dependency.\n",{"id":508,"difficulty":96,"q":509,"a":510},"token-endpoint","How do you implement the `\u002Ftoken` endpoint for the OAuth2 password flow in FastAPI?","Accept `OAuth2PasswordRequestForm` (username + password as form data) and return\na token response:\n\n```python\nfrom fastapi import Depends\nfrom fastapi.security import OAuth2PasswordRequestForm\nfrom jose import jwt\n\n@app.post(\"\u002Ftoken\")\nasync def login(form: OAuth2PasswordRequestForm = Depends()):\n    user = await authenticate_user(form.username, form.password)\n    if not user:\n        raise HTTPException(\n            status_code=400, detail=\"Incorrect username or password\"\n        )\n    access_token = jwt.encode(\n        {\"sub\": str(user.id), \"exp\": datetime.utcnow() + timedelta(minutes=30)},\n        settings.secret_key, algorithm=\"HS256\"\n    )\n    return {\"access_token\": access_token, \"token_type\": \"bearer\"}\n```\n\nThe response must have `access_token` and `token_type` fields — Swagger UI\nreads these to store the token.\n\nRule of thumb: never return the user's password (even hashed) from `\u002Ftoken`;\nreturn only the token and its type.\n",{"id":512,"difficulty":96,"q":513,"a":514},"password-hashing","How do you hash and verify passwords securely in FastAPI?","Use `passlib` with `bcrypt`:\n\n```python\nfrom passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\ndef hash_password(plain: str) -> str:\n    return pwd_context.hash(plain)\n\ndef verify_password(plain: str, hashed: str) -> bool:\n    return pwd_context.verify(plain, hashed)\n\nasync def authenticate_user(username: str, password: str):\n    user = await db.get_user_by_username(username)\n    if not user or not verify_password(password, user.password_hash):\n        return None\n    return user\n```\n\nNever store plain-text passwords. `bcrypt` automatically includes a salt and\nis resistant to GPU-accelerated brute-force attacks.\n\nRule of thumb: use `passlib.CryptContext` with `bcrypt` — it handles salting,\nalgorithm upgrades (`deprecated=\"auto\"`) and constant-time comparison.\n",{"id":516,"difficulty":96,"q":517,"a":518},"get-current-user-dep","Show the standard pattern for a `get_current_user` dependency in FastAPI.","```python\nfrom fastapi import Depends, HTTPException\nfrom fastapi.security import OAuth2PasswordBearer\nfrom jose import jwt, JWTError\nfrom pydantic import BaseModel\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Ftoken\")\n\nclass TokenData(BaseModel):\n    user_id: int\n\nasync def get_current_user(token: str = Depends(oauth2_scheme)):\n    credentials_exception = HTTPException(\n        status_code=401,\n        detail=\"Could not validate credentials\",\n        headers={\"WWW-Authenticate\": \"Bearer\"},\n    )\n    try:\n        payload = jwt.decode(token, settings.secret_key, algorithms=[\"HS256\"])\n        user_id: int = payload.get(\"sub\")\n        if user_id is None:\n            raise credentials_exception\n    except JWTError:\n        raise credentials_exception\n    user = await db.get_user(user_id)\n    if not user:\n        raise credentials_exception\n    return user\n\n@app.get(\"\u002Fme\")\nasync def me(user: User = Depends(get_current_user)):\n    return user\n```\n\nRule of thumb: always include `headers={\"WWW-Authenticate\": \"Bearer\"}` on 401\nresponses — RFC 7235 requires it and some clients depend on it.\n",{"id":520,"difficulty":125,"q":521,"a":522},"oauth2-scopes","How do you implement OAuth2 scopes in FastAPI?","Define scopes in `OAuth2PasswordBearer`, encode them in the token, and verify\nthem with `SecurityScopes`:\n\n```python\nfrom fastapi import Security, SecurityScopes\nfrom fastapi.security import OAuth2PasswordBearer\n\noauth2_scheme = OAuth2PasswordBearer(\n    tokenUrl=\"\u002Ftoken\",\n    scopes={\"read\": \"Read items\", \"write\": \"Create and update items\"},\n)\n\nasync def get_current_user(\n    security_scopes: SecurityScopes,\n    token: str = Depends(oauth2_scheme),\n):\n    payload = jwt.decode(token, settings.secret_key, algorithms=[\"HS256\"])\n    token_scopes = payload.get(\"scopes\", [])\n    for scope in security_scopes.scopes:\n        if scope not in token_scopes:\n            raise HTTPException(\n                403,\n                detail=f\"Scope '{scope}' required\",\n                headers={\"WWW-Authenticate\": f'Bearer scope=\"{security_scopes.scope_str}\"'},\n            )\n    return payload\n\n@app.get(\"\u002Fitems\")\nasync def list_items(user = Security(get_current_user, scopes=[\"read\"])):\n    ...\n```\n\nUse `Security()` (not `Depends()`) to pass scope requirements.\n\nRule of thumb: encode granted scopes in the JWT at login time; verify required\nscopes in `get_current_user` — keep scope logic in the auth dependency, not handlers.\n",{"id":524,"difficulty":104,"q":525,"a":526},"inactive-user","How do you block inactive or disabled users from accessing protected routes?","Add an `is_active` check in `get_current_user` or a separate `require_active_user`\ndependency:\n\n```python\nasync def get_current_active_user(\n    user: User = Depends(get_current_user),\n) -> User:\n    if not user.is_active:\n        raise HTTPException(status_code=400, detail=\"Inactive user\")\n    return user\n\n@app.get(\"\u002Forders\")\nasync def orders(user: User = Depends(get_current_active_user)):\n    ...\n```\n\nSeparating \"can we trust the token?\" (`get_current_user`) from \"is the user\nallowed?\" (`get_current_active_user`) makes the dependency graph cleaner and\neach function independently testable.\n\nRule of thumb: chain fine-grained permission checks as separate dependencies —\ndon't put all guard logic in one giant `get_current_user` function.\n",{"id":528,"difficulty":125,"q":529,"a":530},"refresh-token","How would you implement refresh tokens in a FastAPI application?","Issue two tokens at login: a short-lived access token and a long-lived refresh token.\nStore the refresh token hash in the DB. Provide a `\u002Frefresh` endpoint:\n\n```python\n@app.post(\"\u002Frefresh\")\nasync def refresh_token(refresh_token: str = Body(...)):\n    # 1. Verify it's a valid JWT\n    try:\n        payload = jwt.decode(refresh_token, REFRESH_SECRET, algorithms=[\"HS256\"])\n    except JWTError:\n        raise HTTPException(401, \"Invalid refresh token\")\n\n    # 2. Check it exists and isn't revoked in the DB\n    stored = await db.get_refresh_token(payload[\"jti\"])\n    if not stored or stored.revoked:\n        raise HTTPException(401, \"Refresh token revoked\")\n\n    # 3. Issue new access token (optionally rotate refresh token)\n    new_access = create_access_token(payload[\"sub\"])\n    return {\"access_token\": new_access, \"token_type\": \"bearer\"}\n```\n\nKey design decisions: rotate refresh tokens on each use (reduces replay risk),\nstore a `jti` (JWT ID) for revocation, use a different signing secret for refresh tokens.\n\nRule of thumb: keep access tokens short (15-30 min); keep refresh tokens\nlong (7-30 days) but revocable via a DB lookup.\n",{"id":532,"difficulty":125,"q":533,"a":534},"logout-token-revocation","How do you implement logout \u002F token revocation for JWT-based auth in FastAPI?","JWTs are stateless — you can't \"un-sign\" one. Revocation requires a server-side\nstore:\n\n**Option A — Revocation list (deny list)**:\n```python\n@app.post(\"\u002Flogout\")\nasync def logout(token: str = Depends(oauth2_scheme)):\n    payload = jwt.decode(token, SECRET_KEY, algorithms=[\"HS256\"])\n    jti = payload[\"jti\"]\n    exp = payload[\"exp\"]\n    await redis.setex(f\"revoked:{jti}\", exp - int(time.time()), \"1\")\n    return {\"detail\": \"Logged out\"}\n\n# In get_current_user, after decoding:\nif await redis.exists(f\"revoked:{payload['jti']}\"):\n    raise HTTPException(401, \"Token revoked\")\n```\n\n**Option B — Short expiry + refresh rotation** (no revocation list needed):\nAccess token expires in 5 minutes; compromised tokens are useless quickly.\n\nRule of thumb: for low-security APIs, use short-lived tokens; for high-security\napps (banking, health), maintain a revocation list in Redis keyed by JWT ID.\n",{"id":536,"difficulty":104,"q":537,"a":538},"oauth2-form-vs-json","Why does the OAuth2 `\u002Ftoken` endpoint accept form data instead of JSON?","The **OAuth2 specification (RFC 6749)** requires the token endpoint to accept\n`application\u002Fx-www-form-urlencoded` data — not JSON. This is for historical\ncompatibility with web browsers and existing OAuth2 clients.\n\nFastAPI's `OAuth2PasswordRequestForm` reads:\n- `username` (form field)\n- `password` (form field)\n- `scope` (optional, space-separated)\n- `grant_type` (must be `\"password\"`)\n\n```python\n@app.post(\"\u002Ftoken\")\nasync def login(form: OAuth2PasswordRequestForm = Depends()):\n    # form.username, form.password, form.scopes\n    ...\n```\n\nSwagger UI's \"Authorize\" dialog sends this form automatically.\n\nRule of thumb: never change the `\u002Ftoken` endpoint to JSON — it breaks OAuth2\nlibrary compatibility; keep all other API endpoints as JSON.\n",{"id":540,"difficulty":125,"q":541,"a":542},"cookie-auth","How do you implement cookie-based authentication in FastAPI?","Use `APIKeyCookie` from `fastapi.security` or manually read the cookie:\n\n```python\nfrom fastapi import Cookie, Response\nfrom fastapi.security import APIKeyCookie\n\ncookie_scheme = APIKeyCookie(name=\"session_id\")\n\n@app.post(\"\u002Flogin\")\nasync def login(response: Response, form: OAuth2PasswordRequestForm = Depends()):\n    user = await authenticate_user(form.username, form.password)\n    if not user:\n        raise HTTPException(400, \"Invalid credentials\")\n    token = create_session_token(user.id)\n    response.set_cookie(\n        key=\"session_id\",\n        value=token,\n        httponly=True,    # no JS access\n        secure=True,      # HTTPS only\n        samesite=\"lax\",\n    )\n    return {\"status\": \"logged in\"}\n\n@app.get(\"\u002Fme\")\nasync def me(session: str = Depends(cookie_scheme)):\n    user_id = validate_session(session)\n    return await db.get_user(user_id)\n```\n\nRule of thumb: set `httponly=True`, `secure=True`, and `samesite=\"lax\"` on\nauth cookies — these three flags prevent XSS theft, HTTP sniffing, and CSRF.\n",{"id":544,"difficulty":125,"q":545,"a":546},"two-factor-auth","How would you add TOTP two-factor authentication to a FastAPI app?","Use the `pyotp` library to generate and verify TOTP codes:\n\n```python\nimport pyotp\n\n@app.post(\"\u002F2fa\u002Fsetup\")\nasync def setup_2fa(user: User = Depends(get_current_user)):\n    secret = pyotp.random_base32()\n    await db.save_totp_secret(user.id, secret)\n    totp = pyotp.TOTP(secret)\n    return {\"provisioning_uri\": totp.provisioning_uri(user.email, issuer_name=\"MyApp\")}\n\n@app.post(\"\u002F2fa\u002Fverify\")\nasync def verify_2fa(code: str = Body(...), user: User = Depends(get_current_user)):\n    secret = await db.get_totp_secret(user.id)\n    if not pyotp.TOTP(secret).verify(code):\n        raise HTTPException(401, \"Invalid 2FA code\")\n    # issue the final access token\n    return {\"access_token\": create_access_token(user.id), \"token_type\": \"bearer\"}\n```\n\nThe flow: login → partial token → `\u002F2fa\u002Fverify` with code → full access token.\n\nRule of thumb: issue a scoped \"pending 2FA\" token after password verification;\nonly issue a full-access token after TOTP is confirmed.\n",{"description":94},"FastAPI OAuth2 interview questions — OAuth2PasswordBearer, password flow, token endpoint, scopes and securing routes with OAuth2.","fastapi\u002Fsecurity\u002Foauth2","OAuth2","VfoQ1II42taw05rn_T0KjOE4Ue0T3JvJjExIxKJz_hg",{"id":553,"title":554,"body":555,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":559,"navigation":99,"order":13,"path":560,"questions":561,"questionsCount":214,"related":149,"seo":610,"seoDescription":611,"stem":612,"subtopic":613,"topic":72,"topicSlug":74,"updated":154,"__hash__":614},"qa\u002Ffastapi\u002Ftesting\u002Ftest-client.md","Test Client",{"type":91,"value":556,"toc":557},[],{"title":94,"searchDepth":29,"depth":29,"links":558},[],{},"\u002Ffastapi\u002Ftesting\u002Ftest-client",[562,566,570,574,578,582,586,590,594,598,602,606],{"id":563,"difficulty":104,"q":564,"a":565},"testclient-basics","What is `TestClient` in FastAPI and how do you use it?","`TestClient` (from `starlette.testclient`) wraps your FastAPI app in a\n`requests`-compatible interface. It runs the full ASGI stack synchronously\nso you can test endpoints without starting a real server.\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\n\napp = FastAPI()\n\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int):\n    return {\"id\": id}\n\nclient = TestClient(app)\n\ndef test_get_item():\n    response = client.get(\"\u002Fitems\u002F42\")\n    assert response.status_code == 200\n    assert response.json() == {\"id\": 42}\n```\n\nRule of thumb: create `TestClient` once per module (or as a pytest fixture)\nrather than on every test — instantiation is cheap but consistent fixture\nscope is cleaner.\n",{"id":567,"difficulty":96,"q":568,"a":569},"testclient-lifespan","How do you ensure lifespan events (startup\u002Fshutdown) run in tests?","Use `TestClient` as a **context manager**:\n\n```python\nfrom fastapi.testclient import TestClient\nfrom app.main import app\n\ndef test_with_lifespan():\n    with TestClient(app) as client:\n        # startup has run (DB connected, app.state populated)\n        response = client.get(\"\u002Fhealth\")\n        assert response.status_code == 200\n    # shutdown has run (DB disconnected)\n```\n\nWithout the `with` block, lifespan events don't run, and any code that depends\non `app.state` will fail.\n\nRule of thumb: always use `with TestClient(app) as client:` in tests that need\nstartup resources; plain `TestClient(app)` is fine for stateless routes.\n",{"id":571,"difficulty":104,"q":572,"a":573},"pytest-fixture-client","How do you set up a pytest fixture for a FastAPI `TestClient`?","```python\nimport pytest\nfrom fastapi.testclient import TestClient\nfrom app.main import app\n\n@pytest.fixture(scope=\"module\")\ndef client():\n    with TestClient(app) as c:\n        yield c\n\ndef test_list_items(client):\n    resp = client.get(\"\u002Fitems\")\n    assert resp.status_code == 200\n\ndef test_create_item(client):\n    resp = client.post(\"\u002Fitems\", json={\"name\": \"Widget\", \"price\": 9.99})\n    assert resp.status_code == 201\n```\n\n`scope=\"module\"` starts the app once per module, which is faster than per-test.\nUse `scope=\"function\"` if tests mutate state that must be reset.\n\nRule of thumb: use `scope=\"module\"` for read-heavy test files; use `scope=\"function\"`\nfor tests that write to a database or modify `app.state`.\n",{"id":575,"difficulty":104,"q":576,"a":577},"json-body-test","How do you send a JSON body in a TestClient POST request?","Pass the `json=` keyword argument — TestClient serialises it and sets\n`Content-Type: application\u002Fjson` automatically:\n\n```python\ndef test_create_item(client):\n    response = client.post(\n        \"\u002Fitems\",\n        json={\"name\": \"Widget\", \"price\": 9.99},\n    )\n    assert response.status_code == 201\n    data = response.json()\n    assert data[\"name\"] == \"Widget\"\n    assert \"id\" in data\n```\n\nFor raw string bodies: `data='{\"name\":\"Widget\"}'` with `headers={\"Content-Type\": \"application\u002Fjson\"}`.\n\nRule of thumb: always use `json=` (not `data=`) for JSON payloads — it handles\nserialisation correctly and sets the content type header.\n",{"id":579,"difficulty":104,"q":580,"a":581},"headers-test","How do you send custom headers (e.g., Authorization) in TestClient?","Pass `headers=` dict to the request method:\n\n```python\ndef test_authenticated(client):\n    token = create_test_token(user_id=1)\n    response = client.get(\n        \"\u002Fme\",\n        headers={\"Authorization\": f\"Bearer {token}\"},\n    )\n    assert response.status_code == 200\n```\n\nOr set default headers on the client itself:\n```python\n@pytest.fixture\ndef auth_client(client):\n    token = create_test_token(user_id=1)\n    client.headers.update({\"Authorization\": f\"Bearer {token}\"})\n    return client\n```\n\nRule of thumb: create a separate `auth_client` fixture for tests that always\nneed auth headers — it avoids repeating the header on every request.\n",{"id":583,"difficulty":104,"q":584,"a":585},"query-params-test","How do you test query parameters with TestClient?","Pass `params=` dict:\n\n```python\ndef test_search(client):\n    response = client.get(\"\u002Fitems\", params={\"q\": \"widget\", \"page\": 2, \"size\": 10})\n    assert response.status_code == 200\n    # equivalent to GET \u002Fitems?q=widget&page=2&size=10\n```\n\nFor multi-value query params:\n```python\nresponse = client.get(\"\u002Fitems\", params=[(\"tags\", \"python\"), (\"tags\", \"web\")])\n# GET \u002Fitems?tags=python&tags=web\n```\n\nRule of thumb: use `params=` dict for simple query strings; use a list of tuples\nwhen the same key appears multiple times.\n",{"id":587,"difficulty":96,"q":588,"a":589},"cookies-test","How do you send cookies and test cookie-based auth with TestClient?","Set cookies on the client or per-request:\n\n```python\n# Per-request\ndef test_cookie_auth(client):\n    response = client.get(\"\u002Fprofile\", cookies={\"session\": \"valid-session-token\"})\n    assert response.status_code == 200\n\n# Persistent across requests (simulates browser session)\nclient.cookies.set(\"session\", \"valid-session-token\")\nresponse = client.get(\"\u002Fprofile\")\n```\n\nTestClient automatically carries `Set-Cookie` headers between requests\nwhen used as a context manager — simulating a browser:\n\n```python\nwith TestClient(app) as client:\n    client.post(\"\u002Flogin\", data={\"username\": \"alice\", \"password\": \"secret\"})\n    # client now has the session cookie\n    response = client.get(\"\u002Fprofile\")\n    assert response.status_code == 200\n```\n\nRule of thumb: use the `with TestClient(app)` context manager for tests that\nsimulate a full login → use → logout flow.\n",{"id":591,"difficulty":104,"q":592,"a":593},"form-data-test","How do you test form data submission with TestClient?","Pass `data=` (not `json=`) for URL-encoded form data:\n\n```python\ndef test_login_form(client):\n    response = client.post(\n        \"\u002Ftoken\",\n        data={\"username\": \"alice\", \"password\": \"secret\", \"grant_type\": \"password\"},\n    )\n    assert response.status_code == 200\n    assert \"access_token\" in response.json()\n```\n\nFor multipart file upload:\n```python\ndef test_file_upload(client):\n    response = client.post(\n        \"\u002Fupload\",\n        files={\"file\": (\"report.csv\", b\"id,name\\n1,Alice\", \"text\u002Fcsv\")},\n    )\n    assert response.status_code == 200\n```\n\nRule of thumb: use `data=` for form fields, `files=` for file uploads; never\nmix `json=` with `data=` in the same request.\n",{"id":595,"difficulty":96,"q":596,"a":597},"assert-validation-error","How do you test that FastAPI returns 422 for invalid input?","Send an intentionally malformed request and assert on the 422 status code and\nerror detail structure:\n\n```python\ndef test_invalid_price(client):\n    response = client.post(\"\u002Fitems\", json={\"name\": \"Widget\", \"price\": \"not-a-number\"})\n    assert response.status_code == 422\n    errors = response.json()[\"detail\"]\n    assert any(e[\"loc\"] == [\"body\", \"price\"] for e in errors)\n    assert any(e[\"type\"] == \"float_parsing\" for e in errors)\n```\n\nCheck `loc` (where the error is) and `type` (what kind of error) rather than\nthe `msg` string — messages can change between Pydantic versions.\n\nRule of thumb: assert on `detail[*].loc` and `detail[*].type` for validation\nerrors — these are stable; `msg` wording changes between library versions.\n",{"id":599,"difficulty":96,"q":600,"a":601},"raise-on-server-error","What is `raise_server_exceptions` and when should you disable it?","`TestClient` re-raises server-side exceptions by default (`raise_server_exceptions=True`).\nThis turns a 500 response into a Python exception in the test — useful for\ndebugging but unhelpful when you're testing error handling.\n\n```python\n# Test that a 500 is returned without raising\nclient = TestClient(app, raise_server_exceptions=False)\n\ndef test_internal_error():\n    response = client.get(\"\u002Fbroken\")\n    assert response.status_code == 500\n```\n\nOr use a context manager:\n```python\nwith TestClient(app, raise_server_exceptions=False) as client:\n    resp = client.get(\"\u002Fbroken\")\n    assert resp.status_code == 500\n```\n\nRule of thumb: keep the default (`True`) for development tests so unhandled\nexceptions surface immediately; set `False` when specifically testing error-handling middleware.\n",{"id":603,"difficulty":104,"q":604,"a":605},"test-redirect-follow","Does TestClient follow redirects by default and how do you control this?","`TestClient` follows redirects by default (`follow_redirects=True`). To test\nthat a redirect is issued without following it:\n\n```python\ndef test_redirect(client):\n    response = client.get(\"\u002Fold-path\", follow_redirects=False)\n    assert response.status_code == 301\n    assert response.headers[\"location\"] == \"\u002Fnew-path\"\n```\n\nTo disable redirect following globally:\n```python\nclient = TestClient(app, follow_redirects=False)\n```\n\nRule of thumb: disable `follow_redirects` when testing the redirect itself\n(status code + Location header); keep it enabled when you care about the\nfinal destination response.\n",{"id":607,"difficulty":96,"q":608,"a":609},"dependency-override-test","How do you replace a database dependency with an in-memory fake in tests?","Use `app.dependency_overrides`:\n\n```python\nfrom app.main import app\nfrom app.db import get_db\n\ndef fake_get_db():\n    db = FakeDatabase()\n    db.users = [{\"id\": 1, \"name\": \"Alice\"}]\n    yield db\n\napp.dependency_overrides[get_db] = fake_get_db\n\nclient = TestClient(app)\n\ndef test_list_users():\n    resp = client.get(\"\u002Fusers\")\n    assert resp.status_code == 200\n    assert len(resp.json()) == 1\n```\n\nAlways clean up after the test:\n```python\n@pytest.fixture(autouse=True)\ndef reset_overrides():\n    yield\n    app.dependency_overrides.clear()\n```\n\nRule of thumb: put `app.dependency_overrides.clear()` in an `autouse` fixture\nteardown — stale overrides in one test silently corrupt subsequent tests.\n",{"description":94},"FastAPI TestClient interview questions — requests-style testing, fixtures, status codes, headers, cookies and testing error responses.","fastapi\u002Ftesting\u002Ftest-client","TestClient","IleoACMiE0Rdhnr9eoEdzZ9DlwJxNrNr7NnNqdzKO-s",{"id":616,"title":617,"body":618,"description":94,"difficulty":125,"extension":97,"framework":10,"frameworkSlug":8,"meta":622,"navigation":99,"order":29,"path":623,"questions":624,"questionsCount":148,"related":149,"seo":669,"seoDescription":670,"stem":671,"subtopic":672,"topic":64,"topicSlug":65,"updated":154,"__hash__":673},"qa\u002Ffastapi\u002Fdatabase\u002Fasync-db.md","Async Db",{"type":91,"value":619,"toc":620},[],{"title":94,"searchDepth":29,"depth":29,"links":621},[],{},"\u002Ffastapi\u002Fdatabase\u002Fasync-db",[625,629,633,637,641,645,649,653,657,661,665],{"id":626,"difficulty":96,"q":627,"a":628},"why-async-db","Why use async database drivers with FastAPI instead of sync SQLAlchemy?","Synchronous DB drivers block the OS thread during the query. In a FastAPI\n`async def` handler, this freezes the **entire event loop** — no other requests\ncan be processed until the query returns.\n\n```python\n# BAD — sync driver inside async handler blocks the event loop\n@app.get(\"\u002Fusers\")\nasync def list_users(db: Session = Depends(get_db)):\n    return db.query(User).all()   # blocks event loop for entire query duration\n\n# GOOD — async driver yields control during the query\n@app.get(\"\u002Fusers\")\nasync def list_users(db: AsyncSession = Depends(get_async_db)):\n    result = await db.execute(select(User))\n    return result.scalars().all()\n```\n\nAsync drivers (`asyncpg`, `aiosqlite`, `aiomysql`) suspend the coroutine during\nI\u002FO, letting the event loop handle other requests.\n\nRule of thumb: always use async DB drivers in `async def` FastAPI handlers;\nuse sync drivers only with `def` handlers (FastAPI runs them in a thread pool).\n",{"id":630,"difficulty":96,"q":631,"a":632},"async-engine-setup","How do you set up an async SQLAlchemy engine and session for FastAPI?","```python\nfrom sqlalchemy.ext.asyncio import (\n    create_async_engine, AsyncSession, async_sessionmaker\n)\nfrom sqlalchemy.orm import DeclarativeBase\n\n# asyncpg driver for PostgreSQL\nDATABASE_URL = \"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost\u002Fmydb\"\n\nengine = create_async_engine(DATABASE_URL, pool_size=10, echo=False)\nasync_session_factory = async_sessionmaker(\n    engine, class_=AsyncSession, expire_on_commit=False\n)\n\nclass Base(DeclarativeBase):\n    pass\n```\n\n`expire_on_commit=False` prevents SQLAlchemy from expiring (lazily reloading)\nattributes after a commit — essential in async code where lazy loading would\ntrigger a new I\u002FO call without being awaited.\n\nRule of thumb: always set `expire_on_commit=False` for async sessions — expired\nattributes in async context cause `MissingGreenlet` or `DetachedInstanceError`.\n",{"id":634,"difficulty":96,"q":635,"a":636},"async-get-db","How do you write the `get_async_db` dependency for async SQLAlchemy?","```python\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom fastapi import Depends\n\nasync def get_async_db():\n    async with async_session_factory() as session:\n        yield session\n    # async context manager commits on clean exit, rolls back on exception\n\n# Or explicit try\u002Fexcept for fine-grained control:\nasync def get_async_db():\n    session = AsyncSession(engine)\n    try:\n        yield session\n        await session.commit()\n    except Exception:\n        await session.rollback()\n        raise\n    finally:\n        await session.close()\n\n@app.get(\"\u002Fusers\")\nasync def list_users(db: AsyncSession = Depends(get_async_db)):\n    result = await db.execute(select(User))\n    return result.scalars().all()\n```\n\nRule of thumb: prefer `async with async_session_factory() as session` — it\nhandles commit\u002Frollback automatically and is harder to get wrong.\n",{"id":638,"difficulty":96,"q":639,"a":640},"async-query-patterns","What are the key async query patterns with SQLAlchemy async?","```python\nfrom sqlalchemy import select, func\n\n# Single object by primary key\nuser = await db.get(User, user_id)\n\n# Filtered query\nresult = await db.execute(select(User).where(User.is_active == True))\nusers = result.scalars().all()\n\n# Single result\nresult = await db.execute(select(User).where(User.email == email))\nuser = result.scalar_one_or_none()   # None if not found, raises if multiple\n\n# Count\nresult = await db.execute(select(func.count()).select_from(User))\ncount = result.scalar()\n\n# Insert\ndb.add(User(name=\"Alice\", email=\"alice@example.com\"))\nawait db.commit()\n\n# Update\nawait db.execute(update(User).where(User.id == id).values(name=\"Bob\"))\nawait db.commit()\n```\n\nRule of thumb: always `await db.execute(stmt)` for queries; always `await db.commit()`\nafter writes — forgetting `await` on async methods causes silent failures.\n",{"id":642,"difficulty":125,"q":643,"a":644},"async-relationships","How do you load SQLAlchemy relationships in async code?","Lazy loading is **not available** in async SQLAlchemy — accessing an unloaded\nrelationship raises `MissingGreenlet`. You must eager-load:\n\n```python\nfrom sqlalchemy.orm import selectinload, joinedload\n\n# selectinload — runs a second SELECT IN query\nresult = await db.execute(\n    select(User).options(selectinload(User.posts)).where(User.id == user_id)\n)\nuser = result.scalar_one()\n# user.posts is now loaded — no extra query needed\n\n# joinedload — single JOIN query\nresult = await db.execute(\n    select(Post).options(joinedload(Post.author))\n)\nposts = result.unique().scalars().all()\n```\n\n`result.unique()` is needed after `joinedload` to de-duplicate rows from the JOIN.\n\nRule of thumb: always use `selectinload` (one-to-many) or `joinedload` (many-to-one)\nin async; lazy loading raises an error rather than silently blocking.\n",{"id":646,"difficulty":96,"q":647,"a":648},"asyncpg-vs-psycopg3","What async PostgreSQL drivers work with SQLAlchemy and FastAPI?","| Driver | URL prefix | Notes |\n|--------|-----------|-------|\n| `asyncpg` | `postgresql+asyncpg:\u002F\u002F` | Fastest, pure async; most popular |\n| `psycopg3` (psycopg) | `postgresql+psycopg:\u002F\u002F` | psycopg 3.x; sync + async |\n| `aiopg` | `postgresql+aiopg:\u002F\u002F` | Older, based on psycopg2 |\n\n```bash\npip install asyncpg            # for asyncpg\npip install psycopg[binary]   # for psycopg3\n```\n\n```python\n# asyncpg\nengine = create_async_engine(\"postgresql+asyncpg:\u002F\u002Fuser:pass@host\u002Fdb\")\n\n# psycopg3\nengine = create_async_engine(\"postgresql+psycopg:\u002F\u002Fuser:pass@host\u002Fdb\")\n```\n\n`asyncpg` is the most battle-tested; `psycopg3` supports binary protocol and\nis gaining adoption.\n\nRule of thumb: use `asyncpg` for new projects — it's the most performant and\nhas the widest ecosystem support.\n",{"id":650,"difficulty":104,"q":651,"a":652},"aiosqlite","How do you use SQLite with async FastAPI (e.g., for testing)?","Use the `aiosqlite` driver:\n\n```bash\npip install aiosqlite\n```\n\n```python\nfrom sqlalchemy.ext.asyncio import create_async_engine\n\n# In-memory for tests\nengine = create_async_engine(\"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\", echo=True)\n\n# File-based\nengine = create_async_engine(\"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Ftest.db\")\n```\n\nFor tests, create tables in the test fixture:\n```python\n@pytest.fixture\nasync def db_engine():\n    engine = create_async_engine(\"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\")\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield engine\n    await engine.dispose()\n```\n\nRule of thumb: use `sqlite+aiosqlite:\u002F\u002F\u002F:memory:` in tests for fast, isolated\nDB fixtures — no cleanup needed since the DB disappears when the engine is disposed.\n",{"id":654,"difficulty":125,"q":655,"a":656},"async-session-scoping","What is `async_scoped_session` and when would you use it?","`async_scoped_session` binds sessions to an `asyncio.Task` (coroutine) context\nrather than a thread. It's the async equivalent of `scoped_session`.\n\n```python\nimport asyncio\nfrom sqlalchemy.ext.asyncio import async_scoped_session\n\nAsyncScopedSession = async_scoped_session(\n    async_session_factory,\n    scopefunc=asyncio.current_task,\n)\n```\n\nWith `scopefunc=asyncio.current_task`, each asyncio Task gets its own session.\nCall `AsyncScopedSession.remove()` to close and remove the session at the end\nof a task.\n\nIn FastAPI, the per-request `get_async_db` yield dep is simpler and preferred.\n`async_scoped_session` is useful in background workers or batch scripts where\nyou don't have a FastAPI dependency container.\n\nRule of thumb: use `async_scoped_session` in standalone async scripts; use the\nyield dep pattern in FastAPI request handlers.\n",{"id":658,"difficulty":125,"q":659,"a":660},"run-sync-in-async-db","How do you run synchronous SQLAlchemy operations inside an async context?","Use `connection.run_sync()` to execute sync SQLAlchemy operations on the async\nconnection without blocking the event loop:\n\n```python\nasync with engine.begin() as conn:\n    # run_sync calls sync code inside the driver's thread pool\n    await conn.run_sync(Base.metadata.create_all)\n    await conn.run_sync(Base.metadata.drop_all)\n```\n\nFor executing sync SQLAlchemy session operations (bulk inserts with the legacy\nAPI):\n```python\nasync with AsyncSession(engine) as session:\n    def _sync_bulk_insert(sync_session):\n        sync_session.bulk_insert_mappings(User, data)\n    await session.run_sync(_sync_bulk_insert)\n```\n\nRule of thumb: use `run_sync` only for operations that have no async equivalent\n(Alembic migrations, legacy bulk operations); prefer the async API everywhere else.\n",{"id":662,"difficulty":125,"q":663,"a":664},"connection-pool-async","How does the async SQLAlchemy connection pool behave with multiple Uvicorn workers?","Each Uvicorn worker process has **its own event loop and connection pool**. With\n4 workers and `pool_size=10`, you have 40 connections total to the database.\n\n`asyncpg` uses a native connection pool built on the event loop — connections\nare not thread-safe and must be used within the same event loop they were created on.\n\n```python\nengine = create_async_engine(\n    DATABASE_URL,\n    pool_size=5,         # connections per worker\n    max_overflow=5,      # burst pool per worker\n    pool_timeout=30,\n    pool_recycle=3600,   # re-connect periodically\n)\n```\n\nPre-fork (Gunicorn) models must create the engine **after** forking — creating\nit before the fork shares connection file descriptors across workers, causing\ncorruption.\n\nRule of thumb: create the async engine inside the `lifespan` function so each\nworker process creates its own after forking.\n",{"id":666,"difficulty":125,"q":667,"a":668},"transactions-async","How do you manage database transactions explicitly in async SQLAlchemy?","By default, `AsyncSession` runs in autobegin mode — a transaction starts\nautomatically on the first operation. Use `begin()` for explicit transactions:\n\n```python\nasync with AsyncSession(engine) as session:\n    async with session.begin():\n        # all operations here are in one transaction\n        session.add(User(name=\"Alice\"))\n        session.add(Order(user_id=1, total=99.99))\n        # commit happens automatically when the context manager exits\n\n# Or manually:\nsession = AsyncSession(engine)\ntry:\n    session.add(user)\n    session.add(order)\n    await session.commit()\nexcept Exception:\n    await session.rollback()\n    raise\nfinally:\n    await session.close()\n```\n\nRule of thumb: use `async with session.begin()` for atomic multi-step operations —\nit makes the transaction boundary explicit and ensures rollback on any exception.\n",{"description":94},"FastAPI async database interview questions — AsyncSession, async SQLAlchemy, asyncpg, aiosqlite and event loop compatibility.","fastapi\u002Fdatabase\u002Fasync-db","Async Database","I90QMM52vWiPFlTjknINkakJ_qhvXcpmWK2zkfuOniA",{"id":675,"title":676,"body":677,"description":94,"difficulty":125,"extension":97,"framework":10,"frameworkSlug":8,"meta":681,"navigation":99,"order":29,"path":682,"questions":683,"questionsCount":214,"related":149,"seo":732,"seoDescription":733,"stem":734,"subtopic":735,"topic":46,"topicSlug":48,"updated":154,"__hash__":736},"qa\u002Ffastapi\u002Fdependency-injection\u002Fadvanced-deps.md","Advanced Deps",{"type":91,"value":678,"toc":679},[],{"title":94,"searchDepth":29,"depth":29,"links":680},[],{},"\u002Ffastapi\u002Fdependency-injection\u002Fadvanced-deps",[684,688,692,696,700,704,708,712,716,720,724,728],{"id":685,"difficulty":96,"q":686,"a":687},"yield-dependency","What is a yield dependency in FastAPI and what problem does it solve?","A **yield dependency** is a generator function that uses `yield` to split into\nsetup (before `yield`) and teardown (after `yield`). FastAPI runs the setup,\ninjects the yielded value, runs the handler, then runs the teardown — even if\nthe handler raises an exception.\n\n```python\nfrom sqlalchemy.orm import Session\nfrom app.db import SessionLocal\n\ndef get_db():\n    db = SessionLocal()\n    try:\n        yield db         # handler receives this\n    finally:\n        db.close()       # always runs after the handler\n\n@app.get(\"\u002Fusers\")\nasync def list_users(db: Session = Depends(get_db)):\n    return db.query(User).all()\n```\n\nThis is the standard pattern for DB sessions, file handles, and any resource\nthat must be cleaned up after the request.\n\nRule of thumb: use `yield` deps instead of separate open\u002Fclose functions —\nthe `finally` block guarantees cleanup even when the handler raises.\n",{"id":689,"difficulty":96,"q":690,"a":691},"async-yield-dep","How do you write an async yield dependency?","Use `async def` with `yield` — the same pattern but awaitable:\n\n```python\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom app.db import async_session_factory\n\nasync def get_async_db():\n    async with async_session_factory() as session:\n        yield session\n        await session.commit()   # auto-commit on success\n    # async context manager handles close\n```\n\nOr more explicitly:\n```python\nasync def get_async_db():\n    session = AsyncSession(engine)\n    try:\n        yield session\n        await session.commit()\n    except Exception:\n        await session.rollback()\n        raise\n    finally:\n        await session.close()\n```\n\nRule of thumb: for async databases always use an async yield dep — mixing sync\nSQLAlchemy sessions with `async def` handlers blocks the event loop.\n",{"id":693,"difficulty":125,"q":694,"a":695},"yield-dep-exception","What happens in a yield dependency if the handler raises an exception?","Code **after** `yield` still runs (inside the `finally` block), but code after\n`yield` and outside `finally` does **not** if an exception propagated.\n\n```python\ndef get_db():\n    db = SessionLocal()\n    try:\n        yield db\n        db.commit()    # ← skipped if handler raises\n    except Exception:\n        db.rollback()  # ← runs if handler raises, then re-raises\n        raise\n    finally:\n        db.close()     # ← always runs\n```\n\nFastAPI catches the exception from the handler, runs the dep's `except`\u002F`finally`\nblocks, then re-raises the exception so the error response is still sent to the client.\n\nRule of thumb: always put resource cleanup in `finally`, never rely on code\nafter `yield` running unconditionally — it only runs on success.\n",{"id":697,"difficulty":125,"q":698,"a":699},"multiple-yield-deps","What happens when multiple yield dependencies are active in the same request?","FastAPI runs teardowns in **reverse order** of setup — LIFO (last-in, first-out).\nIf dep A was set up before dep B, B tears down before A.\n\n```python\ndef dep_a():\n    print(\"A setup\")\n    yield \"a\"\n    print(\"A teardown\")   # runs second\n\ndef dep_b():\n    print(\"B setup\")\n    yield \"b\"\n    print(\"B teardown\")   # runs first\n\n@app.get(\"\u002F\")\nasync def handler(a = Depends(dep_a), b = Depends(dep_b)):\n    return {}\n# output: A setup, B setup, B teardown, A teardown\n```\n\nThis mirrors context manager nesting: inner closes before outer.\n\nRule of thumb: if dep B depends on A's resource (e.g., B uses the DB session\nfrom A), declare B's dependency on A explicitly so the order is guaranteed.\n",{"id":701,"difficulty":125,"q":702,"a":703},"background-task-yield-dep","Can a yield dependency's teardown access the response or run background tasks?","Teardown code (after `yield`) runs **after the response is sent** to the client\n— the same lifecycle as `BackgroundTasks`. You cannot modify the response headers\nat this point, but you can perform cleanup I\u002FO.\n\n```python\ndef audit_dep(request: Request):\n    start = time.time()\n    yield\n    elapsed = time.time() - start\n    # log_access(request.url, elapsed)   ← safe here, response already sent\n```\n\nYou **cannot** raise `HTTPException` in teardown (after yield) and expect it to\nchange the response — the response is already sent.\n\nRule of thumb: teardown code is for cleanup only (close sessions, release locks,\nwrite audit logs); never try to alter the HTTP response there.\n",{"id":705,"difficulty":96,"q":706,"a":707},"middleware-vs-dependency","When should you use middleware vs a dependency for cross-cutting concerns?","| Concern | Middleware | Dependency |\n|---------|-----------|-----------|\n| Applies to ALL routes | ✅ easy | Needs global `dependencies=` |\n| Access to response body | ✅ | ❌ |\n| Access to handler's return value | ❌ | ✅ (yield after) |\n| Testable via `dependency_overrides` | ❌ | ✅ |\n| Needs route parameters | ❌ (no routing yet) | ✅ |\n| Short-circuit before routing | ✅ | ❌ |\n\n```python\n# Middleware — good for request timing, response modification\nclass TimingMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        start = time.time()\n        response = await call_next(request)\n        response.headers[\"X-Process-Time\"] = str(time.time() - start)\n        return response\n\n# Dependency — good for auth (needs decoded token in handler)\nasync def get_current_user(token = Depends(oauth2_scheme)): ...\n```\n\nRule of thumb: use middleware for HTTP-level concerns (timing, CORS, compression);\nuse dependencies for application-level concerns (auth, DB sessions, feature flags).\n",{"id":709,"difficulty":96,"q":710,"a":711},"parametrised-dep","How do you create a parameterised dependency (different instances with different config)?","Return a dependency function from a factory function:\n\n```python\nfrom fastapi import Depends, HTTPException\n\ndef require_role(role: str):\n    def _check(user: User = Depends(get_current_user)):\n        if user.role != role:\n            raise HTTPException(403, f\"Role '{role}' required\")\n        return user\n    return _check\n\n@app.get(\"\u002Fadmin\", dependencies=[Depends(require_role(\"admin\"))])\nasync def admin_panel(): ...\n\n@app.get(\"\u002Freports\", dependencies=[Depends(require_role(\"analyst\"))])\nasync def reports(): ...\n```\n\nOr use a class with `__call__` (see \"callable instance dep\" pattern).\n\nRule of thumb: parametrised deps via factory functions are cleaner than a dozen\n`require_admin`, `require_analyst`, etc. — the role is a parameter, not a copy.\n",{"id":713,"difficulty":96,"q":714,"a":715},"context-manager-dep","How do you turn an existing async context manager into a FastAPI dependency?","Use `contextlib.asynccontextmanager` or just write the yield dep directly. If\nthe resource is already an async context manager, delegate to it:\n\n```python\nimport httpx\nfrom fastapi import Depends\n\nasync def get_http_client():\n    async with httpx.AsyncClient() as client:\n        yield client   # handler gets the open client\n\n@app.get(\"\u002Fproxy\")\nasync def proxy(client: httpx.AsyncClient = Depends(get_http_client)):\n    resp = await client.get(\"https:\u002F\u002Fapi.example.com\u002Fdata\")\n    return resp.json()\n```\n\nFastAPI closes the `AsyncClient` after the handler runs.\n\nRule of thumb: wrap third-party async context managers (httpx, aiofiles) in\nyield deps — you get proper lifecycle management without handler boilerplate.\n",{"id":717,"difficulty":125,"q":718,"a":719},"dep-caching-across-requests","Is dependency caching per-request or across requests?","**Per-request only**. FastAPI creates a new dependency cache for each incoming\nrequest; there is no cross-request singleton via `Depends()`.\n\nFor cross-request singletons (connection pools, ML models, HTTP clients):\n- Store them on `app.state` in the `lifespan` function.\n- Or use a module-level variable \u002F `@lru_cache`.\n\n```python\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    app.state.http_client = httpx.AsyncClient()\n    yield\n    await app.state.http_client.aclose()\n\n@app.get(\"\u002Fproxy\")\nasync def proxy(request: Request):\n    client = request.app.state.http_client   # cross-request singleton\n    return await client.get(\"https:\u002F\u002Fapi.example.com\u002F\")\n```\n\nRule of thumb: use `lifespan` + `app.state` for long-lived resources (DB pool,\nML model); use `Depends()` for per-request resources (sessions, auth context).\n",{"id":721,"difficulty":125,"q":722,"a":723},"dep-vs-middleware-order","In what order do global dependencies, middleware, and router dependencies execute?","```\nRequest →\n  Middleware (outermost first) →\n    FastAPI routing →\n      Global app dependencies →\n        Router-level dependencies →\n          Route-level dependencies →\n            Handler body\n          ← Route-level dep teardown\n        ← Router-level dep teardown\n      ← Global dep teardown\n    ← FastAPI exception handling\n  ← Middleware (innermost first on way out)\nResponse\n```\n\nMiddleware wraps everything, including dependency setup\u002Fteardown. Global deps\nset up before router deps, which set up before route deps.\n\nRule of thumb: auth middleware that checks a header before FastAPI even routes\nthe request is faster than a dep (no routing overhead) but less testable; choose\nbased on whether you need route-aware logic.\n",{"id":725,"difficulty":96,"q":726,"a":727},"dep-security-scheme","How do FastAPI security utilities (`OAuth2PasswordBearer`, `HTTPBearer`) relate to `Depends()`?","Security utilities are **callable classes** that implement `SecurityBase`. When\nyou use them with `Depends()`, FastAPI:\n1. Extracts the token (from header\u002Fcookie\u002Fquery).\n2. Adds the scheme to the OpenAPI security definitions automatically.\n3. Makes Swagger UI show the \"Authorize\" button.\n\n```python\nfrom fastapi.security import OAuth2PasswordBearer\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Ftoken\")\n\n@app.get(\"\u002Fme\")\nasync def me(token: str = Depends(oauth2_scheme)):\n    # token is the Bearer value from \"Authorization: Bearer \u003Ctoken>\"\n    ...\n```\n\nWithout security utilities, you'd read the header manually and lose the OpenAPI\nintegration.\n\nRule of thumb: always use FastAPI's security utilities (`OAuth2PasswordBearer`,\n`HTTPBearer`, `APIKeyHeader`) instead of plain `Header()` for auth — the OpenAPI\nintegration is a meaningful developer-experience benefit.\n",{"id":729,"difficulty":104,"q":730,"a":731},"dep-inject-request","How do you access the raw `Request` object inside a FastAPI dependency?","Declare `Request` as a parameter of the dependency function — FastAPI injects it\nautomatically without `Depends()`:\n\n```python\nfrom fastapi import Request, Depends\n\ndef get_client_ip(request: Request) -> str:\n    return request.client.host\n\n@app.get(\"\u002Fitems\")\nasync def list_items(ip: str = Depends(get_client_ip)):\n    return {\"client_ip\": ip}\n```\n\nThe `Request` object gives access to headers, cookies, query string, client\naddress and the raw body stream.\n\nRule of thumb: inject `Request` into dependencies that need HTTP metadata\n(rate limiting by IP, request ID logging) — keep handlers clean of low-level\nHTTP details.\n",{"description":94},"FastAPI advanced dependency injection interview questions — yield dependencies, resource cleanup, middleware vs dependency, async generators and exception handling.","fastapi\u002Fdependency-injection\u002Fadvanced-deps","Advanced Dependencies","Kd6_1pFLpUON6E3jNf0HEsarOU83z-p0hxz5UcMwXYo",{"id":738,"title":739,"body":740,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":744,"navigation":99,"order":29,"path":745,"questions":746,"questionsCount":214,"related":149,"seo":795,"seoDescription":796,"stem":797,"subtopic":739,"topic":81,"topicSlug":83,"updated":154,"__hash__":798},"qa\u002Ffastapi\u002Fdeployment\u002Fmiddleware.md","Middleware",{"type":91,"value":741,"toc":742},[],{"title":94,"searchDepth":29,"depth":29,"links":743},[],{},"\u002Ffastapi\u002Fdeployment\u002Fmiddleware",[747,751,755,759,763,767,771,775,779,783,787,791],{"id":748,"difficulty":104,"q":749,"a":750},"middleware-basics","What is middleware in FastAPI and how does it wrap requests?","Middleware is code that runs **around every request** — before the handler\n(on the way in) and after the handler (on the way out). It can modify the\nrequest, modify the response, short-circuit with an early response, or just\nobserve (logging, timing).\n\n```python\nfrom starlette.middleware.base import BaseHTTPMiddleware\nfrom fastapi import Request\n\nclass TimingMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request: Request, call_next):\n        import time\n        start = time.perf_counter()\n        response = await call_next(request)   # call the rest of the stack\n        elapsed = time.perf_counter() - start\n        response.headers[\"X-Process-Time\"] = f\"{elapsed:.4f}\"\n        return response\n\napp.add_middleware(TimingMiddleware)\n```\n\nRule of thumb: middleware is for cross-cutting concerns (timing, CORS, auth\nheaders, compression); use dependencies for per-route business logic.\n",{"id":752,"difficulty":96,"q":753,"a":754},"middleware-order","What order does middleware execute in FastAPI?","Middleware added with `add_middleware()` wraps the app in **reverse addition\norder** — last added = outermost wrapper = runs first for requests.\n\n```python\napp.add_middleware(A)   # added first → innermost\napp.add_middleware(B)   # added second → middle\napp.add_middleware(C)   # added last  → outermost\n\n# Request:  C → B → A → handler\n# Response: handler → A → B → C\n```\n\nBuilt-in middleware (CORS, GZip, TrustedHost) should typically be outermost\n(added last) so they wrap everything including custom middleware.\n\nRule of thumb: add CORS middleware last (so it wraps everything) and logging\nmiddleware first (so its timing includes all other middleware).\n",{"id":756,"difficulty":96,"q":757,"a":758},"cors-middleware","How do you configure CORS middleware in FastAPI and what are the critical settings?","```python\nfrom fastapi.middleware.cors import CORSMiddleware\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"https:\u002F\u002Fapp.example.com\", \"https:\u002F\u002Fadmin.example.com\"],\n    allow_credentials=True,     # required for cookies\u002Fauth headers cross-origin\n    allow_methods=[\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"],\n    allow_headers=[\"Content-Type\", \"Authorization\", \"X-Request-ID\"],\n    expose_headers=[\"X-Total-Count\"],   # headers the browser can read\n    max_age=3600,               # preflight cache duration (seconds)\n)\n```\n\nCritical interaction: `allow_origins=[\"*\"]` + `allow_credentials=True` is\n**invalid** — browsers reject this combination. You must list exact origins\nwhen using credentials.\n\nRule of thumb: list exact origins, never `\"*\"`, for APIs that send or receive\nauth cookies or `Authorization` headers.\n",{"id":760,"difficulty":104,"q":761,"a":762},"gzip-middleware","How do you enable GZip compression for FastAPI responses?","```python\nfrom starlette.middleware.gzip import GZipMiddleware\n\napp.add_middleware(GZipMiddleware, minimum_size=1000)\n```\n\n`minimum_size` (bytes) — responses smaller than this threshold are not\ncompressed (compression overhead isn't worth it for tiny responses).\n\nGZipMiddleware respects `Accept-Encoding: gzip` from the client — clients\nthat don't support gzip get uncompressed responses.\n\nRule of thumb: enable GZip for APIs that return large JSON payloads (lists of\nitems, reports); don't bother for APIs that only return small objects.\n",{"id":764,"difficulty":104,"q":765,"a":766},"trusted-host-middleware","What does `TrustedHostMiddleware` do and why is it important?","It validates the `Host` header against an allowlist, returning **400** if the\nhost doesn't match. This prevents **Host header injection attacks**:\n\n```python\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\n\napp.add_middleware(\n    TrustedHostMiddleware,\n    allowed_hosts=[\"api.example.com\", \"*.example.com\"],\n)\n```\n\nWithout it, an attacker who routes a request to your server with a forged `Host`\nheader can exploit password reset links, CORS checks, or server-generated URLs\nthat rely on `request.base_url`.\n\nRule of thumb: add `TrustedHostMiddleware` before deploying publicly; list\nall valid hostnames including your CDN\u002Fload balancer's hostname.\n",{"id":768,"difficulty":104,"q":769,"a":770},"https-redirect","How do you redirect all HTTP traffic to HTTPS in FastAPI?","```python\nfrom starlette.middleware.httpsredirect import HTTPSRedirectMiddleware\n\napp.add_middleware(HTTPSRedirectMiddleware)\n```\n\nAll HTTP requests receive a **307 Temporary Redirect** to the HTTPS equivalent.\nBehind a reverse proxy that terminates TLS, the proxy should handle the redirect\ninstead — the FastAPI middleware is a fallback.\n\nNote: this middleware checks the `X-Forwarded-Proto` header set by reverse proxies.\nConfigure Nginx\u002FALB to set this header; otherwise the middleware may redirect\nHTTPS requests (which arrive to Uvicorn as plain HTTP).\n\nRule of thumb: use `HTTPSRedirectMiddleware` as defence-in-depth; configure the\nprimary HTTP→HTTPS redirect at the reverse proxy level.\n",{"id":772,"difficulty":96,"q":773,"a":774},"base-http-middleware","What is `BaseHTTPMiddleware` and what is its performance trade-off?","`BaseHTTPMiddleware` provides a high-level `async dispatch(request, call_next)` \ninterface. It's easy to write but has a performance cost:\n- It buffers the response into memory before passing it to the next middleware.\n- It cannot stream responses incrementally.\n- It adds overhead compared to pure ASGI middleware.\n\n```python\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        request.state.request_id = str(uuid4())\n        response = await call_next(request)\n        response.headers[\"X-Request-ID\"] = request.state.request_id\n        return response\n```\n\nFor high-performance production middleware, implement the raw ASGI interface:\n```python\nclass RawMiddleware:\n    def __init__(self, app):\n        self.app = app\n    async def __call__(self, scope, receive, send):\n        ...\n        await self.app(scope, receive, send)\n```\n\nRule of thumb: use `BaseHTTPMiddleware` for simplicity unless profiling shows\nit as a bottleneck; use raw ASGI middleware for streaming responses.\n",{"id":776,"difficulty":125,"q":777,"a":778},"middleware-exception-handling","Can middleware catch exceptions raised by route handlers?","Yes — exceptions propagate through the middleware stack. Middleware can catch\nthem with `try\u002Fexcept` around `call_next()`:\n\n```python\nclass ErrorLoggingMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        try:\n            response = await call_next(request)\n        except Exception as e:\n            logger.error(\"Unhandled error: %s\", e, exc_info=True)\n            return JSONResponse({\"detail\": \"Internal server error\"}, status_code=500)\n        return response\n```\n\nHowever, `HTTPException` is caught by FastAPI's exception handler **before**\npropagating through middleware — middleware doesn't see handled `HTTPException`\nresponses.\n\nRule of thumb: use middleware to catch **unexpected** exceptions (for logging);\nuse `@app.exception_handler` to convert **expected** domain exceptions to HTTP responses.\n",{"id":780,"difficulty":96,"q":781,"a":782},"request-id-middleware","How do you implement a request ID middleware for distributed tracing?","```python\nimport uuid\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        # Use X-Request-ID if provided (from upstream), else generate one\n        req_id = request.headers.get(\"X-Request-ID\", str(uuid.uuid4()))\n        request.state.request_id = req_id\n        response = await call_next(request)\n        response.headers[\"X-Request-ID\"] = req_id\n        return response\n\napp.add_middleware(RequestIDMiddleware)\n```\n\nIn handlers and dependencies, access via `request.state.request_id` and include\nit in log records for correlation.\n\nRule of thumb: always propagate the request ID from upstream if present — it\nallows tracing a request across multiple services in a distributed system.\n",{"id":784,"difficulty":96,"q":785,"a":786},"session-middleware","How do you add server-side sessions to FastAPI?","Use `starlette.middleware.sessions.SessionMiddleware` (cookie-signed sessions):\n\n```python\nfrom starlette.middleware.sessions import SessionMiddleware\n\napp.add_middleware(\n    SessionMiddleware,\n    secret_key=settings.secret_key,\n    session_cookie=\"session\",\n    max_age=3600,     # 1 hour\n    https_only=True,  # cookie sent over HTTPS only\n    same_site=\"lax\",\n)\n```\n\nAccess in handlers:\n```python\n@app.get(\"\u002Fme\")\nasync def me(request: Request):\n    user_id = request.session.get(\"user_id\")\n    if not user_id:\n        raise HTTPException(401)\n    return {\"user_id\": user_id}\n\n@app.post(\"\u002Flogin\")\nasync def login(request: Request, ...):\n    request.session[\"user_id\"] = user.id\n    return {\"status\": \"logged in\"}\n```\n\nRule of thumb: Starlette sessions store data in a signed cookie — all session\ndata is sent to the browser. Use Redis-backed sessions for sensitive data.\n",{"id":788,"difficulty":125,"q":789,"a":790},"rate-limit-middleware","How do you implement rate limiting as middleware in FastAPI?","```python\nimport time\nfrom collections import defaultdict\nfrom fastapi import Request\nfrom fastapi.responses import JSONResponse\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nclass RateLimitMiddleware(BaseHTTPMiddleware):\n    def __init__(self, app, max_calls: int = 100, period: int = 60):\n        super().__init__(app)\n        self.max_calls = max_calls\n        self.period = period\n        self.calls: dict[str, list[float]] = defaultdict(list)\n\n    async def dispatch(self, request: Request, call_next):\n        key = request.client.host\n        now = time.time()\n        window_start = now - self.period\n\n        # Keep only calls within the window\n        self.calls[key] = [t for t in self.calls[key] if t > window_start]\n\n        if len(self.calls[key]) >= self.max_calls:\n            return JSONResponse({\"detail\": \"Rate limit exceeded\"}, status_code=429)\n\n        self.calls[key].append(now)\n        return await call_next(request)\n\napp.add_middleware(RateLimitMiddleware, max_calls=100, period=60)\n```\n\nFor production use Redis instead of in-memory dict (shared across workers).\n\nRule of thumb: the in-memory dict is per-worker — each worker has separate counters.\nUse Redis for a global rate limit that works across all workers.\n",{"id":792,"difficulty":96,"q":793,"a":794},"middleware-vs-dependency-auth","Should authentication be done in middleware or as a dependency?","| Approach | Pros | Cons |\n|----------|------|------|\n| Middleware | Runs before routing; can block early | Can't access route params; not testable via `dependency_overrides` |\n| Dependency | Testable; has route context; composable | Only runs after routing |\n\nBest practice — **hybrid**:\n- Middleware: lightweight token presence check (is `Authorization` header present?).\n- Dependency: full JWT validation + DB user lookup.\n\n```python\n# middleware — fast early reject\nclass BearerPresenceMiddleware(BaseHTTPMiddleware):\n    UNPROTECTED = {\"\u002Ftoken\", \"\u002Fdocs\", \"\u002Fhealth\"}\n    async def dispatch(self, request, call_next):\n        if request.url.path not in self.UNPROTECTED:\n            if not request.headers.get(\"Authorization\"):\n                return JSONResponse({\"detail\": \"Missing token\"}, status_code=401)\n        return await call_next(request)\n\n# dependency — full validation\nasync def get_current_user(token = Depends(oauth2_scheme)):\n    ...\n```\n\nRule of thumb: use middleware for coarse early rejection; use dependencies for\nfine-grained auth with route context.\n",{"description":94},"FastAPI middleware interview questions — BaseHTTPMiddleware, CORS, GZip, trusted hosts, custom middleware, execution order and performance.","fastapi\u002Fdeployment\u002Fmiddleware","cUfNGerUgiltPnnDvZoyS0WmvV0LGhA6NzX308xPsgY",{"id":800,"title":801,"body":802,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":806,"navigation":99,"order":29,"path":807,"questions":808,"questionsCount":487,"related":149,"seo":864,"seoDescription":865,"stem":866,"subtopic":801,"topic":20,"topicSlug":21,"updated":154,"__hash__":867},"qa\u002Ffastapi\u002Ffundamentals\u002Frequest-lifecycle.md","Request Lifecycle",{"type":91,"value":803,"toc":804},[],{"title":94,"searchDepth":29,"depth":29,"links":805},[],{},"\u002Ffastapi\u002Ffundamentals\u002Frequest-lifecycle",[809,813,816,820,824,828,832,836,840,844,848,852,856,860],{"id":810,"difficulty":96,"q":811,"a":812},"request-lifecycle-overview","Walk through the lifecycle of a FastAPI HTTP request from socket to response.","1. **Uvicorn** accepts the TCP connection and parses HTTP\u002F1.1 or HTTP\u002F2 bytes\n   into an ASGI `scope + receive\u002Fsend` pair.\n2. **Middleware stack** (outermost first) wraps the request. Each middleware can\n   short-circuit or modify `request` before passing to the next layer.\n3. **FastAPI router** matches the URL path + HTTP method to a route handler.\n   If no match → 404 `HTTPException`.\n4. **Dependency injection** resolves all `Depends()` in the handler signature,\n   recursively, before the handler runs.\n5. **Parameter extraction & Pydantic validation** — path params, query params,\n   headers, cookies and the request body are parsed and validated against type\n   annotations. Validation error → 422 `RequestValidationError`.\n6. **Handler executes** — `async def` on the event loop, `def` in a thread pool.\n7. **Response serialization** — the return value is filtered through\n   `response_model` (if set), then serialized to JSON via Pydantic.\n8. **Middleware stack** (innermost first on the way out) can modify the response.\n9. Uvicorn writes the HTTP response bytes to the socket.\n\nRule of thumb: middleware wraps everything; dependency injection happens before\nvalidation; validation happens before the handler body.\n",{"id":752,"difficulty":96,"q":814,"a":815},"In what order does FastAPI execute multiple middleware?","Middleware added with `app.add_middleware()` wraps the app **from the outside in**:\nthe last middleware added becomes the outermost layer. For requests, outermost\nruns first; for responses, innermost runs first (onion model).\n\n```python\napp.add_middleware(TimingMiddleware)   # added first → innermost layer\napp.add_middleware(AuthMiddleware)     # added second → outermost layer\n\n# Request flow:  AuthMiddleware → TimingMiddleware → route handler\n# Response flow: route handler → TimingMiddleware → AuthMiddleware\n```\n\nThis mirrors how ASGI middleware chains work in Starlette. FastAPI also applies\nits own built-in error handler and routing logic inside the inner layers.\n\nRule of thumb: add logging\u002Ftiming middleware last so it wraps everything,\nincluding auth middleware.\n",{"id":817,"difficulty":104,"q":818,"a":819},"http-exception","How does `HTTPException` work in FastAPI?","`HTTPException` is FastAPI's way to abort a request with a specific HTTP status\ncode and detail message. Raising it anywhere in a handler or dependency\nimmediately skips the rest of the stack and returns a JSON error response.\n\n```python\nfrom fastapi import HTTPException\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(item_id: int):\n    item = await db.get(item_id)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n    return item\n# Response: {\"detail\": \"Item not found\"} with HTTP 404\n```\n\nYou can add custom `headers` for auth challenges:\n```python\nraise HTTPException(\n    status_code=401,\n    detail=\"Not authenticated\",\n    headers={\"WWW-Authenticate\": \"Bearer\"},\n)\n```\n\nRule of thumb: raise `HTTPException` for expected error conditions\n(not found, unauthorized); let unhandled exceptions propagate to the\nglobal exception handler for unexpected errors.\n",{"id":821,"difficulty":96,"q":822,"a":823},"exception-handlers","How do you add a custom exception handler in FastAPI?","Use `@app.exception_handler(ExcType)` to register a handler that catches a\nspecific exception class anywhere in the request chain.\n\n```python\nfrom fastapi import Request\nfrom fastapi.responses import JSONResponse\n\nclass InsufficientFundsError(Exception):\n    def __init__(self, balance: float):\n        self.balance = balance\n\n@app.exception_handler(InsufficientFundsError)\nasync def funds_handler(request: Request, exc: InsufficientFundsError):\n    return JSONResponse(\n        status_code=402,\n        content={\"detail\": f\"Balance too low: {exc.balance}\"},\n    )\n```\n\nTo override the default validation error handler:\n```python\nfrom fastapi.exceptions import RequestValidationError\n\n@app.exception_handler(RequestValidationError)\nasync def validation_handler(request: Request, exc: RequestValidationError):\n    return JSONResponse(status_code=422, content={\"errors\": exc.errors()})\n```\n\nRule of thumb: use `HTTPException` for expected HTTP errors; use custom exception\nhandlers to convert domain exceptions into HTTP responses without polluting handlers.\n",{"id":825,"difficulty":104,"q":826,"a":827},"request-validation-error","What response does FastAPI return when request validation fails?","FastAPI returns **HTTP 422 Unprocessable Entity** with a JSON body that lists\nevery field that failed validation. This is automatic — you don't write any\nvalidation code yourself.\n\n```json\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"body\", \"email\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"name\": \"Alice\"},\n      \"url\": \"https:\u002F\u002Ferrors.pydantic.dev\u002F2.0\u002Fv\u002Fmissing\"\n    }\n  ]\n}\n```\n\nThe `loc` array pinpoints where the bad value came from: `[\"body\", \"field\"]`\nfor body params, `[\"query\", \"name\"]` for query params, `[\"path\", \"id\"]` for\npath params.\n\nRule of thumb: 422 = you sent bad data; 400 = request was intentionally rejected\nby application logic; treat them differently in client error handling.\n",{"id":829,"difficulty":125,"q":830,"a":831},"dependency-resolution-order","In what order does FastAPI resolve dependencies, and what happens if two dependencies share a sub-dependency?","FastAPI builds a **dependency graph** at startup by inspecting type annotations\nof all handlers. At request time it resolves **depth-first**: sub-dependencies\nare resolved before the dependencies that need them.\n\n**Shared dependencies are called once per request** (by default). If two\ndependencies both declare `Depends(get_db)`, FastAPI calls `get_db()` once and\npasses the same object to both.\n\n```python\nasync def get_db():        # called once even if used by two deps\n    async with AsyncSession() as session:\n        yield session\n\nasync def get_current_user(db = Depends(get_db)):\n    ...\n\nasync def check_permission(db = Depends(get_db)):\n    ...\n\n@app.get(\"\u002Fsecure\")\nasync def handler(\n    user = Depends(get_current_user),\n    perm = Depends(check_permission),  # same db session as above\n):\n    ...\n```\n\nTo opt out of caching (force a fresh call), pass `use_cache=False`:\n`Depends(get_db, use_cache=False)`.\n\nRule of thumb: shared sub-dependencies are singletons per request — rely on this\nto share a DB session cleanly without a thread-local or global.\n",{"id":833,"difficulty":96,"q":834,"a":835},"response-serialization","How does FastAPI serialize the value returned from a route handler?","1. If a `response_model` is set, FastAPI uses Pydantic to validate and filter\n   the return value through that model (strips extra fields, applies aliases, etc.).\n2. The filtered Python object is converted to a JSON-compatible dict via\n   Pydantic's `model_dump(mode=\"json\")`.\n3. The dict is serialized to JSON bytes using `orjson` (if installed) or the\n   standard `json` module.\n4. The bytes are sent as `Content-Type: application\u002Fjson`.\n\n```python\nclass UserOut(BaseModel):\n    id: int\n    name: str\n    # no `password` field → never leaked\n\n@app.get(\"\u002Fusers\u002F{id}\", response_model=UserOut)\nasync def get_user(id: int) -> User:\n    return await db.get(User, id)   # User has password field — filtered out\n```\n\nIf no `response_model` is set, FastAPI calls `jsonable_encoder()` on the return\nvalue, which handles datetime, UUID, Enum etc.\n\nRule of thumb: always set `response_model` on endpoints that return ORM objects\nto prevent accidental data leakage.\n",{"id":837,"difficulty":96,"q":838,"a":839},"background-task-in-lifecycle","Where in the request lifecycle do BackgroundTasks run?","`BackgroundTasks` run **after the HTTP response has been sent** to the client.\nThe route handler adds tasks; FastAPI sends the response; then the tasks execute\nsequentially in the same event loop (for async tasks) or thread pool (for sync tasks).\n\n```python\nfrom fastapi import BackgroundTasks\n\ndef send_email(to: str, msg: str):\n    ...   # slow SMTP call — runs after response is sent\n\n@app.post(\"\u002Fsignup\")\nasync def signup(email: str, background_tasks: BackgroundTasks):\n    await db.create_user(email)\n    background_tasks.add_task(send_email, email, \"Welcome!\")\n    return {\"status\": \"created\"}   # response sent immediately\n```\n\nBecause they run in the same process, background tasks share memory with the app\nbut have **no access** to the request object once it's closed.\n\nRule of thumb: use `BackgroundTasks` for quick fire-and-forget work (email,\nanalytics); use a dedicated task queue (Celery, ARQ) for retryable or long-running jobs.\n",{"id":841,"difficulty":96,"q":842,"a":843},"jsonable-encoder","What is `jsonable_encoder` and when do you need it?","`jsonable_encoder` converts Python objects that aren't natively JSON-serialisable\n(datetime, UUID, Decimal, Pydantic models, ORM objects) into JSON-compatible\nPython primitives (str, int, dict, list).\n\n```python\nfrom fastapi.encoders import jsonable_encoder\nfrom datetime import datetime\n\nobj = {\"created\": datetime(2026, 1, 1), \"id\": uuid4()}\nsafe = jsonable_encoder(obj)\n# {\"created\": \"2026-01-01T00:00:00\", \"id\": \"550e8400-...\"}\n```\n\nFastAPI calls it automatically when serializing responses. You need to call it\nmanually when:\n- Storing something in a cache or NoSQL DB as JSON.\n- Passing an object to a `JSONResponse` constructor directly.\n\n```python\nreturn JSONResponse(content=jsonable_encoder(my_pydantic_model))\n```\n\nRule of thumb: let FastAPI call `jsonable_encoder` implicitly through `response_model`;\ncall it explicitly only when building `JSONResponse` objects manually.\n",{"id":845,"difficulty":104,"q":846,"a":847},"request-body-parsing","How does FastAPI decide where to look for a parameter — path, query, or body?","FastAPI uses the **parameter name and type annotation** as signals:\n\n| Source | Signal |\n|--------|--------|\n| Path parameter | name appears in the route path string `\u002F{name}` |\n| Query parameter | simple type (`str`, `int`, `float`, `bool`, `Optional`) not in path |\n| Request body | Pydantic `BaseModel` subclass (or annotated with `Body()`) |\n| Header | annotated with `Header()` |\n| Cookie | annotated with `Cookie()` |\n\n```python\n@app.put(\"\u002Fitems\u002F{item_id}\")\nasync def update_item(\n    item_id: int,          # path  — in \"{item_id}\"\n    q: str | None = None,  # query — simple type, not in path\n    item: Item,            # body  — Pydantic model\n):\n    ...\n```\n\nRule of thumb: if the name matches the path template, it's a path param; if it's\na Pydantic model it's a body; everything else defaults to query.\n",{"id":849,"difficulty":104,"q":850,"a":851},"response-status-code","How do you change the default response status code in FastAPI?","Pass `status_code` to the route decorator. FastAPI returns 200 by default for GET\nand 200 for POST; 201 is the semantic choice for resource creation.\n\n```python\nfrom fastapi import status\n\n@app.post(\"\u002Fitems\", status_code=status.HTTP_201_CREATED)\nasync def create_item(item: Item):\n    saved = await db.save(item)\n    return saved\n```\n\nTo set the status code dynamically inside the handler, inject the `Response` object:\n\n```python\nfrom fastapi import Response\n\n@app.get(\"\u002Fmaybe\")\nasync def maybe(response: Response, id: int):\n    item = await db.get(id)\n    if not item:\n        response.status_code = 404\n        return None\n    return item\n```\n\nRule of thumb: set status codes at the decorator level for fixed codes;\ninject `Response` only when the code depends on runtime logic.\n",{"id":853,"difficulty":96,"q":854,"a":855},"path-vs-router-prefix","What is the difference between setting a path in `app.include_router(prefix=...)` vs the route decorator itself?","`prefix` in `include_router` is a **path segment prepended to all routes** in\nthat router. The route decorator path is the **specific endpoint path within the\nrouter**. They concatenate.\n\n```python\nrouter = APIRouter()\n\n@router.get(\"\u002Fitems\")           # partial path\nasync def list_items(): ...\n\n@router.get(\"\u002Fitems\u002F{id}\")      # partial path\nasync def get_item(id: int): ...\n\napp.include_router(router, prefix=\"\u002Fv1\")\n# registered as: GET \u002Fv1\u002Fitems  and  GET \u002Fv1\u002Fitems\u002F{id}\n```\n\nSetting the full path in the decorator makes the router useless as a module\nprefix. Putting everything in `prefix` lets you re-mount the same router at\ndifferent prefixes (e.g., `\u002Fv1` and `\u002Fv2`).\n\nRule of thumb: keep route decorators as relative sub-paths; use `prefix` in\n`include_router` for versioning or top-level resource grouping.\n",{"id":857,"difficulty":104,"q":858,"a":859},"trailing-slash","Does FastAPI treat `\u002Fitems` and `\u002Fitems\u002F` as the same route?","**No.** By default FastAPI treats trailing slashes as distinct routes and does\n**not** redirect. A GET to `\u002Fitems\u002F` returns 404 if only `\u002Fitems` is registered.\n\n```python\n@app.get(\"\u002Fitems\")     # only matches \u002Fitems\nasync def list_items(): ...\n```\n\nOptions:\n1. Register both: `@app.get(\"\u002Fitems\")` and `@app.get(\"\u002Fitems\u002F\")`.\n2. Use the `redirect_slashes=False` or `redirect_slashes=True` flag on `FastAPI()`:\n   `app = FastAPI(redirect_slashes=True)` — a trailing-slash request is 307-redirected\n   to the non-slash version.\n\n```python\napp = FastAPI(redirect_slashes=True)\n```\n\nRule of thumb: disable automatic trailing-slash handling (`redirect_slashes=False`)\nfor strict APIs; enable it (`True`) for user-facing endpoints where clients vary.\n",{"id":861,"difficulty":96,"q":862,"a":863},"openapi-schema-generation","When is the OpenAPI schema generated and can it be disabled?","FastAPI generates the OpenAPI schema **at import\u002Fstartup time**, not per-request.\nIt inspects route decorators, type annotations and Pydantic models once and caches\nthe result. The `\u002Fopenapi.json` endpoint just serves this cached dict.\n\nTo disable the docs entirely (e.g., in production):\n\n```python\napp = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)\n```\n\nTo move the schema to a non-default path:\n```python\napp = FastAPI(openapi_url=\"\u002Fapi\u002Fv1\u002Fopenapi.json\")\n```\n\nLazy generation (compute only on first request) can be achieved by subclassing:\n```python\nclass LazyFastAPI(FastAPI):\n    def openapi(self):\n        if not self.openapi_schema:\n            self.openapi_schema = get_openapi(...)\n        return self.openapi_schema\n```\n\nRule of thumb: disable `openapi_url` in production if you don't want to expose\nyour API schema publicly; keep it enabled in staging for team tooling.\n",{"description":94},"FastAPI request lifecycle interview questions — middleware chain, routing, dependency resolution, validation, handler execution and response serialization.","fastapi\u002Ffundamentals\u002Frequest-lifecycle","FiKs69biTzPCIi3Wz4Yb1o_O1sXxKJrz07q25xL6GBg",{"id":869,"title":870,"body":871,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":875,"navigation":99,"order":29,"path":876,"questions":877,"questionsCount":214,"related":149,"seo":926,"seoDescription":927,"stem":928,"subtopic":929,"topic":37,"topicSlug":39,"updated":154,"__hash__":930},"qa\u002Ffastapi\u002Fpydantic\u002Fvalidation.md","Validation",{"type":91,"value":872,"toc":873},[],{"title":94,"searchDepth":29,"depth":29,"links":874},[],{},"\u002Ffastapi\u002Fpydantic\u002Fvalidation",[878,882,886,890,894,898,902,906,910,914,918,922],{"id":879,"difficulty":96,"q":880,"a":881},"field-validator-basics","How do you write a custom field validator in Pydantic v2?","Use `@field_validator(\"field_name\")` decorator on a classmethod:\n\n```python\nfrom pydantic import BaseModel, field_validator\n\nclass User(BaseModel):\n    username: str\n    age: int\n\n    @field_validator(\"username\")\n    @classmethod\n    def username_must_be_alphanumeric(cls, v: str) -> str:\n        if not v.isalnum():\n            raise ValueError(\"username must be alphanumeric\")\n        return v.lower()   # transform the value\n\n    @field_validator(\"age\")\n    @classmethod\n    def age_must_be_positive(cls, v: int) -> int:\n        if v \u003C 0:\n            raise ValueError(\"age must be non-negative\")\n        return v\n```\n\n`ValueError` messages are caught by Pydantic and included in the `ValidationError`\ndetail. You can also raise `PydanticCustomError` for more structured errors.\n\nRule of thumb: field validators run per-field; use them for constraints that\ncan't be expressed with `Field()` parameters (cross-value logic belongs in\nmodel validators).\n",{"id":883,"difficulty":96,"q":884,"a":885},"field-validator-modes","What are the `before`, `after`, `wrap` and `plain` modes for `@field_validator`?","`mode` controls when the validator runs relative to Pydantic's built-in type coercion:\n\n| Mode | Runs | Input type | Use case |\n|------|------|------------|----------|\n| `\"before\"` (default v2) | before coercion | raw input (str\u002Fdict\u002Fetc.) | normalize raw strings |\n| `\"after\"` | after coercion | declared Python type | validate typed value |\n| `\"wrap\"` | wraps coercion | raw + handler callable | conditional coercion |\n| `\"plain\"` | replaces coercion | raw input | fully custom type parsing |\n\n```python\nfrom pydantic import BaseModel, field_validator\n\nclass Order(BaseModel):\n    amount: float\n\n    @field_validator(\"amount\", mode=\"before\")\n    @classmethod\n    def strip_currency_symbol(cls, v):\n        if isinstance(v, str):\n            return v.lstrip(\"$£€\")   # \"£9.99\" → \"9.99\"\n        return v\n```\n\nRule of thumb: use `mode=\"before\"` to normalise raw strings before Pydantic\ntries to parse them; use `mode=\"after\"` for logic that needs the typed value.\n",{"id":887,"difficulty":96,"q":888,"a":889},"multiple-fields-validator","How do you validate multiple fields with a single `@field_validator`?","Pass multiple field names to `@field_validator`. The validator is called once\nper field listed:\n\n```python\nfrom pydantic import BaseModel, field_validator\n\nclass Product(BaseModel):\n    name: str\n    description: str\n\n    @field_validator(\"name\", \"description\")\n    @classmethod\n    def must_not_be_blank(cls, v: str) -> str:\n        if not v.strip():\n            raise ValueError(\"must not be blank or whitespace\")\n        return v.strip()\n```\n\nThe validator receives one field value at a time — it does not receive both\nsimultaneously. For cross-field logic, use `@model_validator`.\n\nRule of thumb: share validators across fields with multiple names in the\ndecorator to keep DRY; use `@model_validator` when the logic depends on comparing\nfield values to each other.\n",{"id":891,"difficulty":96,"q":892,"a":893},"model-validator","What is `@model_validator` and when do you need it instead of `@field_validator`?","`@model_validator` runs once on the **whole model** (all fields at once). Use it\nfor cross-field constraints.\n\n```python\nfrom pydantic import BaseModel, model_validator\n\nclass DateRange(BaseModel):\n    start: date\n    end: date\n\n    @model_validator(mode=\"after\")\n    def end_after_start(self) -> \"DateRange\":\n        if self.end \u003C= self.start:\n            raise ValueError(\"end must be after start\")\n        return self\n```\n\n`mode=\"after\"` — runs after all fields are coerced and validated; `self` is the\nmodel instance.\n`mode=\"before\"` — runs before field validation; receives raw data as a dict.\n\nRule of thumb: `@field_validator` for single-field logic; `@model_validator(mode=\"after\")`\nfor cross-field constraints like \"end after start\" or \"confirm password matches\".\n",{"id":895,"difficulty":125,"q":896,"a":897},"model-validator-before","When would you use `@model_validator(mode=\"before\")` and what does it receive?","`mode=\"before\"` intercepts the raw input **before any field parsing**. The\nvalidator receives a dict (or arbitrary input) and must return a dict.\n\n```python\nfrom pydantic import BaseModel, model_validator\n\nclass FlexibleUser(BaseModel):\n    name: str\n    email: str\n\n    @model_validator(mode=\"before\")\n    @classmethod\n    def coerce_legacy_format(cls, data):\n        # support both {\"name\": ...} and {\"full_name\": ...}\n        if \"full_name\" in data and \"name\" not in data:\n            data[\"name\"] = data.pop(\"full_name\")\n        return data\n```\n\nUse cases: renaming legacy fields, setting computed defaults before field\nvalidation, accepting multiple input shapes.\n\nRule of thumb: `mode=\"before\"` is for input normalisation at the whole-model\nlevel; prefer `mode=\"after\"` for validation because you get typed values.\n",{"id":899,"difficulty":125,"q":900,"a":901},"custom-type","How do you create a custom Pydantic type with its own validation logic?","Annotate a class with `__get_validators__` (v1) or implement `__get_pydantic_core_schema__`\n(v2). For simple cases, use `Annotated` with `AfterValidator`:\n\n```python\nfrom typing import Annotated\nfrom pydantic import AfterValidator, BaseModel\n\ndef validate_isbn(v: str) -> str:\n    v = v.replace(\"-\", \"\")\n    if len(v) not in (10, 13):\n        raise ValueError(\"ISBN must be 10 or 13 digits\")\n    return v\n\nISBN = Annotated[str, AfterValidator(validate_isbn)]\n\nclass Book(BaseModel):\n    title: str\n    isbn: ISBN\n```\n\nFor full custom types with JSON Schema:\n```python\nfrom pydantic import GetCoreSchemaHandler\nfrom pydantic_core import core_schema\n\nclass PositiveDecimal:\n    @classmethod\n    def __get_pydantic_core_schema__(cls, source, handler: GetCoreSchemaHandler):\n        return core_schema.no_info_after_validator_function(cls, core_schema.decimal_schema())\n```\n\nRule of thumb: use `Annotated[T, AfterValidator(fn)]` for simple reusable\nvalidators; implement `__get_pydantic_core_schema__` only for complex custom types.\n",{"id":903,"difficulty":96,"q":904,"a":905},"pydantic-error-structure","What does a `ValidationError` from Pydantic look like and how do you access its details?","`ValidationError` has an `.errors()` method returning a list of error dicts:\n\n```python\nfrom pydantic import BaseModel, ValidationError\n\nclass Item(BaseModel):\n    name: str\n    price: float\n\ntry:\n    Item(name=\"\", price=-1)\nexcept ValidationError as e:\n    print(e.errors())\n# [\n#   {\"type\": \"value_error\", \"loc\": (\"name\",), \"msg\": \"...\", \"input\": \"\"},\n#   {\"type\": \"greater_than\", \"loc\": (\"price\",), \"msg\": \"...\", \"input\": -1}\n# ]\n```\n\nEach error has:\n- `type` — Pydantic error code\n- `loc` — tuple of keys pinpointing the failing field\n- `msg` — human-readable message\n- `input` — the value that failed\n\nFastAPI captures this and returns it as the 422 response `detail` array.\n\nRule of thumb: in unit tests, assert on `exc.errors()[0][\"type\"]` to test\nspecific error codes rather than message strings that may change.\n",{"id":907,"difficulty":125,"q":908,"a":909},"pydantic-strict-mode","What is strict mode in Pydantic v2 and when would you enable it?","Strict mode disables Pydantic's **type coercion** — values must already be the\ncorrect Python type; no implicit conversion happens.\n\n```python\nfrom pydantic import BaseModel, ConfigDict\n\nclass StrictItem(BaseModel):\n    model_config = ConfigDict(strict=True)\n    price: float\n    count: int\n\nStrictItem(price=9.99, count=3)     # OK\nStrictItem(price=\"9.99\", count=3)   # ValidationError — \"9.99\" is str, not float\n```\n\nPer-field: `Field(strict=True)` or `Strict` annotated type.\n\nUse cases: data coming from other Python code (not raw JSON) where silent coercion\nhides bugs; internal domain objects that must have exact types.\n\nRule of thumb: don't use strict mode for API request models — clients send JSON\nwhere numbers may arrive as strings; use it for internal\u002FDTO models between Python layers.\n",{"id":911,"difficulty":96,"q":912,"a":913},"validator-skip-on-none","How do you skip a validator when the field value is `None`?","Pass `skip_on_failure=True` (for `mode=\"after\"`) or check for `None` at the top\nof the validator. With `mode=\"before\"` and optional fields, validators run even\non `None` inputs by default.\n\n```python\nfrom pydantic import BaseModel, field_validator\n\nclass Profile(BaseModel):\n    bio: str | None = None\n\n    @field_validator(\"bio\", mode=\"after\")\n    @classmethod\n    def bio_max_sentences(cls, v: str | None) -> str | None:\n        if v is None:\n            return v    # skip check\n        if v.count(\".\") > 5:\n            raise ValueError(\"bio may not exceed 5 sentences\")\n        return v\n```\n\nAlternatively, in Pydantic v2 you can annotate with `Optional` inside\n`Annotated` and chain with `BeforeValidator` that returns `None` early.\n\nRule of thumb: always guard against `None` at the top of validators for\noptional fields — a missed check causes a confusing `AttributeError`.\n",{"id":915,"difficulty":104,"q":916,"a":917},"validator-return-value","Must a Pydantic field validator return a value?","Yes — the return value of a `@field_validator` replaces the field's value.\nForgetting to `return v` silently sets the field to `None`.\n\n```python\n# WRONG — returns None, field becomes None\n@field_validator(\"name\")\n@classmethod\ndef check_name(cls, v: str) -> str:\n    if not v:\n        raise ValueError(\"required\")\n    # forgot return v!\n\n# CORRECT\n@field_validator(\"name\")\n@classmethod\ndef check_name(cls, v: str) -> str:\n    if not v:\n        raise ValueError(\"required\")\n    return v   # must return the (possibly transformed) value\n```\n\nRule of thumb: end every `@field_validator` with a `return v` — validators\nthat only validate (no transform) still must return the original value.\n",{"id":919,"difficulty":96,"q":920,"a":921},"before-validator-annotated","What is `BeforeValidator` \u002F `AfterValidator` in Pydantic v2 `Annotated` style?","These are functional wrappers used inside `Annotated` to attach validators\nwithout subclassing `BaseModel`:\n\n```python\nfrom typing import Annotated\nfrom pydantic import BaseModel, BeforeValidator, AfterValidator\n\ndef strip_spaces(v: str) -> str:\n    return v.strip()\n\ndef ensure_lower(v: str) -> str:\n    return v.lower()\n\nCleanStr = Annotated[str, BeforeValidator(strip_spaces), AfterValidator(ensure_lower)]\n\nclass User(BaseModel):\n    username: CleanStr\n```\n\nThis creates **reusable annotated types** you can import and use across models\nwithout copy-pasting validators.\n\nRule of thumb: define common transformations as `Annotated` types (`Email`,\n`SlugStr`, `PositiveDecimal`) and import them — it's DRY and keeps validators\nout of model classes.\n",{"id":923,"difficulty":125,"q":924,"a":925},"cross-model-validation","How do you validate that two related request models are consistent with each other in FastAPI?","Accept both models as body parameters and validate their relationship in the\nhandler (or in a dedicated service function):\n\n```python\nclass DateRangeFilter(BaseModel):\n    start: date\n    end: date\n\n    @model_validator(mode=\"after\")\n    def end_after_start(self) -> \"DateRangeFilter\":\n        if self.end \u003C self.start:\n            raise ValueError(\"end must be >= start\")\n        return self\n\nclass ReportRequest(BaseModel):\n    range: DateRangeFilter\n    metrics: list[str]\n\n@app.post(\"\u002Freports\")\nasync def generate_report(req: ReportRequest):\n    ...\n```\n\nFor cross-model checks that span the body and path\u002Fquery params, perform the\ncheck in the handler and raise `HTTPException(422, ...)`.\n\nRule of thumb: encode single-model invariants in `@model_validator`; encode\ncross-model or cross-layer invariants in the handler or service layer.\n",{"description":94},"FastAPI Pydantic validator interview questions — field_validator, model_validator, before\u002Fafter mode, custom types and raising ValidationError.","fastapi\u002Fpydantic\u002Fvalidation","Validators","EXg87mVabg1_159V7NR7LEHuU_pfzrq0kolsiqv3NcI",{"id":932,"title":933,"body":934,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":938,"navigation":99,"order":29,"path":939,"questions":940,"questionsCount":415,"related":149,"seo":993,"seoDescription":994,"stem":995,"subtopic":933,"topic":28,"topicSlug":30,"updated":154,"__hash__":996},"qa\u002Ffastapi\u002Frouting\u002Frequest-body.md","Request Body",{"type":91,"value":935,"toc":936},[],{"title":94,"searchDepth":29,"depth":29,"links":937},[],{},"\u002Ffastapi\u002Frouting\u002Frequest-body",[941,945,949,953,957,961,965,969,973,977,981,985,989],{"id":942,"difficulty":104,"q":943,"a":944},"json-body-basics","How do you declare a JSON request body in FastAPI?","Declare a function parameter typed as a Pydantic `BaseModel` subclass. FastAPI\nreads the request body as JSON, parses it, and validates it against the model.\n\n```python\nfrom pydantic import BaseModel\nfrom fastapi import FastAPI\n\nclass Item(BaseModel):\n    name: str\n    price: float\n    in_stock: bool = True\n\napp = FastAPI()\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item):\n    return item\n# POST \u002Fitems  body: {\"name\": \"Widget\", \"price\": 9.99}\n# → Item(name='Widget', price=9.99, in_stock=True)\n```\n\nFastAPI returns 422 if the body is missing, malformed JSON, or fails validation.\n\nRule of thumb: one Pydantic model = one request body; the model name appears in\nthe OpenAPI schema as the expected input shape.\n",{"id":946,"difficulty":104,"q":947,"a":948},"optional-body","How do you make the request body optional in FastAPI?","Annotate the body parameter with `T | None = None`:\n\n```python\nclass Item(BaseModel):\n    name: str\n    price: float\n\n@app.patch(\"\u002Fitems\u002F{id}\")\nasync def update_item(id: int, item: Item | None = None):\n    if item is None:\n        return {\"updated\": False}\n    return {\"updated\": True, \"item\": item}\n```\n\nA `None` default makes the body optional; FastAPI won't error if the client\nsends an empty body. For partial updates prefer a model where all fields are\n`Optional` rather than making the whole body optional.\n\nRule of thumb: for PATCH endpoints, make individual fields optional inside the\nmodel rather than making the entire body optional.\n",{"id":950,"difficulty":96,"q":951,"a":952},"nested-models","How does FastAPI handle nested Pydantic models in the request body?","Nest a `BaseModel` inside another `BaseModel`. FastAPI validates the full\nnested structure and generates the OpenAPI schema with `$ref` references.\n\n```python\nclass Address(BaseModel):\n    street: str\n    city: str\n    postcode: str\n\nclass User(BaseModel):\n    name: str\n    email: str\n    address: Address       # nested model\n\n@app.post(\"\u002Fusers\")\nasync def create_user(user: User):\n    return user\n# body: {\"name\": \"Alice\", \"email\": \"a@b.com\",\n#         \"address\": {\"street\": \"1 Main St\", \"city\": \"London\", \"postcode\": \"SW1A\"}}\n```\n\nArbitrarily deep nesting is supported; Pydantic validates all levels.\n\nRule of thumb: prefer deep models over flat ones with `address_street`,\n`address_city` prefixes — nested models are cleaner and reusable across endpoints.\n",{"id":954,"difficulty":96,"q":955,"a":956},"multiple-body-params","How do you declare multiple Pydantic models as separate body parameters?","When you declare two Pydantic model parameters, FastAPI expects the body to be a\n**JSON object keyed by parameter name**:\n\n```python\nclass Item(BaseModel):\n    name: str\n    price: float\n\nclass Supplier(BaseModel):\n    name: str\n    contact: str\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, supplier: Supplier):\n    return {\"item\": item, \"supplier\": supplier}\n# body: {\"item\": {\"name\": \"Widget\", \"price\": 9.99},\n#         \"supplier\": {\"name\": \"Acme\", \"contact\": \"info@acme.com\"}}\n```\n\nRule of thumb: multiple body models wrap themselves under their parameter names\nautomatically; use this to bundle related but distinct payloads in one request.\n",{"id":958,"difficulty":96,"q":959,"a":960},"body-singular","How do you mix a Pydantic body model with a singular body value?","Use `Body(embed=True)` on the scalar value. This forces FastAPI to wrap it under\nits parameter name in the expected JSON object.\n\n```python\nfrom fastapi import Body\nfrom typing import Annotated\n\nclass Item(BaseModel):\n    name: str\n\n@app.put(\"\u002Fitems\u002F{id}\")\nasync def update_item(\n    id: int,\n    item: Item,\n    importance: Annotated[int, Body(ge=1, le=5, embed=True)],\n):\n    return {\"item\": item, \"importance\": importance}\n# body: {\"item\": {\"name\": \"Widget\"}, \"importance\": 3}\n```\n\nWithout `embed=True`, FastAPI would expect `importance` at the top level of the\nJSON, which conflicts with `item`'s keys.\n\nRule of thumb: any singular `Body()` value alongside a Pydantic model needs\n`embed=True` so FastAPI knows to namespace it.\n",{"id":962,"difficulty":96,"q":963,"a":964},"body-field-extra","What happens when the client sends extra fields not defined in the Pydantic model?","By default (Pydantic v2), extra fields are **ignored** — they're stripped silently\nand don't reach the handler.\n\n```python\nclass Item(BaseModel):\n    name: str\n    price: float\n# body: {\"name\": \"Widget\", \"price\": 9.99, \"secret\": \"ignored\"}\n# → Item(name='Widget', price=9.99)  — secret dropped\n```\n\nYou can change this behaviour via `model_config`:\n\n```python\nfrom pydantic import BaseModel, ConfigDict\n\nclass StrictItem(BaseModel):\n    model_config = ConfigDict(extra=\"forbid\")  # 422 if extra fields sent\n    name: str\n    price: float\n```\n\nOptions: `\"ignore\"` (default), `\"allow\"` (keep in `model.model_extra`),\n`\"forbid\"` (raise validation error).\n\nRule of thumb: use `extra=\"forbid\"` for request bodies to catch client typos\nearly; use `extra=\"ignore\"` for internal models where schema drift is acceptable.\n",{"id":966,"difficulty":96,"q":967,"a":968},"list-body","How do you accept a JSON array (list) as the root of the request body?","Type-annotate the body parameter as `list[MyModel]`:\n\n```python\n@app.post(\"\u002Fitems\u002Fbulk\")\nasync def bulk_create(items: list[Item]):\n    return {\"count\": len(items)}\n# body: [{\"name\": \"A\", \"price\": 1.0}, {\"name\": \"B\", \"price\": 2.0}]\n```\n\nFastAPI validates each element against `Item`. For a top-level list, there is\nno \"outer key\" — the raw JSON array is the entire body.\n\nRule of thumb: use bulk endpoints for batch operations; add a reasonable size\nlimit (`len(items) \u003C= 100`) inside the handler to prevent abuse.\n",{"id":970,"difficulty":96,"q":971,"a":972},"form-data-body","How do you accept HTML form data instead of JSON in FastAPI?","Use `Form()` annotation and install `python-multipart`:\n\n```python\nfrom fastapi import Form\nfrom typing import Annotated\n\n@app.post(\"\u002Flogin\")\nasync def login(\n    username: Annotated[str, Form()],\n    password: Annotated[str, Form()],\n):\n    return {\"user\": username}\n# Content-Type: application\u002Fx-www-form-urlencoded\n# body: username=alice&password=secret\n```\n\nYou **cannot mix** `Form()` and a Pydantic JSON body in the same handler — HTTP\nonly allows one `Content-Type` per request.\n\nRule of thumb: use `Form()` for OAuth2 password flows and HTML `\u003Cform>` submissions;\nuse JSON body everywhere else.\n",{"id":974,"difficulty":96,"q":975,"a":976},"file-upload-body","How do you accept a file alongside form fields in FastAPI?","Use `UploadFile` for the file and `Form()` for accompanying fields. Both are\nmultipart\u002Fform-data.\n\n```python\nfrom fastapi import UploadFile, Form\nfrom typing import Annotated\n\n@app.post(\"\u002Fupload\")\nasync def upload(\n    file: UploadFile,\n    description: Annotated[str, Form()],\n):\n    content = await file.read()\n    return {\n        \"filename\": file.filename,\n        \"size\": len(content),\n        \"description\": description,\n    }\n```\n\n`UploadFile` wraps a `SpooledTemporaryFile`; use `await file.read()` for small\nfiles, iterate in chunks for large ones.\n\nRule of thumb: always `await file.seek(0)` before re-reading a file that was\nalready partially read elsewhere in the handler.\n",{"id":978,"difficulty":104,"q":979,"a":980},"body-validation-error-format","What does a FastAPI body validation error response look like?","HTTP **422 Unprocessable Entity** with a JSON body listing each failing field:\n\n```json\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"body\", \"price\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"name\": \"Widget\"},\n      \"url\": \"https:\u002F\u002Ferrors.pydantic.dev\u002F2.0\u002Fv\u002Fmissing\"\n    },\n    {\n      \"type\": \"float_parsing\",\n      \"loc\": [\"body\", \"price\"],\n      \"msg\": \"Input should be a valid number\",\n      \"input\": \"not-a-number\"\n    }\n  ]\n}\n```\n\n`loc` is a breadcrumb path: `[\"body\", \"field_name\"]` for top-level fields,\n`[\"body\", \"nested\", \"field\"]` for nested models.\n\nRule of thumb: parse `detail[*].loc` in client code to map validation errors\nback to specific form fields for UX display.\n",{"id":982,"difficulty":104,"q":983,"a":984},"content-type-requirement","What Content-Type header does FastAPI expect for JSON body requests?","FastAPI expects `Content-Type: application\u002Fjson` for Pydantic body parameters.\nIf the client sends a different or missing `Content-Type`, FastAPI attempts to\nparse the body as JSON anyway — but tools like Swagger UI always send the header.\n\n```http\nPOST \u002Fitems HTTP\u002F1.1\nContent-Type: application\u002Fjson\n\n{\"name\": \"Widget\", \"price\": 9.99}\n```\n\nFor form data: `application\u002Fx-www-form-urlencoded` or `multipart\u002Fform-data`.\nFor raw file streams: `application\u002Foctet-stream` with `Request` body directly.\n\nRule of thumb: always set `Content-Type: application\u002Fjson` explicitly in REST\nclients — don't rely on default behaviour.\n",{"id":986,"difficulty":125,"q":987,"a":988},"raw-request-body","How do you read the raw request body bytes in FastAPI without Pydantic parsing?","Inject the `Request` object and call `await request.body()`:\n\n```python\nfrom fastapi import Request\n\n@app.post(\"\u002Fwebhook\")\nasync def webhook(request: Request):\n    raw = await request.body()       # bytes\n    payload = json.loads(raw)\n    signature = request.headers.get(\"X-Signature\")\n    verify_signature(raw, signature) # HMAC check on raw bytes\n    return {\"ok\": True}\n```\n\nYou cannot mix `await request.body()` with a Pydantic body parameter in the same\nhandler — FastAPI reads the body stream once; `body()` consumes it before Pydantic can.\n\nRule of thumb: use raw body access for webhooks that need HMAC verification\non the exact bytes; use Pydantic models for everything else.\n",{"id":990,"difficulty":125,"q":991,"a":992},"body-max-size","How do you enforce a maximum request body size in FastAPI?","FastAPI has no built-in body size limit. Enforce it in middleware:\n\n```python\nfrom fastapi import Request\nfrom fastapi.responses import JSONResponse\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nMAX_BODY = 1 * 1024 * 1024  # 1 MB\n\nclass LimitBodyMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request: Request, call_next):\n        if request.headers.get(\"content-length\"):\n            if int(request.headers[\"content-length\"]) > MAX_BODY:\n                return JSONResponse({\"detail\": \"Body too large\"}, status_code=413)\n        return await call_next(request)\n\napp.add_middleware(LimitBodyMiddleware)\n```\n\nFor production, configure the limit at the reverse proxy (Nginx `client_max_body_size`\nor Uvicorn `--limit-concurrency`) rather than in application code.\n\nRule of thumb: enforce body size at the reverse proxy level for efficiency;\nadd middleware as a defence-in-depth layer inside the app.\n",{"description":94},"FastAPI request body interview questions — Pydantic models as bodies, Body(), nested models, multiple bodies, form data, file uploads and embed.","fastapi\u002Frouting\u002Frequest-body","Ju078x3mX7g8VqxLdOPN9YTmVhYdnoMSpKenH_qpqkU",{"id":998,"title":999,"body":1000,"description":94,"difficulty":125,"extension":97,"framework":10,"frameworkSlug":8,"meta":1004,"navigation":99,"order":29,"path":1005,"questions":1006,"questionsCount":148,"related":149,"seo":1051,"seoDescription":1052,"stem":1053,"subtopic":1054,"topic":55,"topicSlug":57,"updated":154,"__hash__":1055},"qa\u002Ffastapi\u002Fsecurity\u002Fjwt.md","Jwt",{"type":91,"value":1001,"toc":1002},[],{"title":94,"searchDepth":29,"depth":29,"links":1003},[],{},"\u002Ffastapi\u002Fsecurity\u002Fjwt",[1007,1011,1015,1019,1023,1027,1031,1035,1039,1043,1047],{"id":1008,"difficulty":104,"q":1009,"a":1010},"what-is-jwt","What is a JWT and what are its three parts?","A **JSON Web Token** (JWT) is a compact, URL-safe string that encodes a signed\nJSON payload. It has three dot-separated Base64URL-encoded parts:\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9   ← Header\n.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxN}  ← Payload (claims)\n.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c  ← Signature\n```\n\n- **Header**: algorithm and token type (`{\"alg\": \"HS256\", \"typ\": \"JWT\"}`).\n- **Payload**: claims — standard (`sub`, `exp`, `iat`) and custom (`role`, `scopes`).\n- **Signature**: HMAC or RSA\u002FEC signature over header + payload.\n\nJWTs are **not encrypted by default** — the payload is only Base64-encoded.\nAnyone can decode it. The signature only proves it wasn't tampered with.\n\nRule of thumb: never put sensitive data (passwords, PII) in JWT payload — use\nJWE (encrypted JWT) if you need to protect the payload contents.\n",{"id":1012,"difficulty":96,"q":1013,"a":1014},"encode-decode-jwt","How do you encode and decode a JWT in a FastAPI app?","Install `python-jose[cryptography]` and use `jose.jwt`:\n\n```python\nfrom datetime import datetime, timedelta, timezone\nfrom jose import jwt, JWTError\n\nSECRET_KEY = \"your-256-bit-secret\"\nALGORITHM = \"HS256\"\n\ndef create_access_token(user_id: int, expires_minutes: int = 30) -> str:\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": datetime.now(timezone.utc) + timedelta(minutes=expires_minutes),\n        \"iat\": datetime.now(timezone.utc),\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\ndef decode_access_token(token: str) -> dict:\n    try:\n        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])\n    except JWTError:\n        raise HTTPException(401, \"Invalid token\")\n```\n\nRule of thumb: always pass `algorithms=` as a list (not a string) to `jwt.decode()` —\nthe list prevents algorithm-confusion attacks where an attacker switches from RS256 to HS256.\n",{"id":1016,"difficulty":96,"q":1017,"a":1018},"jwt-expiry","How does JWT expiry work and how does FastAPI check it?","The `exp` claim is a Unix timestamp. `python-jose` automatically validates it\nduring `jwt.decode()` — expired tokens raise `ExpiredSignatureError` (a subclass\nof `JWTError`).\n\n```python\nfrom jose import ExpiredSignatureError, JWTError\n\nasync def get_current_user(token: str = Depends(oauth2_scheme)):\n    try:\n        payload = jwt.decode(token, SECRET_KEY, algorithms=[\"HS256\"])\n    except ExpiredSignatureError:\n        raise HTTPException(401, \"Token has expired\")\n    except JWTError:\n        raise HTTPException(401, \"Invalid token\")\n    return payload\n```\n\nClock skew: `python-jose` allows a small tolerance via `options={\"leeway\": 10}` (10 seconds).\n\nRule of thumb: catch `ExpiredSignatureError` separately from other `JWTError` subtypes\nso you can give clients a specific \"please refresh\" error instead of a generic \"invalid token\".\n",{"id":1020,"difficulty":96,"q":1021,"a":1022},"jwt-claims","What standard JWT claims should you include and what do they mean?","| Claim | Name | Meaning |\n|-------|------|---------|\n| `sub` | Subject | Who the token is about (user ID) |\n| `exp` | Expiration | Unix timestamp when token expires |\n| `iat` | Issued At | Unix timestamp when token was created |\n| `nbf` | Not Before | Token not valid before this timestamp |\n| `jti` | JWT ID | Unique ID for revocation |\n| `iss` | Issuer | Who created the token |\n| `aud` | Audience | Who the token is intended for |\n\n```python\npayload = {\n    \"sub\": str(user.id),\n    \"exp\": utcnow + timedelta(minutes=30),\n    \"iat\": utcnow,\n    \"jti\": str(uuid4()),     # for revocation\n    \"iss\": \"https:\u002F\u002Fmyapi.example.com\",\n}\n```\n\nRule of thumb: always set `sub`, `exp`, and `iat`; add `jti` if you need\ntoken revocation; add `iss`\u002F`aud` for multi-service architectures.\n",{"id":1024,"difficulty":125,"q":1025,"a":1026},"hs256-vs-rs256","What is the difference between HS256 and RS256 JWT signing and when do you use each?","| | HS256 (HMAC) | RS256 (RSA) |\n|---|---|---|\n| Key type | Shared secret | Public\u002Fprivate key pair |\n| Who can sign | Anyone with the secret | Only the private key holder |\n| Who can verify | Anyone with the secret | Anyone with the public key |\n| Use case | Single service | Multiple services \u002F external clients |\n\n```python\n# HS256 — single service\njwt.encode(payload, \"shared-secret\", algorithm=\"HS256\")\njwt.decode(token,  \"shared-secret\", algorithms=[\"HS256\"])\n\n# RS256 — multi-service (sign with private, verify with public)\njwt.encode(payload, private_key, algorithm=\"RS256\")\njwt.decode(token,  public_key,   algorithms=[\"RS256\"])\n```\n\nWith RS256, you can publish the public key (JWKS endpoint) so other services\nverify tokens without ever seeing the private key.\n\nRule of thumb: use HS256 for simple single-service auth; use RS256 when tokens\nneed to be verified by external services or published via a JWKS endpoint.\n",{"id":1028,"difficulty":125,"q":1029,"a":1030},"algorithm-confusion","What is the JWT algorithm confusion attack and how does FastAPI prevent it?","If a service uses RS256 but `jwt.decode()` is called with `algorithms=[\"RS256\", \"HS256\"]`,\nan attacker can forge a token by taking the RS256 public key (which is public),\nusing it as an HMAC secret, and signing a new token with HS256. The server\naccepts it because HS256 is allowed.\n\nPrevention:\n```python\n# SAFE — only allow the expected algorithm\njwt.decode(token, PUBLIC_KEY, algorithms=[\"RS256\"])   # never include HS256\n\n# DANGEROUS\njwt.decode(token, PUBLIC_KEY, algorithms=[\"RS256\", \"HS256\"])\n```\n\nAlso: never use `algorithms=None` or skip algorithm validation.\n\nRule of thumb: always pin the algorithm to exactly one value matching what you\nuse to sign — allow-listing multiple algorithms invites confusion attacks.\n",{"id":1032,"difficulty":125,"q":1033,"a":1034},"jwt-none-attack","What is the JWT \"none\" algorithm attack?","Early JWT libraries accepted tokens with `\"alg\": \"none\"` — meaning no signature\nis needed. An attacker could forge any payload, set `alg: none`, and the library\nwould accept it without any verification.\n\n```\n# Malicious token — unsigned\n{\"alg\": \"none\", \"typ\": \"JWT\"}.{\"sub\": \"admin\"}.\n```\n\nModern libraries (`python-jose`, `PyJWT`) reject `none` by default. Ensure\n`\"none\"` is not in your `algorithms=` list:\n\n```python\njwt.decode(token, SECRET, algorithms=[\"HS256\"])   # \"none\" not listed → rejected\n```\n\nRule of thumb: never include `\"none\"` in `algorithms=`; if your library has a\n`verify_signature=False` option, never use it in production.\n",{"id":1036,"difficulty":96,"q":1037,"a":1038},"jwt-storage","Where should JWTs be stored on the client side, and what are the security trade-offs?","| Storage | XSS risk | CSRF risk | Notes |\n|---------|----------|-----------|-------|\n| `localStorage` | High | None | JS-accessible — token stolen by XSS |\n| `sessionStorage` | High | None | Same as localStorage but tab-scoped |\n| `httpOnly` cookie | None | Medium | Inaccessible to JS; add SameSite=Lax |\n\nBest practice — **`httpOnly`, `Secure`, `SameSite=Lax` cookie**:\n```python\nresponse.set_cookie(\n    key=\"access_token\",\n    value=token,\n    httponly=True,\n    secure=True,\n    samesite=\"lax\",\n    max_age=1800,\n)\n```\n\n`SameSite=Lax` blocks cross-site POST requests (CSRF for state-changing operations)\nwhile allowing cross-site GET navigation.\n\nRule of thumb: store tokens in `httpOnly` cookies, not `localStorage` — XSS is\nmore common than CSRF, and `httpOnly` provides a meaningful defence against it.\n",{"id":1040,"difficulty":125,"q":1041,"a":1042},"token-rotation","What is refresh token rotation and why is it more secure than long-lived access tokens?","**Refresh token rotation** issues a new refresh token on every use and immediately\ninvalidates the old one. If a stolen refresh token is used, the legitimate user's\nnext request will fail (old token invalid) — alerting you to a compromise.\n\n```python\n@app.post(\"\u002Frefresh\")\nasync def refresh(old_token: str = Body(...)):\n    payload = decode_refresh_token(old_token)\n    jti = payload[\"jti\"]\n\n    # Check old token exists and isn't already used\n    stored = await db.get_refresh_token(jti)\n    if not stored or stored.used:\n        # Token reuse detected — revoke all user tokens\n        await db.revoke_all_tokens(payload[\"sub\"])\n        raise HTTPException(401, \"Refresh token reuse detected\")\n\n    await db.mark_used(jti)\n    new_refresh = create_refresh_token(payload[\"sub\"])\n    new_access  = create_access_token(payload[\"sub\"])\n    await db.store_refresh_token(new_refresh)\n    return {\"access_token\": new_access, \"refresh_token\": new_refresh}\n```\n\nRule of thumb: implement refresh token rotation for any app with sensitive data —\na detected reuse should trigger automatic revocation of all user sessions.\n",{"id":1044,"difficulty":125,"q":1045,"a":1046},"jwt-decode-without-verify","How do you decode a JWT payload without verifying the signature, and when is this safe?","Pass `options={\"verify_signature\": False}` to `jwt.decode()` in `python-jose`,\nor use `PyJWT` with `algorithms=[]`:\n\n```python\n# python-jose\npayload = jwt.decode(\n    token,\n    key=\"\",\n    algorithms=[\"HS256\"],\n    options={\"verify_signature\": False},\n)\n```\n\nThis is **never safe** for authenticating users. Valid uses:\n- Logging the `sub` from an **already-verified** token that expired (for debugging).\n- Inspecting headers to decide which key\u002Falgorithm to use before re-decoding with verification.\n\nRule of thumb: never use unverified JWT decoding in the auth path — it is only\nfor tooling and debugging flows where the token has already been verified elsewhere.\n",{"id":1048,"difficulty":104,"q":1049,"a":1050},"jwt-size","What are the practical size limits for JWT tokens and how do they affect HTTP?","JWTs have no spec-defined size limit, but practical limits exist:\n- **HTTP headers**: servers typically cap individual headers at 8 KB.\n- **Cookies**: limited to 4 KB per cookie.\n- **Typical JWT**: a token with 5-10 claims is 200-500 bytes after Base64URL encoding.\n\nProblems arise when you embed large payloads (roles list, user profile) in the token:\n\n```python\n# BAD — embedding permissions in token can push it over 4 KB\npayload = {\"sub\": \"1\", \"permissions\": list_of_200_permissions}\n\n# GOOD — store just the user ID; look up permissions from DB\u002Fcache\npayload = {\"sub\": \"1\", \"exp\": ..., \"role\": \"admin\"}\n```\n\nRule of thumb: keep JWT payloads under 1 KB — store only stable identity claims\n(`sub`, `role`, `scopes`); look up volatile permissions from cache at request time.\n",{"description":94},"FastAPI JWT interview questions — encoding, decoding, python-jose, expiry, claims, signature verification and common JWT pitfalls.","fastapi\u002Fsecurity\u002Fjwt","JWT Tokens","a8L1LKxnRwm7dhkGZHtjql_sWeh8ZBuxGKpnQdkRifU",{"id":1057,"title":1058,"body":1059,"description":94,"difficulty":125,"extension":97,"framework":10,"frameworkSlug":8,"meta":1063,"navigation":99,"order":29,"path":1064,"questions":1065,"questionsCount":148,"related":149,"seo":1110,"seoDescription":1111,"stem":1112,"subtopic":1058,"topic":72,"topicSlug":74,"updated":154,"__hash__":1113},"qa\u002Ffastapi\u002Ftesting\u002Fasync-testing.md","Async Testing",{"type":91,"value":1060,"toc":1061},[],{"title":94,"searchDepth":29,"depth":29,"links":1062},[],{},"\u002Ffastapi\u002Ftesting\u002Fasync-testing",[1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,1106],{"id":1067,"difficulty":96,"q":1068,"a":1069},"why-async-tests","Why do you need special handling for async tests in FastAPI?","`TestClient` is synchronous — it works for most FastAPI tests. But some scenarios\nrequire true async tests:\n- Testing async dependencies directly (e.g., `get_async_db`).\n- Using async database fixtures (creating tables, seeding data).\n- Testing WebSocket connections with `AsyncClient`.\n- Testing code that uses `asyncio.gather` or `asyncio.create_task`.\n\nPlain `pytest` doesn't run `async def` test functions. You need a plugin:\n\n```bash\npip install anyio[trio] pytest-anyio   # recommended with FastAPI\u002FStarlette\n# OR\npip install pytest-asyncio\n```\n\nRule of thumb: use `TestClient` (sync) for HTTP endpoint tests — it's simpler;\nuse async tests only when you genuinely need to `await` inside the test body.\n",{"id":1071,"difficulty":96,"q":1072,"a":1073},"pytest-anyio-setup","How do you configure `pytest-anyio` for FastAPI async tests?","Mark async tests with `@pytest.mark.anyio` or configure anyio as the default\nasync mode:\n\n```python\n# conftest.py — set anyio as default for the whole session\nimport pytest\n\n@pytest.fixture(scope=\"session\")\ndef anyio_backend():\n    return \"asyncio\"\n```\n\n```python\n# test_async.py\nimport pytest\n\n@pytest.mark.anyio\nasync def test_async_endpoint():\n    async with AsyncClient(transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\") as client:\n        response = await client.get(\"\u002Fitems\")\n        assert response.status_code == 200\n```\n\nRule of thumb: set `anyio_backend = \"asyncio\"` globally in `conftest.py` to\navoid repeating the marker on every async test.\n",{"id":1075,"difficulty":96,"q":1076,"a":1077},"async-client","How do you use `httpx.AsyncClient` to test a FastAPI app?","Use `ASGITransport` to route requests directly to the ASGI app without a network:\n\n```python\nimport pytest\nimport httpx\nfrom httpx import AsyncClient, ASGITransport\nfrom app.main import app\n\n@pytest.mark.anyio\nasync def test_get_item():\n    async with AsyncClient(\n        transport=ASGITransport(app=app),\n        base_url=\"http:\u002F\u002Ftestserver\",\n    ) as client:\n        response = await client.get(\"\u002Fitems\u002F1\")\n        assert response.status_code == 200\n        assert response.json()[\"id\"] == 1\n```\n\n`ASGITransport` calls the app's ASGI interface directly — no TCP socket needed.\n\nRule of thumb: use `AsyncClient` for async tests; use `TestClient` for sync\ntests — `AsyncClient` requires `async with` and an event loop.\n",{"id":1079,"difficulty":96,"q":1080,"a":1081},"async-client-auth","How do you add auth headers to an `httpx.AsyncClient` for multiple test requests?","Set `headers=` in the `AsyncClient` constructor or use an `httpx.Auth` class:\n\n```python\n@pytest.mark.anyio\nasync def test_authenticated():\n    token = create_test_token(user_id=1)\n    async with AsyncClient(\n        transport=ASGITransport(app=app),\n        base_url=\"http:\u002F\u002Ftestserver\",\n        headers={\"Authorization\": f\"Bearer {token}\"},\n    ) as client:\n        resp = await client.get(\"\u002Fme\")\n        assert resp.status_code == 200\n        resp2 = await client.get(\"\u002Forders\")\n        assert resp2.status_code == 200\n```\n\nRule of thumb: set auth headers in the constructor for tests that make multiple\nauthenticated requests; pass `headers=` per-request when mixing auth states.\n",{"id":1083,"difficulty":125,"q":1084,"a":1085},"async-db-fixture","How do you create an async database fixture for FastAPI integration tests?","```python\n# conftest.py\nimport pytest\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\nfrom app.db.base import Base\nfrom app.main import app\nfrom app.db.session import get_async_db\n\nTEST_DB = \"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\"\n\n@pytest.fixture(scope=\"session\")\ndef anyio_backend():\n    return \"asyncio\"\n\n@pytest.fixture(scope=\"session\")\nasync def db_engine():\n    engine = create_async_engine(TEST_DB)\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield engine\n    await engine.dispose()\n\n@pytest.fixture\nasync def db_session(db_engine):\n    factory = async_sessionmaker(db_engine, expire_on_commit=False)\n    async with factory() as session:\n        yield session\n        await session.rollback()   # undo test writes\n\n@pytest.fixture\nasync def async_client(db_session):\n    async def override_db():\n        yield db_session\n    app.dependency_overrides[get_async_db] = override_db\n    async with AsyncClient(transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\") as c:\n        yield c\n    app.dependency_overrides.clear()\n```\n\nRule of thumb: roll back after each test (`await session.rollback()`) rather\nthan truncating tables — rollback is faster and avoids FK constraint issues.\n",{"id":1087,"difficulty":125,"q":1088,"a":1089},"websocket-testing","How do you test WebSocket endpoints in FastAPI?","Use `TestClient` with the `websocket_connect()` context manager:\n\n```python\nfrom fastapi.testclient import TestClient\n\ndef test_websocket_echo():\n    with TestClient(app) as client:\n        with client.websocket_connect(\"\u002Fws\") as ws:\n            ws.send_text(\"Hello\")\n            data = ws.receive_text()\n            assert data == \"Echo: Hello\"\n```\n\nFor async WebSocket tests with `httpx`:\n```python\n@pytest.mark.anyio\nasync def test_ws_async():\n    async with AsyncClient(transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\") as client:\n        async with client.websocket_connect(\"\u002Fws\") as ws:\n            await ws.send_text(\"ping\")\n            msg = await ws.receive_text()\n            assert msg == \"pong\"\n```\n\nRule of thumb: use `TestClient.websocket_connect` for simple sync WS tests;\nuse async `AsyncClient` when the WS handler is complex and needs concurrent I\u002FO.\n",{"id":1091,"difficulty":125,"q":1092,"a":1093},"event-loop-scope","What is event loop scope in pytest-asyncio\u002Fanyio and why does it matter?","Each pytest scope (`function`, `module`, `session`) can have its own event loop.\nBy default, each async test gets a **fresh event loop** (`scope=\"function\"`).\n\nThis matters because:\n- `scope=\"session\"` async fixtures (like a DB engine) must share an event loop\n  with the tests that use them.\n- Mixing scopes can cause \"Event loop is closed\" or \"attached to different loop\" errors.\n\nWith `pytest-anyio`, configure the scope:\n```python\n# conftest.py\n@pytest.fixture(scope=\"session\")\ndef anyio_backend():\n    return \"asyncio\"\n```\n\nThis uses a session-scoped event loop — all async fixtures and tests share one loop.\n\nRule of thumb: set `scope=\"session\"` for `anyio_backend` when you have\nsession-scoped async fixtures (DB engines, HTTP clients) — otherwise they can't\nbe awaited from function-scoped tests.\n",{"id":1095,"difficulty":125,"q":1096,"a":1097},"trio-backend","Can FastAPI tests run on Trio instead of asyncio?","Yes — FastAPI is built on Starlette which uses `anyio`, supporting both asyncio\nand Trio. Change the backend in conftest:\n\n```python\n@pytest.fixture(scope=\"session\")\ndef anyio_backend():\n    return \"trio\"   # or \"asyncio\"\n```\n\nAnd mark tests:\n```python\n@pytest.mark.anyio\nasync def test_endpoint():\n    async with AsyncClient(...) as client:\n        ...\n```\n\nRunning on Trio can catch asyncio-specific bugs (e.g., code that relies on\nasyncio internals rather than anyio primitives).\n\nRule of thumb: run your test suite on both asyncio and Trio once in CI to catch\nportability issues; ship on asyncio for production (most libraries are asyncio-native).\n",{"id":1099,"difficulty":96,"q":1100,"a":1101},"mock-async-service","How do you mock an async function dependency in a FastAPI test?","Use `unittest.mock.AsyncMock` or override the dependency:\n\n```python\nfrom unittest.mock import AsyncMock, patch\n\n# Option A — patch the async function directly\n@pytest.mark.anyio\nasync def test_with_mock():\n    with patch(\"app.services.email.send_email\", new_callable=AsyncMock) as mock_email:\n        async with AsyncClient(transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\") as c:\n            resp = await c.post(\"\u002Fsignup\", json={\"email\": \"a@b.com\"})\n        assert resp.status_code == 201\n        mock_email.assert_called_once_with(\"a@b.com\", \"Welcome!\")\n\n# Option B — dependency override\nasync def fake_send_email(*args, **kwargs):\n    pass   # no-op\n\napp.dependency_overrides[send_email_dep] = lambda: fake_send_email\n```\n\nRule of thumb: use `dependency_overrides` for FastAPI-injected async deps;\nuse `AsyncMock` + `patch` for module-level async functions not in the DI graph.\n",{"id":1103,"difficulty":125,"q":1104,"a":1105},"testing-background-tasks","How do you test that a BackgroundTask was scheduled and optionally verify it ran?","**Verify it was scheduled** — check the response immediately, background not run yet:\n```python\ndef test_signup_queues_email(client):\n    resp = client.post(\"\u002Fsignup\", json={\"email\": \"a@b.com\"})\n    assert resp.status_code == 201\n    # email runs after response — we can't assert it ran synchronously\n```\n\n**Verify it ran** — `TestClient` runs background tasks before closing:\n```python\nemails_sent = []\n\ndef fake_send_email(to: str, msg: str):\n    emails_sent.append(to)\n\napp.dependency_overrides[get_email_sender] = lambda: fake_send_email\n\ndef test_email_sent(client):\n    with TestClient(app) as c:    # lifespan + BG tasks run\n        c.post(\"\u002Fsignup\", json={\"email\": \"a@b.com\"})\n    assert \"a@b.com\" in emails_sent\n```\n\nRule of thumb: `TestClient` (sync) runs background tasks before `__exit__` —\nuse it to verify task execution; for async tests you may need to `await` explicitly.\n",{"id":1107,"difficulty":96,"q":1108,"a":1109},"snapshot-testing","How do you test the exact JSON shape of a FastAPI response (snapshot testing)?","Use `syrupy` (snapshot testing library) or assert field-by-field:\n\n```python\n# Field-by-field (explicit)\ndef test_item_response(client):\n    resp = client.get(\"\u002Fitems\u002F1\")\n    data = resp.json()\n    assert data.keys() == {\"id\", \"name\", \"price\", \"created_at\"}\n    assert isinstance(data[\"id\"], int)\n    assert isinstance(data[\"price\"], float)\n\n# Snapshot (syrupy)\ndef test_item_snapshot(client, snapshot):\n    resp = client.get(\"\u002Fitems\u002F1\")\n    assert resp.json() == snapshot   # first run saves; subsequent runs compare\n```\n\nRule of thumb: explicit field assertions are more maintainable than full JSON\nsnapshots for frequently changing response shapes — snapshots are great for\nstable, complex nested responses.\n",{"description":94},"FastAPI async testing interview questions — pytest-anyio, httpx AsyncClient, async fixtures, event loop scope and testing async dependencies.","fastapi\u002Ftesting\u002Fasync-testing","Nyo-aoIOW4IEDEjf65eVCrhZM1CkcWl5y76T2eotItY",{"id":1115,"title":1116,"body":1117,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1121,"navigation":99,"order":38,"path":1122,"questions":1123,"questionsCount":214,"related":149,"seo":1172,"seoDescription":1173,"stem":1174,"subtopic":1175,"topic":64,"topicSlug":65,"updated":154,"__hash__":1176},"qa\u002Ffastapi\u002Fdatabase\u002Fmigrations.md","Migrations",{"type":91,"value":1118,"toc":1119},[],{"title":94,"searchDepth":29,"depth":29,"links":1120},[],{},"\u002Ffastapi\u002Fdatabase\u002Fmigrations",[1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168],{"id":1125,"difficulty":104,"q":1126,"a":1127},"what-is-alembic","What is Alembic and why do you use it with FastAPI instead of `create_all`?","**Alembic** is SQLAlchemy's official database migration tool. It tracks schema\nchanges as versioned migration scripts and applies them incrementally.\n\n`Base.metadata.create_all()` only **creates missing tables** — it cannot alter\nexisting columns, add indexes, rename tables, or roll back changes. Alembic can\ndo all of this and keeps an audit trail.\n\n```\nalembic upgrade head     # apply all pending migrations\nalembic downgrade -1     # roll back the last migration\nalembic history          # show migration history\n```\n\nRule of thumb: use `create_all` only in local dev and test databases; use\nAlembic for every schema change in staging and production.\n",{"id":1129,"difficulty":104,"q":1130,"a":1131},"alembic-init","How do you initialise Alembic in a FastAPI project?","```bash\npip install alembic\nalembic init alembic    # creates alembic\u002F directory and alembic.ini\n```\n\nDirectory structure created:\n```\nalembic\u002F\n├── env.py          # migration environment (connect to DB)\n├── script.py.mako  # template for new migration files\n└── versions\u002F       # generated migration scripts live here\nalembic.ini         # config (sqlalchemy.url, etc.)\n```\n\nEdit `alembic.ini` to set the DB URL, or set it dynamically in `env.py`:\n```python\n# alembic\u002Fenv.py\nfrom app.config import settings\nconfig.set_main_option(\"sqlalchemy.url\", settings.database_url)\n```\n\nRule of thumb: set the DB URL from environment variables in `env.py` rather\nthan hard-coding it in `alembic.ini` — `alembic.ini` is committed to git.\n",{"id":1133,"difficulty":96,"q":1134,"a":1135},"alembic-env-py","What changes do you make to `alembic\u002Fenv.py` to support autogenerate?","Point `target_metadata` to your `Base.metadata` so Alembic can compare the\ncurrent schema against your ORM models:\n\n```python\n# alembic\u002Fenv.py\nfrom app.db.base import Base          # import Base\nfrom app.db.models import user, order  # ensure all models are imported!\n\ntarget_metadata = Base.metadata\n\ndef run_migrations_online():\n    connectable = engine_from_config(\n        config.get_section(config.config_ini_section),\n        prefix=\"sqlalchemy.\",\n    )\n    with connectable.connect() as connection:\n        context.configure(connection=connection, target_metadata=target_metadata)\n        with context.begin_transaction():\n            context.run_migrations()\n```\n\nThe model imports are critical — Alembic only knows about tables whose models\nare imported at migration time.\n\nRule of thumb: create a `db\u002Fbase_all.py` that imports every ORM model and\nimport it in `env.py` — it's easy to forget a new model.\n",{"id":1137,"difficulty":104,"q":1138,"a":1139},"autogenerate-migration","How do you auto-generate a migration script with Alembic?","```bash\nalembic revision --autogenerate -m \"add users table\"\n```\n\nAlembic compares `target_metadata` (your ORM models) to the current DB schema\nand generates a script with the diff:\n\n```python\n# alembic\u002Fversions\u002Fabc123_add_users_table.py\ndef upgrade():\n    op.create_table(\n        \"users\",\n        sa.Column(\"id\", sa.Integer(), nullable=False),\n        sa.Column(\"name\", sa.String(100), nullable=False),\n        sa.PrimaryKeyConstraint(\"id\"),\n    )\n\ndef downgrade():\n    op.drop_table(\"users\")\n```\n\nAlways **review the generated script** before applying — Alembic can't detect\nrenames (it sees a drop + create) and may miss custom indexes or constraints.\n\nRule of thumb: treat autogenerate as a starting point, not a final answer —\nalways read the diff and edit it if needed before committing.\n",{"id":1141,"difficulty":104,"q":1142,"a":1143},"upgrade-downgrade","How do you apply and roll back Alembic migrations?","```bash\n# Apply all pending migrations\nalembic upgrade head\n\n# Apply exactly N migrations forward\nalembic upgrade +2\n\n# Roll back the most recent migration\nalembic downgrade -1\n\n# Roll back to a specific revision\nalembic downgrade abc123\n\n# Roll back everything\nalembic downgrade base\n```\n\nAlembic tracks the current revision in the `alembic_version` table in the DB.\n\nRule of thumb: always write a proper `downgrade()` function — even if you never\nplan to roll back in production, it's invaluable in development and CI.\n",{"id":1145,"difficulty":125,"q":1146,"a":1147},"alembic-async","How do you configure Alembic to run async migrations (asyncpg)?","Use `AsyncEngine` and `run_async_migrations` in `env.py`:\n\n```python\n# alembic\u002Fenv.py\nimport asyncio\nfrom sqlalchemy.ext.asyncio import create_async_engine\n\ndef do_run_migrations(connection):\n    context.configure(connection=connection, target_metadata=target_metadata)\n    with context.begin_transaction():\n        context.run_migrations()\n\nasync def run_async_migrations():\n    engine = create_async_engine(settings.database_url)\n    async with engine.connect() as conn:\n        await conn.run_sync(do_run_migrations)\n    await engine.dispose()\n\ndef run_migrations_online():\n    asyncio.run(run_async_migrations())\n```\n\nThe key is `conn.run_sync(do_run_migrations)` — Alembic's migration API is\nsynchronous; you bridge it with `run_sync`.\n\nRule of thumb: async migration setup is boilerplate — copy the pattern from the\nofficial Alembic docs rather than hand-rolling it.\n",{"id":1149,"difficulty":125,"q":1150,"a":1151},"migration-in-production","What is the best practice for running Alembic migrations in production?","Never run migrations from inside the FastAPI `lifespan` function in production.\nInstead:\n\n**Option A — Pre-deployment step** (recommended):\n```bash\n# In CI\u002FCD pipeline, before rolling out new pods\nalembic upgrade head\n```\n\n**Option B — Init container** (Kubernetes):\n```yaml\ninitContainers:\n  - name: migrate\n    image: myapp:latest\n    command: [\"alembic\", \"upgrade\", \"head\"]\n```\n\nReasons to avoid in-app migrations:\n- Multiple pods start simultaneously → race condition running migrations twice.\n- A failed migration crashes the app before it can serve requests.\n- Slow migrations block the startup health check.\n\nRule of thumb: decouple migrations from app startup — run them as a separate\nstep with exactly one executor, with a rollback plan if the migration fails.\n",{"id":1153,"difficulty":125,"q":1154,"a":1155},"data-migration","How do you write a data migration in Alembic (not just schema changes)?","Use `op.execute()` for SQL or `op.get_bind()` to access the connection for\nORM-style operations:\n\n```python\nfrom alembic import op\nimport sqlalchemy as sa\n\ndef upgrade():\n    # Schema change\n    op.add_column(\"users\", sa.Column(\"full_name\", sa.String(200)))\n\n    # Data migration — populate full_name from existing columns\n    conn = op.get_bind()\n    conn.execute(\n        sa.text(\"UPDATE users SET full_name = first_name || ' ' || last_name\")\n    )\n\n    # Make it non-nullable now that data is populated\n    op.alter_column(\"users\", \"full_name\", nullable=False)\n\ndef downgrade():\n    op.drop_column(\"users\", \"full_name\")\n```\n\nRule of thumb: do schema changes, then data migration, then add constraints —\nnever add a NOT NULL constraint before populating the column.\n",{"id":1157,"difficulty":125,"q":1158,"a":1159},"multiple-heads","What is a merge migration in Alembic and when do you need one?","A **multiple heads** situation occurs when two developers create migrations from\nthe same base revision — Alembic has two \"latest\" revisions and doesn't know\nwhich to apply next.\n\n```bash\nalembic heads   # shows: abc123  xyz789  (two heads)\nalembic merge heads -m \"merge feature_a and feature_b\"\n```\n\nThis creates a new migration that has both revisions as parents, resolving the\ndivergence. The merge migration itself is usually empty.\n\nRule of thumb: include the `alembic_version` table (or the `versions\u002F` directory)\nin code review — catch diverged heads early rather than after merging to main.\n",{"id":1161,"difficulty":104,"q":1162,"a":1163},"alembic-version-table","What is the `alembic_version` table and what does it contain?","Alembic creates a single-row table `alembic_version` in the target database to\ntrack which revision is currently applied:\n\n```sql\nSELECT * FROM alembic_version;\n-- version_num\n-- abc123def456   (hash of the latest applied migration)\n```\n\nWhen you run `alembic upgrade head`, it reads this table, finds all unapplied\nrevisions in order, runs their `upgrade()` functions, and updates `version_num`.\n\nRule of thumb: never edit `alembic_version` manually — if it gets out of sync,\nuse `alembic stamp \u003Crevision>` to set it without running migration code.\n",{"id":1165,"difficulty":96,"q":1166,"a":1167},"alembic-stamp","What does `alembic stamp` do and when would you use it?","`alembic stamp \u003Crevision>` sets the `alembic_version` table to the given revision\n**without running any migration code**. Use it when:\n\n1. You're bootstrapping a new DB that already has the schema (e.g., cloned from\n   prod) and you want to mark it as current without running all migrations.\n2. You manually applied a schema change and want Alembic to know it's done.\n\n```bash\nalembic stamp head      # mark DB as fully migrated (no code runs)\nalembic stamp abc123    # mark a specific revision as current\n```\n\nRule of thumb: use `alembic stamp head` when creating a fresh DB from `create_all`\nto keep Alembic in sync; never use it to skip failed migrations in production.\n",{"id":1169,"difficulty":96,"q":1170,"a":1171},"migration-testing","How do you test Alembic migrations in a CI pipeline?","Run upgrade + downgrade in CI against a test database:\n\n```bash\n# In CI\nalembic upgrade head      # apply all migrations\nalembic downgrade base    # roll all the way back\nalembic upgrade head      # re-apply (round-trip test)\n```\n\nFor individual migration testing in pytest:\n```python\n@pytest.fixture\ndef migrated_db(postgres_url):\n    engine = create_engine(postgres_url)\n    with engine.begin() as conn:\n        alembic_cfg = Config(\"alembic.ini\")\n        alembic_cfg.set_main_option(\"sqlalchemy.url\", postgres_url)\n        command.upgrade(alembic_cfg, \"head\")\n    yield engine\n    with engine.begin() as conn:\n        command.downgrade(alembic_cfg, \"base\")\n```\n\nRule of thumb: test up + down in CI on every PR — a migration that upgrades\nsuccessfully but can't downgrade will strand you if a release needs to roll back.\n",{"description":94},"FastAPI Alembic migration interview questions — setup, autogenerate, upgrade, downgrade, async migrations and production best practices.","fastapi\u002Fdatabase\u002Fmigrations","Alembic Migrations","2iCSPW0I4hfqjDOfBqCpp72k6A21Eg3YDczynF-U0Uw",{"id":1178,"title":1179,"body":1180,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1184,"navigation":99,"order":38,"path":1185,"questions":1186,"questionsCount":148,"related":149,"seo":1231,"seoDescription":1232,"stem":1233,"subtopic":1234,"topic":46,"topicSlug":48,"updated":154,"__hash__":1235},"qa\u002Ffastapi\u002Fdependency-injection\u002Flifespan.md","Lifespan",{"type":91,"value":1181,"toc":1182},[],{"title":94,"searchDepth":29,"depth":29,"links":1183},[],{},"\u002Ffastapi\u002Fdependency-injection\u002Flifespan",[1187,1191,1195,1199,1203,1207,1211,1215,1219,1223,1227],{"id":1188,"difficulty":104,"q":1189,"a":1190},"lifespan-basics","What is the `lifespan` parameter in FastAPI and what does it replace?","`lifespan` is an `async` context manager function that runs **startup** code\nbefore `yield` and **shutdown** code after it. It was introduced to replace\nthe older `@app.on_event(\"startup\")` \u002F `@app.on_event(\"shutdown\")` decorators,\nwhich are deprecated.\n\n```python\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # startup\n    await db_pool.connect()\n    print(\"DB connected\")\n    yield\n    # shutdown\n    await db_pool.disconnect()\n    print(\"DB disconnected\")\n\napp = FastAPI(lifespan=lifespan)\n```\n\nRule of thumb: always use `lifespan=` for new apps — it's the modern approach\nand groups startup\u002Fshutdown logic in one readable function.\n",{"id":1192,"difficulty":104,"q":1193,"a":1194},"on-event-deprecated","Why are `@app.on_event(\"startup\")` decorators deprecated and what should you use instead?","The decorator approach had two problems:\n1. Startup and shutdown logic is split across two separate functions — harder\n   to read the resource lifecycle.\n2. You can't use `async with` or `yield`-based context managers in event handlers.\n\nThe `lifespan` context manager fixes both:\n\n```python\n# Old (deprecated)\n@app.on_event(\"startup\")\nasync def startup():\n    await cache.connect()\n\n@app.on_event(\"shutdown\")\nasync def shutdown():\n    await cache.disconnect()\n\n# New (preferred)\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await cache.connect()\n    yield\n    await cache.disconnect()\n\napp = FastAPI(lifespan=lifespan)\n```\n\nRule of thumb: migrate `@app.on_event` to `lifespan` in any new or refactored\ncodebase — it's just moving the two functions into a context manager.\n",{"id":1196,"difficulty":96,"q":1197,"a":1198},"app-state","How do you share objects (DB pool, HTTP client) across all requests in FastAPI?","Store them on `app.state` inside the `lifespan` function. `app.state` is a\n`Starlette` `State` object — a simple namespace you can attach anything to.\n\n```python\nimport httpx\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI, Request\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    app.state.http_client = httpx.AsyncClient()\n    yield\n    await app.state.http_client.aclose()\n\napp = FastAPI(lifespan=lifespan)\n\n@app.get(\"\u002Fproxy\")\nasync def proxy(request: Request):\n    client = request.app.state.http_client\n    resp = await client.get(\"https:\u002F\u002Fapi.example.com\u002Fdata\")\n    return resp.json()\n```\n\nRule of thumb: use `app.state` for truly shared singletons (connection pools,\nloaded ML models, config); use `Depends()` for per-request resources.\n",{"id":1200,"difficulty":96,"q":1201,"a":1202},"lifespan-with-db","Show the standard pattern for initialising an async SQLAlchemy engine in lifespan.","```python\nfrom contextlib import asynccontextmanager\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker\nfrom fastapi import FastAPI\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    engine = create_async_engine(settings.database_url, pool_size=10)\n    app.state.db_factory = async_sessionmaker(engine, expire_on_commit=False)\n    yield\n    await engine.dispose()\n\napp = FastAPI(lifespan=lifespan)\n\n# Dependency that uses the shared engine\nasync def get_db(request: Request):\n    async with request.app.state.db_factory() as session:\n        yield session\n```\n\nThe engine (and its connection pool) is created once at startup and disposed\ngracefully at shutdown.\n\nRule of thumb: create the engine in `lifespan`, not at module level — it lets\nyou inject different engines in tests via `lifespan` override.\n",{"id":1204,"difficulty":125,"q":1205,"a":1206},"composing-lifespans","How do you compose multiple startup\u002Fshutdown concerns without one giant lifespan function?","Nest `asynccontextmanager` functions inside the main lifespan:\n\n```python\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def db_lifespan(app):\n    engine = create_async_engine(settings.db_url)\n    app.state.engine = engine\n    yield\n    await engine.dispose()\n\n@asynccontextmanager\nasync def cache_lifespan(app):\n    app.state.redis = Redis.from_url(settings.redis_url)\n    yield\n    await app.state.redis.aclose()\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    async with db_lifespan(app):\n        async with cache_lifespan(app):\n            yield\n\napp = FastAPI(lifespan=lifespan)\n```\n\nEach sub-lifespan owns its resource; teardown runs in reverse nesting order.\n\nRule of thumb: one context manager per resource — compose them in the main\n`lifespan` rather than putting everything into one giant function.\n",{"id":1208,"difficulty":96,"q":1209,"a":1210},"lifespan-state-access","How do you access `app.state` from inside a dependency or middleware?","Three ways, depending on context:\n\n**In a dependency** — inject `Request`:\n```python\ndef get_client(request: Request) -> httpx.AsyncClient:\n    return request.app.state.http_client\n```\n\n**In middleware** — `request.app.state` is available the same way:\n```python\nasync def dispatch(self, request: Request, call_next):\n    client = request.app.state.http_client\n    ...\n```\n\n**At module level** — capture the `app` reference directly (only if in the\nsame module):\n```python\nclient = app.state.http_client   # only works after lifespan startup\n```\n\nRule of thumb: always access `app.state` through `request.app` in deps and\nmiddleware — it avoids circular imports and works correctly in tests with\n`TestClient`.\n",{"id":1212,"difficulty":125,"q":1213,"a":1214},"lifespan-testing","How do you test lifespan events in FastAPI?","`TestClient` runs the full lifespan when used as a context manager:\n\n```python\nfrom fastapi.testclient import TestClient\nfrom app.main import app\n\ndef test_lifespan_sets_state():\n    with TestClient(app) as client:\n        # startup has run; app.state is populated\n        resp = client.get(\"\u002Fhealth\")\n        assert resp.status_code == 200\n    # shutdown has run; resources are closed\n```\n\nFor async tests with `httpx.AsyncClient`:\n```python\nfrom httpx import AsyncClient, ASGITransport\nimport pytest\n\n@pytest.mark.anyio\nasync def test_endpoint():\n    async with AsyncClient(\n        transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\"\n    ) as client:\n        resp = await client.get(\"\u002Fitems\")\n        assert resp.status_code == 200\n```\n\nRule of thumb: always use `TestClient` as a context manager (`with TestClient(app)`)\nso startup and shutdown run — a non-context-manager `TestClient` skips them.\n",{"id":1216,"difficulty":125,"q":1217,"a":1218},"lifespan-exception-handling","What happens if an exception is raised during startup in the `lifespan` function?","If an exception propagates out of the code before `yield`, FastAPI never starts\naccepting requests — the application exits. The teardown code (after `yield`)\ndoes **not** run because `yield` was never reached.\n\n```python\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    try:\n        await db.connect()        # if this raises, app won't start\n    except ConnectionError as e:\n        log.critical(\"DB unavailable: %s\", e)\n        raise                     # startup fails, process exits\n    yield\n    await db.disconnect()         # never reached if startup failed\n```\n\nFor resources that you want to clean up even if a later startup step fails,\nnest the `try\u002Ffinally` around each step individually.\n\nRule of thumb: let startup exceptions propagate — a crashed startup is\nbetter than a running app with a broken DB connection.\n",{"id":1220,"difficulty":96,"q":1221,"a":1222},"router-vs-app-lifespan","Can `APIRouter` have its own lifespan?","No — `APIRouter` does not support `lifespan`. Only the root `FastAPI` instance\naccepts a lifespan function. To organise per-module startup logic, use helper\ncontext managers that the root `lifespan` composes:\n\n```python\n# routers\u002Fusers.py\n@asynccontextmanager\nasync def users_lifespan(app):\n    app.state.user_cache = {}\n    yield\n    app.state.user_cache.clear()\n\n# main.py\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    async with users_lifespan(app):\n        async with orders_lifespan(app):\n            yield\n\napp = FastAPI(lifespan=lifespan)\n```\n\nRule of thumb: keep the root `lifespan` as a compositor; define per-module\nstartup as `asynccontextmanager` functions and import them into main.\n",{"id":1224,"difficulty":96,"q":1225,"a":1226},"background-startup","How do you run a background task continuously from application startup?","Use `anyio.create_task_group()` or `asyncio.create_task()` inside the lifespan:\n\n```python\nimport asyncio\nfrom contextlib import asynccontextmanager\n\nasync def poll_metrics():\n    while True:\n        await push_metrics_to_monitoring()\n        await asyncio.sleep(60)\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    task = asyncio.create_task(poll_metrics())\n    yield\n    task.cancel()\n    try:\n        await task\n    except asyncio.CancelledError:\n        pass\n```\n\nAlways cancel and await the task on shutdown — leaving it running causes\n\"Task was destroyed but it is pending!\" warnings in tests.\n\nRule of thumb: cancel background tasks explicitly in the shutdown phase;\nstore the `Task` handle on `app.state` if other parts of the app need to check it.\n",{"id":1228,"difficulty":104,"q":1229,"a":1230},"request-state","What is `request.state` and how does it differ from `app.state`?","`request.state` is a per-request `State` object — separate for every HTTP\nrequest, discarded after the response is sent. `app.state` lives for the\nentire application lifetime.\n\n```python\n# Middleware sets per-request data\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request: Request, call_next):\n        request.state.request_id = str(uuid4())\n        return await call_next(request)\n\n# Handler or dependency reads it\n@app.get(\"\u002Fitems\")\nasync def list_items(request: Request):\n    rid = request.state.request_id\n    return {\"request_id\": rid}\n```\n\nRule of thumb: use `request.state` for per-request metadata (trace IDs, auth\ncontext set by middleware); use `app.state` for shared singletons.\n",{"description":94},"FastAPI lifespan interview questions — asynccontextmanager, startup\u002Fshutdown, app.state, resource pooling and testing with lifespan.","fastapi\u002Fdependency-injection\u002Flifespan","Lifespan & App State","TvEk1ojPF6D2IHDvxb_Q10SSpFcZyCAfjEuwKc2-RqM",{"id":1237,"title":1238,"body":1239,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1243,"navigation":99,"order":38,"path":1244,"questions":1245,"questionsCount":148,"related":149,"seo":1290,"seoDescription":1291,"stem":1292,"subtopic":1238,"topic":81,"topicSlug":83,"updated":154,"__hash__":1293},"qa\u002Ffastapi\u002Fdeployment\u002Fbackground-tasks.md","Background Tasks",{"type":91,"value":1240,"toc":1241},[],{"title":94,"searchDepth":29,"depth":29,"links":1242},[],{},"\u002Ffastapi\u002Fdeployment\u002Fbackground-tasks",[1246,1250,1254,1258,1262,1266,1270,1274,1278,1282,1286],{"id":1247,"difficulty":104,"q":1248,"a":1249},"background-tasks-basics","What are `BackgroundTasks` in FastAPI and how do you use them?","`BackgroundTasks` lets you schedule functions to run **after the HTTP response\nis sent** to the client. The handler returns immediately; the task runs in the\nsame process after the response is fully written.\n\n```python\nfrom fastapi import BackgroundTasks, FastAPI\n\napp = FastAPI()\n\ndef write_log(message: str):\n    with open(\"log.txt\", \"a\") as f:\n        f.write(message + \"\\n\")\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    result = await db.save(item)\n    background_tasks.add_task(write_log, f\"Created item {result.id}\")\n    return result   # response sent; write_log runs after\n```\n\nRule of thumb: inject `BackgroundTasks` as a parameter — FastAPI provides it\nautomatically; never instantiate it yourself.\n",{"id":1251,"difficulty":96,"q":1252,"a":1253},"async-background-task","Can background tasks in FastAPI be async functions?","Yes — `add_task` accepts both `async def` and regular `def` functions.\n\n```python\nasync def send_welcome_email(email: str):\n    await smtp_client.send(email, \"Welcome!\")\n\ndef sync_audit_log(event: str):\n    logger.info(event)    # sync — runs in thread pool\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate, background_tasks: BackgroundTasks):\n    new_user = await db.create(user)\n    background_tasks.add_task(send_welcome_email, new_user.email)   # async\n    background_tasks.add_task(sync_audit_log, f\"signup:{new_user.id}\")  # sync\n    return new_user\n```\n\nAsync tasks run on the event loop; sync tasks run in the thread pool.\nTasks execute **sequentially** in the order they were added.\n\nRule of thumb: prefer async background tasks in an async app — they don't\nblock the event loop and don't require thread pool slots.\n",{"id":1255,"difficulty":96,"q":1256,"a":1257},"background-task-ordering","In what order do multiple `BackgroundTasks` run?","**Sequentially, in the order they were added.** If task A runs 2 seconds and\ntask B runs 1 second, total background time is 3 seconds.\n\n```python\nbackground_tasks.add_task(task_a)   # runs first\nbackground_tasks.add_task(task_b)   # runs second, after task_a completes\n```\n\nFor parallel background work, use `asyncio.gather()` inside a single async task:\n\n```python\nasync def parallel_tasks(email: str, item_id: int):\n    await asyncio.gather(\n        send_email(email),\n        update_analytics(item_id),\n    )\n\nbackground_tasks.add_task(parallel_tasks, user.email, item.id)\n```\n\nRule of thumb: if background tasks are independent and their combined duration\nmatters, wrap them in an `asyncio.gather` inside a single background task.\n",{"id":1259,"difficulty":96,"q":1260,"a":1261},"background-task-error-handling","What happens if a background task raises an exception in FastAPI?","The exception is logged (Uvicorn logs it as an unhandled exception) but\n**does not affect the response** — it was already sent. The client never\nknows the task failed.\n\n```python\ndef risky_task():\n    raise ValueError(\"Something went wrong\")   # logged, response already sent\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    background_tasks.add_task(risky_task)\n    return item   # this 200 response is already sent before risky_task runs\n```\n\nFor reliable task execution (retries, persistence), use a proper task queue\n(Celery, ARQ, RQ) where tasks can be retried on failure.\n\nRule of thumb: use `BackgroundTasks` only for best-effort work (email\nnotification, audit logging); never for work that must succeed (payment processing,\ndata integrity).\n",{"id":1263,"difficulty":96,"q":1264,"a":1265},"background-task-vs-queue","When should you use `BackgroundTasks` vs a proper task queue like Celery?","| Concern | BackgroundTasks | Task queue (Celery\u002FARQ) |\n|---------|----------------|------------------------|\n| Must succeed | ❌ no retries | ✅ configurable retries |\n| Survives crash\u002Frestart | ❌ lost if process dies | ✅ persisted in broker |\n| Long-running (minutes) | ❌ blocks worker | ✅ separate worker |\n| Rate-limited external API | ❌ no throttling | ✅ rate limiting |\n| Monitor\u002Finspect tasks | ❌ | ✅ Flower, ARQ dashboard |\n| Simple fire-and-forget | ✅ | overkill |\n\n```python\n# BackgroundTasks — fine for quick, best-effort\nbackground_tasks.add_task(send_analytics_event, event_data)\n\n# Celery — required for reliability\nprocess_payment.delay(order_id)   # persisted in Redis\u002FRabbitMQ\n```\n\nRule of thumb: if the task must succeed, use a task queue; if losing it\noccasionally is acceptable, `BackgroundTasks` is simpler.\n",{"id":1267,"difficulty":125,"q":1268,"a":1269},"celery-fastapi","How do you integrate Celery with a FastAPI application?","```python\n# celery_app.py\nfrom celery import Celery\n\ncelery = Celery(\n    \"myapp\",\n    broker=\"redis:\u002F\u002Flocalhost:6379\u002F0\",\n    backend=\"redis:\u002F\u002Flocalhost:6379\u002F1\",\n)\n\n@celery.task\ndef send_email_task(to: str, subject: str, body: str):\n    smtp.send(to, subject, body)\n```\n\n```python\n# FastAPI route\nfrom celery_app import send_email_task\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate):\n    new_user = await db.create(user)\n    send_email_task.delay(new_user.email, \"Welcome!\", \"...\")\n    return new_user\n```\n\nStart workers separately:\n```bash\ncelery -A celery_app worker --loglevel=info\n```\n\nRule of thumb: keep Celery tasks in a separate module from FastAPI routes —\ntasks should be importable by Celery workers that don't start the web server.\n",{"id":1271,"difficulty":125,"q":1272,"a":1273},"arq-fastapi","What is ARQ and why might you prefer it over Celery for a FastAPI app?","**ARQ** (Async Redis Queue) is a task queue built for asyncio — tasks are `async def`\nfunctions, the worker is an event loop, and it uses Redis as the broker.\n\n```python\n# tasks.py\nfrom arq import cron\n\nasync def send_welcome_email(ctx, email: str):\n    await ctx[\"smtp\"].send(email, \"Welcome!\")\n\nclass WorkerSettings:\n    functions = [send_welcome_email]\n    redis_settings = RedisSettings()\n```\n\n```python\n# FastAPI\nfrom arq import create_pool\n\n@asynccontextmanager\nasync def lifespan(app):\n    app.state.arq = await create_pool(RedisSettings())\n    yield\n    await app.state.arq.aclose()\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate, request: Request):\n    await request.app.state.arq.enqueue_job(\"send_welcome_email\", user.email)\n    return {\"status\": \"created\"}\n```\n\nARQ advantages over Celery for async apps: native async, simpler setup,\nno serialisation overhead for Python objects, type hints work naturally.\n\nRule of thumb: choose ARQ for async-first apps; choose Celery when you need\nmature monitoring (Flower), scheduled tasks (Celery Beat), or a non-Python worker ecosystem.\n",{"id":1275,"difficulty":125,"q":1276,"a":1277},"background-task-db-session","How do you use a database session inside a FastAPI background task?","Background tasks run **after the request session is closed** — you cannot reuse\nthe request's DB session. Create a new session inside the task:\n\n```python\nfrom sqlalchemy.orm import Session\nfrom app.db import SessionLocal\n\ndef send_order_confirmation(order_id: int):\n    db = SessionLocal()\n    try:\n        order = db.get(Order, order_id)\n        send_email(order.user.email, f\"Order {order_id} confirmed\")\n        db.commit()\n    finally:\n        db.close()\n\n@app.post(\"\u002Forders\")\nasync def create_order(data: OrderCreate, background_tasks: BackgroundTasks):\n    order = await db.create(data)\n    background_tasks.add_task(send_order_confirmation, order.id)\n    return order\n```\n\nPass the **ID**, not the ORM object — ORM objects are tied to a session; they\ncan't be used in a different session.\n\nRule of thumb: always open a fresh DB session inside background tasks; never\npass ORM objects from the request session into background tasks.\n",{"id":1279,"difficulty":125,"q":1280,"a":1281},"periodic-tasks","How do you run a periodic\u002Fscheduled task in FastAPI?","**Option A — APScheduler** (in-process):\n```python\nfrom apscheduler.schedulers.asyncio import AsyncIOScheduler\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def lifespan(app):\n    scheduler = AsyncIOScheduler()\n    scheduler.add_job(cleanup_expired_tokens, \"interval\", minutes=30)\n    scheduler.start()\n    yield\n    scheduler.shutdown()\n\napp = FastAPI(lifespan=lifespan)\n```\n\n**Option B — ARQ cron jobs**:\n```python\nclass WorkerSettings:\n    cron_jobs = [\n        cron(cleanup_expired_tokens, minute={0, 30}),  # every 30 min\n    ]\n```\n\n**Option C — External scheduler** (cron, Kubernetes CronJob, AWS EventBridge):\nHits a `\u002Finternal\u002Fcleanup` endpoint at the scheduled time.\n\nRule of thumb: in-process schedulers (APScheduler) are convenient but tasks\nrun on every pod in a scaled deployment; use a single external scheduler or a\ndistributed lock to prevent duplicate execution.\n",{"id":1283,"difficulty":96,"q":1284,"a":1285},"task-result","How do you return a task ID and let clients poll for the result in FastAPI?","The async job pattern: accept the request, enqueue the task, return a task ID;\nprovide a polling endpoint.\n\n```python\nimport uuid\nfrom arq import create_pool\n\n@app.post(\"\u002Freports\", status_code=202)\nasync def generate_report(request: Request, params: ReportParams):\n    task_id = str(uuid.uuid4())\n    await request.app.state.arq.enqueue_job(\"build_report\", task_id, params.dict())\n    return {\"task_id\": task_id, \"status\": \"pending\"}\n\n@app.get(\"\u002Freports\u002F{task_id}\")\nasync def get_report_status(task_id: str, request: Request):\n    job = await request.app.state.arq.job(task_id)\n    if job is None:\n        raise HTTPException(404)\n    status = await job.status()\n    if status == \"complete\":\n        result = await job.result()\n        return {\"status\": \"complete\", \"result\": result}\n    return {\"status\": status}\n```\n\nRule of thumb: return **202 Accepted** for async tasks — it signals that the\nrequest was accepted but processing isn't complete yet.\n",{"id":1287,"difficulty":125,"q":1288,"a":1289},"background-task-memory-leak","What is a common memory leak pattern with FastAPI background tasks?","Capturing large objects in the background task's closure keeps them alive until\nthe task completes — even after the request is done.\n\n```python\n# BAD — the entire response object stays in memory during task\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    all_items = await db.get_all_items()   # large list\n    background_tasks.add_task(process_items, all_items)  # captured in closure\n    return item\n\n# GOOD — pass just the IDs; let the task load what it needs\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    new_item = await db.save(item)\n    background_tasks.add_task(process_new_item, new_item.id)  # just an int\n    return new_item\n```\n\nRule of thumb: pass primitive values (IDs, strings) to background tasks —\nnot large ORM results, response objects, or request streams.\n",{"description":94},"FastAPI background task interview questions — BackgroundTasks, task queues, Celery, ARQ, when to use each and common pitfalls.","fastapi\u002Fdeployment\u002Fbackground-tasks","-WcStUf5UsPUsZy-rQs45Aes9QrVEJgM5dHfqle9Ltg",{"id":1295,"title":1296,"body":1297,"description":94,"difficulty":104,"extension":97,"framework":10,"frameworkSlug":8,"meta":1301,"navigation":99,"order":38,"path":1302,"questions":1303,"questionsCount":348,"related":149,"seo":1364,"seoDescription":1365,"stem":1366,"subtopic":1296,"topic":20,"topicSlug":21,"updated":154,"__hash__":1367},"qa\u002Ffastapi\u002Ffundamentals\u002Fpath-operations.md","Path Operations",{"type":91,"value":1298,"toc":1299},[],{"title":94,"searchDepth":29,"depth":29,"links":1300},[],{},"\u002Ffastapi\u002Ffundamentals\u002Fpath-operations",[1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360],{"id":1305,"difficulty":104,"q":1306,"a":1307},"what-is-path-operation","What is a \"path operation\" in FastAPI?","A **path operation** is the combination of an HTTP method and a URL path bound\nto a Python function via a decorator. FastAPI registers it as a route and\nauto-generates the OpenAPI schema entry for it.\n\n```python\n@app.get(\"\u002Fitems\")           # GET  \u002Fitems  → list_items\nasync def list_items():\n    return [{\"id\": 1}]\n\n@app.post(\"\u002Fitems\")          # POST \u002Fitems  → create_item\nasync def create_item(item: Item):\n    return item\n```\n\nEach decorator (`@app.get`, `@app.post`, `@app.put`, `@app.patch`,\n`@app.delete`, `@app.head`, `@app.options`, `@app.trace`) corresponds to\nan HTTP method. The same path can be registered for multiple methods.\n\nRule of thumb: one decorator = one path operation = one OpenAPI endpoint entry.\n",{"id":1309,"difficulty":104,"q":1310,"a":1311},"http-methods","Which HTTP methods does FastAPI support as route decorators?","FastAPI mirrors standard HTTP methods:\n\n| Decorator | Method | Typical use |\n|-----------|--------|-------------|\n| `@app.get` | GET | Read resource |\n| `@app.post` | POST | Create resource |\n| `@app.put` | PUT | Full update\u002Freplace |\n| `@app.patch` | PATCH | Partial update |\n| `@app.delete` | DELETE | Remove resource |\n| `@app.head` | HEAD | Like GET but no body |\n| `@app.options` | OPTIONS | CORS preflight \u002F introspection |\n| `@app.trace` | TRACE | Diagnostic loopback |\n\n```python\n@app.patch(\"\u002Fitems\u002F{id}\")\nasync def update_partial(id: int, patch: ItemPatch):\n    ...\n```\n\nRule of thumb: use PUT for full replacement, PATCH for partial updates — FastAPI\ndoesn't enforce semantics, but your OpenAPI schema and clients will reflect the choice.\n",{"id":1313,"difficulty":96,"q":1314,"a":1315},"operation-id","What is an `operation_id` and why does it matter?","The `operation_id` is a **unique string identifier** for an OpenAPI operation.\nCode generators (openapi-generator, orval) use it as the function name in\ngenerated clients. FastAPI auto-generates it from the handler function name\nand the path, but you can override it.\n\n```python\n@app.get(\n    \"\u002Fitems\u002F{item_id}\",\n    operation_id=\"retrieve_item\",   # generated client function: retrieve_item()\n)\nasync def get_item(item_id: int):\n    ...\n```\n\nWithout an explicit `operation_id`, FastAPI generates something like\n`get_item_items__item_id__get` — verbose and brittle across renames.\n\nRule of thumb: set explicit `operation_id` values for any endpoint consumed by\ngenerated client code to keep client method names stable across refactors.\n",{"id":1317,"difficulty":104,"q":1318,"a":1319},"tags-and-grouping","How do `tags` work in FastAPI route decorators?","`tags` is a list of strings that group related operations in the Swagger UI and\nReDoc docs. They have no effect on routing — they're purely for documentation.\n\n```python\n@app.get(\"\u002Fusers\", tags=[\"users\"])\nasync def list_users(): ...\n\n@app.post(\"\u002Fusers\", tags=[\"users\"])\nasync def create_user(): ...\n\n@app.get(\"\u002Forders\", tags=[\"orders\"])\nasync def list_orders(): ...\n```\n\nYou can also set tags at the `APIRouter` level so every route in the router\ninherits them:\n\n```python\nrouter = APIRouter(prefix=\"\u002Fusers\", tags=[\"users\"])\napp.include_router(router)\n```\n\nRule of thumb: set tags at the router level for consistency; override per-route\nonly when a route genuinely belongs to a different OpenAPI group.\n",{"id":1321,"difficulty":104,"q":1322,"a":1323},"deprecated-routes","How do you mark a route as deprecated in FastAPI?","Pass `deprecated=True` to the route decorator. FastAPI adds the OpenAPI\n`deprecated: true` flag, which Swagger UI displays with a strikethrough.\n\n```python\n@app.get(\"\u002Fold-endpoint\", deprecated=True)\nasync def old_endpoint():\n    return RedirectResponse(\"\u002Fnew-endpoint\", status_code=301)\n```\n\nDeprecating in OpenAPI doesn't prevent the route from being called — you still\nneed to keep the handler working. For hard removal, emit a `Deprecation` header\nwith a sunset date, then delete the route in a future release.\n\nRule of thumb: always mark deprecated routes in OpenAPI before removing them\nso generated clients surface the warning during their build step.\n",{"id":1325,"difficulty":104,"q":1326,"a":1327},"response-description","How do you document response codes and descriptions in FastAPI?","Use `responses` dict in the route decorator to document non-default status codes.\nThe `summary` and `description` parameters document the operation itself.\n\n```python\n@app.get(\n    \"\u002Fitems\u002F{id}\",\n    summary=\"Retrieve an item\",\n    description=\"Returns the full item record. Returns 404 if the item does not exist.\",\n    responses={\n        404: {\"description\": \"Item not found\"},\n        200: {\"description\": \"The item\", \"model\": Item},\n    },\n)\nasync def get_item(id: int):\n    ...\n```\n\nFastAPI also reads the handler's docstring as the operation description:\n\n```python\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int):\n    \"\"\"Return a single item by its numeric ID.\"\"\"\n    ...\n```\n\nRule of thumb: use the docstring for prose descriptions; use the `responses` dict\nto document error codes and their schemas.\n",{"id":1329,"difficulty":96,"q":1330,"a":1331},"multiple-responses","How do you declare that a route can return different response models for different status codes?","Use the `responses` parameter with a dict keyed by status code. For typed bodies\npass `\"model\": PydanticModel`.\n\n```python\nfrom fastapi.responses import JSONResponse\nfrom typing import Union\n\nclass Item(BaseModel): id: int; name: str\nclass Message(BaseModel): message: str\n\n@app.get(\n    \"\u002Fitems\u002F{id}\",\n    response_model=Item,                      # 200 default\n    responses={404: {\"model\": Message}},      # 404 documented\n)\nasync def get_item(id: int) -> Union[Item, JSONResponse]:\n    item = await db.get(id)\n    if not item:\n        return JSONResponse(\n            status_code=404,\n            content={\"message\": \"not found\"},\n        )\n    return item\n```\n\nNote: FastAPI only **validates** the `response_model`; additional entries in\n`responses` are documentation-only.\n\nRule of thumb: document all realistic non-200 responses in `responses={}` even if\nFastAPI won't validate them — clients and SDK generators rely on the schema.\n",{"id":1333,"difficulty":96,"q":1334,"a":1335},"path-order-matters","Why does route order matter in FastAPI, and what is the common pitfall?","FastAPI matches routes **in the order they are registered**. The first matching\nroute wins. If a fixed path like `\u002Fitems\u002Fexport` is registered after a\nparameterised path like `\u002Fitems\u002F{id}`, the `{id}` route captures the string\n`\"export\"` as the value of `id`.\n\n```python\n# WRONG order — \u002Fitems\u002Fexport matches the first route with id=\"export\"\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int): ...   # FastAPI raises 422 because \"export\" is not int\n\n@app.get(\"\u002Fitems\u002Fexport\")\nasync def export_items(): ...      # never reached\n\n# CORRECT order — fixed paths before parameterised\n@app.get(\"\u002Fitems\u002Fexport\")\nasync def export_items(): ...\n\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int): ...\n```\n\nRule of thumb: register fixed\u002Fstatic paths before parameterised ones at the same\ndepth — or use type constraints (`{id:int}`) to let FastAPI discriminate automatically.\n",{"id":1337,"difficulty":96,"q":1338,"a":1339},"path-converters","How do you constrain a path parameter to a specific type in the URL pattern?","FastAPI uses Starlette's **path converters** in the URL string to constrain path\nparameters without Pydantic at the routing layer:\n\n```python\n@app.get(\"\u002Fitems\u002F{item_id:int}\")    # only matches if segment is an integer\nasync def get_item(item_id: int): ...\n\n@app.get(\"\u002Ffiles\u002F{file_path:path}\") # matches any path, including slashes\nasync def get_file(file_path: str): ...\n```\n\nConverters: `str` (default), `int`, `float`, `uuid`, `path`.\n\nCombining with Pydantic type annotations provides two-level validation:\nStarlette rejects non-int URLs at routing time; Pydantic further validates range\nor constraints in the handler.\n\nRule of thumb: use `:int` or `:uuid` converters in paths to prevent wrong-type\nsegments from even reaching the handler — cleaner 404 instead of 422.\n",{"id":1341,"difficulty":96,"q":1342,"a":1343},"route-include-router","What is `APIRouter` and why should you use it instead of `app` directly?","`APIRouter` is FastAPI's mechanism for splitting route definitions across multiple\nfiles. Routes are registered on the router, then the router is mounted onto the\napp (or another router) with a prefix and shared config.\n\n```python\n# routers\u002Fusers.py\nfrom fastapi import APIRouter\nrouter = APIRouter(prefix=\"\u002Fusers\", tags=[\"users\"])\n\n@router.get(\"\u002F\")\nasync def list_users(): ...\n\n@router.get(\"\u002F{id}\")\nasync def get_user(id: int): ...\n```\n\n```python\n# main.py\nfrom routers import users\napp.include_router(users.router)\n# registers: GET \u002Fusers\u002F  and  GET \u002Fusers\u002F{id}\n```\n\nBenefits: logical grouping, reusable prefix\u002Ftags\u002Fdependencies, testable in\nisolation, avoids a 1000-line main.py.\n\nRule of thumb: one `APIRouter` per resource or domain concept; mount all routers\nin `main.py`.\n",{"id":1345,"difficulty":96,"q":1346,"a":1347},"include-router-dependencies","How do you apply a dependency to every route in an `APIRouter`?","Pass `dependencies=[Depends(fn)]` to `APIRouter()` or `include_router()`. Every\nroute in that router runs the dependency before the handler — useful for auth or\nrate-limiting.\n\n```python\nfrom fastapi import APIRouter, Depends\nfrom .auth import verify_token\n\n# Option A: on the router itself\nrouter = APIRouter(\n    prefix=\"\u002Fadmin\",\n    dependencies=[Depends(verify_token)],\n)\n\n# Option B: at mount time (non-destructive)\napp.include_router(router, dependencies=[Depends(verify_token)])\n```\n\nBoth options stack — if you set deps in both places, both run.\nRouter-level deps run before route-level deps.\n\nRule of thumb: put auth\u002Frate-limiting deps at the router level so you can't\naccidentally forget them on a new route; put business-logic deps at the route level.\n",{"id":1349,"difficulty":96,"q":1350,"a":1351},"return-type-annotation","What is the effect of adding a return type annotation to a FastAPI handler?","FastAPI uses the return type annotation in two ways:\n1. **OpenAPI schema** — the annotation becomes the documented response schema.\n2. **Response validation** — if `response_model` is also set, it overrides; if not,\n   FastAPI uses the return annotation as the implicit `response_model`.\n\n```python\nclass UserOut(BaseModel):\n    id: int\n    name: str\n\n@app.get(\"\u002Fusers\u002F{id}\")\nasync def get_user(id: int) -> UserOut:   # annotation drives the schema\n    return await db.get(User, id)\n```\n\nSince Pydantic v2 + FastAPI 0.100+, you can return an ORM object that matches\n`UserOut` and FastAPI will serialise it. Using `response_model=UserOut` in the\ndecorator is equivalent but takes precedence.\n\nRule of thumb: use return type annotations as the primary way to document response\nschemas — it's more Pythonic and keeps the decorator line shorter.\n",{"id":1353,"difficulty":104,"q":1354,"a":1355},"no-content-response","How do you return a 204 No Content response from FastAPI?","Set `status_code=204` in the decorator and return `None` (or nothing). FastAPI\nsends no body for 204 responses.\n\n```python\nfrom fastapi import status\nfrom fastapi.responses import Response\n\n@app.delete(\"\u002Fitems\u002F{id}\", status_code=status.HTTP_204_NO_CONTENT)\nasync def delete_item(id: int):\n    await db.delete(id)\n    # implicit return None → no body sent\n```\n\nAlternatively, return `Response(status_code=204)` explicitly when you need to\navoid FastAPI trying to serialise `None`.\n\nRule of thumb: always use 204 for successful DELETE operations that return no\nbody — clients rely on the status code to know there's nothing to parse.\n",{"id":1357,"difficulty":96,"q":1358,"a":1359},"websocket-route","How do you define a WebSocket route in FastAPI?","Use `@app.websocket(\"\u002Fpath\")` and declare the handler with a `WebSocket`\nparameter. The handler stays alive in a loop, sending and receiving messages\nuntil the connection closes.\n\n```python\nfrom fastapi import WebSocket\n\n@app.websocket(\"\u002Fws\")\nasync def websocket_endpoint(ws: WebSocket):\n    await ws.accept()\n    try:\n        while True:\n            data = await ws.receive_text()\n            await ws.send_text(f\"Echo: {data}\")\n    except WebSocketDisconnect:\n        pass   # client closed the connection\n```\n\nWebSocket handlers can use `Depends()` just like HTTP handlers, making it easy\nto authenticate the upgrade request before `ws.accept()`.\n\nRule of thumb: always call `await ws.accept()` before sending; catch\n`WebSocketDisconnect` to clean up resources when the client leaves.\n",{"id":1361,"difficulty":104,"q":1362,"a":1363},"static-files","How do you serve static files in FastAPI?","Mount Starlette's `StaticFiles` on a path:\n\n```python\nfrom fastapi.staticfiles import StaticFiles\n\napp.mount(\"\u002Fstatic\", StaticFiles(directory=\"static\"), name=\"static\")\n```\n\nRequests to `\u002Fstatic\u002Flogo.png` serve `.\u002Fstatic\u002Flogo.png` directly without going\nthrough FastAPI's routing. It's handled at the ASGI mount level, so it bypasses\nmiddleware that is added _after_ the mount.\n\nFor a single-page app (SPA) fallback:\n```python\napp.mount(\"\u002F\", StaticFiles(directory=\"dist\", html=True), name=\"spa\")\n```\n`html=True` serves `index.html` for any path not found in the directory.\n\nRule of thumb: use `StaticFiles` for assets; for dynamically generated file\ndownloads use `FileResponse` inside a regular route handler.\n",{"description":94},"FastAPI path operations interview questions — decorators, HTTP methods, status codes, operation metadata, tags, deprecated routes and include_router.","fastapi\u002Ffundamentals\u002Fpath-operations","9QLxXiT9OhguZvSQ91Ol9hHDan8TJJBNIKJvImgvmSc",{"id":1369,"title":1370,"body":1371,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1375,"navigation":99,"order":38,"path":1376,"questions":1377,"questionsCount":415,"related":149,"seo":1430,"seoDescription":1431,"stem":1432,"subtopic":1370,"topic":37,"topicSlug":39,"updated":154,"__hash__":1433},"qa\u002Ffastapi\u002Fpydantic\u002Fserialization.md","Serialization",{"type":91,"value":1372,"toc":1373},[],{"title":94,"searchDepth":29,"depth":29,"links":1374},[],{},"\u002Ffastapi\u002Fpydantic\u002Fserialization",[1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426],{"id":1379,"difficulty":104,"q":1380,"a":1381},"model-dump-basics","How do you convert a Pydantic model to a Python dict?","Call `.model_dump()` (Pydantic v2). It replaces the v1 `.dict()` method.\n\n```python\nfrom pydantic import BaseModel\n\nclass Item(BaseModel):\n    name: str\n    price: float\n    in_stock: bool = True\n\nitem = Item(name=\"Widget\", price=9.99)\nitem.model_dump()\n# {\"name\": \"Widget\", \"price\": 9.99, \"in_stock\": True}\n```\n\nKey keyword arguments:\n- `exclude_none=True` — drop `None` values\n- `exclude_unset=True` — drop fields not explicitly set\n- `exclude={\"field\"}` — drop specific fields\n- `include={\"field\"}` — keep only specific fields\n- `mode=\"json\"` — convert to JSON-safe types (datetime → str, UUID → str)\n\nRule of thumb: use `model_dump(mode=\"json\")` before storing to NoSQL or\npassing to `JSONResponse(content=...)` — it handles non-serialisable types.\n",{"id":1383,"difficulty":104,"q":1384,"a":1385},"model-dump-json","What does `model_dump_json()` do and when should you use it over `model_dump()`?","`.model_dump_json()` serialises directly to a **JSON string** (bytes) without\ngoing through a Python dict intermediate. It's faster because Pydantic uses its\nRust core to serialise.\n\n```python\nitem = Item(name=\"Widget\", price=9.99)\nitem.model_dump_json()\n# b'{\"name\":\"Widget\",\"price\":9.99,\"in_stock\":true}'\n\n# round-trip: parse from JSON string\nItem.model_validate_json('{\"name\":\"Widget\",\"price\":9.99}')\n```\n\nUse `model_dump_json()` when writing to a cache, message queue, or file\nwhere you need a JSON string directly.\n\nRule of thumb: use `model_dump()` when you need a Python dict (to merge, modify,\nor pass to another dict); use `model_dump_json()` when you need a string\u002Fbytes.\n",{"id":1387,"difficulty":96,"q":1388,"a":1389},"serialisation-alias","How do you use aliases during serialisation (output) in Pydantic v2?","By default, aliases affect **input** (parsing) only. To use aliases during\noutput (serialisation), pass `by_alias=True` to `model_dump()`:\n\n```python\nfrom pydantic import BaseModel, Field\n\nclass Order(BaseModel):\n    order_id: int = Field(alias=\"orderId\")\n    item_count: int = Field(alias=\"itemCount\")\n\norder = Order.model_validate({\"orderId\": 42, \"itemCount\": 3})\n\norder.model_dump()              # {\"order_id\": 42, \"item_count\": 3}  (Python names)\norder.model_dump(by_alias=True) # {\"orderId\": 42, \"itemCount\": 3}   (aliases)\n```\n\nIn FastAPI, set `response_model_by_alias=True` in the decorator to use aliases\nin the HTTP response:\n```python\n@app.get(\"\u002Forders\u002F{id}\", response_model=Order, response_model_by_alias=True)\n```\n\nRule of thumb: if your API contract uses camelCase, set `by_alias=True` globally\nin serialisation; don't mix Python names and aliases in the same API response.\n",{"id":1391,"difficulty":96,"q":1392,"a":1393},"field-serializer","How do you customise how a specific field is serialised with `@field_serializer`?","Use `@field_serializer(\"field_name\")` to override the default serialisation for\na single field:\n\n```python\nfrom pydantic import BaseModel, field_serializer\nfrom datetime import datetime\n\nclass Event(BaseModel):\n    name: str\n    created_at: datetime\n\n    @field_serializer(\"created_at\")\n    def serialise_dt(self, v: datetime) -> str:\n        return v.strftime(\"%Y-%m-%d %H:%M\")   # custom format instead of ISO 8601\n\ne = Event(name=\"Launch\", created_at=datetime(2026, 6, 20, 9, 0))\ne.model_dump()\n# {\"name\": \"Launch\", \"created_at\": \"2026-06-20 09:00\"}\n```\n\nRule of thumb: use `@field_serializer` when you need a non-default format for\ndates, decimals, or custom types; prefer standard ISO 8601 datetimes unless\nthe client explicitly requires a different format.\n",{"id":1395,"difficulty":125,"q":1396,"a":1397},"model-serializer","What is `@model_serializer` and when would you use it?","`@model_serializer` replaces the entire default serialisation of a model with\na custom function. The function receives `self` (the model) and must return a\nJSON-serialisable value.\n\n```python\nfrom pydantic import BaseModel, model_serializer\n\nclass Money(BaseModel):\n    amount: int     # stored in cents\n    currency: str\n\n    @model_serializer\n    def to_dict(self):\n        return {\n            \"display\": f\"{self.amount \u002F 100:.2f} {self.currency}\",\n            \"cents\": self.amount,\n        }\n\nm = Money(amount=999, currency=\"USD\")\nm.model_dump()\n# {\"display\": \"9.99 USD\", \"cents\": 999}\n```\n\nRule of thumb: use `@model_serializer` only when the serialised shape differs\nfundamentally from the model's fields (e.g., presenting a monetary value as\ndisplay text + machine value).\n",{"id":1399,"difficulty":96,"q":1400,"a":1401},"exclude-unset-serialisation","Why is `exclude_unset=True` important for PATCH endpoints?","`exclude_unset=True` returns only the fields the client **explicitly sent**,\nskipping fields that were left at their defaults. This is the correct behaviour\nfor a PATCH — you only update what was provided.\n\n```python\nclass ItemUpdate(BaseModel):\n    name: str | None = None\n    price: float | None = None\n    in_stock: bool | None = None\n\n@app.patch(\"\u002Fitems\u002F{id}\")\nasync def patch_item(id: int, patch: ItemUpdate):\n    updates = patch.model_dump(exclude_unset=True)\n    # client sent {\"price\": 14.99}\n    # updates = {\"price\": 14.99}  — name and in_stock NOT included\n    await db.update(id, updates)\n    return await db.get(id)\n```\n\nWithout `exclude_unset=True`, `updates` would include `{\"name\": None, \"price\": 14.99,\n\"in_stock\": None}`, accidentally overwriting existing values with `None`.\n\nRule of thumb: always use `model_dump(exclude_unset=True)` in PATCH handlers —\nit prevents accidental nulling of fields the client didn't mention.\n",{"id":1403,"difficulty":96,"q":1404,"a":1405},"json-encoders","How do you configure Pydantic to encode custom types (like Decimal or UUID) to JSON?","In Pydantic v2 with `mode=\"json\"` or `model_dump_json()`, standard types like\n`datetime`, `UUID`, `Decimal`, and `Enum` are handled automatically.\n\nFor custom third-party types, use `@field_serializer`:\n\n```python\nfrom decimal import Decimal\nfrom pydantic import BaseModel, field_serializer\n\nclass Price(BaseModel):\n    amount: Decimal\n\n    @field_serializer(\"amount\")\n    def encode_decimal(self, v: Decimal) -> str:\n        return str(v)   # \"9.99\" instead of Decimal(\"9.99\")\n\nPrice(amount=Decimal(\"9.99\")).model_dump(mode=\"json\")\n# {\"amount\": \"9.99\"}\n```\n\nRule of thumb: run `model_dump(mode=\"json\")` in tests to check that all fields\nare JSON-serialisable — catch `TypeError` early before it surfaces in production.\n",{"id":1407,"difficulty":96,"q":1408,"a":1409},"response-model-serialisation-flow","What is the exact serialisation flow when FastAPI returns a Pydantic model?","1. Handler returns a Python value (dict, ORM object, or Pydantic model).\n2. FastAPI calls `response_model.model_validate(value)` to filter and coerce it.\n3. The validated Pydantic instance is passed to `jsonable_encoder()` which calls\n   `model.model_dump(mode=\"json\")` internally.\n4. The resulting JSON-safe dict is serialised to bytes with `json.dumps()`\n   (or `orjson.dumps()` if `ORJSONResponse` is configured).\n5. Bytes are sent as `Content-Type: application\u002Fjson`.\n\n```python\n# simplified internal equivalent\nvalidated = ResponseModel.model_validate(handler_return)\npayload   = jsonable_encoder(validated)   # → JSON-safe dict\nbody      = json.dumps(payload).encode()   # → bytes\n```\n\nRule of thumb: understanding this flow explains why `response_model` filters\nextra fields (step 2) and why datetime values arrive as ISO strings (step 3).\n",{"id":1411,"difficulty":96,"q":1412,"a":1413},"nested-serialisation","How does Pydantic handle serialisation of nested models?","Nested `BaseModel` instances are serialised recursively. `.model_dump()` returns\nnested dicts; `.model_dump_json()` returns a flat JSON string.\n\n```python\nclass Address(BaseModel):\n    city: str\n    country: str\n\nclass User(BaseModel):\n    name: str\n    address: Address\n\nu = User(name=\"Alice\", address=Address(city=\"London\", country=\"UK\"))\nu.model_dump()\n# {\"name\": \"Alice\", \"address\": {\"city\": \"London\", \"country\": \"UK\"}}\n```\n\n`exclude` and `include` work recursively:\n```python\nu.model_dump(exclude={\"address\": {\"country\"}})\n# {\"name\": \"Alice\", \"address\": {\"city\": \"London\"}}\n```\n\nRule of thumb: test serialisation of nested models explicitly — a missing\n`from_attributes=True` on an inner model is a common bug when using ORM objects.\n",{"id":1415,"difficulty":104,"q":1416,"a":1417},"list-serialisation","How do you serialise a list of Pydantic models to JSON?","Call `.model_dump()` on each element, or use a `RootModel` for a top-level list:\n\n```python\nitems = [Item(name=\"A\", price=1.0), Item(name=\"B\", price=2.0)]\n[i.model_dump() for i in items]\n# [{\"name\": \"A\", \"price\": 1.0}, {\"name\": \"B\", \"price\": 2.0}]\n```\n\nIn FastAPI, returning a `list[Item]` from a handler with `response_model=list[Item]`\nhandles this automatically.\n\nFor a root-level list model:\n```python\nfrom pydantic import RootModel\n\nclass ItemList(RootModel[list[Item]]):\n    pass\n\nItemList([Item(name=\"A\", price=1.0)]).model_dump()\n# [{\"name\": \"A\", \"price\": 1.0}]\n```\n\nRule of thumb: let FastAPI handle list serialisation via `response_model=list[MyModel]`;\nuse `RootModel` only when you need to attach methods or validators to the list itself.\n",{"id":1419,"difficulty":96,"q":1420,"a":1421},"model-copy-update","How do you create a modified copy of a Pydantic model without mutating the original?","Use `.model_copy(update={...})` (v2 replacement for `.copy(update=...)`):\n\n```python\noriginal = Item(name=\"Widget\", price=9.99, in_stock=True)\nupdated = original.model_copy(update={\"price\": 14.99})\n\nprint(original.price)  # 9.99   — unchanged\nprint(updated.price)   # 14.99  — new copy\n```\n\nThis is useful in PATCH handlers after merging the update dict with the existing\nDB row's values:\n\n```python\ndb_item = await db.get(id)\npydantic_item = ItemOut.model_validate(db_item)\nmerged = pydantic_item.model_copy(update=patch.model_dump(exclude_unset=True))\n```\n\nRule of thumb: use `model_copy(update=...)` instead of mutating model attributes\ndirectly — it keeps models immutable and makes the data flow explicit.\n",{"id":1423,"difficulty":104,"q":1424,"a":1425},"serialise-enum","How does Pydantic serialise Python Enum values?","By default, Pydantic serialises an `Enum` to its `.value`. String enums\n(`str, Enum`) serialise as plain strings; int enums as integers.\n\n```python\nfrom enum import Enum\nfrom pydantic import BaseModel\n\nclass Status(str, Enum):\n    active = \"active\"\n    inactive = \"inactive\"\n\nclass User(BaseModel):\n    status: Status\n\nUser(status=Status.active).model_dump()\n# {\"status\": \"active\"}   — the string value, not the Enum object\n```\n\nIf you need the enum member name instead of value:\n```python\nuser.model_dump(mode=\"python\")   # {\"status\": \u003CStatus.active: 'active'>}\n```\n\nRule of thumb: always inherit from `str` (or `int`) when defining enums for\nPydantic models — it ensures JSON-safe serialisation without extra config.\n",{"id":1427,"difficulty":125,"q":1428,"a":1429},"custom-json-schema","How do you customise the JSON Schema generated for a Pydantic model?","Use `json_schema_extra` in `model_config` to add or override schema properties:\n\n```python\nfrom pydantic import BaseModel, ConfigDict\n\nclass Item(BaseModel):\n    model_config = ConfigDict(\n        json_schema_extra={\n            \"title\": \"Inventory Item\",\n            \"examples\": [\n                {\"name\": \"Widget\", \"price\": 9.99}\n            ],\n        }\n    )\n    name: str\n    price: float\n```\n\nFor programmatic customisation (add\u002Fremove properties):\n```python\nmodel_config = ConfigDict(\n    json_schema_extra=lambda schema: schema.update({\"deprecated\": True})\n)\n```\n\nRule of thumb: use `json_schema_extra` to add `examples` and `title` to your\nmodels — Swagger UI renders examples in the \"Try it out\" body, saving testers time.\n",{"description":94},"FastAPI Pydantic serialization interview questions — model_dump, model_dump_json, aliases, computed fields, custom serializers and JSON encoding.","fastapi\u002Fpydantic\u002Fserialization","4U5BqhZb56t9jr1i-1r3zHccnOkJicu8N1PgKoi4I-A",{"id":1435,"title":1436,"body":1437,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1441,"navigation":99,"order":38,"path":1442,"questions":1443,"questionsCount":415,"related":149,"seo":1496,"seoDescription":1497,"stem":1498,"subtopic":1436,"topic":28,"topicSlug":30,"updated":154,"__hash__":1499},"qa\u002Ffastapi\u002Frouting\u002Fresponse-models.md","Response Models",{"type":91,"value":1438,"toc":1439},[],{"title":94,"searchDepth":29,"depth":29,"links":1440},[],{},"\u002Ffastapi\u002Frouting\u002Fresponse-models",[1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492],{"id":1445,"difficulty":104,"q":1446,"a":1447},"response-model-basics","What does `response_model` do in a FastAPI route decorator?","`response_model` tells FastAPI which Pydantic model to use for **validating and\nfiltering** the handler's return value before serialising it to JSON.\n\n```python\nclass UserIn(BaseModel):\n    name: str\n    password: str          # sensitive — never leak this\n\nclass UserOut(BaseModel):\n    id: int\n    name: str              # no password field\n\n@app.post(\"\u002Fusers\", response_model=UserOut)\nasync def create_user(user: UserIn) -> UserOut:\n    db_user = await db.create(user)\n    return db_user         # password field is stripped by response_model\n```\n\nFastAPI:\n1. Validates the return value against `UserOut`.\n2. Strips fields not in `UserOut` (e.g., `password`).\n3. Documents `UserOut` as the response schema in OpenAPI.\n\nRule of thumb: always set `response_model` on any endpoint that returns ORM\nobjects or models with sensitive fields.\n",{"id":1449,"difficulty":96,"q":1450,"a":1451},"response-model-vs-return-annotation","What is the difference between `response_model=` and a return type annotation?","Both tell FastAPI the response shape, but `response_model=` **overrides** the\nreturn annotation for serialisation and filtering:\n\n```python\n# return annotation only — used for schema AND serialisation\n@app.get(\"\u002Fusers\u002F{id}\")\nasync def get_user(id: int) -> UserOut:\n    return await db.get(User, id)\n\n# response_model wins — annotation is for type checkers only\n@app.get(\"\u002Fusers\u002F{id}\", response_model=UserOut)\nasync def get_user(id: int) -> User:   # mypy sees User; FastAPI uses UserOut\n    return await db.get(User, id)\n```\n\nUse the return annotation when they're the same type (clean, Pythonic).\nUse `response_model=` when the serialised type differs from what the handler\nactually returns (ORM model → Pydantic output DTO).\n\nRule of thumb: prefer the return annotation; add `response_model=` only when\nyou need to filter or transform the handler's return value.\n",{"id":1453,"difficulty":104,"q":1454,"a":1455},"exclude-none","How do you omit `None` fields from a FastAPI JSON response?","Set `response_model_exclude_none=True` in the route decorator:\n\n```python\nclass Report(BaseModel):\n    total: int\n    average: float | None = None\n    notes: str | None = None\n\n@app.get(\"\u002Freport\", response_model=Report, response_model_exclude_none=True)\nasync def get_report():\n    return {\"total\": 100}\n    # average and notes are None → omitted: {\"total\": 100}\n```\n\nRule of thumb: use `exclude_none=True` for sparse responses where `None`\nmeans \"not applicable\" — keeps the JSON payload small and clean.\n",{"id":1457,"difficulty":96,"q":1458,"a":1459},"exclude-unset","What is `response_model_exclude_unset` and when is it useful?","`response_model_exclude_unset=True` strips fields that were **not explicitly\nset** in the return value — it distinguishes between \"not set\" and \"set to None\".\n\n```python\nclass Item(BaseModel):\n    name: str\n    description: str | None = None\n    price: float | None = None\n\n@app.patch(\"\u002Fitems\u002F{id}\", response_model=Item, response_model_exclude_unset=True)\nasync def patch_item(id: int, patch: Item):\n    existing = await db.get(id)\n    updated_data = patch.model_dump(exclude_unset=True)\n    existing.update(updated_data)\n    return existing\n```\n\nThis is critical for PATCH semantics: you only return (and store) fields the\nclient actually sent, not defaults.\n\nRule of thumb: use `exclude_unset=True` on PATCH endpoints — it lets clients\nsend partial updates without overwriting fields they didn't mention.\n",{"id":1461,"difficulty":96,"q":1462,"a":1463},"response-model-exclude","How do you always exclude specific fields from the response without a separate output model?","Use `response_model_exclude={\"field1\", \"field2\"}` in the route decorator:\n\n```python\nclass User(BaseModel):\n    id: int\n    name: str\n    password_hash: str\n    internal_notes: str | None = None\n\n@app.get(\n    \"\u002Fusers\u002F{id}\",\n    response_model=User,\n    response_model_exclude={\"password_hash\", \"internal_notes\"},\n)\nasync def get_user(id: int):\n    return await db.get(id)\n```\n\nThis avoids creating a separate `UserOut` model for simple exclusion cases.\nFor complex filtering (renames, computed fields), a dedicated output model is cleaner.\n\nRule of thumb: use `response_model_exclude` for 1-2 fields; create a dedicated\noutput model when you exclude three or more fields or need renames\u002Faliases.\n",{"id":1465,"difficulty":96,"q":1466,"a":1467},"json-response","When should you return `JSONResponse` directly instead of a dict or Pydantic model?","Return `JSONResponse` when you need to:\n- Set a **non-default status code** dynamically.\n- Set **custom response headers**.\n- Bypass `response_model` filtering (e.g., a pre-serialised payload).\n\n```python\nfrom fastapi.responses import JSONResponse\n\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int):\n    item = await db.get(id)\n    if not item:\n        return JSONResponse(\n            status_code=404,\n            content={\"detail\": \"Not found\"},\n            headers={\"X-Request-ID\": \"abc123\"},\n        )\n    return item   # normal path uses response_model\n```\n\n`JSONResponse` bypasses Pydantic serialisation — pass JSON-safe Python primitives\nor use `jsonable_encoder()` first.\n\nRule of thumb: return dicts\u002Fmodels 95% of the time; reach for `JSONResponse`\nonly when you need direct control over status code or headers.\n",{"id":1469,"difficulty":96,"q":1470,"a":1471},"response-classes","What response classes does FastAPI\u002FStarlette provide and when do you use each?","| Class | Use case |\n|-------|----------|\n| `JSONResponse` (default) | JSON API responses |\n| `HTMLResponse` | Rendered HTML pages |\n| `PlainTextResponse` | Plain text, logs |\n| `RedirectResponse` | 301\u002F302\u002F307 redirects |\n| `FileResponse` | Send a file with proper headers |\n| `StreamingResponse` | Large responses, real-time data |\n| `ORJSONResponse` | Faster JSON via `orjson` |\n| `UJSONResponse` | Faster JSON via `ujson` |\n\n```python\nfrom fastapi.responses import FileResponse, StreamingResponse\n\n@app.get(\"\u002Fdownload\")\nasync def download():\n    return FileResponse(\"report.pdf\", filename=\"report.pdf\")\n\n@app.get(\"\u002Fstream\")\nasync def stream():\n    async def generate():\n        for chunk in large_data():\n            yield chunk\n    return StreamingResponse(generate(), media_type=\"text\u002Fplain\")\n```\n\nRule of thumb: set `default_response_class=ORJSONResponse` on the `FastAPI()`\ninstance to get faster serialisation across all endpoints with no other changes.\n",{"id":1473,"difficulty":96,"q":1474,"a":1475},"orjson-response","How do you use `ORJSONResponse` for faster JSON serialisation in FastAPI?","`orjson` is a Rust-backed JSON library that is 5-10× faster than the stdlib\n`json` module. Install it with `pip install orjson`, then:\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.responses import ORJSONResponse\n\n# Make it the default for all routes\napp = FastAPI(default_response_class=ORJSONResponse)\n\n@app.get(\"\u002Fitems\")\nasync def list_items():\n    return [{\"id\": 1}, {\"id\": 2}]\n```\n\nOr per-route:\n```python\n@app.get(\"\u002Fitems\", response_class=ORJSONResponse)\nasync def list_items():\n    ...\n```\n\n`orjson` natively handles `datetime`, `UUID`, `bytes` and NumPy arrays without\n`jsonable_encoder`.\n\nRule of thumb: switch to `ORJSONResponse` globally for any throughput-sensitive\nAPI — it's a one-line change with measurable latency improvement.\n",{"id":1477,"difficulty":125,"q":1478,"a":1479},"streaming-response","How do you stream a large response in FastAPI without loading it all into memory?","Return a `StreamingResponse` with an async (or sync) generator:\n\n```python\nfrom fastapi.responses import StreamingResponse\nimport asyncio\n\nasync def generate_csv():\n    yield \"id,name\\n\"\n    async for row in db.stream_all_users():\n        yield f\"{row.id},{row.name}\\n\"\n\n@app.get(\"\u002Fexport\u002Fusers.csv\")\nasync def export_users():\n    return StreamingResponse(\n        generate_csv(),\n        media_type=\"text\u002Fcsv\",\n        headers={\"Content-Disposition\": \"attachment; filename=users.csv\"},\n    )\n```\n\nThe client receives bytes as they arrive; the server never holds the full dataset\nin memory.\n\nRule of thumb: use `StreamingResponse` for exports, large file downloads, and\nserver-sent events — never load a 1M-row dataset into a list before sending.\n",{"id":1481,"difficulty":104,"q":1482,"a":1483},"redirect-response","How do you issue a redirect from a FastAPI route?","Return a `RedirectResponse`:\n\n```python\nfrom fastapi.responses import RedirectResponse\nfrom fastapi import status\n\n@app.get(\"\u002Fold-path\")\nasync def old_path():\n    return RedirectResponse(\n        url=\"\u002Fnew-path\",\n        status_code=status.HTTP_301_MOVED_PERMANENTLY,\n    )\n\n# Temporary redirect (default 307)\n@app.get(\"\u002Flogin\")\nasync def login_redirect():\n    return RedirectResponse(url=\"\u002Fauth\u002Flogin\")\n```\n\nDefault status code for `RedirectResponse` is **307 Temporary Redirect**, which\npreserves the HTTP method. Use 301 for permanent SEO-friendly redirects, 302 for\ntemporary, 303 to force GET on redirect.\n\nRule of thumb: use 307 to preserve POST method on redirect; use 303 to convert\na POST response to a GET (Post\u002FRedirect\u002FGet pattern).\n",{"id":1485,"difficulty":96,"q":1486,"a":1487},"response-model-include","How do you include only specific fields in the response (whitelist instead of blacklist)?","Use `response_model_include={\"field1\", \"field2\"}`:\n\n```python\nclass User(BaseModel):\n    id: int\n    name: str\n    email: str\n    phone: str | None = None\n    internal_id: str\n\n@app.get(\n    \"\u002Fusers\u002F{id}\u002Fpublic\",\n    response_model=User,\n    response_model_include={\"id\", \"name\"},   # only these two fields\n)\nasync def get_user_public(id: int):\n    return await db.get(id)\n# response: {\"id\": 42, \"name\": \"Alice\"}\n```\n\nRule of thumb: prefer `response_model_exclude` for \"strip secrets\" use cases and\na dedicated output model for complex projections — `response_model_include` with\nmany field names becomes hard to maintain.\n",{"id":1489,"difficulty":96,"q":1490,"a":1491},"custom-response-headers","How do you add custom headers to a FastAPI response?","Inject the `Response` object into the handler and set headers on it:\n\n```python\nfrom fastapi import Response\n\n@app.get(\"\u002Fitems\")\nasync def list_items(response: Response):\n    items = await db.all()\n    response.headers[\"X-Total-Count\"] = str(len(items))\n    response.headers[\"Cache-Control\"] = \"max-age=60\"\n    return items   # normal JSON response with extra headers\n```\n\nYou can also set headers via `JSONResponse(headers={...})` or middleware.\nInjecting `Response` is the cleanest option when the route already returns a\nplain dict\u002Fmodel.\n\nRule of thumb: inject `Response` for per-request headers (pagination counts,\nrate-limit info); use middleware for headers that apply to all responses (CORS, CSP).\n",{"id":1493,"difficulty":96,"q":1494,"a":1495},"background-tasks-response","How does a route return a response immediately while also scheduling a background task?","Inject `BackgroundTasks` and add tasks before returning:\n\n```python\nfrom fastapi import BackgroundTasks\n\ndef notify_admin(item_id: int):\n    send_email(\"admin@example.com\", f\"New item {item_id} created\")\n\n@app.post(\"\u002Fitems\", status_code=201)\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    new_item = await db.create(item)\n    background_tasks.add_task(notify_admin, new_item.id)\n    return new_item   # response is sent immediately; email sent after\n```\n\nFastAPI sends the HTTP response, then runs the background task(s) sequentially.\nThe client gets its 201 Created without waiting for the email.\n\nRule of thumb: use `BackgroundTasks` for fast, non-retryable work; use a task\nqueue (Celery, ARQ) for anything that must succeed or be retried.\n",{"description":94},"FastAPI response model interview questions — response_model, filtering fields, exclude_none, exclude_unset, JSONResponse, custom response classes and status codes.","fastapi\u002Frouting\u002Fresponse-models","czdTRQmnhNWC4pVOTDpgTUqZ_vSQlGS0ToZb22Gtox4",{"id":1501,"title":1502,"body":1503,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1507,"navigation":99,"order":38,"path":1508,"questions":1509,"questionsCount":148,"related":149,"seo":1554,"seoDescription":1555,"stem":1556,"subtopic":1557,"topic":55,"topicSlug":57,"updated":154,"__hash__":1558},"qa\u002Ffastapi\u002Fsecurity\u002Fapi-keys.md","Api Keys",{"type":91,"value":1504,"toc":1505},[],{"title":94,"searchDepth":29,"depth":29,"links":1506},[],{},"\u002Ffastapi\u002Fsecurity\u002Fapi-keys",[1510,1514,1518,1522,1526,1530,1534,1538,1542,1546,1550],{"id":1511,"difficulty":104,"q":1512,"a":1513},"api-key-header","How do you implement API key authentication via a header in FastAPI?","Use `APIKeyHeader` from `fastapi.security`:\n\n```python\nfrom fastapi import Depends, HTTPException\nfrom fastapi.security.api_key import APIKeyHeader\n\nAPI_KEY = \"my-secret-key\"\napi_key_header = APIKeyHeader(name=\"X-API-Key\")\n\nasync def verify_api_key(api_key: str = Depends(api_key_header)):\n    if api_key != API_KEY:\n        raise HTTPException(status_code=403, detail=\"Invalid API key\")\n    return api_key\n\n@app.get(\"\u002Fdata\", dependencies=[Depends(verify_api_key)])\nasync def get_data():\n    return {\"data\": \"secret\"}\n```\n\n`APIKeyHeader` extracts the named header and registers the scheme in OpenAPI.\n\nRule of thumb: use headers for API keys (not query params) — headers are less\nlikely to appear in server access logs and browser history.\n",{"id":1515,"difficulty":104,"q":1516,"a":1517},"api-key-query","How do you accept an API key as a query parameter in FastAPI?","Use `APIKeyQuery`:\n\n```python\nfrom fastapi.security.api_key import APIKeyQuery\n\napi_key_query = APIKeyQuery(name=\"api_key\")\n\nasync def verify_key(key: str = Depends(api_key_query)):\n    if key != settings.api_key:\n        raise HTTPException(403, \"Invalid API key\")\n\n@app.get(\"\u002Fexport\", dependencies=[Depends(verify_key)])\nasync def export():\n    ...\n# GET \u002Fexport?api_key=abc123\n```\n\nQuery param keys are **visible in logs, browser history, and URLs shared\naccidentally** — avoid them for sensitive APIs.\n\nRule of thumb: only use query param API keys for webhooks or download links\nwhere adding a header is impossible; always prefer header-based keys.\n",{"id":1519,"difficulty":104,"q":1520,"a":1521},"api-key-cookie","How do you use `APIKeyCookie` for session-based authentication?","```python\nfrom fastapi.security.api_key import APIKeyCookie\n\ncookie_scheme = APIKeyCookie(name=\"session\")\n\nasync def get_session(session: str = Depends(cookie_scheme)):\n    user_id = validate_session_token(session)\n    if not user_id:\n        raise HTTPException(403, \"Invalid session\")\n    return user_id\n\n@app.get(\"\u002Fprofile\")\nasync def profile(user_id: int = Depends(get_session)):\n    return await db.get_user(user_id)\n```\n\nFor security, always set `httponly=True` and `secure=True` when setting the\nsession cookie in the login endpoint.\n\nRule of thumb: use `APIKeyCookie` for browser-based sessions; add CSRF\nprotection (double-submit cookie or `SameSite=Lax`) for any state-changing routes.\n",{"id":1523,"difficulty":125,"q":1524,"a":1525},"multiple-auth-schemes","How do you support multiple authentication methods (header key or Bearer token) on the same endpoint?","Declare both dependencies and make them optional; raise if neither is provided:\n\n```python\nfrom fastapi.security import HTTPBearer, APIKeyHeader\n\nbearer = HTTPBearer(auto_error=False)\napi_key_header = APIKeyHeader(name=\"X-API-Key\", auto_error=False)\n\nasync def authenticate(\n    bearer_token: HTTPAuthorizationCredentials | None = Depends(bearer),\n    api_key: str | None = Depends(api_key_header),\n):\n    if bearer_token:\n        return verify_jwt(bearer_token.credentials)\n    if api_key:\n        return verify_api_key(api_key)\n    raise HTTPException(401, \"Authentication required\")\n\n@app.get(\"\u002Fdata\")\nasync def data(user = Depends(authenticate)):\n    return user\n```\n\n`auto_error=False` prevents FastAPI from raising 403 when the header is absent,\ngiving your code a chance to check the alternative.\n\nRule of thumb: set `auto_error=False` on all optional schemes and raise\nexplicitly at the end — otherwise FastAPI returns 403 as soon as one scheme is missing.\n",{"id":1527,"difficulty":96,"q":1528,"a":1529},"https-tls","Why must FastAPI APIs run over HTTPS and how do you enforce it?","Without HTTPS, API keys and JWTs in headers are transmitted in plaintext —\nanyone on the same network can intercept them.\n\nIn production, TLS termination is handled by the reverse proxy (Nginx, Caddy,\nAWS ALB) — not Uvicorn itself:\n\n```nginx\nserver {\n    listen 443 ssl;\n    ssl_certificate     \u002Fetc\u002Fssl\u002Fcert.pem;\n    ssl_certificate_key \u002Fetc\u002Fssl\u002Fkey.pem;\n    location \u002F { proxy_pass http:\u002F\u002F127.0.0.1:8000; }\n}\n```\n\nIn FastAPI, redirect HTTP → HTTPS with `HTTPSRedirectMiddleware`:\n\n```python\nfrom starlette.middleware.httpsredirect import HTTPSRedirectMiddleware\napp.add_middleware(HTTPSRedirectMiddleware)\n```\n\nRule of thumb: terminate TLS at the load balancer\u002Freverse proxy; use\n`HTTPSRedirectMiddleware` as a safety net to catch direct HTTP connections.\n",{"id":1531,"difficulty":96,"q":1532,"a":1533},"cors","What is CORS and how do you configure it in FastAPI?","**CORS** (Cross-Origin Resource Sharing) is a browser security policy. A browser\nblocks JavaScript from reading a response from a different origin unless the server\nincludes the appropriate `Access-Control-Allow-*` headers.\n\nFastAPI provides `CORSMiddleware`:\n\n```python\nfrom fastapi.middleware.cors import CORSMiddleware\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"https:\u002F\u002Fmyapp.example.com\"],  # never \"*\" in production with credentials\n    allow_credentials=True,\n    allow_methods=[\"GET\", \"POST\", \"PUT\", \"DELETE\"],\n    allow_headers=[\"Content-Type\", \"Authorization\"],\n)\n```\n\n`allow_origins=[\"*\"]` is fine for public APIs with no auth; for credentialed\nrequests (cookies, auth headers) you must list exact origins.\n\nRule of thumb: list exact allowed origins rather than `\"*\"` for any API that\nhandles auth — browsers won't send credentials to `\"*\"` anyway, and it forces\nyou to be explicit.\n",{"id":1535,"difficulty":125,"q":1536,"a":1537},"csrf-protection","When is CSRF protection needed in a FastAPI application and how do you implement it?","CSRF (Cross-Site Request Forgery) attacks are relevant when you use **cookie-based\nauth**. JWT in `Authorization: Bearer` headers is immune to CSRF because\ncross-origin forms can't set custom headers.\n\nFor cookie auth, implement the **Double Submit Cookie** pattern:\n\n```python\n@app.post(\"\u002Flogin\")\nasync def login(response: Response, ...):\n    csrf_token = secrets.token_urlsafe(32)\n    response.set_cookie(\"session\", session_token, httponly=True, secure=True)\n    response.set_cookie(\"csrf_token\", csrf_token, secure=True)   # NOT httponly\n    return {\"csrf_token\": csrf_token}\n\n# Client sends X-CSRF-Token header with every mutating request\n@app.post(\"\u002Forders\")\nasync def create_order(\n    x_csrf_token: str = Header(),\n    csrf_cookie: str = Cookie(alias=\"csrf_token\"),\n):\n    if x_csrf_token != csrf_cookie:\n        raise HTTPException(403, \"CSRF token mismatch\")\n```\n\n`SameSite=Lax` on session cookies also blocks most CSRF without a token.\n\nRule of thumb: if using cookie auth, set `SameSite=Lax` as the minimum; add\nthe double-submit CSRF token for state-changing endpoints (POST, PUT, DELETE).\n",{"id":1539,"difficulty":96,"q":1540,"a":1541},"rate-limiting","How do you implement rate limiting in FastAPI?","Use a dependency that checks a Redis counter:\n\n```python\nimport time\nfrom fastapi import Depends, HTTPException, Request\n\nasync def rate_limit(request: Request, calls: int = 100, period: int = 60):\n    key = f\"rate:{request.client.host}\"\n    pipe = redis.pipeline()\n    pipe.incr(key)\n    pipe.expire(key, period)\n    count, _ = await pipe.execute()\n    if count > calls:\n        raise HTTPException(\n            status_code=429,\n            detail=\"Too many requests\",\n            headers={\"Retry-After\": str(period)},\n        )\n\n@app.get(\"\u002Fitems\", dependencies=[Depends(rate_limit)])\nasync def list_items():\n    ...\n```\n\nFor production, use `slowapi` (wraps `limits` library with FastAPI integration):\n```python\nfrom slowapi import Limiter\nlimiter = Limiter(key_func=get_remote_address)\n```\n\nRule of thumb: implement rate limiting at the reverse proxy for brute-force\ndefence; implement in the app for per-user\u002Fper-token granular limits.\n",{"id":1543,"difficulty":96,"q":1544,"a":1545},"sensitive-data-in-logs","How do you prevent API keys and tokens from appearing in FastAPI logs?","FastAPI\u002FUvicorn access logs include the full URL path with query params —\nAPI keys in query params will appear in logs.\n\nMitigations:\n1. **Use headers, not query params**, for API keys (they're not in access logs).\n2. **Custom log filter** to redact header values:\n\n```python\nimport logging\n\nclass RedactAuthFilter(logging.Filter):\n    def filter(self, record: logging.LogRecord) -> bool:\n        msg = str(record.getMessage())\n        record.msg = msg.replace(settings.api_key, \"***\")\n        return True\n```\n\n3. For Uvicorn's access log, use `--no-access-log` and write your own structured\n   middleware that explicitly controls what's logged.\n\nRule of thumb: never put auth credentials in query params; treat all incoming\n`Authorization` header values as secrets and never log them verbatim.\n",{"id":1547,"difficulty":96,"q":1548,"a":1549},"trusted-hosts-middleware","What does `TrustedHostMiddleware` do in FastAPI?","`TrustedHostMiddleware` rejects requests whose `Host` header doesn't match an\nallowed list, preventing **Host header injection** attacks.\n\n```python\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\n\napp.add_middleware(\n    TrustedHostMiddleware,\n    allowed_hosts=[\"api.example.com\", \"*.example.com\"],\n)\n```\n\nWithout it, an attacker who can route traffic to your server can send a request\nwith `Host: evil.com` — this can affect password reset links, CORS checks that\nrely on the `Host` header, and server-generated URLs.\n\nRule of thumb: add `TrustedHostMiddleware` in production with the exact\nhostname(s) — it's a one-liner defence against Host header injection.\n",{"id":1551,"difficulty":96,"q":1552,"a":1553},"sql-injection-prevention","How does FastAPI\u002FPydantic help prevent SQL injection, and what should you still watch out for?","Pydantic validates and coerces input types — but it does **not** sanitise SQL.\nSQL injection prevention requires using parameterised queries (ORM or raw):\n\n```python\n# SAFE — SQLAlchemy parameterises automatically\nresult = await db.execute(select(User).where(User.name == username))\n\n# SAFE — raw SQL with bound parameters\nawait db.execute(text(\"SELECT * FROM users WHERE name = :name\"), {\"name\": username})\n\n# DANGEROUS — string interpolation\nawait db.execute(f\"SELECT * FROM users WHERE name = '{username}'\")\n```\n\nPydantic validates that `username` is a `str` — but a string can still contain\nSQL metacharacters. Always use parameterised queries regardless of validation.\n\nRule of thumb: let the ORM\u002Fquery builder parameterise for you; never interpolate\nuser input directly into SQL strings, even after Pydantic validation.\n",{"description":94},"FastAPI API key interview questions — APIKeyHeader, APIKeyQuery, APIKeyCookie, HTTPS, CORS, rate limiting and CSRF protection.","fastapi\u002Fsecurity\u002Fapi-keys","API Keys","hweGFrlIhWcir-N9DcspnE7K2iZ-Th8MrQXiIbqLccs",{"id":1560,"title":1561,"body":1562,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1566,"navigation":99,"order":38,"path":1567,"questions":1568,"questionsCount":148,"related":149,"seo":1613,"seoDescription":1614,"stem":1615,"subtopic":1561,"topic":72,"topicSlug":74,"updated":154,"__hash__":1616},"qa\u002Ffastapi\u002Ftesting\u002Fdependency-overrides.md","Dependency Overrides",{"type":91,"value":1563,"toc":1564},[],{"title":94,"searchDepth":29,"depth":29,"links":1565},[],{},"\u002Ffastapi\u002Ftesting\u002Fdependency-overrides",[1569,1573,1577,1581,1585,1589,1593,1597,1601,1605,1609],{"id":1570,"difficulty":104,"q":1571,"a":1572},"dependency-overrides-basics","What is `app.dependency_overrides` and how do you use it in tests?","`app.dependency_overrides` is a dict on the `FastAPI` instance. Map an original\ndependency function to a replacement callable — FastAPI calls the replacement\ninstead during test requests.\n\n```python\nfrom app.main import app\nfrom app.auth import get_current_user\nfrom fastapi.testclient import TestClient\n\ndef mock_user():\n    return {\"id\": 1, \"name\": \"Alice\", \"role\": \"admin\"}\n\napp.dependency_overrides[get_current_user] = mock_user\n\nclient = TestClient(app)\n\ndef test_protected_endpoint():\n    resp = client.get(\"\u002Fadmin\u002Fstats\")\n    assert resp.status_code == 200\n```\n\nRule of thumb: use `dependency_overrides` to replace any injected function —\nDB sessions, auth, settings, external API clients — without touching app code.\n",{"id":1574,"difficulty":96,"q":1575,"a":1576},"cleanup-overrides","How do you ensure dependency overrides don't leak between tests?","Use a pytest `autouse` fixture to clear overrides after every test:\n\n```python\nimport pytest\nfrom app.main import app\n\n@pytest.fixture(autouse=True)\ndef reset_dependency_overrides():\n    yield\n    app.dependency_overrides.clear()\n```\n\nOr clear per-override:\n```python\n@pytest.fixture\ndef with_mock_user():\n    app.dependency_overrides[get_current_user] = lambda: {\"id\": 1}\n    yield\n    del app.dependency_overrides[get_current_user]\n```\n\nRule of thumb: always clean up overrides in fixture teardown — a leaked override\nin one test silently causes wrong behaviour in subsequent tests.\n",{"id":1578,"difficulty":104,"q":1579,"a":1580},"override-return-value","Does the override function need the same signature as the original dependency?","No — the override only needs to return a compatible value. FastAPI replaces the\nentire callable, so the override's parameters are independently resolved by DI.\n\n```python\n# Original dep — reads from DB\nasync def get_current_user(token: str = Depends(oauth2_scheme)):\n    payload = decode_jwt(token)\n    return await db.get_user(payload[\"sub\"])\n\n# Override — returns a hardcoded user, no token needed\ndef mock_admin_user():\n    return User(id=1, name=\"Admin\", role=\"admin\")\n\napp.dependency_overrides[get_current_user] = mock_admin_user\n```\n\nThe override receives its own deps from DI — if it has parameters, they'll be\ninjected. If it has none, it just returns the hardcoded value.\n\nRule of thumb: keep override functions as simple as possible — bare lambdas or\nzero-argument functions returning fake objects are ideal for tests.\n",{"id":1582,"difficulty":96,"q":1583,"a":1584},"override-with-settings","How do you override `BaseSettings` in tests using dependency overrides?","Override the `get_settings` dependency (the `@lru_cache` wrapper):\n\n```python\nfrom app.config import get_settings, Settings\nfrom app.main import app\n\ndef test_settings():\n    test_settings = Settings(\n        database_url=\"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\",\n        secret_key=\"test-only-secret\",\n        debug=True,\n    )\n    app.dependency_overrides[get_settings] = lambda: test_settings\n\n    with TestClient(app) as client:\n        resp = client.get(\"\u002Fconfig\")\n        assert resp.json()[\"debug\"] is True\n\n    app.dependency_overrides.clear()\n```\n\nAlso clear the `@lru_cache` to prevent stale settings:\n```python\nget_settings.cache_clear()\n```\n\nRule of thumb: always pair `dependency_overrides[get_settings]` with\n`get_settings.cache_clear()` to prevent the cached production settings from\nbeing used despite the override.\n",{"id":1586,"difficulty":96,"q":1587,"a":1588},"override-db-session","Show the standard pattern for replacing the DB session dependency in tests.","```python\n# conftest.py\nimport pytest\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import sessionmaker, Session\nfrom fastapi.testclient import TestClient\nfrom app.main import app\nfrom app.db.session import get_db\nfrom app.db.base import Base\n\nTEST_ENGINE = create_engine(\"sqlite:\u002F\u002F\u002F:memory:\", connect_args={\"check_same_thread\": False})\nTestingSessionLocal = sessionmaker(bind=TEST_ENGINE)\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef create_tables():\n    Base.metadata.create_all(bind=TEST_ENGINE)\n    yield\n    Base.metadata.drop_all(bind=TEST_ENGINE)\n\n@pytest.fixture\ndef db_session():\n    session = TestingSessionLocal()\n    try:\n        yield session\n    finally:\n        session.rollback()\n        session.close()\n\n@pytest.fixture\ndef client(db_session):\n    def override_get_db():\n        yield db_session\n    app.dependency_overrides[get_db] = override_get_db\n    with TestClient(app) as c:\n        yield c\n    app.dependency_overrides.clear()\n```\n\nRule of thumb: roll back the test session instead of committing — it gives\neach test an isolated, empty state without truncating tables.\n",{"id":1590,"difficulty":96,"q":1591,"a":1592},"override-external-api","How do you mock an external HTTP API call injected as a dependency?","Inject the HTTP client as a dependency, then override it in tests:\n\n```python\n# In app code\nimport httpx\n\nasync def get_http_client() -> httpx.AsyncClient:\n    async with httpx.AsyncClient() as client:\n        yield client\n\n@app.get(\"\u002Fweather\")\nasync def weather(city: str, client: httpx.AsyncClient = Depends(get_http_client)):\n    resp = await client.get(f\"https:\u002F\u002Fapi.weather.com\u002F{city}\")\n    return resp.json()\n```\n\n```python\n# In tests — replace with a respx mock\nimport respx\nimport httpx\n\n@respx.mock\ndef test_weather(client):\n    respx.get(\"https:\u002F\u002Fapi.weather.com\u002FLondon\").mock(\n        return_value=httpx.Response(200, json={\"temp\": 15})\n    )\n    resp = client.get(\"\u002Fweather?city=London\")\n    assert resp.json()[\"temp\"] == 15\n```\n\nOr inject a fake client via override:\n```python\nasync def fake_http_client():\n    yield FakeAsyncClient()\n\napp.dependency_overrides[get_http_client] = fake_http_client\n```\n\nRule of thumb: inject HTTP clients as dependencies rather than instantiating\nthem inside handlers — it makes them mockable without `patch()`.\n",{"id":1594,"difficulty":125,"q":1595,"a":1596},"partial-override","How do you override only part of a dependency chain (one level deep)?","Override just the sub-dependency, leaving higher-level deps intact:\n\n```python\n# Dependency chain: get_db → get_current_user → require_admin\n# We want to test require_admin with a specific user but real DB logic\n\ndef fixed_user():\n    return User(id=99, name=\"Test Admin\", role=\"admin\")\n\n# Override only get_current_user — get_db still uses real DI\napp.dependency_overrides[get_current_user] = fixed_user\n\n# require_admin still calls its Depends(get_current_user) — but gets our mock\n```\n\nFastAPI resolves the override at the point where the original dep is declared —\nanything that `Depends()` on it gets the override's return value.\n\nRule of thumb: override at the lowest practical level — overriding `get_current_user`\n(not `require_admin`) tests `require_admin`'s logic with controlled user data.\n",{"id":1598,"difficulty":96,"q":1599,"a":1600},"class-dep-override","How do you override a class-based dependency in FastAPI tests?","Class-based deps are instantiated by FastAPI on each call. Override with another\ncallable (class, function, or lambda) that returns a compatible object:\n\n```python\nclass PaginationDep:\n    def __init__(self, page: int = 1, size: int = 20):\n        self.skip = (page - 1) * size\n        self.limit = size\n\n# Override with a fixed pagination\nclass FixedPagination:\n    skip = 0\n    limit = 5\n\napp.dependency_overrides[PaginationDep] = FixedPagination\n\ndef test_first_page(client):\n    resp = client.get(\"\u002Fitems\")\n    data = resp.json()\n    assert len(data) \u003C= 5\n```\n\nRule of thumb: match the override's public interface (attributes\u002Fmethods your\nhandler uses) rather than its constructor — duck typing is sufficient.\n",{"id":1602,"difficulty":96,"q":1603,"a":1604},"override-scope-per-test","How do you apply a dependency override to only one test without affecting others?","Use a pytest fixture with function scope (the default):\n\n```python\n@pytest.fixture\ndef admin_override():\n    app.dependency_overrides[get_current_user] = lambda: {\"id\": 1, \"role\": \"admin\"}\n    yield\n    del app.dependency_overrides[get_current_user]\n\ndef test_admin_only(client, admin_override):\n    resp = client.delete(\"\u002Fusers\u002F99\")\n    assert resp.status_code == 200\n\ndef test_normal_user(client):\n    # admin_override not active — real get_current_user used\n    resp = client.delete(\"\u002Fusers\u002F99\")\n    assert resp.status_code == 403\n```\n\nRule of thumb: use per-test fixtures with explicit teardown for scoped overrides —\ndon't set overrides directly in test bodies without a cleanup mechanism.\n",{"id":1606,"difficulty":125,"q":1607,"a":1608},"verify-dep-called","How do you verify that a dependency was called with specific arguments in a test?","Replace the dependency with a `MagicMock` or a spy function:\n\n```python\nfrom unittest.mock import MagicMock, call\n\nmock_audit = MagicMock()\n\ndef audit_override(request: Request, user = Depends(get_current_user)):\n    mock_audit(path=str(request.url), user_id=user[\"id\"])\n\napp.dependency_overrides[audit_dep] = audit_override\n\ndef test_audit_logged(client):\n    client.get(\"\u002Fitems\")\n    mock_audit.assert_called_once()\n    call_args = mock_audit.call_args\n    assert call_args.kwargs[\"user_id\"] == 1\n```\n\nRule of thumb: spy on dependencies rather than internal functions — testing\nthrough the DI graph tests the real integration path.\n",{"id":1610,"difficulty":125,"q":1611,"a":1612},"dependency-override-with-yield","Can you use a yield function as a dependency override?","Yes — yield-based overrides work exactly like regular yield dependencies:\n\n```python\nclass FakeDB:\n    def __init__(self):\n        self.data = [{\"id\": 1, \"name\": \"Alice\"}]\n\n    def get_all(self):\n        return self.data\n\nasync def fake_get_db():\n    db = FakeDB()\n    db.data.append({\"id\": 2, \"name\": \"Bob\"})  # setup\n    yield db\n    db.data.clear()                              # teardown (after handler)\n\napp.dependency_overrides[get_db] = fake_get_db\n```\n\nThe teardown (after `yield`) still runs after the handler completes — useful for\nresetting state in the override itself.\n\nRule of thumb: use yield overrides when the fake resource needs setup and\ncleanup; use simple lambdas\u002Ffunctions for stateless fakes.\n",{"description":94},"FastAPI dependency override interview questions — app.dependency_overrides, isolating tests, mocking auth, DB session swapping and cleanup patterns.","fastapi\u002Ftesting\u002Fdependency-overrides","175Uw5rR-p9_L-Whet4tK7RU-i9X1_m80vF3wA-Egnk",{"id":1618,"title":1619,"body":1620,"description":94,"difficulty":104,"extension":97,"framework":10,"frameworkSlug":8,"meta":1624,"navigation":99,"order":47,"path":1625,"questions":1626,"questionsCount":487,"related":149,"seo":1683,"seoDescription":1684,"stem":1685,"subtopic":1686,"topic":20,"topicSlug":21,"updated":154,"__hash__":1687},"qa\u002Ffastapi\u002Ffundamentals\u002Ftype-hints.md","Type Hints",{"type":91,"value":1621,"toc":1622},[],{"title":94,"searchDepth":29,"depth":29,"links":1623},[],{},"\u002Ffastapi\u002Ffundamentals\u002Ftype-hints",[1627,1631,1635,1639,1643,1647,1651,1655,1659,1663,1667,1671,1675,1679],{"id":1628,"difficulty":104,"q":1629,"a":1630},"why-type-hints","Why are Python type hints central to how FastAPI works?","FastAPI uses **runtime type inspection** (via `typing`, `inspect` and Pydantic)\nto derive three things from your function signatures automatically:\n\n1. **Where** each parameter comes from (path, query, body, header).\n2. **How** to parse and validate the incoming value (int, str, Pydantic model).\n3. **What** the OpenAPI schema should look like.\n\n```python\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: int,          # → path int, validated, documented as integer\n    q: str | None = None,  # → optional query string, documented\n    item: Item,            # → JSON body parsed into Pydantic model\n):\n    ...\n```\n\nWithout type hints, FastAPI cannot infer any of this — parameters become\nuntyped strings and no schema is generated.\n\nRule of thumb: in FastAPI, type annotations are not optional documentation —\nthey are the mechanism that drives parsing, validation, and the API contract.\n",{"id":1632,"difficulty":104,"q":1633,"a":1634},"optional-parameters","How do you declare an optional query parameter in FastAPI?","Give the parameter a default value of `None` and annotate it with\n`str | None` (Python 3.10+) or `Optional[str]` (Python 3.9-).\n\n```python\nfrom typing import Optional\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    q: str | None = None,          # optional, defaults to None\n    limit: int = 10,               # optional with a non-None default\n    offset: int = 0,\n):\n    ...\n```\n\nIn the OpenAPI schema, parameters with defaults appear as `required: false`.\nFastAPI only marks a query param `required: true` when there is **no default**.\n\nRule of thumb: `= None` makes any parameter optional; omit the default to make\nit required.\n",{"id":1636,"difficulty":96,"q":1637,"a":1638},"annotated","What is `Annotated` and how does FastAPI use it?","`Annotated[T, metadata]` (from `typing`) attaches extra metadata to a type\nwithout changing the type itself. FastAPI reads the metadata to configure\nparameter behaviour — Query constraints, Body options, Depends() etc.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query, Path\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: Annotated[int, Path(ge=1)],          # path param, must be >= 1\n    q: Annotated[str | None, Query(max_length=50)] = None,\n):\n    ...\n```\n\nThis keeps type information separate from validation metadata and works better\nwith type checkers (mypy, pyright) than the old `item_id: int = Path(ge=1)` style.\n\nRule of thumb: prefer `Annotated[T, Field(...)]` style in new code — it separates\nthe type from the constraint and is more explicit about what each annotation means.\n",{"id":1640,"difficulty":96,"q":1641,"a":1642},"enum-annotation","How do you restrict a parameter to a fixed set of values using an Enum?","Annotate the parameter with a `str` (or `int`) Enum subclass. FastAPI validates\nthe incoming value against the enum members and documents the allowed values in\nOpenAPI.\n\n```python\nfrom enum import Enum\n\nclass Size(str, Enum):\n    small = \"small\"\n    medium = \"medium\"\n    large = \"large\"\n\n@app.get(\"\u002Fitems\")\nasync def list_items(size: Size = Size.medium):\n    return {\"size\": size.value}\n```\n\nInheriting from both `str` and `Enum` ensures the value is JSON-serialisable\nas a string directly.\n\nRule of thumb: use `str` enums for path\u002Fquery params, `int` enums for numeric\ncodes; always inherit from `str`\u002F`int` so serialisation is automatic.\n",{"id":1644,"difficulty":96,"q":1645,"a":1646},"list-query-param","How do you accept a list of values from a query parameter?","Annotate with `list[str]` (or `List[str]`) and use `Query()` to tell FastAPI\nto collect multiple values for the same key.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    tags: Annotated[list[str], Query()] = [],\n):\n    return {\"tags\": tags}\n# GET \u002Fitems?tags=python&tags=fastapi → {\"tags\": [\"python\", \"fastapi\"]}\n```\n\nWithout `Query()`, a bare `list[str]` annotation would be treated as a body\nparameter by FastAPI.\n\nRule of thumb: always wrap `list[T]` query params with `Query()` to signal that\nmultiple same-key values should be collected into a list.\n",{"id":1648,"difficulty":104,"q":1649,"a":1650},"header-parameter","How do you read a request header in FastAPI?","Annotate the parameter with `Header()`. FastAPI automatically converts the\nparameter name from snake_case to the HTTP header's hyphen-case format.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Header\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    x_token: Annotated[str | None, Header()] = None,\n):\n    # reads the \"X-Token\" HTTP header\n    return {\"token\": x_token}\n```\n\nTo read a header with underscores in the name (non-standard), pass\n`convert_underscores=False` to `Header()`.\n\nRule of thumb: FastAPI converts `_` → `-` automatically for headers; use\n`convert_underscores=False` only for custom non-standard header names.\n",{"id":1652,"difficulty":104,"q":1653,"a":1654},"cookie-parameter","How do you read a cookie value in a FastAPI handler?","Annotate the parameter with `Cookie()`.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Cookie\n\n@app.get(\"\u002Fme\")\nasync def current_user(\n    session_id: Annotated[str | None, Cookie()] = None,\n):\n    if not session_id:\n        raise HTTPException(status_code=401)\n    return {\"session\": session_id}\n```\n\nCookies are accessible only via `Cookie()` — they won't be picked up as query\nor path params. To set a cookie in the response, inject `Response` and call\n`response.set_cookie(key, value)`.\n\nRule of thumb: read cookies with `Cookie()`, write them with `response.set_cookie()`\ninjected via the `Response` parameter.\n",{"id":1656,"difficulty":96,"q":1657,"a":1658},"pydantic-field-in-handler","How do you add validation constraints (min\u002Fmax, regex) to a query parameter?","Use `Query()` with constraint kwargs inside `Annotated`. These map to Pydantic\nfield constraints under the hood.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def search(\n    q: Annotated[str, Query(min_length=3, max_length=100, pattern=r\"^\\w+$\")],\n    page: Annotated[int, Query(ge=1, le=1000)] = 1,\n):\n    ...\n```\n\nConstraints are reflected in the OpenAPI schema so clients can validate before\nsending.\n\nRule of thumb: put constraint kwargs in `Query()`\u002F`Path()`\u002F`Body()` — never\nvalidate manually in the handler body for data that comes from the request.\n",{"id":1660,"difficulty":96,"q":1661,"a":1662},"body-singular-field","How do you accept a single non-model value in the request body?","Use `Body()` annotation. Without it, FastAPI treats simple types as query params.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Body\n\n@app.post(\"\u002Fitems\u002F{id}\u002Ftag\")\nasync def add_tag(\n    id: int,\n    tag: Annotated[str, Body()],   # reads {\"tag\": \"python\"} or just \"python\" with embed=False\n):\n    ...\n```\n\nFor a single-field body that must be wrapped in a key:\n```python\ntag: Annotated[str, Body(embed=True)]   # expects {\"tag\": \"value\"}\n```\n\nRule of thumb: use a Pydantic model whenever you have multiple body fields;\nuse `Body()` only for a single primitive value that doesn't warrant a model.\n",{"id":1664,"difficulty":96,"q":1665,"a":1666},"form-data","How do you accept form data (not JSON) in FastAPI?","Use `Form()` instead of `Body()`. Install `python-multipart` first.\n\n```python\nfrom fastapi import Form\n\n@app.post(\"\u002Flogin\")\nasync def login(\n    username: Annotated[str, Form()],\n    password: Annotated[str, Form()],\n):\n    ...\n```\n\nNote: **you cannot mix JSON body and `Form()` fields in the same handler** —\nthe `Content-Type` is either `application\u002Fjson` or `application\u002Fx-www-form-urlencoded`\u002F\n`multipart\u002Fform-data`, not both.\n\nRule of thumb: use `Form()` for HTML form submissions or OAuth2 password flows;\nuse JSON body for REST API calls.\n",{"id":1668,"difficulty":96,"q":1669,"a":1670},"file-upload","How do you accept a file upload in FastAPI?","Use `UploadFile` for streaming (recommended) or `bytes` for small in-memory files.\n\n```python\nfrom fastapi import UploadFile, File\n\n@app.post(\"\u002Fupload\")\nasync def upload(file: UploadFile):\n    content = await file.read()\n    return {\"filename\": file.filename, \"size\": len(content)}\n```\n\n`UploadFile` attributes: `filename`, `content_type`, `file` (SpooledTemporaryFile).\nFor multiple files: `files: list[UploadFile]`.\n\nAlways `await file.read()` — the file object is async. For large files, read in\nchunks to avoid loading the whole thing into memory:\n\n```python\nwhile chunk := await file.read(8192):\n    process(chunk)\n```\n\nRule of thumb: prefer `UploadFile` over `bytes` — it streams large files without\nloading them into memory; `bytes` is fine only for small known-size uploads.\n",{"id":1672,"difficulty":96,"q":1673,"a":1674},"response-model-exclude-none","How do you exclude `None` fields from a response in FastAPI?","Set `response_model_exclude_none=True` in the route decorator. FastAPI calls\n`model_dump(exclude_none=True)` on the Pydantic response model before serialising.\n\n```python\nclass UserOut(BaseModel):\n    id: int\n    name: str\n    bio: str | None = None\n\n@app.get(\"\u002Fusers\u002F{id}\", response_model=UserOut, response_model_exclude_none=True)\nasync def get_user(id: int):\n    return {\"id\": 1, \"name\": \"Alice\"}\n    # bio is None → omitted from response: {\"id\": 1, \"name\": \"Alice\"}\n```\n\nRelated flags: `response_model_exclude_unset=True` (skip fields not explicitly set\nby the handler) and `response_model_exclude={\"internal_field\"}` (always strip specific fields).\n\nRule of thumb: use `exclude_none=True` for sparse models where `None` means\n\"not present\"; use `exclude_unset=True` when you want PATCH-style partial output.\n",{"id":1676,"difficulty":125,"q":1677,"a":1678},"union-response","How do you type a route that can return one of two Pydantic models?","Use `Union[ModelA, ModelB]` (or `ModelA | ModelB`) as the `response_model`.\nFastAPI includes both schemas in the OpenAPI spec.\n\n```python\nfrom typing import Union\n\nclass Cat(BaseModel): name: str; meows: bool\nclass Dog(BaseModel): name: str; barks: bool\n\n@app.get(\"\u002Fpet\u002F{id}\", response_model=Union[Cat, Dog])\nasync def get_pet(id: int) -> Cat | Dog:\n    pet = await db.get_pet(id)\n    return pet   # FastAPI picks the matching model for serialisation\n```\n\nFastAPI serialises using the **first matching model** from left to right in the\nUnion. For discriminated unions, use Pydantic's `discriminator` field for\nunambiguous selection.\n\nRule of thumb: discriminated unions (with a `type: Literal[\"cat\"]` field) are\ncleaner than bare `Union` — they document intent and avoid ambiguous serialisation.\n",{"id":1680,"difficulty":125,"q":1681,"a":1682},"generic-response","How do you create a generic paginated response type in FastAPI?","Use `Generic[T]` from `typing` with a Pydantic `BaseModel`:\n\n```python\nfrom typing import Generic, TypeVar\nfrom pydantic import BaseModel\n\nT = TypeVar(\"T\")\n\nclass Page(BaseModel, Generic[T]):\n    items: list[T]\n    total: int\n    page: int\n    size: int\n\nclass UserOut(BaseModel):\n    id: int\n    name: str\n\n@app.get(\"\u002Fusers\", response_model=Page[UserOut])\nasync def list_users(page: int = 1, size: int = 20):\n    users, total = await db.paginate_users(page, size)\n    return Page(items=users, total=total, page=page, size=size)\n```\n\nPydantic v2 fully supports generic models and FastAPI generates the correct\nOpenAPI schema for each concrete instantiation.\n\nRule of thumb: define one `Page[T]` generic model and reuse it across all list\nendpoints — it keeps pagination schemas consistent and avoids duplication.\n",{"description":94},"FastAPI type hints interview questions — how Python annotations drive parameter parsing, validation, editor support and OpenAPI schema generation.","fastapi\u002Ffundamentals\u002Ftype-hints","Type Hints & FastAPI","Nr51v6fMWEJZeiFZKRQE-kYRliezxU93jinjdgYPU5E",{"id":1689,"title":1690,"body":1691,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1695,"navigation":99,"order":47,"path":1696,"questions":1697,"questionsCount":214,"related":149,"seo":1746,"seoDescription":1747,"stem":1748,"subtopic":1749,"topic":37,"topicSlug":39,"updated":154,"__hash__":1750},"qa\u002Ffastapi\u002Fpydantic\u002Fsettings.md","Settings",{"type":91,"value":1692,"toc":1693},[],{"title":94,"searchDepth":29,"depth":29,"links":1694},[],{},"\u002Ffastapi\u002Fpydantic\u002Fsettings",[1698,1702,1706,1710,1714,1718,1722,1726,1730,1734,1738,1742],{"id":1699,"difficulty":104,"q":1700,"a":1701},"basesettings-basics","What is `BaseSettings` and why use it for FastAPI configuration?","`BaseSettings` (from `pydantic-settings`) is a `BaseModel` subclass that reads\nfield values from **environment variables** automatically. It gives you typed,\nvalidated configuration without manual `os.getenv()` calls.\n\n```python\nfrom pydantic_settings import BaseSettings\n\nclass Settings(BaseSettings):\n    database_url: str\n    secret_key: str\n    debug: bool = False\n    max_connections: int = 10\n\nsettings = Settings()\n# reads DATABASE_URL, SECRET_KEY, DEBUG, MAX_CONNECTIONS from env\n```\n\nBenefits over raw `os.getenv()`:\n- Pydantic validates types and raises a clear error at startup if required vars are missing.\n- Settings are documented as a class — easy to audit.\n- Works with `.env` files, Docker secrets, and AWS Parameter Store.\n\nRule of thumb: put all configuration in a `BaseSettings` class; never scatter\n`os.getenv()` calls across the codebase.\n",{"id":1703,"difficulty":104,"q":1704,"a":1705},"env-file","How do you load configuration from a `.env` file with `BaseSettings`?","Configure `model_config` with `env_file`:\n\n```python\nfrom pydantic_settings import BaseSettings\n\nclass Settings(BaseSettings):\n    model_config = {\"env_file\": \".env\", \"env_file_encoding\": \"utf-8\"}\n\n    database_url: str\n    secret_key: str\n    debug: bool = False\n```\n\n`.env` file:\n```\nDATABASE_URL=postgresql+asyncpg:\u002F\u002Fuser:pass@localhost\u002Fmydb\nSECRET_KEY=supersecret\nDEBUG=true\n```\n\nMultiple `.env` files (later overrides earlier):\n```python\nmodel_config = {\"env_file\": (\".env\", \".env.local\")}\n```\n\nRule of thumb: commit `.env.example` with dummy values and add `.env` to\n`.gitignore` — never commit real secrets to version control.\n",{"id":1707,"difficulty":96,"q":1708,"a":1709},"lru-cache-settings","How do you avoid re-reading environment variables on every request in FastAPI?","Wrap the `Settings()` constructor in `@lru_cache` and inject it via `Depends`:\n\n```python\nfrom functools import lru_cache\nfrom fastapi import Depends\n\n@lru_cache\ndef get_settings() -> Settings:\n    return Settings()   # reads env\u002Ffile once; cached for process lifetime\n\n@app.get(\"\u002Finfo\")\nasync def info(settings: Settings = Depends(get_settings)):\n    return {\"debug\": settings.debug}\n```\n\n`@lru_cache` makes `get_settings()` a singleton: the first call creates the\nobject; subsequent calls return the same instance. Safe because environment\nvariables don't change during a process lifetime.\n\nRule of thumb: always wrap settings construction in `@lru_cache` — reading env\nvars is cheap, but parsing and validating with Pydantic on every request adds\nunnecessary overhead.\n",{"id":1711,"difficulty":104,"q":1712,"a":1713},"settings-case-sensitivity","Are `BaseSettings` environment variable names case-sensitive?","By default **case-insensitive** on all platforms. `DATABASE_URL`,\n`database_url`, and `Database_Url` all resolve to the `database_url` field.\n\n```python\nclass Settings(BaseSettings):\n    database_url: str   # matches DATABASE_URL, database_url, etc.\n```\n\nTo enforce case-sensitive env var names:\n```python\nmodel_config = {\"case_sensitive\": True}\n```\n\nRule of thumb: use UPPERCASE for environment variable names by convention\n(Linux, 12-factor apps); keep field names lowercase — the case-insensitive\nmatching bridges them automatically.\n",{"id":1715,"difficulty":96,"q":1716,"a":1717},"nested-settings","How do you organise complex settings into nested groups with `BaseSettings`?","Use nested Pydantic models. `BaseSettings` reads nested values via a delimiter\nprefix in the env var name:\n\n```python\nfrom pydantic import BaseModel\nfrom pydantic_settings import BaseSettings\n\nclass DatabaseSettings(BaseModel):\n    url: str\n    pool_size: int = 5\n\nclass Settings(BaseSettings):\n    model_config = {\"env_nested_delimiter\": \"__\"}\n    database: DatabaseSettings\n    debug: bool = False\n\n# env vars: DATABASE__URL=postgres:\u002F\u002F..., DATABASE__POOL_SIZE=10\n```\n\nRule of thumb: use `env_nested_delimiter=\"__\"` for nested settings — it's the\nconventional double-underscore pattern in 12-factor apps.\n",{"id":1719,"difficulty":96,"q":1720,"a":1721},"secrets-directory","How do you load secrets from files (e.g., Docker secrets) with `BaseSettings`?","Point `secrets_dir` to the directory where secret files live. Each file named\nafter the env var contains the secret value.\n\n```python\nfrom pydantic_settings import BaseSettings\n\nclass Settings(BaseSettings):\n    model_config = {\"secrets_dir\": \"\u002Frun\u002Fsecrets\"}\n    database_password: str   # reads \u002Frun\u002Fsecrets\u002Fdatabase_password\n    api_key: str             # reads \u002Frun\u002Fsecrets\u002Fapi_key\n```\n\nDocker Swarm and Kubernetes both mount secrets as files at a known path.\nThis approach keeps secrets out of environment variables (less visible in\n`ps` output and container inspection).\n\nRule of thumb: prefer secrets-as-files over env var secrets in containerised\ndeployments — they integrate cleanly with Kubernetes Secrets and Docker secrets.\n",{"id":1723,"difficulty":96,"q":1724,"a":1725},"settings-override-in-tests","How do you override settings values in tests?","Override the `get_settings` dependency on the test app:\n\n```python\nfrom fastapi.testclient import TestClient\nfrom app.main import app\nfrom app.config import get_settings, Settings\n\ndef get_test_settings():\n    return Settings(\n        database_url=\"sqlite:\u002F\u002F\u002F:memory:\",\n        secret_key=\"test-secret\",\n        debug=True,\n    )\n\napp.dependency_overrides[get_settings] = get_test_settings\nclient = TestClient(app)\n```\n\nAlternatively, set environment variables before the settings are loaded:\n```python\nimport os\nos.environ[\"DATABASE_URL\"] = \"sqlite:\u002F\u002F\u002F:memory:\"\n```\n\nRule of thumb: use `dependency_overrides` — it's explicit, isolated per test\nfile, and doesn't pollute `os.environ` for other tests.\n",{"id":1727,"difficulty":96,"q":1728,"a":1729},"settings-validators","Can you add validators to `BaseSettings` fields?","Yes — `BaseSettings` inherits from `BaseModel`, so `@field_validator` and\n`@model_validator` work identically:\n\n```python\nfrom pydantic import field_validator\nfrom pydantic_settings import BaseSettings\n\nclass Settings(BaseSettings):\n    allowed_hosts: list[str] = [\"*\"]\n    cors_origins: str = \"\"\n\n    @field_validator(\"cors_origins\", mode=\"before\")\n    @classmethod\n    def parse_cors(cls, v: str) -> list[str]:\n        return [origin.strip() for origin in v.split(\",\") if origin.strip()]\n```\n\nThis pattern lets you store a comma-separated env var and parse it into a list.\n\nRule of thumb: use `@field_validator(mode=\"before\")` in `BaseSettings` to parse\ncompound env vars (comma-separated lists, JSON strings) into the target type.\n",{"id":1731,"difficulty":96,"q":1732,"a":1733},"settings-singleton-pattern","What is the singleton pattern for settings in FastAPI and what is the risk of using a module-level instance?","A **module-level singleton**:\n```python\n# config.py\nsettings = Settings()   # created once at import time\n```\n\nThis works in production but breaks in tests: if `os.environ` is patched after\nimport, the singleton already holds the old values.\n\nThe **`Depends` + `@lru_cache` pattern** is safer:\n```python\n@lru_cache\ndef get_settings() -> Settings:\n    return Settings()\n\n# In tests:\napp.dependency_overrides[get_settings] = lambda: Settings(debug=True)\n```\n\nThe `lru_cache` singleton is invalidated between tests by clearing the cache:\n```python\nget_settings.cache_clear()\n```\n\nRule of thumb: use `Depends(get_settings)` with `@lru_cache` for testability;\navoid module-level `settings = Settings()` in anything you'll need to test.\n",{"id":1735,"difficulty":104,"q":1736,"a":1737},"settings-field-types","What Python types work well with `BaseSettings` for environment variables?","All Pydantic-supported types work. Pydantic coerces the string value from the\nenv var to the declared type:\n\n| Python type | Env var example |\n|-------------|-----------------|\n| `str` | `SECRET_KEY=abc` |\n| `int` | `PORT=8000` |\n| `float` | `TIMEOUT=30.5` |\n| `bool` | `DEBUG=true` \u002F `DEBUG=1` |\n| `list[str]` | `TAGS=[\"a\",\"b\"]` (JSON) or use `@field_validator` |\n| `HttpUrl` | `BASE_URL=https:\u002F\u002Fexample.com` |\n| `SecretStr` | `PASSWORD=secret` (masked in repr) |\n\n```python\nfrom pydantic import SecretStr, HttpUrl\n\nclass Settings(BaseSettings):\n    database_password: SecretStr    # hidden in logs\n    api_base_url: HttpUrl           # validated URL\n```\n\nRule of thumb: use `SecretStr` for passwords and tokens — it masks the value in\n`repr()` and `str()`, preventing accidental logging.\n",{"id":1739,"difficulty":104,"q":1740,"a":1741},"settings-multiple-envs","How do you support different settings for development, staging and production?","Load different `.env` files based on an `APP_ENV` env var:\n\n```python\nimport os\nfrom pydantic_settings import BaseSettings\n\nclass Settings(BaseSettings):\n    model_config = {\n        \"env_file\": f\".env.{os.getenv('APP_ENV', 'development')}\"\n    }\n    database_url: str\n    debug: bool = False\n```\n\nFile layout:\n```\n.env.development   → local dev DB, DEBUG=true\n.env.staging       → staging DB, DEBUG=false\n.env.production    → prod secrets (not committed)\n```\n\nIn CI\u002FCD and production, inject all values directly as environment variables\nrather than relying on files.\n\nRule of thumb: in production, always prefer environment variables over `.env`\nfiles — files can be accidentally committed or left on disk.\n",{"id":1743,"difficulty":104,"q":1744,"a":1745},"pydantic-settings-install","Is `BaseSettings` included in Pydantic itself and what do you need to install?","Since Pydantic v2, `BaseSettings` has been **moved to a separate package**:\n`pydantic-settings`. It must be installed separately:\n\n```bash\npip install pydantic-settings\n```\n\nImport:\n```python\nfrom pydantic_settings import BaseSettings\n```\n\nIn Pydantic v1, `BaseSettings` was part of `pydantic` itself\n(`from pydantic import BaseSettings`) — a common migration mistake is forgetting\nto install `pydantic-settings` after upgrading.\n\nRule of thumb: add `pydantic-settings` to `requirements.txt` or `pyproject.toml`\nexplicitly — it is not pulled in by `fastapi` or `pydantic` alone.\n",{"description":94},"FastAPI Pydantic settings interview questions — BaseSettings, env vars, .env files, secrets, nested settings and caching with lru_cache.","fastapi\u002Fpydantic\u002Fsettings","Settings Management","qUIpbUnYHzlmYx-BO9rV1OP-iYYDvd_l2Q22HsoNIo8",{"id":1752,"title":1753,"body":1754,"description":94,"difficulty":96,"extension":97,"framework":10,"frameworkSlug":8,"meta":1758,"navigation":99,"order":47,"path":1759,"questions":1760,"questionsCount":415,"related":149,"seo":1813,"seoDescription":1814,"stem":1815,"subtopic":1816,"topic":28,"topicSlug":30,"updated":154,"__hash__":1817},"qa\u002Ffastapi\u002Frouting\u002Frouters.md","Routers",{"type":91,"value":1755,"toc":1756},[],{"title":94,"searchDepth":29,"depth":29,"links":1757},[],{},"\u002Ffastapi\u002Frouting\u002Frouters",[1761,1765,1769,1773,1777,1781,1785,1789,1793,1797,1801,1805,1809],{"id":1762,"difficulty":104,"q":1763,"a":1764},"apirouter-basics","What is `APIRouter` and why use it instead of the `app` object directly?","`APIRouter` is a mini-app that collects route definitions and is then mounted\nonto the main `FastAPI` instance. It lets you split routes across files without\na circular import on the `app` object.\n\n```python\n# routers\u002Fitems.py\nfrom fastapi import APIRouter\nrouter = APIRouter()\n\n@router.get(\"\u002F\")\nasync def list_items(): ...\n\n@router.post(\"\u002F\")\nasync def create_item(): ...\n```\n\n```python\n# main.py\nfrom fastapi import FastAPI\nfrom routers import items\n\napp = FastAPI()\napp.include_router(items.router, prefix=\"\u002Fitems\", tags=[\"items\"])\n```\n\nRule of thumb: one router per resource\u002Fdomain; import and mount them all in\n`main.py` — this is the FastAPI equivalent of Flask Blueprints.\n",{"id":1766,"difficulty":104,"q":1767,"a":1768},"prefix-tags","What do `prefix` and `tags` do in `include_router`?","`prefix` prepends a path segment to every route in the router.\n`tags` groups every route under a documentation category in Swagger UI.\n\n```python\napp.include_router(\n    users_router,\n    prefix=\"\u002Fusers\",      # \u002Flist → \u002Fusers\u002Flist\n    tags=[\"users\"],        # grouped under \"users\" in docs\n)\napp.include_router(\n    orders_router,\n    prefix=\"\u002Forders\",\n    tags=[\"orders\"],\n)\n```\n\nYou can also set `prefix` and `tags` on the `APIRouter` constructor — useful\nwhen the router \"owns\" its prefix. Setting them in `include_router` is cleaner\nfor versioned mounts.\n\nRule of thumb: set `prefix` and `tags` on `APIRouter()` if the router always\nbelongs to one resource; set them in `include_router` if you mount the same\nrouter at multiple prefixes (e.g., versioning).\n",{"id":1770,"difficulty":96,"q":1771,"a":1772},"router-dependencies","How do you apply a dependency to every route in a router?","Pass `dependencies=[Depends(fn)]` to the `APIRouter` constructor or to\n`include_router`:\n\n```python\nfrom fastapi import APIRouter, Depends\nfrom .auth import verify_api_key\n\nrouter = APIRouter(\n    prefix=\"\u002Fadmin\",\n    tags=[\"admin\"],\n    dependencies=[Depends(verify_api_key)],\n)\n\n@router.get(\"\u002Fstats\")\nasync def stats(): ...  # verify_api_key runs automatically\n```\n\nDependencies at the router level run before route-level dependencies. Both sets\nstack — they don't replace each other.\n\nRule of thumb: put auth\u002Frate-limit dependencies on the router so new routes\ncan't accidentally bypass them.\n",{"id":1774,"difficulty":96,"q":1775,"a":1776},"nested-routers","How do you nest one `APIRouter` inside another?","Call `parent_router.include_router(child_router, prefix=...)`:\n\n```python\n# routers\u002Forders\u002Fitems.py\nitems_router = APIRouter()\n\n@items_router.get(\"\u002F\")\nasync def list_order_items(): ...\n\n# routers\u002Forders\u002F__init__.py\norders_router = APIRouter(prefix=\"\u002Forders\")\norders_router.include_router(items_router, prefix=\"\u002F{order_id}\u002Fitems\")\n\n# main.py\napp.include_router(orders_router)\n# → GET \u002Forders\u002F{order_id}\u002Fitems\u002F\n```\n\nNesting is unlimited; prefixes concatenate.\n\nRule of thumb: nest routers to mirror resource hierarchy in the URL — it keeps\npath prefixes DRY and makes the app structure easy to navigate.\n",{"id":1778,"difficulty":96,"q":1779,"a":1780},"router-response-model","Can you set a default `response_model` or `status_code` at the router level?","Not directly on `APIRouter`, but you can set `responses` (error response docs)\nand `dependencies` at the router level. Default `response_model` and `status_code`\nmust still be set per-route.\n\nA common pattern is to use `route_class` to inject default behaviour:\n\n```python\nfrom fastapi.routing import APIRoute\n\nclass LoggedRoute(APIRoute):\n    def get_route_handler(self):\n        original = super().get_route_handler()\n        async def custom(request):\n            log_request(request)\n            return await original(request)\n        return custom\n\nrouter = APIRouter(route_class=LoggedRoute)\n```\n\nRule of thumb: use `route_class` for cross-cutting concerns (logging, timing);\nset `response_model` and `status_code` per route for explicitness.\n",{"id":1782,"difficulty":96,"q":1783,"a":1784},"include-router-responses","How do you add common error responses to every route in a router?","Pass `responses` to `include_router`. These are merged with per-route responses\nin the OpenAPI schema.\n\n```python\napp.include_router(\n    admin_router,\n    prefix=\"\u002Fadmin\",\n    responses={\n        401: {\"description\": \"Not authenticated\"},\n        403: {\"description\": \"Forbidden\"},\n    },\n)\n```\n\nAll routes in `admin_router` now document 401 and 403 in the schema without\nrepeating it on every decorator.\n\nRule of thumb: declare common error responses (401, 403, 429) at the router\nlevel; declare business-logic errors (404, 409) at the individual route level.\n",{"id":1786,"difficulty":125,"q":1787,"a":1788},"mount-sub-application","What is the difference between `app.include_router()` and `app.mount()`?","`include_router` integrates routes into the **same FastAPI app** — they share\nmiddleware, exception handlers, dependency injection and the OpenAPI schema.\n\n`mount` attaches a **separate ASGI application** at a path prefix. The mounted\napp has its own middleware and schema; requests to its prefix are fully delegated.\n\n```python\n# include_router — routes join the parent app\napp.include_router(users_router, prefix=\"\u002Fusers\")\n\n# mount — separate sub-app, own \u002Fdocs\nv2_app = FastAPI()\napp.mount(\"\u002Fv2\", v2_app)\n```\n\n`mount` is appropriate for: serving static files (`StaticFiles`), mounting a\nseparate versioned API that has diverged significantly, or embedding non-FastAPI\nASGI apps.\n\nRule of thumb: use `include_router` for 95% of cases; use `mount` when you\ngenuinely need a separate ASGI application with independent middleware.\n",{"id":1790,"difficulty":96,"q":1791,"a":1792},"large-app-structure","What is the recommended file structure for a large FastAPI application?","```\napp\u002F\n├── main.py            # FastAPI() instance, include all routers\n├── dependencies.py    # shared Depends() functions\n├── models\u002F\n│   ├── user.py        # Pydantic + ORM models\n│   └── order.py\n├── routers\u002F\n│   ├── users.py       # APIRouter for \u002Fusers\n│   └── orders.py      # APIRouter for \u002Forders\n├── services\u002F\n│   ├── user_service.py\n│   └── order_service.py\n└── db\u002F\n    ├── session.py     # engine + get_db dependency\n    └── models.py      # SQLAlchemy ORM models\n```\n\n```python\n# main.py\nfrom fastapi import FastAPI\nfrom app.routers import users, orders\n\napp = FastAPI()\napp.include_router(users.router, prefix=\"\u002Fusers\", tags=[\"users\"])\napp.include_router(orders.router, prefix=\"\u002Forders\", tags=[\"orders\"])\n```\n\nRule of thumb: keep route handlers thin — they call service functions, not\nbusiness logic directly; separate ORM models from Pydantic schemas.\n",{"id":1794,"difficulty":125,"q":1795,"a":1796},"router-lifespan","Can `APIRouter` have its own lifespan events?","No — `APIRouter` has no built-in lifespan support. Lifespan events belong to\nthe `FastAPI` app. For modular startup\u002Fshutdown, compose context managers inside\nthe single app lifespan:\n\n```python\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def db_lifespan(app):\n    await db.connect()\n    yield\n    await db.disconnect()\n\n@asynccontextmanager\nasync def cache_lifespan(app):\n    await cache.connect()\n    yield\n    await cache.disconnect()\n\n@asynccontextmanager\nasync def app_lifespan(app):\n    async with db_lifespan(app):\n        async with cache_lifespan(app):\n            yield\n\napp = FastAPI(lifespan=app_lifespan)\n```\n\nRule of thumb: compose multiple async context managers inside the app's lifespan\nfunction rather than trying to distribute startup across routers.\n",{"id":1798,"difficulty":104,"q":1799,"a":1800},"router-override-response-class","How do you set a default response class for all routes in a router?","Pass `default_response_class` to the `APIRouter`:\n\n```python\nfrom fastapi import APIRouter\nfrom fastapi.responses import ORJSONResponse\n\nrouter = APIRouter(default_response_class=ORJSONResponse)\n\n@router.get(\"\u002Fitems\")\nasync def list_items():\n    return [{\"id\": 1}]   # serialised via orjson automatically\n```\n\nOr set it on the `FastAPI()` instance to affect all routes:\n```python\napp = FastAPI(default_response_class=ORJSONResponse)\n```\n\nRule of thumb: set `ORJSONResponse` globally on the app for consistent fast\nserialisation; override per-route only for special cases like `HTMLResponse`.\n",{"id":1802,"difficulty":96,"q":1803,"a":1804},"testing-router-independently","How do you test an `APIRouter` independently without instantiating the full `FastAPI` app?","Wrap the router in a minimal `FastAPI` app in the test file:\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\nfrom app.routers.users import router\n\ntest_app = FastAPI()\ntest_app.include_router(router, prefix=\"\u002Fusers\")\n\nclient = TestClient(test_app)\n\ndef test_list_users():\n    resp = client.get(\"\u002Fusers\u002F\")\n    assert resp.status_code == 200\n```\n\nOverride dependencies on `test_app` with `test_app.dependency_overrides` to\ninject fakes without touching the real app object.\n\nRule of thumb: always test routers through a minimal `FastAPI` wrapper — it\nexercises the full request\u002Fresponse pipeline including middleware and DI.\n",{"id":1806,"difficulty":96,"q":1807,"a":1808},"conditional-router","How do you conditionally include a router (e.g., only in development)?","Use a simple `if` guard before `include_router`:\n\n```python\nimport os\nfrom fastapi import FastAPI\nfrom app.routers import debug_tools\n\napp = FastAPI()\n\nif os.getenv(\"ENV\") == \"development\":\n    app.include_router(debug_tools.router, prefix=\"\u002Fdebug\")\n```\n\nThis is evaluated at import time, so the routes are never registered in\nproduction even if someone guesses the URL.\n\nRule of thumb: gate debug\u002Fadmin routers behind an env check at startup rather\nthan `include_in_schema=False` — environment-gated routes truly don't exist in\nproduction, not just hidden from docs.\n",{"id":1810,"difficulty":104,"q":1811,"a":1812},"api-prefix-global","How do you add a global `\u002Fapi` prefix to all FastAPI routes?","Two approaches:\n\n**Option A** — prefix every `include_router` call:\n```python\napp.include_router(users_router, prefix=\"\u002Fapi\u002Fv1\u002Fusers\")\n```\n\n**Option B** — mount the FastAPI app on a prefix using Starlette's root_path:\n```python\napp = FastAPI(root_path=\"\u002Fapi\u002Fv1\")\n```\n`root_path` adjusts the OpenAPI `servers` base URL but doesn't actually prefix routes\nin the ASGI routing — use a reverse proxy rewrite for that.\n\nThe cleanest approach is to create a single top-level router:\n```python\napi_router = APIRouter(prefix=\"\u002Fapi\u002Fv1\")\napi_router.include_router(users_router, prefix=\"\u002Fusers\")\napp.include_router(api_router)\n```\n\nRule of thumb: collect all routers into one `api_router` with the version prefix,\nthen mount it once — adding a new version means creating a second top-level router.\n",{"description":94},"FastAPI APIRouter interview questions — include_router, prefix, tags, dependencies, nested routers, versioning and large app structure.","fastapi\u002Frouting\u002Frouters","Routers & Structure","Ti4CtE-G9LsSIpv_43X1Im6g2JPBHAPMeRwQBI0s5bo",{"id":1819,"title":1820,"body":1821,"description":94,"difficulty":104,"extension":97,"framework":10,"frameworkSlug":8,"meta":1825,"navigation":99,"order":56,"path":1826,"questions":1827,"questionsCount":415,"related":149,"seo":1880,"seoDescription":1881,"stem":1882,"subtopic":1883,"topic":20,"topicSlug":21,"updated":154,"__hash__":1884},"qa\u002Ffastapi\u002Ffundamentals\u002Fopenapi-docs.md","Openapi Docs",{"type":91,"value":1822,"toc":1823},[],{"title":94,"searchDepth":29,"depth":29,"links":1824},[],{},"\u002Ffastapi\u002Ffundamentals\u002Fopenapi-docs",[1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876],{"id":1829,"difficulty":104,"q":1830,"a":1831},"openapi-auto-generation","How does FastAPI generate an OpenAPI schema automatically?","At application startup FastAPI inspects every registered route — its path,\nHTTP method, type annotations, Pydantic models, `Query`\u002F`Body`\u002F`Header`\ndefinitions and docstrings — and assembles an **OpenAPI 3.x** JSON document.\n\n```python\napp = FastAPI(title=\"My API\", version=\"1.0.0\")\n\n@app.get(\"\u002Fitems\u002F{id}\", summary=\"Fetch an item\")\nasync def get_item(id: int) -> Item:\n    \"\"\"Return a single item by its numeric ID.\"\"\"\n    ...\n# OpenAPI schema available at \u002Fopenapi.json\n```\n\nThe schema drives:\n- `\u002Fdocs` — Swagger UI (interactive browser)\n- `\u002Fredoc` — ReDoc (readable reference)\n- Client code generators (`orval`, `openapi-generator`)\n\nRule of thumb: treat the auto-generated schema as the source of truth for your\nAPI contract; add `summary`, `description`, `responses` and tags to keep it accurate.\n",{"id":1833,"difficulty":104,"q":1834,"a":1835},"swagger-vs-redoc","What is the difference between Swagger UI (`\u002Fdocs`) and ReDoc (`\u002Fredoc`) in FastAPI?","Both render the same OpenAPI schema, but for different audiences:\n\n| | Swagger UI | ReDoc |\n|---|---|---|\n| URL | `\u002Fdocs` | `\u002Fredoc` |\n| Primary use | Interactive testing (try-it-out) | Readable documentation |\n| Layout | Split-pane, request builder | Three-panel, prose-first |\n| Authentication | OAuth2\u002FBearer flow built-in | Read-only |\n\n```python\napp = FastAPI(\n    docs_url=\"\u002Fdocs\",       # default\n    redoc_url=\"\u002Fredoc\",     # default\n)\n```\n\nBoth can be moved or disabled:\n```python\napp = FastAPI(docs_url=None, redoc_url=\"\u002Fapi-docs\")\n```\n\nRule of thumb: expose both; use Swagger UI for development\u002Ftesting, share ReDoc\nlinks for external consumer documentation.\n",{"id":1837,"difficulty":104,"q":1838,"a":1839},"disable-docs-production","How do you disable the API docs in production?","Set `docs_url=None` and `redoc_url=None` (and optionally `openapi_url=None`)\nin the `FastAPI()` constructor. Typically done via an environment flag:\n\n```python\nimport os\nfrom fastapi import FastAPI\n\nDEBUG = os.getenv(\"DEBUG\", \"false\").lower() == \"true\"\n\napp = FastAPI(\n    docs_url=\"\u002Fdocs\" if DEBUG else None,\n    redoc_url=\"\u002Fredoc\" if DEBUG else None,\n    openapi_url=\"\u002Fopenapi.json\" if DEBUG else None,\n)\n```\n\nSetting `openapi_url=None` prevents schema scraping even if someone guesses the\n`\u002Fdocs` URL, since the UI needs the schema to render.\n\nRule of thumb: disable the schema endpoint (`openapi_url=None`) in production —\nleaking your API structure is a security surface even if no credentials are exposed.\n",{"id":1841,"difficulty":96,"q":1842,"a":1843},"custom-openapi","How do you customise the generated OpenAPI schema in FastAPI?","Override `app.openapi()` to intercept and modify the schema dict before it's served:\n\n```python\nfrom fastapi.openapi.utils import get_openapi\n\ndef custom_openapi():\n    if app.openapi_schema:\n        return app.openapi_schema\n    schema = get_openapi(\n        title=\"My API\",\n        version=\"2.0.0\",\n        description=\"Full API description with Markdown\",\n        routes=app.routes,\n    )\n    # add a custom security scheme\n    schema[\"components\"][\"securitySchemes\"] = {\n        \"ApiKey\": {\"type\": \"apiKey\", \"in\": \"header\", \"name\": \"X-API-Key\"}\n    }\n    app.openapi_schema = schema\n    return schema\n\napp.openapi = custom_openapi\n```\n\nRule of thumb: override `app.openapi()` only for cross-cutting schema changes\n(logos, extra security schemes); use per-route `responses={}` for endpoint-level docs.\n",{"id":1845,"difficulty":104,"q":1846,"a":1847},"openapi-tags-metadata","How do you add descriptions to OpenAPI tags?","Pass `openapi_tags` to `FastAPI()`. Each entry maps a tag name to a description\nand optional external documentation link.\n\n```python\ntags_metadata = [\n    {\"name\": \"users\", \"description\": \"Operations with users. The **login** flow.\"},\n    {\"name\": \"items\", \"description\": \"Manage items. So _fancy_\", \"externalDocs\": {\n        \"description\": \"More docs\", \"url\": \"https:\u002F\u002Fexample.com\"\n    }},\n]\n\napp = FastAPI(openapi_tags=tags_metadata)\n```\n\nTags that appear in routes but not in `openapi_tags` still show up in the docs —\nthey just won't have descriptions.\n\nRule of thumb: define `openapi_tags` whenever the API has more than three resource\ngroups — it makes the Swagger UI navigable for external consumers.\n",{"id":1849,"difficulty":96,"q":1850,"a":1851},"bearer-auth-swagger","How do you add Bearer token authentication to Swagger UI in FastAPI?","Declare an `OAuth2PasswordBearer` or `HTTPBearer` security scheme. FastAPI\nadds the \"Authorize\" button to Swagger UI automatically.\n\n```python\nfrom fastapi.security import HTTPBearer\n\nbearer = HTTPBearer()\n\n@app.get(\"\u002Fsecure\", dependencies=[Depends(bearer)])\nasync def secure_endpoint():\n    return {\"ok\": True}\n```\n\nFor a full OAuth2 password flow with the Swagger \"Authorize\" dialog:\n\n```python\nfrom fastapi.security import OAuth2PasswordBearer\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Ftoken\")\n\n@app.get(\"\u002Fme\")\nasync def me(token: str = Depends(oauth2_scheme)):\n    ...\n```\n\nRule of thumb: using `OAuth2PasswordBearer` (not just `HTTPBearer`) gives you\nthe full username\u002Fpassword login form in Swagger UI — useful for manual testing.\n",{"id":1853,"difficulty":104,"q":1854,"a":1855},"openapi-version","Which version of the OpenAPI specification does FastAPI generate by default?","FastAPI generates **OpenAPI 3.1.0** schemas by default as of v0.99+\n(previously 3.0.x). OpenAPI 3.1 uses JSON Schema 2020-12 for component schemas,\nwhich means it properly supports `null` types, `$ref` alongside other keywords, etc.\n\n```python\napp = FastAPI()\n# GET \u002Fopenapi.json → {\"openapi\": \"3.1.0\", ...}\n```\n\nIf you need 3.0.x compatibility (e.g., older code generators):\n```python\napp = FastAPI(openapi_version=\"3.0.3\")\n```\n\nRule of thumb: stay on 3.1.0 for new projects; pin to 3.0.x only if your\ntoolchain doesn't support 3.1 yet.\n",{"id":1857,"difficulty":96,"q":1858,"a":1859},"schema-extra-example","How do you add examples to the OpenAPI schema for a Pydantic model?","In Pydantic v2, use `model_config` with `json_schema_extra`:\n\n```python\nfrom pydantic import BaseModel, ConfigDict\n\nclass Item(BaseModel):\n    model_config = ConfigDict(\n        json_schema_extra={\n            \"examples\": [{\"name\": \"Widget\", \"price\": 9.99}]\n        }\n    )\n    name: str\n    price: float\n```\n\nFor field-level examples, use `Field(examples=[...])`:\n\n```python\nfrom pydantic import Field\n\nclass Item(BaseModel):\n    name: str = Field(examples=[\"Widget\", \"Gadget\"])\n    price: float = Field(gt=0, examples=[9.99])\n```\n\nRule of thumb: add at least one realistic example per model — Swagger UI's\n\"Try it out\" pre-fills request bodies from examples, saving testers time.\n",{"id":1861,"difficulty":104,"q":1862,"a":1863},"path-operation-include-in-schema","How do you hide a route from the OpenAPI schema?","Set `include_in_schema=False` in the route decorator. The endpoint still works\n— it just won't appear in `\u002Fopenapi.json` or the docs UI.\n\n```python\n@app.get(\"\u002Finternal\u002Fhealth\", include_in_schema=False)\nasync def health():\n    return {\"status\": \"ok\"}\n```\n\nCommon uses: health-check endpoints, internal debug routes, legacy redirects\nthat you don't want to document publicly.\n\nRule of thumb: use `include_in_schema=False` for infra\u002Fops endpoints that aren't\npart of the public API contract.\n",{"id":1865,"difficulty":125,"q":1866,"a":1867},"api-versioning-strategies","What are the common strategies for API versioning in FastAPI?","**URL prefix versioning** (most common):\n```python\nv1 = APIRouter(prefix=\"\u002Fv1\")\nv2 = APIRouter(prefix=\"\u002Fv2\")\napp.include_router(v1)\napp.include_router(v2)\n```\n\n**Multiple FastAPI apps mounted with `Mount`**:\n```python\nfrom starlette.routing import Mount\nv1_app = FastAPI()\nv2_app = FastAPI()\napp = FastAPI()\napp.mount(\"\u002Fv1\", v1_app)\napp.mount(\"\u002Fv2\", v2_app)\n# Each sub-app has its own \u002Fdocs\n```\n\n**Header versioning** (clean URLs, harder to implement):\n```python\n@app.get(\"\u002Fitems\")\nasync def items(accept_version: str = Header(default=\"v1\")):\n    if accept_version == \"v2\":\n        ...\n```\n\nURL prefix is recommended for REST APIs because it's cacheable, bookmarkable\nand obvious in logs.\n\nRule of thumb: use URL prefix (`\u002Fv1`, `\u002Fv2`) with separate `APIRouter` instances\nper version; mounted sub-apps are the cleanest option when versions diverge significantly.\n",{"id":1869,"difficulty":96,"q":1870,"a":1871},"openapi-response-headers","How do you document custom response headers in the OpenAPI schema?","Add the headers to the `responses` dict in the route decorator under the status\ncode's `headers` key:\n\n```python\n@app.get(\n    \"\u002Fitems\",\n    responses={\n        200: {\n            \"headers\": {\n                \"X-Total-Count\": {\n                    \"description\": \"Total number of items\",\n                    \"schema\": {\"type\": \"integer\"},\n                }\n            }\n        }\n    },\n)\nasync def list_items(response: Response):\n    items = await db.all()\n    response.headers[\"X-Total-Count\"] = str(len(items))\n    return items\n```\n\nRule of thumb: document custom response headers in `responses={}` so consumers\nknow to look for them; set them at runtime by injecting `Response`.\n",{"id":1873,"difficulty":125,"q":1874,"a":1875},"openapi-callbacks","What are OpenAPI callbacks and when would you define them in FastAPI?","**Callbacks** document webhooks that your API will call on the consumer's server\nafter a certain event. They're outbound HTTP calls your API makes, documented\nas if they were inbound routes.\n\n```python\nfrom fastapi import APIRouter\n\ninvoicing_callback = APIRouter()\n\n@invoicing_callback.post(\"{$callback_url}\u002Finvoice\")\ndef invoice_notification(body: InvoiceEvent): ...\n\n@app.post(\"\u002Fsubscriptions\", callbacks=invoicing_callback.routes)\nasync def create_subscription(subscription: Subscription):\n    # after payment succeeds, your server will POST to subscription.callback_url\n    ...\n```\n\nRule of thumb: define callbacks when your API sends webhook notifications —\nit lets consumers generate typed handlers for the events you'll push to them.\n",{"id":1877,"difficulty":125,"q":1878,"a":1879},"swagger-ui-oauth2-config","How do you configure Swagger UI's OAuth2 settings (client ID, scopes) in FastAPI?","Pass `swagger_ui_init_oauth` to `FastAPI()`:\n\n```python\napp = FastAPI(\n    swagger_ui_init_oauth={\n        \"clientId\": \"my-client-id\",\n        \"scopes\": \"openid profile email\",\n        \"usePkceWithAuthorizationCodeGrant\": True,\n    }\n)\n```\n\nThis pre-fills the \"Authorize\" dialog in Swagger UI so developers don't have\nto type the client ID on every test session.\n\nFor full Swagger UI parameter control:\n```python\napp = FastAPI(\n    swagger_ui_parameters={\n        \"deepLinking\": True,\n        \"persistAuthorization\": True,   # keeps auth token across page refreshes\n    }\n)\n```\n\nRule of thumb: set `persistAuthorization: True` in development environments so\ntesters don't lose their JWT token every time they reload Swagger UI.\n",{"description":94},"FastAPI OpenAPI and docs interview questions — Swagger UI, ReDoc, schema customisation, security schemes, tags, versioning and disabling docs in production.","fastapi\u002Ffundamentals\u002Fopenapi-docs","OpenAPI & Docs","1i7CKl5qMzgKHDOxS30S_FxxsQvYZtjaUErzhfUAl5A",1782244096177]