[{"data":1,"prerenderedAt":218},["ShallowReactive",2],{"topic-fastapi-database":3},{"framework":4,"topic":15,"subtopics":23},{"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",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":11,"slug":20,"stem":21,"__hash__":22},"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",[24,94,153],{"id":25,"title":26,"body":27,"description":31,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":36,"navigation":37,"order":13,"path":38,"questions":39,"questionsCount":86,"related":87,"seo":88,"seoDescription":89,"stem":90,"subtopic":91,"topic":19,"topicSlug":20,"updated":92,"__hash__":93},"qa\u002Ffastapi\u002Fdatabase\u002Fsqlalchemy.md","Sqlalchemy",{"type":28,"value":29,"toc":30},"minimark",[],{"title":31,"searchDepth":32,"depth":32,"links":33},"",2,[],"medium","md",{},true,"\u002Ffastapi\u002Fdatabase\u002Fsqlalchemy",[40,45,49,53,57,61,66,70,74,78,82],{"id":41,"difficulty":42,"q":43,"a":44},"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":46,"difficulty":42,"q":47,"a":48},"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":50,"difficulty":34,"q":51,"a":52},"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":54,"difficulty":34,"q":55,"a":56},"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":58,"difficulty":34,"q":59,"a":60},"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":62,"difficulty":63,"q":64,"a":65},"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":67,"difficulty":34,"q":68,"a":69},"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":71,"difficulty":34,"q":72,"a":73},"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":75,"difficulty":63,"q":76,"a":77},"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":79,"difficulty":42,"q":80,"a":81},"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":83,"difficulty":34,"q":84,"a":85},"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":31},"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":95,"title":96,"body":97,"description":31,"difficulty":63,"extension":35,"framework":10,"frameworkSlug":8,"meta":101,"navigation":37,"order":32,"path":102,"questions":103,"questionsCount":86,"related":87,"seo":148,"seoDescription":149,"stem":150,"subtopic":151,"topic":19,"topicSlug":20,"updated":92,"__hash__":152},"qa\u002Ffastapi\u002Fdatabase\u002Fasync-db.md","Async Db",{"type":28,"value":98,"toc":99},[],{"title":31,"searchDepth":32,"depth":32,"links":100},[],{},"\u002Ffastapi\u002Fdatabase\u002Fasync-db",[104,108,112,116,120,124,128,132,136,140,144],{"id":105,"difficulty":34,"q":106,"a":107},"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":109,"difficulty":34,"q":110,"a":111},"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":113,"difficulty":34,"q":114,"a":115},"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":117,"difficulty":34,"q":118,"a":119},"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":121,"difficulty":63,"q":122,"a":123},"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":125,"difficulty":34,"q":126,"a":127},"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":129,"difficulty":42,"q":130,"a":131},"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":133,"difficulty":63,"q":134,"a":135},"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":137,"difficulty":63,"q":138,"a":139},"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":141,"difficulty":63,"q":142,"a":143},"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":145,"difficulty":63,"q":146,"a":147},"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":31},"FastAPI async database interview questions — AsyncSession, async SQLAlchemy, asyncpg, aiosqlite and event loop compatibility.","fastapi\u002Fdatabase\u002Fasync-db","Async Database","I90QMM52vWiPFlTjknINkakJ_qhvXcpmWK2zkfuOniA",{"id":154,"title":155,"body":156,"description":31,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":160,"navigation":37,"order":161,"path":162,"questions":163,"questionsCount":212,"related":87,"seo":213,"seoDescription":214,"stem":215,"subtopic":216,"topic":19,"topicSlug":20,"updated":92,"__hash__":217},"qa\u002Ffastapi\u002Fdatabase\u002Fmigrations.md","Migrations",{"type":28,"value":157,"toc":158},[],{"title":31,"searchDepth":32,"depth":32,"links":159},[],{},3,"\u002Ffastapi\u002Fdatabase\u002Fmigrations",[164,168,172,176,180,184,188,192,196,200,204,208],{"id":165,"difficulty":42,"q":166,"a":167},"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":169,"difficulty":42,"q":170,"a":171},"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":173,"difficulty":34,"q":174,"a":175},"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":177,"difficulty":42,"q":178,"a":179},"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":181,"difficulty":42,"q":182,"a":183},"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":185,"difficulty":63,"q":186,"a":187},"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":189,"difficulty":63,"q":190,"a":191},"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":193,"difficulty":63,"q":194,"a":195},"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":197,"difficulty":63,"q":198,"a":199},"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":201,"difficulty":42,"q":202,"a":203},"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":205,"difficulty":34,"q":206,"a":207},"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":209,"difficulty":34,"q":210,"a":211},"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",12,{"description":31},"FastAPI Alembic migration interview questions — setup, autogenerate, upgrade, downgrade, async migrations and production best practices.","fastapi\u002Fdatabase\u002Fmigrations","Alembic Migrations","2iCSPW0I4hfqjDOfBqCpp72k6A21Eg3YDczynF-U0Uw",1782244096242]