[{"data":1,"prerenderedAt":1168},["ShallowReactive",2],{"page-\u002Fasync-engines-dialects-and-connection-pooling\u002Fintegrating-sqlalchemy-async-with-fastapi-and-starlette\u002F":3},{"id":4,"title":5,"body":6,"description":81,"extension":1162,"meta":1163,"navigation":147,"path":1164,"seo":1165,"stem":1166,"__hash__":1167},"content\u002Fasync-engines-dialects-and-connection-pooling\u002Fintegrating-sqlalchemy-async-with-fastapi-and-starlette\u002Findex.md","Integrating SQLAlchemy Async with FastAPI and Starlette",{"type":7,"value":8,"toc":1151},"minimark",[9,13,18,27,36,40,55,75,423,427,446,454,458,469,686,690,708,722,935,939,952,955,959,973,976,980,1089,1093,1107,1126,1141,1147],[10,11,5],"h1",{"id":12},"integrating-sqlalchemy-async-with-fastapi-and-starlette",[14,15,17],"h2",{"id":16},"architectural-foundations-of-async-database-workflows","Architectural Foundations of Async Database Workflows",[19,20,21,22,26],"p",{},"FastAPI and Starlette operate on the ASGI specification, leveraging a single-threaded, cooperative concurrency model driven by ",[23,24,25],"code",{},"asyncio",". When integrating an ORM into this environment, the primary architectural constraint is preserving the event loop's responsiveness. Synchronous I\u002FO operations block the loop, causing request queuing and degraded throughput under load. SQLAlchemy 2.0 addresses this by providing native async execution paths that yield control back to the event loop during network waits, disk I\u002FO, and query compilation.",[19,28,29,30,35],{},"Establishing a baseline I\u002FO concurrency model requires strict adherence to async\u002Fawait boundaries. Every database interaction must be explicitly awaited, and synchronous ORM constructs must be isolated from the main event loop. For a comprehensive breakdown of how async execution interacts with connection pooling and dialect-specific event hooks, consult the foundational documentation on ",[31,32,34],"a",{"href":33},"\u002Fasync-engines-dialects-and-connection-pooling\u002F","Async Engines, Dialects, and Connection Pooling",". Understanding these principles prevents subtle latency spikes and ensures that your application scales linearly with concurrent connections.",[14,37,39],{"id":38},"engine-initialization-and-connection-pool-configuration","Engine Initialization and Connection Pool Configuration",[19,41,42,43,46,47,50,51,54],{},"Production deployments demand deterministic resource management. The ",[23,44,45],{},"create_async_engine"," factory must be configured with explicit pool parameters that align with your database server's capacity and your application's concurrency profile. Over-provisioning ",[23,48,49],{},"pool_size"," wastes memory and connection slots, while under-provisioning causes ",[23,52,53],{},"QueuePool"," exhaustion during traffic spikes.",[19,56,57,58,61,62,65,66,69,70,74],{},"FastAPI's lifespan context manager provides the ideal boundary for engine initialization and graceful teardown. By tying pool creation to application startup and ",[23,59,60],{},"engine.dispose()"," to shutdown, you prevent connection leaks during hot reloads or deployment rollouts. Detailed tuning strategies for ",[23,63,64],{},"pool_pre_ping",", ",[23,67,68],{},"pool_recycle",", and overflow thresholds are covered in ",[31,71,73],{"href":72},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002F","Configuring Async Engines and Connection Pools",".",[76,77,82],"pre",{"className":78,"code":79,"language":80,"meta":81,"style":81},"language-python shiki shiki-themes github-light github-dark","from contextlib import asynccontextmanager\nfrom typing import AsyncGenerator\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\nfrom fastapi import FastAPI\n\nDATABASE_URL = \"postgresql+asyncpg:\u002F\u002Fuser:password@localhost:5432\u002Fappdb\"\n\n# Production-ready async engine configuration\nengine = create_async_engine(\n DATABASE_URL,\n pool_size=20,\n max_overflow=10,\n pool_recycle=1800, # Recycle connections before DB idle timeout\n pool_pre_ping=True, # Validate connections before checkout\n echo=False, # Disable in production\n)\n\nasync_session_factory = async_sessionmaker(\n bind=engine,\n class_=AsyncSession,\n expire_on_commit=False, # Prevents lazy-load access post-commit\n)\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:\n # Startup: Engine is already created at module level\n yield\n # Shutdown: Gracefully return all connections to the pool\n await engine.dispose()\n\napp = FastAPI(lifespan=lifespan)\n","python","",[23,83,84,103,116,129,142,149,163,168,175,187,196,210,223,239,255,271,277,282,293,304,315,330,335,340,347,372,378,384,390,399,404],{"__ignoreMap":81},[85,86,89,93,97,100],"span",{"class":87,"line":88},"line",1,[85,90,92],{"class":91},"szBVR","from",[85,94,96],{"class":95},"sVt8B"," contextlib ",[85,98,99],{"class":91},"import",[85,101,102],{"class":95}," asynccontextmanager\n",[85,104,106,108,111,113],{"class":87,"line":105},2,[85,107,92],{"class":91},[85,109,110],{"class":95}," typing ",[85,112,99],{"class":91},[85,114,115],{"class":95}," AsyncGenerator\n",[85,117,119,121,124,126],{"class":87,"line":118},3,[85,120,92],{"class":91},[85,122,123],{"class":95}," sqlalchemy.ext.asyncio ",[85,125,99],{"class":91},[85,127,128],{"class":95}," create_async_engine, async_sessionmaker, AsyncSession\n",[85,130,132,134,137,139],{"class":87,"line":131},4,[85,133,92],{"class":91},[85,135,136],{"class":95}," fastapi ",[85,138,99],{"class":91},[85,140,141],{"class":95}," FastAPI\n",[85,143,145],{"class":87,"line":144},5,[85,146,148],{"emptyLinePlaceholder":147},true,"\n",[85,150,152,156,159],{"class":87,"line":151},6,[85,153,155],{"class":154},"sj4cs","DATABASE_URL",[85,157,158],{"class":91}," =",[85,160,162],{"class":161},"sZZnC"," \"postgresql+asyncpg:\u002F\u002Fuser:password@localhost:5432\u002Fappdb\"\n",[85,164,166],{"class":87,"line":165},7,[85,167,148],{"emptyLinePlaceholder":147},[85,169,171],{"class":87,"line":170},8,[85,172,174],{"class":173},"sJ8bj","# Production-ready async engine configuration\n",[85,176,178,181,184],{"class":87,"line":177},9,[85,179,180],{"class":95},"engine ",[85,182,183],{"class":91},"=",[85,185,186],{"class":95}," create_async_engine(\n",[85,188,190,193],{"class":87,"line":189},10,[85,191,192],{"class":154}," DATABASE_URL",[85,194,195],{"class":95},",\n",[85,197,199,203,205,208],{"class":87,"line":198},11,[85,200,202],{"class":201},"s4XuR"," pool_size",[85,204,183],{"class":91},[85,206,207],{"class":154},"20",[85,209,195],{"class":95},[85,211,213,216,218,221],{"class":87,"line":212},12,[85,214,215],{"class":201}," max_overflow",[85,217,183],{"class":91},[85,219,220],{"class":154},"10",[85,222,195],{"class":95},[85,224,226,229,231,234,236],{"class":87,"line":225},13,[85,227,228],{"class":201}," pool_recycle",[85,230,183],{"class":91},[85,232,233],{"class":154},"1800",[85,235,65],{"class":95},[85,237,238],{"class":173},"# Recycle connections before DB idle timeout\n",[85,240,242,245,247,250,252],{"class":87,"line":241},14,[85,243,244],{"class":201}," pool_pre_ping",[85,246,183],{"class":91},[85,248,249],{"class":154},"True",[85,251,65],{"class":95},[85,253,254],{"class":173},"# Validate connections before checkout\n",[85,256,258,261,263,266,268],{"class":87,"line":257},15,[85,259,260],{"class":201}," echo",[85,262,183],{"class":91},[85,264,265],{"class":154},"False",[85,267,65],{"class":95},[85,269,270],{"class":173},"# Disable in production\n",[85,272,274],{"class":87,"line":273},16,[85,275,276],{"class":95},")\n",[85,278,280],{"class":87,"line":279},17,[85,281,148],{"emptyLinePlaceholder":147},[85,283,285,288,290],{"class":87,"line":284},18,[85,286,287],{"class":95},"async_session_factory ",[85,289,183],{"class":91},[85,291,292],{"class":95}," async_sessionmaker(\n",[85,294,296,299,301],{"class":87,"line":295},19,[85,297,298],{"class":201}," bind",[85,300,183],{"class":91},[85,302,303],{"class":95},"engine,\n",[85,305,307,310,312],{"class":87,"line":306},20,[85,308,309],{"class":201}," class_",[85,311,183],{"class":91},[85,313,314],{"class":95},"AsyncSession,\n",[85,316,318,321,323,325,327],{"class":87,"line":317},21,[85,319,320],{"class":201}," expire_on_commit",[85,322,183],{"class":91},[85,324,265],{"class":154},[85,326,65],{"class":95},[85,328,329],{"class":173},"# Prevents lazy-load access post-commit\n",[85,331,333],{"class":87,"line":332},22,[85,334,276],{"class":95},[85,336,338],{"class":87,"line":337},23,[85,339,148],{"emptyLinePlaceholder":147},[85,341,343],{"class":87,"line":342},24,[85,344,346],{"class":345},"sScJk","@asynccontextmanager\n",[85,348,350,353,356,359,362,365,367,369],{"class":87,"line":349},25,[85,351,352],{"class":91},"async",[85,354,355],{"class":91}," def",[85,357,358],{"class":345}," lifespan",[85,360,361],{"class":95},"(app: FastAPI) -> AsyncGenerator[",[85,363,364],{"class":154},"None",[85,366,65],{"class":95},[85,368,364],{"class":154},[85,370,371],{"class":95},"]:\n",[85,373,375],{"class":87,"line":374},26,[85,376,377],{"class":173}," # Startup: Engine is already created at module level\n",[85,379,381],{"class":87,"line":380},27,[85,382,383],{"class":91}," yield\n",[85,385,387],{"class":87,"line":386},28,[85,388,389],{"class":173}," # Shutdown: Gracefully return all connections to the pool\n",[85,391,393,396],{"class":87,"line":392},29,[85,394,395],{"class":91}," await",[85,397,398],{"class":95}," engine.dispose()\n",[85,400,402],{"class":87,"line":401},30,[85,403,148],{"emptyLinePlaceholder":147},[85,405,407,410,412,415,418,420],{"class":87,"line":406},31,[85,408,409],{"class":95},"app ",[85,411,183],{"class":91},[85,413,414],{"class":95}," FastAPI(",[85,416,417],{"class":201},"lifespan",[85,419,183],{"class":91},[85,421,422],{"class":95},"lifespan)\n",[14,424,426],{"id":425},"driver-selection-and-dialect-mapping","Driver Selection and Dialect Mapping",[19,428,429,430,433,434,437,438,441,442,445],{},"The async dialect string dictates the underlying network driver and its interaction with the event loop. PostgreSQL deployments typically choose between ",[23,431,432],{},"postgresql+asyncpg"," and ",[23,435,436],{},"postgresql+psycopg"," (async mode). ",[23,439,440],{},"asyncpg"," is highly optimized for raw performance, featuring zero-copy decoding, native prepared statement caching, and minimal connection handshake latency. However, it enforces strict type mapping and lacks some legacy PostgreSQL features. Conversely, ",[23,443,444],{},"psycopg"," (v3) offers broader compatibility, synchronous fallback capabilities, and more forgiving type coercion at a slight performance premium.",[19,447,448,449,453],{},"Driver selection directly impacts query routing efficiency and memory footprint. Evaluate your workload's read\u002Fwrite ratio, JSONB usage, and connection churn before committing to a dialect. The comparative analysis in ",[31,450,452],{"href":451},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fchoosing-between-asyncpg-and-psycopg-async-drivers\u002F","Choosing Between asyncpg and psycopg Async Drivers"," provides empirical benchmarks to guide your architecture decision.",[14,455,457],{"id":456},"dependency-injection-and-asyncsession-lifecycle","Dependency Injection and AsyncSession Lifecycle",[19,459,460,461,464,465,468],{},"FastAPI's dependency injection system is the optimal mechanism for request-scoped ",[23,462,463],{},"AsyncSession"," management. Each incoming HTTP request should receive a fresh session, guaranteeing transaction isolation and preventing cross-request state pollution. Wrapping route execution in ",[23,466,467],{},"async with session.begin():"," establishes explicit transaction boundaries, ensuring automatic commit on success and automatic rollback on unhandled exceptions or HTTP errors.",[76,470,472],{"className":78,"code":471,"language":80,"meta":81,"style":81},"from typing import AsyncGenerator\nfrom fastapi import Depends, HTTPException, status\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.exc import SQLAlchemyError\n\nasync def get_db_session() -> AsyncGenerator[AsyncSession, None]:\n \"\"\"\n FastAPI dependency yielding a request-scoped AsyncSession.\n Automatically commits on success, rolls back on failure, and closes the session.\n \"\"\"\n async with async_session_factory() as session:\n try:\n # Explicit transaction boundary\n async with session.begin():\n yield session\n except SQLAlchemyError as exc:\n await session.rollback()\n raise HTTPException(\n status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,\n detail=\"Database transaction failed. Operation rolled back.\"\n ) from exc\n finally:\n # Ensures connection is returned to pool even on early returns\n await session.close()\n",[23,473,474,484,495,506,518,522,538,543,548,553,557,574,582,587,596,604,617,624,632,647,657,667,674,679],{"__ignoreMap":81},[85,475,476,478,480,482],{"class":87,"line":88},[85,477,92],{"class":91},[85,479,110],{"class":95},[85,481,99],{"class":91},[85,483,115],{"class":95},[85,485,486,488,490,492],{"class":87,"line":105},[85,487,92],{"class":91},[85,489,136],{"class":95},[85,491,99],{"class":91},[85,493,494],{"class":95}," Depends, HTTPException, status\n",[85,496,497,499,501,503],{"class":87,"line":118},[85,498,92],{"class":91},[85,500,123],{"class":95},[85,502,99],{"class":91},[85,504,505],{"class":95}," AsyncSession\n",[85,507,508,510,513,515],{"class":87,"line":131},[85,509,92],{"class":91},[85,511,512],{"class":95}," sqlalchemy.exc ",[85,514,99],{"class":91},[85,516,517],{"class":95}," SQLAlchemyError\n",[85,519,520],{"class":87,"line":144},[85,521,148],{"emptyLinePlaceholder":147},[85,523,524,526,528,531,534,536],{"class":87,"line":151},[85,525,352],{"class":91},[85,527,355],{"class":91},[85,529,530],{"class":345}," get_db_session",[85,532,533],{"class":95},"() -> AsyncGenerator[AsyncSession, ",[85,535,364],{"class":154},[85,537,371],{"class":95},[85,539,540],{"class":87,"line":165},[85,541,542],{"class":161}," \"\"\"\n",[85,544,545],{"class":87,"line":170},[85,546,547],{"class":161}," FastAPI dependency yielding a request-scoped AsyncSession.\n",[85,549,550],{"class":87,"line":177},[85,551,552],{"class":161}," Automatically commits on success, rolls back on failure, and closes the session.\n",[85,554,555],{"class":87,"line":189},[85,556,542],{"class":161},[85,558,559,562,565,568,571],{"class":87,"line":198},[85,560,561],{"class":91}," async",[85,563,564],{"class":91}," with",[85,566,567],{"class":95}," async_session_factory() ",[85,569,570],{"class":91},"as",[85,572,573],{"class":95}," session:\n",[85,575,576,579],{"class":87,"line":212},[85,577,578],{"class":91}," try",[85,580,581],{"class":95},":\n",[85,583,584],{"class":87,"line":225},[85,585,586],{"class":173}," # Explicit transaction boundary\n",[85,588,589,591,593],{"class":87,"line":241},[85,590,561],{"class":91},[85,592,564],{"class":91},[85,594,595],{"class":95}," session.begin():\n",[85,597,598,601],{"class":87,"line":257},[85,599,600],{"class":91}," yield",[85,602,603],{"class":95}," session\n",[85,605,606,609,612,614],{"class":87,"line":273},[85,607,608],{"class":91}," except",[85,610,611],{"class":95}," SQLAlchemyError ",[85,613,570],{"class":91},[85,615,616],{"class":95}," exc:\n",[85,618,619,621],{"class":87,"line":279},[85,620,395],{"class":91},[85,622,623],{"class":95}," session.rollback()\n",[85,625,626,629],{"class":87,"line":284},[85,627,628],{"class":91}," raise",[85,630,631],{"class":95}," HTTPException(\n",[85,633,634,637,639,642,645],{"class":87,"line":295},[85,635,636],{"class":201}," status_code",[85,638,183],{"class":91},[85,640,641],{"class":95},"status.",[85,643,644],{"class":154},"HTTP_500_INTERNAL_SERVER_ERROR",[85,646,195],{"class":95},[85,648,649,652,654],{"class":87,"line":306},[85,650,651],{"class":201}," detail",[85,653,183],{"class":91},[85,655,656],{"class":161},"\"Database transaction failed. Operation rolled back.\"\n",[85,658,659,662,664],{"class":87,"line":317},[85,660,661],{"class":95}," ) ",[85,663,92],{"class":91},[85,665,666],{"class":95}," exc\n",[85,668,669,672],{"class":87,"line":332},[85,670,671],{"class":91}," finally",[85,673,581],{"class":95},[85,675,676],{"class":87,"line":337},[85,677,678],{"class":173}," # Ensures connection is returned to pool even on early returns\n",[85,680,681,683],{"class":87,"line":342},[85,682,395],{"class":91},[85,684,685],{"class":95}," session.close()\n",[14,687,689],{"id":688},"execution-context-and-greenlet-interoperability","Execution Context and Greenlet Interoperability",[19,691,692,693,65,696,699,700,703,704,707],{},"SQLAlchemy's ORM was historically synchronous. While SQLAlchemy 2.0 provides native async query execution (",[23,694,695],{},"session.execute()",[23,697,698],{},"session.scalars()","), certain legacy ORM operations or third-party extensions still rely on synchronous blocking calls. Invoking these directly inside an ",[23,701,702],{},"async def"," route handler will trigger a ",[23,705,706],{},"GreenletSpawnError"," or silently block the ASGI event loop.",[19,709,710,711,714,715,717,718,74],{},"To safely execute synchronous ORM logic within an async context, use ",[23,712,713],{},"session.run_sync()",". This method offloads the blocking operation to a thread pool managed by SQLAlchemy's greenlet integration, preserving event loop responsiveness. Detailed diagnostic steps and isolation patterns for resolving ",[23,716,706],{}," scenarios are documented in ",[31,719,721],{"href":720},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fintegrating-sqlalchemy-async-with-fastapi-and-starlette\u002Ffixing-greenletspawnerror-in-async-sqlalchemy-workflows\u002F","Fixing GreenletSpawnError in Async SQLAlchemy Workflows",[76,723,725],{"className":78,"code":724,"language":80,"meta":81,"style":81},"from typing import AsyncGenerator, Sequence\nfrom sqlalchemy import select, insert, text\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom myapp.models import User\n\nasync def fetch_active_users(session: AsyncSession) -> Sequence[User]:\n stmt = select(User).where(User.is_active.is_(True))\n result = await session.scalars(stmt)\n return result.all()\n\nasync def stream_large_dataset(session: AsyncSession) -> AsyncGenerator[User, None]:\n \"\"\"Memory-efficient iteration using server-side cursors.\"\"\"\n stmt = select(User).order_by(User.created_at)\n async for user in await session.stream(stmt):\n yield user\n\nasync def update_last_login(session: AsyncSession, user_id: int) -> None:\n stmt = text(\"UPDATE users SET last_login = NOW() WHERE id = :uid\")\n await session.execute(stmt, {\"uid\": user_id})\n",[23,726,727,738,750,760,772,776,788,803,815,823,827,843,848,857,875,882,886,908,922],{"__ignoreMap":81},[85,728,729,731,733,735],{"class":87,"line":88},[85,730,92],{"class":91},[85,732,110],{"class":95},[85,734,99],{"class":91},[85,736,737],{"class":95}," AsyncGenerator, Sequence\n",[85,739,740,742,745,747],{"class":87,"line":105},[85,741,92],{"class":91},[85,743,744],{"class":95}," sqlalchemy ",[85,746,99],{"class":91},[85,748,749],{"class":95}," select, insert, text\n",[85,751,752,754,756,758],{"class":87,"line":118},[85,753,92],{"class":91},[85,755,123],{"class":95},[85,757,99],{"class":91},[85,759,505],{"class":95},[85,761,762,764,767,769],{"class":87,"line":131},[85,763,92],{"class":91},[85,765,766],{"class":95}," myapp.models ",[85,768,99],{"class":91},[85,770,771],{"class":95}," User\n",[85,773,774],{"class":87,"line":144},[85,775,148],{"emptyLinePlaceholder":147},[85,777,778,780,782,785],{"class":87,"line":151},[85,779,352],{"class":91},[85,781,355],{"class":91},[85,783,784],{"class":345}," fetch_active_users",[85,786,787],{"class":95},"(session: AsyncSession) -> Sequence[User]:\n",[85,789,790,793,795,798,800],{"class":87,"line":165},[85,791,792],{"class":95}," stmt ",[85,794,183],{"class":91},[85,796,797],{"class":95}," select(User).where(User.is_active.is_(",[85,799,249],{"class":154},[85,801,802],{"class":95},"))\n",[85,804,805,808,810,812],{"class":87,"line":170},[85,806,807],{"class":95}," result ",[85,809,183],{"class":91},[85,811,395],{"class":91},[85,813,814],{"class":95}," session.scalars(stmt)\n",[85,816,817,820],{"class":87,"line":177},[85,818,819],{"class":91}," return",[85,821,822],{"class":95}," result.all()\n",[85,824,825],{"class":87,"line":189},[85,826,148],{"emptyLinePlaceholder":147},[85,828,829,831,833,836,839,841],{"class":87,"line":198},[85,830,352],{"class":91},[85,832,355],{"class":91},[85,834,835],{"class":345}," stream_large_dataset",[85,837,838],{"class":95},"(session: AsyncSession) -> AsyncGenerator[User, ",[85,840,364],{"class":154},[85,842,371],{"class":95},[85,844,845],{"class":87,"line":212},[85,846,847],{"class":161}," \"\"\"Memory-efficient iteration using server-side cursors.\"\"\"\n",[85,849,850,852,854],{"class":87,"line":225},[85,851,792],{"class":95},[85,853,183],{"class":91},[85,855,856],{"class":95}," select(User).order_by(User.created_at)\n",[85,858,859,861,864,867,870,872],{"class":87,"line":241},[85,860,561],{"class":91},[85,862,863],{"class":91}," for",[85,865,866],{"class":95}," user ",[85,868,869],{"class":91},"in",[85,871,395],{"class":91},[85,873,874],{"class":95}," session.stream(stmt):\n",[85,876,877,879],{"class":87,"line":257},[85,878,600],{"class":91},[85,880,881],{"class":95}," user\n",[85,883,884],{"class":87,"line":273},[85,885,148],{"emptyLinePlaceholder":147},[85,887,888,890,892,895,898,901,904,906],{"class":87,"line":279},[85,889,352],{"class":91},[85,891,355],{"class":91},[85,893,894],{"class":345}," update_last_login",[85,896,897],{"class":95},"(session: AsyncSession, user_id: ",[85,899,900],{"class":154},"int",[85,902,903],{"class":95},") -> ",[85,905,364],{"class":154},[85,907,581],{"class":95},[85,909,910,912,914,917,920],{"class":87,"line":284},[85,911,792],{"class":95},[85,913,183],{"class":91},[85,915,916],{"class":95}," text(",[85,918,919],{"class":161},"\"UPDATE users SET last_login = NOW() WHERE id = :uid\"",[85,921,276],{"class":95},[85,923,924,926,929,932],{"class":87,"line":295},[85,925,395],{"class":91},[85,927,928],{"class":95}," session.execute(stmt, {",[85,930,931],{"class":161},"\"uid\"",[85,933,934],{"class":95},": user_id})\n",[14,936,938],{"id":937},"legacy-migration-pathways","Legacy Migration Pathways",[19,940,941,942,945,946,948,949,951],{},"Transitioning from WSGI-based frameworks (e.g., Flask, Django) to FastAPI requires an incremental refactoring strategy. Directly swapping synchronous ",[23,943,944],{},"Session"," for ",[23,947,463],{}," without adjusting route signatures and middleware chains will break transaction scopes and introduce race conditions. The recommended approach involves isolating database access behind a repository pattern, converting synchronous endpoints to ",[23,950,702],{}," one by one, and gradually migrating middleware to ASGI-compatible alternatives.",[19,953,954],{},"Phased rollout strategies, including dual-read\u002Fwrite routing and shadow testing, minimize production risk during the transition. A comprehensive blueprint for mapping synchronous scopes to async equivalents is available in Migrating Synchronous Flask Apps to Async SQLAlchemy.",[14,956,958],{"id":957},"background-processing-and-deferred-execution","Background Processing and Deferred Execution",[19,960,961,962,965,966,968,969,972],{},"FastAPI's ",[23,963,964],{},"BackgroundTasks"," execute after the HTTP response is returned, operating outside the original request lifecycle. This creates a critical boundary violation if background functions attempt to reuse the request-scoped ",[23,967,463],{},", resulting in ",[23,970,971],{},"InvalidRequestError"," or detached session exceptions. Background database operations must instantiate their own sessions, explicitly manage transaction lifecycles, and return connections to the pool before the background thread terminates.",[19,974,975],{},"When designing deferred workflows, ensure that connection borrowing respects pool limits and that long-running tasks do not starve foreground request handlers. Best practices for managing out-of-request transaction scopes and pool allocation are detailed in Using SQLAlchemy 2.0 with FastAPI Background Tasks.",[14,977,979],{"id":978},"production-pitfalls-mitigations","Production Pitfalls & Mitigations",[981,982,983,999],"table",{},[984,985,986],"thead",{},[987,988,989,993,996],"tr",{},[990,991,992],"th",{},"Pitfall",[990,994,995],{},"Root Cause",[990,997,998],{},"Mitigation Strategy",[1000,1001,1002,1024,1053,1072],"tbody",{},[987,1003,1004,1008,1014],{},[1005,1006,1007],"td",{},"Blocking the ASGI event loop with synchronous ORM calls",[1005,1009,1010,1011,1013],{},"Invoking legacy sync methods directly in ",[23,1012,702],{}," routes",[1005,1015,1016,1017,1019,1020,1023],{},"Use ",[23,1018,713],{}," or refactor to native ",[23,1021,1022],{},"await session.execute()"," patterns",[987,1025,1026,1029,1039],{},[1005,1027,1028],{},"Connection pool exhaustion from unhandled exceptions",[1005,1030,1031,1032,1035,1036],{},"HTTP errors bypassing ",[23,1033,1034],{},"session.close()"," or ",[23,1037,1038],{},"session.rollback()",[1005,1040,1041,1042,1045,1046,1049,1050],{},"Wrap sessions in FastAPI ",[23,1043,1044],{},"Depends"," with ",[23,1047,1048],{},"try\u002Fexcept\u002Ffinally"," and explicit ",[23,1051,1052],{},"async with session.begin()",[987,1054,1055,1058,1066],{},[1005,1056,1057],{},"Dialect mismatch errors",[1005,1059,1060,1061,433,1063,1065],{},"Mixing ",[23,1062,440],{},[23,1064,444],{}," connection strings or drivers in the same engine",[1005,1067,1068,1069,1071],{},"Standardize on a single async dialect per engine; validate ",[23,1070,155],{}," at startup",[987,1073,1074,1080,1083],{},[1005,1075,1076,1077,1079],{},"Improper ",[23,1078,964],{}," usage",[1005,1081,1082],{},"Reusing request-scoped sessions in deferred functions",[1005,1084,1085,1086,1088],{},"Instantiate fresh ",[23,1087,463],{}," within the background function; never share session instances across boundaries",[14,1090,1092],{"id":1091},"faq","FAQ",[19,1094,1095,1099,1100,1103,1104,1106],{},[1096,1097,1098],"strong",{},"How do I prevent connection pool exhaustion in high-concurrency FastAPI deployments?","\nEnforce strict ",[23,1101,1102],{},"max_overflow"," limits, configure ",[23,1105,68],{}," below database idle timeouts, and guarantee session closure via FastAPI dependency injection teardown hooks. Monitor pool utilization with Prometheus metrics and scale horizontally before vertically.",[19,1108,1109,1112,1113,1115,1116,65,1119,65,1122,1125],{},[1096,1110,1111],{},"Can I use SQLAlchemy 2.0 ORM features with asyncpg?","\nYes, SQLAlchemy 2.0 natively supports ",[23,1114,432],{},", enabling fully async query compilation, execution, and result streaming without greenlet fallbacks. All modern ORM constructs (",[23,1117,1118],{},"select()",[23,1120,1121],{},"insert()",[23,1123,1124],{},"update()",") compile to async-compatible SQL.",[19,1127,1128,1131,1132,1134,1135,1137,1138,1140],{},[1096,1129,1130],{},"What is the recommended way to handle database transactions across multiple async endpoints?","\nScope ",[23,1133,463],{}," per-request using FastAPI ",[23,1136,1044],{},", wrap route logic in ",[23,1139,467],{}," for atomic transactions, and rely on automatic rollback on unhandled exceptions. Avoid cross-request transaction sharing; use distributed transaction patterns or message queues for multi-service consistency.",[19,1142,1143,1146],{},[1096,1144,1145],{},"How does Starlette handle async database connections differently from standard WSGI frameworks?","\nStarlette operates on an ASGI event loop, enabling true non-blocking I\u002FO for database drivers, whereas WSGI frameworks require thread pools or external workers (e.g., Gunicorn with gevent) to simulate concurrency. ASGI eliminates thread-switching overhead and scales I\u002FO-bound workloads with significantly lower memory footprint.",[1148,1149,1150],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":81,"searchDepth":105,"depth":105,"links":1152},[1153,1154,1155,1156,1157,1158,1159,1160,1161],{"id":16,"depth":105,"text":17},{"id":38,"depth":105,"text":39},{"id":425,"depth":105,"text":426},{"id":456,"depth":105,"text":457},{"id":688,"depth":105,"text":689},{"id":937,"depth":105,"text":938},{"id":957,"depth":105,"text":958},{"id":978,"depth":105,"text":979},{"id":1091,"depth":105,"text":1092},"md",{},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fintegrating-sqlalchemy-async-with-fastapi-and-starlette",{"title":5,"description":81},"async-engines-dialects-and-connection-pooling\u002Fintegrating-sqlalchemy-async-with-fastapi-and-starlette\u002Findex","xtA3ZapAL4Heheao2p7_RqA_G4TFqlAvSdbzUfXZPCc",1778149144400]