[{"data":1,"prerenderedAt":2059},["ShallowReactive",2],{"page-\u002Falembic-async-migrations-and-schema-evolution\u002Fconfiguring-alembic-with-async-sqlalchemy-engines\u002Fsetting-up-alembic-env-py-for-asyncpg\u002F":3},{"id":4,"title":5,"body":6,"description":2051,"extension":2052,"meta":2053,"navigation":110,"path":2055,"seo":2056,"stem":2057,"__hash__":2058},"content\u002Falembic-async-migrations-and-schema-evolution\u002Fconfiguring-alembic-with-async-sqlalchemy-engines\u002Fsetting-up-alembic-env-py-for-asyncpg\u002Findex.md","Setting Up Alembic env.py for asyncpg",{"type":7,"value":8,"toc":2041},"minimark",[9,13,36,41,51,57,251,256,800,804,810,833,849,856,877,881,1106,1110,1115,1124,1140,1378,1384,1829,1847,1865,1869,1877,1898,1909,1937,1945,1962,1972,1992,1996,2037],[10,11,5],"h1",{"id":12},"setting-up-alembic-envpy-for-asyncpg",[14,15,16,17,21,22,25,26,29,30,35],"p",{},"Setting up Alembic's ",[18,19,20],"code",{},"env.py"," for asyncpg requires replacing the default synchronous engine with an ",[18,23,24],{},"AsyncEngine"," and bridging Alembic's synchronous migration context to the async runtime via ",[18,27,28],{},"conn.run_sync()"," — a complete walkthrough is part of the broader ",[31,32,34],"a",{"href":33},"\u002Falembic-async-migrations-and-schema-evolution\u002Fconfiguring-alembic-with-async-sqlalchemy-engines\u002F","Configuring Alembic with Async SQLAlchemy Engines"," guide.",[37,38,40],"h2",{"id":39},"quick-answer","Quick Answer",[14,42,43,44,46,47,50],{},"The default ",[18,45,20],{}," generated by ",[18,48,49],{},"alembic init"," is built for synchronous drivers. Below is a direct comparison of the legacy pattern and the correct async version for asyncpg.",[14,52,53],{},[54,55,56],"strong",{},"Legacy sync env.py (do not use with asyncpg)",[58,59,64],"pre",{"className":60,"code":61,"language":62,"meta":63,"style":63},"language-python shiki shiki-themes github-light github-dark","# env.py — synchronous, not compatible with asyncpg\nfrom sqlalchemy import engine_from_config, pool\nfrom alembic import context\n\nconfig = context.config\n\ndef run_migrations_online() -> None:\n    connectable = engine_from_config(\n        config.get_section(config.config_ini_section, {}),\n        prefix=\"sqlalchemy.\",\n        poolclass=pool.NullPool,\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","python","",[18,65,66,75,92,105,112,124,129,149,160,166,182,193,199,214,236,245],{"__ignoreMap":63},[67,68,71],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"sJ8bj","# env.py — synchronous, not compatible with asyncpg\n",[67,76,78,82,86,89],{"class":69,"line":77},2,[67,79,81],{"class":80},"szBVR","from",[67,83,85],{"class":84},"sVt8B"," sqlalchemy ",[67,87,88],{"class":80},"import",[67,90,91],{"class":84}," engine_from_config, pool\n",[67,93,95,97,100,102],{"class":69,"line":94},3,[67,96,81],{"class":80},[67,98,99],{"class":84}," alembic ",[67,101,88],{"class":80},[67,103,104],{"class":84}," context\n",[67,106,108],{"class":69,"line":107},4,[67,109,111],{"emptyLinePlaceholder":110},true,"\n",[67,113,115,118,121],{"class":69,"line":114},5,[67,116,117],{"class":84},"config ",[67,119,120],{"class":80},"=",[67,122,123],{"class":84}," context.config\n",[67,125,127],{"class":69,"line":126},6,[67,128,111],{"emptyLinePlaceholder":110},[67,130,132,135,139,142,146],{"class":69,"line":131},7,[67,133,134],{"class":80},"def",[67,136,138],{"class":137},"sScJk"," run_migrations_online",[67,140,141],{"class":84},"() -> ",[67,143,145],{"class":144},"sj4cs","None",[67,147,148],{"class":84},":\n",[67,150,152,155,157],{"class":69,"line":151},8,[67,153,154],{"class":84},"    connectable ",[67,156,120],{"class":80},[67,158,159],{"class":84}," engine_from_config(\n",[67,161,163],{"class":69,"line":162},9,[67,164,165],{"class":84},"        config.get_section(config.config_ini_section, {}),\n",[67,167,169,173,175,179],{"class":69,"line":168},10,[67,170,172],{"class":171},"s4XuR","        prefix",[67,174,120],{"class":80},[67,176,178],{"class":177},"sZZnC","\"sqlalchemy.\"",[67,180,181],{"class":84},",\n",[67,183,185,188,190],{"class":69,"line":184},11,[67,186,187],{"class":171},"        poolclass",[67,189,120],{"class":80},[67,191,192],{"class":84},"pool.NullPool,\n",[67,194,196],{"class":69,"line":195},12,[67,197,198],{"class":84},"    )\n",[67,200,202,205,208,211],{"class":69,"line":201},13,[67,203,204],{"class":80},"    with",[67,206,207],{"class":84}," connectable.connect() ",[67,209,210],{"class":80},"as",[67,212,213],{"class":84}," connection:\n",[67,215,217,220,223,225,228,231,233],{"class":69,"line":216},14,[67,218,219],{"class":84},"        context.configure(",[67,221,222],{"class":171},"connection",[67,224,120],{"class":80},[67,226,227],{"class":84},"connection, ",[67,229,230],{"class":171},"target_metadata",[67,232,120],{"class":80},[67,234,235],{"class":84},"target_metadata)\n",[67,237,239,242],{"class":69,"line":238},15,[67,240,241],{"class":80},"        with",[67,243,244],{"class":84}," context.begin_transaction():\n",[67,246,248],{"class":69,"line":247},16,[67,249,250],{"class":84},"            context.run_migrations()\n",[14,252,253],{},[54,254,255],{},"Modern async env.py for asyncpg",[58,257,259],{"className":60,"code":258,"language":62,"meta":63,"style":63},"# env.py — async-compatible with asyncpg\nimport asyncio\nimport os\nfrom logging.config import fileConfig\n\nfrom sqlalchemy.ext.asyncio import create_async_engine\nfrom sqlalchemy import pool\n\nfrom alembic import context\n\n# Import your declarative Base so autogenerate can inspect metadata\nfrom app.models import Base  # User, Order, Product, Invoice, Tenant models live here\n\nconfig = context.config\ntarget_metadata = Base.metadata\n\nif config.config_file_name is not None:\n    fileConfig(config.config_file_name)\n\nDATABASE_URL = os.getenv(\"DATABASE_URL\", \"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost:5432\u002Fappdb\")\n\n\ndef run_migrations_offline() -> None:\n    \"\"\"Generate SQL script without a live database connection.\"\"\"\n    url = config.get_main_option(\"sqlalchemy.url\")\n    context.configure(\n        url=url,\n        target_metadata=target_metadata,\n        literal_binds=True,\n        dialect_opts={\"paramstyle\": \"named\"},\n    )\n    with context.begin_transaction():\n        context.run_migrations()\n\n\ndef do_run_migrations(connection) -> None:\n    \"\"\"Synchronous callback executed inside run_sync().\"\"\"\n    context.configure(connection=connection, target_metadata=target_metadata)\n    with context.begin_transaction():\n        context.run_migrations()\n\n\nasync def run_async_migrations() -> None:\n    \"\"\"Create an async engine and delegate to the sync callback via run_sync.\"\"\"\n    connectable = create_async_engine(DATABASE_URL, poolclass=pool.NullPool)\n\n    async with connectable.connect() as conn:\n        await conn.run_sync(do_run_migrations)\n\n    await connectable.dispose()\n\n\ndef run_migrations_online() -> None:\n    \"\"\"Entry point for online migration mode.\"\"\"\n    asyncio.run(run_async_migrations())\n\n\nif context.is_offline_mode():\n    run_migrations_offline()\nelse:\n    run_migrations_online()\n",[18,260,261,266,273,280,292,296,308,319,323,333,337,342,357,361,369,379,383,403,409,414,438,443,448,462,468,484,490,501,512,525,548,553,560,566,571,576,591,597,615,622,627,632,637,655,661,683,688,704,713,718,727,732,737,750,756,762,767,772,780,786,794],{"__ignoreMap":63},[67,262,263],{"class":69,"line":70},[67,264,265],{"class":73},"# env.py — async-compatible with asyncpg\n",[67,267,268,270],{"class":69,"line":77},[67,269,88],{"class":80},[67,271,272],{"class":84}," asyncio\n",[67,274,275,277],{"class":69,"line":94},[67,276,88],{"class":80},[67,278,279],{"class":84}," os\n",[67,281,282,284,287,289],{"class":69,"line":107},[67,283,81],{"class":80},[67,285,286],{"class":84}," logging.config ",[67,288,88],{"class":80},[67,290,291],{"class":84}," fileConfig\n",[67,293,294],{"class":69,"line":114},[67,295,111],{"emptyLinePlaceholder":110},[67,297,298,300,303,305],{"class":69,"line":126},[67,299,81],{"class":80},[67,301,302],{"class":84}," sqlalchemy.ext.asyncio ",[67,304,88],{"class":80},[67,306,307],{"class":84}," create_async_engine\n",[67,309,310,312,314,316],{"class":69,"line":131},[67,311,81],{"class":80},[67,313,85],{"class":84},[67,315,88],{"class":80},[67,317,318],{"class":84}," pool\n",[67,320,321],{"class":69,"line":151},[67,322,111],{"emptyLinePlaceholder":110},[67,324,325,327,329,331],{"class":69,"line":162},[67,326,81],{"class":80},[67,328,99],{"class":84},[67,330,88],{"class":80},[67,332,104],{"class":84},[67,334,335],{"class":69,"line":168},[67,336,111],{"emptyLinePlaceholder":110},[67,338,339],{"class":69,"line":184},[67,340,341],{"class":73},"# Import your declarative Base so autogenerate can inspect metadata\n",[67,343,344,346,349,351,354],{"class":69,"line":195},[67,345,81],{"class":80},[67,347,348],{"class":84}," app.models ",[67,350,88],{"class":80},[67,352,353],{"class":84}," Base  ",[67,355,356],{"class":73},"# User, Order, Product, Invoice, Tenant models live here\n",[67,358,359],{"class":69,"line":201},[67,360,111],{"emptyLinePlaceholder":110},[67,362,363,365,367],{"class":69,"line":216},[67,364,117],{"class":84},[67,366,120],{"class":80},[67,368,123],{"class":84},[67,370,371,374,376],{"class":69,"line":238},[67,372,373],{"class":84},"target_metadata ",[67,375,120],{"class":80},[67,377,378],{"class":84}," Base.metadata\n",[67,380,381],{"class":69,"line":247},[67,382,111],{"emptyLinePlaceholder":110},[67,384,386,389,392,395,398,401],{"class":69,"line":385},17,[67,387,388],{"class":80},"if",[67,390,391],{"class":84}," config.config_file_name ",[67,393,394],{"class":80},"is",[67,396,397],{"class":80}," not",[67,399,400],{"class":144}," None",[67,402,148],{"class":84},[67,404,406],{"class":69,"line":405},18,[67,407,408],{"class":84},"    fileConfig(config.config_file_name)\n",[67,410,412],{"class":69,"line":411},19,[67,413,111],{"emptyLinePlaceholder":110},[67,415,417,420,423,426,429,432,435],{"class":69,"line":416},20,[67,418,419],{"class":144},"DATABASE_URL",[67,421,422],{"class":80}," =",[67,424,425],{"class":84}," os.getenv(",[67,427,428],{"class":177},"\"DATABASE_URL\"",[67,430,431],{"class":84},", ",[67,433,434],{"class":177},"\"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost:5432\u002Fappdb\"",[67,436,437],{"class":84},")\n",[67,439,441],{"class":69,"line":440},21,[67,442,111],{"emptyLinePlaceholder":110},[67,444,446],{"class":69,"line":445},22,[67,447,111],{"emptyLinePlaceholder":110},[67,449,451,453,456,458,460],{"class":69,"line":450},23,[67,452,134],{"class":80},[67,454,455],{"class":137}," run_migrations_offline",[67,457,141],{"class":84},[67,459,145],{"class":144},[67,461,148],{"class":84},[67,463,465],{"class":69,"line":464},24,[67,466,467],{"class":177},"    \"\"\"Generate SQL script without a live database connection.\"\"\"\n",[67,469,471,474,476,479,482],{"class":69,"line":470},25,[67,472,473],{"class":84},"    url ",[67,475,120],{"class":80},[67,477,478],{"class":84}," config.get_main_option(",[67,480,481],{"class":177},"\"sqlalchemy.url\"",[67,483,437],{"class":84},[67,485,487],{"class":69,"line":486},26,[67,488,489],{"class":84},"    context.configure(\n",[67,491,493,496,498],{"class":69,"line":492},27,[67,494,495],{"class":171},"        url",[67,497,120],{"class":80},[67,499,500],{"class":84},"url,\n",[67,502,504,507,509],{"class":69,"line":503},28,[67,505,506],{"class":171},"        target_metadata",[67,508,120],{"class":80},[67,510,511],{"class":84},"target_metadata,\n",[67,513,515,518,520,523],{"class":69,"line":514},29,[67,516,517],{"class":171},"        literal_binds",[67,519,120],{"class":80},[67,521,522],{"class":144},"True",[67,524,181],{"class":84},[67,526,528,531,533,536,539,542,545],{"class":69,"line":527},30,[67,529,530],{"class":171},"        dialect_opts",[67,532,120],{"class":80},[67,534,535],{"class":84},"{",[67,537,538],{"class":177},"\"paramstyle\"",[67,540,541],{"class":84},": ",[67,543,544],{"class":177},"\"named\"",[67,546,547],{"class":84},"},\n",[67,549,551],{"class":69,"line":550},31,[67,552,198],{"class":84},[67,554,556,558],{"class":69,"line":555},32,[67,557,204],{"class":80},[67,559,244],{"class":84},[67,561,563],{"class":69,"line":562},33,[67,564,565],{"class":84},"        context.run_migrations()\n",[67,567,569],{"class":69,"line":568},34,[67,570,111],{"emptyLinePlaceholder":110},[67,572,574],{"class":69,"line":573},35,[67,575,111],{"emptyLinePlaceholder":110},[67,577,579,581,584,587,589],{"class":69,"line":578},36,[67,580,134],{"class":80},[67,582,583],{"class":137}," do_run_migrations",[67,585,586],{"class":84},"(connection) -> ",[67,588,145],{"class":144},[67,590,148],{"class":84},[67,592,594],{"class":69,"line":593},37,[67,595,596],{"class":177},"    \"\"\"Synchronous callback executed inside run_sync().\"\"\"\n",[67,598,600,603,605,607,609,611,613],{"class":69,"line":599},38,[67,601,602],{"class":84},"    context.configure(",[67,604,222],{"class":171},[67,606,120],{"class":80},[67,608,227],{"class":84},[67,610,230],{"class":171},[67,612,120],{"class":80},[67,614,235],{"class":84},[67,616,618,620],{"class":69,"line":617},39,[67,619,204],{"class":80},[67,621,244],{"class":84},[67,623,625],{"class":69,"line":624},40,[67,626,565],{"class":84},[67,628,630],{"class":69,"line":629},41,[67,631,111],{"emptyLinePlaceholder":110},[67,633,635],{"class":69,"line":634},42,[67,636,111],{"emptyLinePlaceholder":110},[67,638,640,643,646,649,651,653],{"class":69,"line":639},43,[67,641,642],{"class":80},"async",[67,644,645],{"class":80}," def",[67,647,648],{"class":137}," run_async_migrations",[67,650,141],{"class":84},[67,652,145],{"class":144},[67,654,148],{"class":84},[67,656,658],{"class":69,"line":657},44,[67,659,660],{"class":177},"    \"\"\"Create an async engine and delegate to the sync callback via run_sync.\"\"\"\n",[67,662,664,666,668,671,673,675,678,680],{"class":69,"line":663},45,[67,665,154],{"class":84},[67,667,120],{"class":80},[67,669,670],{"class":84}," create_async_engine(",[67,672,419],{"class":144},[67,674,431],{"class":84},[67,676,677],{"class":171},"poolclass",[67,679,120],{"class":80},[67,681,682],{"class":84},"pool.NullPool)\n",[67,684,686],{"class":69,"line":685},46,[67,687,111],{"emptyLinePlaceholder":110},[67,689,691,694,697,699,701],{"class":69,"line":690},47,[67,692,693],{"class":80},"    async",[67,695,696],{"class":80}," with",[67,698,207],{"class":84},[67,700,210],{"class":80},[67,702,703],{"class":84}," conn:\n",[67,705,707,710],{"class":69,"line":706},48,[67,708,709],{"class":80},"        await",[67,711,712],{"class":84}," conn.run_sync(do_run_migrations)\n",[67,714,716],{"class":69,"line":715},49,[67,717,111],{"emptyLinePlaceholder":110},[67,719,721,724],{"class":69,"line":720},50,[67,722,723],{"class":80},"    await",[67,725,726],{"class":84}," connectable.dispose()\n",[67,728,730],{"class":69,"line":729},51,[67,731,111],{"emptyLinePlaceholder":110},[67,733,735],{"class":69,"line":734},52,[67,736,111],{"emptyLinePlaceholder":110},[67,738,740,742,744,746,748],{"class":69,"line":739},53,[67,741,134],{"class":80},[67,743,138],{"class":137},[67,745,141],{"class":84},[67,747,145],{"class":144},[67,749,148],{"class":84},[67,751,753],{"class":69,"line":752},54,[67,754,755],{"class":177},"    \"\"\"Entry point for online migration mode.\"\"\"\n",[67,757,759],{"class":69,"line":758},55,[67,760,761],{"class":84},"    asyncio.run(run_async_migrations())\n",[67,763,765],{"class":69,"line":764},56,[67,766,111],{"emptyLinePlaceholder":110},[67,768,770],{"class":69,"line":769},57,[67,771,111],{"emptyLinePlaceholder":110},[67,773,775,777],{"class":69,"line":774},58,[67,776,388],{"class":80},[67,778,779],{"class":84}," context.is_offline_mode():\n",[67,781,783],{"class":69,"line":782},59,[67,784,785],{"class":84},"    run_migrations_offline()\n",[67,787,789,792],{"class":69,"line":788},60,[67,790,791],{"class":80},"else",[67,793,148],{"class":84},[67,795,797],{"class":69,"line":796},61,[67,798,799],{"class":84},"    run_migrations_online()\n",[37,801,803],{"id":802},"execution-context-async-workflow-integration","Execution Context & Async Workflow Integration",[14,805,806,807,809],{},"Alembic's migration context was designed around synchronous DBAPI connections. Every internal operation — schema comparison, DDL emission, transaction management — calls blocking methods on a raw ",[18,808,222],{}," object. asyncpg, on the other hand, exposes only coroutine-based methods and refuses to be driven from a synchronous call stack without an event loop.",[14,811,812,813,816,817,820,821,824,825,828,829,832],{},"The bridge is ",[18,814,815],{},"AsyncConnection.run_sync()",". When you call ",[18,818,819],{},"await conn.run_sync(do_run_migrations)",", SQLAlchemy creates a temporary synchronous shim that passes a wrapped connection object to your callback. Inside ",[18,822,823],{},"do_run_migrations",", all operations appear synchronous to Alembic, but the underlying I\u002FO is dispatched through the running event loop. The key rule is that ",[18,826,827],{},"run_sync"," must be called inside an active ",[18,830,831],{},"async with connectable.connect()"," block — the synchronous connection shim is only valid for the lifetime of that async context manager.",[14,834,835,836,839,840,843,844,848],{},"The ",[18,837,838],{},"asyncio.run()"," call in ",[18,841,842],{},"run_migrations_online()"," is the correct entry point. It creates a fresh event loop, runs the coroutine to completion, and tears the loop down. This matches how the ",[31,845,847],{"href":846},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002F","async engine and connection pool lifecycle"," is designed to work: each migration run is a discrete, self-contained process, not a long-lived application server.",[14,850,851,852,855],{},"Using ",[18,853,854],{},"pool.NullPool"," is intentional. Migrations run as a CLI process, not a persistent service. Connection pooling would leave dangling connections when the process exits, potentially blocking other operations on short-lived database server configurations.",[14,857,858,859,861,862,865,866,869,870,873,874,876],{},"Reading the ",[18,860,419],{}," from the environment with ",[18,863,864],{},"os.getenv()"," rather than hardcoding it in ",[18,867,868],{},"alembic.ini"," is a production requirement. Secrets stored in INI files end up in version control; environment variables are injected by your deployment platform. The online migration path uses the env var exclusively. Note that the offline path still reads ",[18,871,872],{},"config.get_main_option(\"sqlalchemy.url\")"," from ",[18,875,868],{}," — the implications of that asymmetry are covered in the error table below.",[37,878,880],{"id":879},"resolving-warnings-errors-common-mistakes","Resolving Warnings, Errors & Common Mistakes",[882,883,884,900],"table",{},[885,886,887],"thead",{},[888,889,890,894,897],"tr",{},[891,892,893],"th",{},"Exact warning \u002F error string",[891,895,896],{},"Root cause",[891,898,899],{},"Production fix",[901,902,903,930,960,992,1034,1061,1075],"tbody",{},[888,904,905,911,917],{},[906,907,908],"td",{},[18,909,910],{},"sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called",[906,912,913,914,916],{},"Async SQLAlchemy code (like a lazy-loaded relationship) was invoked from inside a synchronous call stack without a greenlet context. In migrations this usually means ",[18,915,823],{}," is calling async model methods.",[906,918,919,920,922,923,926,927,929],{},"Keep ",[18,921,823],{}," purely synchronous. Never call ",[18,924,925],{},"await"," inside it. All async work must happen before or after ",[18,928,28],{},".",[888,931,932,937,946],{},[906,933,934],{},[18,935,936],{},"RuntimeError: got Future \u003CFuture ...> attached to a different loop",[906,938,939,940,942,943,945],{},"An ",[18,941,24],{}," created in one event loop is being reused in another — common when a long-lived engine from the application layer is imported and passed to ",[18,944,838],{},", which spins up a new loop.",[906,947,948,949,951,952,955,956,959],{},"Create a fresh ",[18,950,24],{}," inside ",[18,953,954],{},"run_async_migrations()"," rather than importing the application's shared engine. The ",[18,957,958],{},"NullPool"," ensures disposal is immediate.",[888,961,962,967,978],{},[906,963,964],{},[18,965,966],{},"sqlalchemy.exc.OperationalError: (sqlalchemy.dialects.postgresql.asyncpg.Error) Can't operate on a closed transaction",[906,968,969,971,972,974,975,977],{},[18,970,823],{}," is being called after the ",[18,973,831],{}," block has exited, meaning the connection was already returned or closed before ",[18,976,827],{}," completed.",[906,979,980,981,983,984,987,988,991],{},"Ensure ",[18,982,819],{}," is called within the ",[18,985,986],{},"async with connectable.connect() as conn:"," block, not after it. Never cache the ",[18,989,990],{},"conn"," object and use it outside the context manager.",[888,993,994,999,1011],{},[906,995,996],{},[18,997,998],{},"alembic.util.exc.CommandError: Can't proceed with --autogenerate option; environment script \u002Fpath\u002Fenv.py does not provide a MetaData object",[906,1000,1001,1003,1004,1006,1007,1010],{},[18,1002,230],{}," is ",[18,1005,145],{}," because the import of ",[18,1008,1009],{},"Base.metadata"," failed silently or was never assigned.",[906,1012,1013,1014,1017,1018,431,1021,431,1024,431,1027,431,1030,1033],{},"Verify your model module path. Add a print statement temporarily: ",[18,1015,1016],{},"print(Base.metadata.tables.keys())"," just after the import to confirm the models are loaded. All tables — ",[18,1019,1020],{},"users",[18,1022,1023],{},"orders",[18,1025,1026],{},"products",[18,1028,1029],{},"invoices",[18,1031,1032],{},"tenants"," — should appear.",[888,1035,1036,1042,1051],{},[906,1037,1038,1041],{},[18,1039,1040],{},"FAILED: Can't locate revision identified by '...'"," in offline mode despite valid revision IDs",[906,1043,1044,1045,873,1048,1050],{},"Offline mode reads ",[18,1046,1047],{},"sqlalchemy.url",[18,1049,868],{},". If that key is absent or left as the placeholder, Alembic silently switches dialect handling, which can cause revision lookup failures on some backends.",[906,1052,1053,1054,1057,1058,1060],{},"Set ",[18,1055,1056],{},"sqlalchemy.url = postgresql+asyncpg:\u002F\u002F"," (a dummy value is fine for offline SQL generation) in ",[18,1059,868],{}," even when online mode uses env vars. Offline mode only needs the URL to determine dialect; it never opens a connection.",[888,1062,1063,1069,1072],{},[906,1064,1065,1068],{},[18,1066,1067],{},"DeprecationWarning: The current Greenlet was not spawned from a greenlet.run()"," (SQLAlchemy \u003C 2.0)",[906,1070,1071],{},"Using SQLAlchemy 1.4 async APIs with Alembic 1.7 or earlier produces this warning because the greenlet interop layer changed.",[906,1073,1074],{},"Upgrade to SQLAlchemy 2.0+ and Alembic 1.10+. The async interop was stabilized in these releases.",[888,1076,1077,1087,1092],{},[906,1078,1079,1082,1083,1086],{},[18,1080,1081],{},"asyncpg.exceptions.TooManyConnectionsError"," during ",[18,1084,1085],{},"alembic upgrade head"," in CI",[906,1088,1089,1091],{},[18,1090,958],{}," is not set, so the engine allocates its default pool. In CI environments with aggressive parallelism, multiple migration steps can exhaust the server's connection limit.",[906,1093,1094,1095,1098,1099,951,1102,1105],{},"Always pass ",[18,1096,1097],{},"poolclass=pool.NullPool"," to ",[18,1100,1101],{},"create_async_engine",[18,1103,1104],{},"run_async_migrations",". Migrations are sequential by nature; pooling provides no benefit and adds risk.",[37,1107,1109],{"id":1108},"advanced-async-envpy-optimization","Advanced Async env.py Optimization",[1111,1112,1114],"h3",{"id":1113},"sharing-configuration-between-the-application-engine-and-migrations","Sharing configuration between the application engine and migrations",[14,1116,1117,1118,1120,1121,1123],{},"A common pattern is to centralize engine configuration in a single module, then import it in both the application and ",[18,1119,20],{},". The naive approach — importing the application's ",[18,1122,24],{}," instance directly — causes the \"different loop\" error described above because the application engine is often created at module import time, before any event loop exists.",[14,1125,1126,1127,1131,1132,1135,1136,1139],{},"The correct approach is to share the ",[1128,1129,1130],"em",{},"configuration"," rather than the ",[1128,1133,1134],{},"engine object",". Extract a ",[18,1137,1138],{},"get_engine_kwargs()"," factory:",[58,1141,1143],{"className":60,"code":1142,"language":62,"meta":63,"style":63},"# app\u002Fdatabase.py\nimport os\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\nfrom sqlalchemy import pool\n\nDATABASE_URL = os.getenv(\"DATABASE_URL\", \"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost:5432\u002Fappdb\")\n\ndef get_engine_kwargs() -> dict:\n    \"\"\"Return engine configuration shared by the app and Alembic migrations.\"\"\"\n    return {\n        \"url\": DATABASE_URL,\n        \"pool_size\": 10,\n        \"max_overflow\": 5,\n        \"pool_pre_ping\": True,\n        \"connect_args\": {\n            \"server_settings\": {\"application_name\": \"myapp\"},\n            \"statement_cache_size\": 0,  # required for pgbouncer compatibility\n        },\n    }\n\n# Application uses a long-lived pooled engine\nengine = create_async_engine(**get_engine_kwargs())\nSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)\n",[18,1144,1145,1150,1156,1167,1177,1181,1197,1201,1215,1220,1228,1239,1251,1263,1274,1282,1300,1316,1321,1326,1330,1335,1350],{"__ignoreMap":63},[67,1146,1147],{"class":69,"line":70},[67,1148,1149],{"class":73},"# app\u002Fdatabase.py\n",[67,1151,1152,1154],{"class":69,"line":77},[67,1153,88],{"class":80},[67,1155,279],{"class":84},[67,1157,1158,1160,1162,1164],{"class":69,"line":94},[67,1159,81],{"class":80},[67,1161,302],{"class":84},[67,1163,88],{"class":80},[67,1165,1166],{"class":84}," create_async_engine, async_sessionmaker, AsyncSession\n",[67,1168,1169,1171,1173,1175],{"class":69,"line":107},[67,1170,81],{"class":80},[67,1172,85],{"class":84},[67,1174,88],{"class":80},[67,1176,318],{"class":84},[67,1178,1179],{"class":69,"line":114},[67,1180,111],{"emptyLinePlaceholder":110},[67,1182,1183,1185,1187,1189,1191,1193,1195],{"class":69,"line":126},[67,1184,419],{"class":144},[67,1186,422],{"class":80},[67,1188,425],{"class":84},[67,1190,428],{"class":177},[67,1192,431],{"class":84},[67,1194,434],{"class":177},[67,1196,437],{"class":84},[67,1198,1199],{"class":69,"line":131},[67,1200,111],{"emptyLinePlaceholder":110},[67,1202,1203,1205,1208,1210,1213],{"class":69,"line":151},[67,1204,134],{"class":80},[67,1206,1207],{"class":137}," get_engine_kwargs",[67,1209,141],{"class":84},[67,1211,1212],{"class":144},"dict",[67,1214,148],{"class":84},[67,1216,1217],{"class":69,"line":162},[67,1218,1219],{"class":177},"    \"\"\"Return engine configuration shared by the app and Alembic migrations.\"\"\"\n",[67,1221,1222,1225],{"class":69,"line":168},[67,1223,1224],{"class":80},"    return",[67,1226,1227],{"class":84}," {\n",[67,1229,1230,1233,1235,1237],{"class":69,"line":184},[67,1231,1232],{"class":177},"        \"url\"",[67,1234,541],{"class":84},[67,1236,419],{"class":144},[67,1238,181],{"class":84},[67,1240,1241,1244,1246,1249],{"class":69,"line":195},[67,1242,1243],{"class":177},"        \"pool_size\"",[67,1245,541],{"class":84},[67,1247,1248],{"class":144},"10",[67,1250,181],{"class":84},[67,1252,1253,1256,1258,1261],{"class":69,"line":201},[67,1254,1255],{"class":177},"        \"max_overflow\"",[67,1257,541],{"class":84},[67,1259,1260],{"class":144},"5",[67,1262,181],{"class":84},[67,1264,1265,1268,1270,1272],{"class":69,"line":216},[67,1266,1267],{"class":177},"        \"pool_pre_ping\"",[67,1269,541],{"class":84},[67,1271,522],{"class":144},[67,1273,181],{"class":84},[67,1275,1276,1279],{"class":69,"line":238},[67,1277,1278],{"class":177},"        \"connect_args\"",[67,1280,1281],{"class":84},": {\n",[67,1283,1284,1287,1290,1293,1295,1298],{"class":69,"line":247},[67,1285,1286],{"class":177},"            \"server_settings\"",[67,1288,1289],{"class":84},": {",[67,1291,1292],{"class":177},"\"application_name\"",[67,1294,541],{"class":84},[67,1296,1297],{"class":177},"\"myapp\"",[67,1299,547],{"class":84},[67,1301,1302,1305,1307,1310,1313],{"class":69,"line":385},[67,1303,1304],{"class":177},"            \"statement_cache_size\"",[67,1306,541],{"class":84},[67,1308,1309],{"class":144},"0",[67,1311,1312],{"class":84},",  ",[67,1314,1315],{"class":73},"# required for pgbouncer compatibility\n",[67,1317,1318],{"class":69,"line":405},[67,1319,1320],{"class":84},"        },\n",[67,1322,1323],{"class":69,"line":411},[67,1324,1325],{"class":84},"    }\n",[67,1327,1328],{"class":69,"line":416},[67,1329,111],{"emptyLinePlaceholder":110},[67,1331,1332],{"class":69,"line":440},[67,1333,1334],{"class":73},"# Application uses a long-lived pooled engine\n",[67,1336,1337,1340,1342,1344,1347],{"class":69,"line":445},[67,1338,1339],{"class":84},"engine ",[67,1341,120],{"class":80},[67,1343,670],{"class":84},[67,1345,1346],{"class":80},"**",[67,1348,1349],{"class":84},"get_engine_kwargs())\n",[67,1351,1352,1355,1357,1360,1363,1365,1368,1371,1373,1376],{"class":69,"line":450},[67,1353,1354],{"class":84},"SessionLocal ",[67,1356,120],{"class":80},[67,1358,1359],{"class":84}," async_sessionmaker(engine, ",[67,1361,1362],{"class":171},"class_",[67,1364,120],{"class":80},[67,1366,1367],{"class":84},"AsyncSession, ",[67,1369,1370],{"class":171},"expire_on_commit",[67,1372,120],{"class":80},[67,1374,1375],{"class":144},"False",[67,1377,437],{"class":84},[14,1379,1380,1381,1383],{},"Then in ",[18,1382,20],{},", build a migration-specific engine from the same configuration dict:",[58,1385,1387],{"className":60,"code":1386,"language":62,"meta":63,"style":63},"# env.py — advanced version sharing app configuration\nimport asyncio\nimport os\nfrom logging.config import fileConfig\nfrom sqlalchemy.ext.asyncio import create_async_engine\nfrom sqlalchemy import pool\nfrom alembic import context\n\nfrom app.models import Base\nfrom app.database import get_engine_kwargs  # shared config, not the engine object\n\nconfig = context.config\ntarget_metadata = Base.metadata\n\nif config.config_file_name is not None:\n    fileConfig(config.config_file_name)\n\n\ndef do_run_migrations(connection) -> None:\n    context.configure(connection=connection, target_metadata=target_metadata)\n    with context.begin_transaction():\n        context.run_migrations()\n\n\nasync def run_async_migrations() -> None:\n    # Override pool settings for migration context\n    kwargs = get_engine_kwargs()\n    kwargs[\"poolclass\"] = pool.NullPool\n    kwargs.pop(\"pool_size\", None)\n    kwargs.pop(\"max_overflow\", None)\n\n    connectable = create_async_engine(**kwargs)\n    async with connectable.connect() as conn:\n        await conn.run_sync(do_run_migrations)\n    await connectable.dispose()\n\n\ndef run_migrations_offline() -> None:\n    url = config.get_main_option(\"sqlalchemy.url\")\n    context.configure(\n        url=url,\n        target_metadata=target_metadata,\n        literal_binds=True,\n        dialect_opts={\"paramstyle\": \"named\"},\n    )\n    with context.begin_transaction():\n        context.run_migrations()\n\n\ndef run_migrations_online() -> None:\n    asyncio.run(run_async_migrations())\n\n\nif context.is_offline_mode():\n    run_migrations_offline()\nelse:\n    run_migrations_online()\n",[18,1388,1389,1394,1400,1406,1416,1426,1436,1446,1450,1461,1476,1480,1488,1496,1500,1514,1518,1522,1526,1538,1554,1560,1564,1568,1572,1586,1591,1601,1617,1631,1644,1648,1661,1673,1679,1685,1689,1693,1705,1717,1721,1729,1737,1747,1763,1767,1773,1777,1781,1785,1797,1801,1805,1809,1815,1819,1825],{"__ignoreMap":63},[67,1390,1391],{"class":69,"line":70},[67,1392,1393],{"class":73},"# env.py — advanced version sharing app configuration\n",[67,1395,1396,1398],{"class":69,"line":77},[67,1397,88],{"class":80},[67,1399,272],{"class":84},[67,1401,1402,1404],{"class":69,"line":94},[67,1403,88],{"class":80},[67,1405,279],{"class":84},[67,1407,1408,1410,1412,1414],{"class":69,"line":107},[67,1409,81],{"class":80},[67,1411,286],{"class":84},[67,1413,88],{"class":80},[67,1415,291],{"class":84},[67,1417,1418,1420,1422,1424],{"class":69,"line":114},[67,1419,81],{"class":80},[67,1421,302],{"class":84},[67,1423,88],{"class":80},[67,1425,307],{"class":84},[67,1427,1428,1430,1432,1434],{"class":69,"line":126},[67,1429,81],{"class":80},[67,1431,85],{"class":84},[67,1433,88],{"class":80},[67,1435,318],{"class":84},[67,1437,1438,1440,1442,1444],{"class":69,"line":131},[67,1439,81],{"class":80},[67,1441,99],{"class":84},[67,1443,88],{"class":80},[67,1445,104],{"class":84},[67,1447,1448],{"class":69,"line":151},[67,1449,111],{"emptyLinePlaceholder":110},[67,1451,1452,1454,1456,1458],{"class":69,"line":162},[67,1453,81],{"class":80},[67,1455,348],{"class":84},[67,1457,88],{"class":80},[67,1459,1460],{"class":84}," Base\n",[67,1462,1463,1465,1468,1470,1473],{"class":69,"line":168},[67,1464,81],{"class":80},[67,1466,1467],{"class":84}," app.database ",[67,1469,88],{"class":80},[67,1471,1472],{"class":84}," get_engine_kwargs  ",[67,1474,1475],{"class":73},"# shared config, not the engine object\n",[67,1477,1478],{"class":69,"line":184},[67,1479,111],{"emptyLinePlaceholder":110},[67,1481,1482,1484,1486],{"class":69,"line":195},[67,1483,117],{"class":84},[67,1485,120],{"class":80},[67,1487,123],{"class":84},[67,1489,1490,1492,1494],{"class":69,"line":201},[67,1491,373],{"class":84},[67,1493,120],{"class":80},[67,1495,378],{"class":84},[67,1497,1498],{"class":69,"line":216},[67,1499,111],{"emptyLinePlaceholder":110},[67,1501,1502,1504,1506,1508,1510,1512],{"class":69,"line":238},[67,1503,388],{"class":80},[67,1505,391],{"class":84},[67,1507,394],{"class":80},[67,1509,397],{"class":80},[67,1511,400],{"class":144},[67,1513,148],{"class":84},[67,1515,1516],{"class":69,"line":247},[67,1517,408],{"class":84},[67,1519,1520],{"class":69,"line":385},[67,1521,111],{"emptyLinePlaceholder":110},[67,1523,1524],{"class":69,"line":405},[67,1525,111],{"emptyLinePlaceholder":110},[67,1527,1528,1530,1532,1534,1536],{"class":69,"line":411},[67,1529,134],{"class":80},[67,1531,583],{"class":137},[67,1533,586],{"class":84},[67,1535,145],{"class":144},[67,1537,148],{"class":84},[67,1539,1540,1542,1544,1546,1548,1550,1552],{"class":69,"line":416},[67,1541,602],{"class":84},[67,1543,222],{"class":171},[67,1545,120],{"class":80},[67,1547,227],{"class":84},[67,1549,230],{"class":171},[67,1551,120],{"class":80},[67,1553,235],{"class":84},[67,1555,1556,1558],{"class":69,"line":440},[67,1557,204],{"class":80},[67,1559,244],{"class":84},[67,1561,1562],{"class":69,"line":445},[67,1563,565],{"class":84},[67,1565,1566],{"class":69,"line":450},[67,1567,111],{"emptyLinePlaceholder":110},[67,1569,1570],{"class":69,"line":464},[67,1571,111],{"emptyLinePlaceholder":110},[67,1573,1574,1576,1578,1580,1582,1584],{"class":69,"line":470},[67,1575,642],{"class":80},[67,1577,645],{"class":80},[67,1579,648],{"class":137},[67,1581,141],{"class":84},[67,1583,145],{"class":144},[67,1585,148],{"class":84},[67,1587,1588],{"class":69,"line":486},[67,1589,1590],{"class":73},"    # Override pool settings for migration context\n",[67,1592,1593,1596,1598],{"class":69,"line":492},[67,1594,1595],{"class":84},"    kwargs ",[67,1597,120],{"class":80},[67,1599,1600],{"class":84}," get_engine_kwargs()\n",[67,1602,1603,1606,1609,1612,1614],{"class":69,"line":503},[67,1604,1605],{"class":84},"    kwargs[",[67,1607,1608],{"class":177},"\"poolclass\"",[67,1610,1611],{"class":84},"] ",[67,1613,120],{"class":80},[67,1615,1616],{"class":84}," pool.NullPool\n",[67,1618,1619,1622,1625,1627,1629],{"class":69,"line":514},[67,1620,1621],{"class":84},"    kwargs.pop(",[67,1623,1624],{"class":177},"\"pool_size\"",[67,1626,431],{"class":84},[67,1628,145],{"class":144},[67,1630,437],{"class":84},[67,1632,1633,1635,1638,1640,1642],{"class":69,"line":527},[67,1634,1621],{"class":84},[67,1636,1637],{"class":177},"\"max_overflow\"",[67,1639,431],{"class":84},[67,1641,145],{"class":144},[67,1643,437],{"class":84},[67,1645,1646],{"class":69,"line":550},[67,1647,111],{"emptyLinePlaceholder":110},[67,1649,1650,1652,1654,1656,1658],{"class":69,"line":555},[67,1651,154],{"class":84},[67,1653,120],{"class":80},[67,1655,670],{"class":84},[67,1657,1346],{"class":80},[67,1659,1660],{"class":84},"kwargs)\n",[67,1662,1663,1665,1667,1669,1671],{"class":69,"line":562},[67,1664,693],{"class":80},[67,1666,696],{"class":80},[67,1668,207],{"class":84},[67,1670,210],{"class":80},[67,1672,703],{"class":84},[67,1674,1675,1677],{"class":69,"line":568},[67,1676,709],{"class":80},[67,1678,712],{"class":84},[67,1680,1681,1683],{"class":69,"line":573},[67,1682,723],{"class":80},[67,1684,726],{"class":84},[67,1686,1687],{"class":69,"line":578},[67,1688,111],{"emptyLinePlaceholder":110},[67,1690,1691],{"class":69,"line":593},[67,1692,111],{"emptyLinePlaceholder":110},[67,1694,1695,1697,1699,1701,1703],{"class":69,"line":599},[67,1696,134],{"class":80},[67,1698,455],{"class":137},[67,1700,141],{"class":84},[67,1702,145],{"class":144},[67,1704,148],{"class":84},[67,1706,1707,1709,1711,1713,1715],{"class":69,"line":617},[67,1708,473],{"class":84},[67,1710,120],{"class":80},[67,1712,478],{"class":84},[67,1714,481],{"class":177},[67,1716,437],{"class":84},[67,1718,1719],{"class":69,"line":624},[67,1720,489],{"class":84},[67,1722,1723,1725,1727],{"class":69,"line":629},[67,1724,495],{"class":171},[67,1726,120],{"class":80},[67,1728,500],{"class":84},[67,1730,1731,1733,1735],{"class":69,"line":634},[67,1732,506],{"class":171},[67,1734,120],{"class":80},[67,1736,511],{"class":84},[67,1738,1739,1741,1743,1745],{"class":69,"line":639},[67,1740,517],{"class":171},[67,1742,120],{"class":80},[67,1744,522],{"class":144},[67,1746,181],{"class":84},[67,1748,1749,1751,1753,1755,1757,1759,1761],{"class":69,"line":657},[67,1750,530],{"class":171},[67,1752,120],{"class":80},[67,1754,535],{"class":84},[67,1756,538],{"class":177},[67,1758,541],{"class":84},[67,1760,544],{"class":177},[67,1762,547],{"class":84},[67,1764,1765],{"class":69,"line":663},[67,1766,198],{"class":84},[67,1768,1769,1771],{"class":69,"line":685},[67,1770,204],{"class":80},[67,1772,244],{"class":84},[67,1774,1775],{"class":69,"line":690},[67,1776,565],{"class":84},[67,1778,1779],{"class":69,"line":706},[67,1780,111],{"emptyLinePlaceholder":110},[67,1782,1783],{"class":69,"line":715},[67,1784,111],{"emptyLinePlaceholder":110},[67,1786,1787,1789,1791,1793,1795],{"class":69,"line":720},[67,1788,134],{"class":80},[67,1790,138],{"class":137},[67,1792,141],{"class":84},[67,1794,145],{"class":144},[67,1796,148],{"class":84},[67,1798,1799],{"class":69,"line":729},[67,1800,761],{"class":84},[67,1802,1803],{"class":69,"line":734},[67,1804,111],{"emptyLinePlaceholder":110},[67,1806,1807],{"class":69,"line":739},[67,1808,111],{"emptyLinePlaceholder":110},[67,1810,1811,1813],{"class":69,"line":752},[67,1812,388],{"class":80},[67,1814,779],{"class":84},[67,1816,1817],{"class":69,"line":758},[67,1818,785],{"class":84},[67,1820,1821,1823],{"class":69,"line":764},[67,1822,791],{"class":80},[67,1824,148],{"class":84},[67,1826,1827],{"class":69,"line":769},[67,1828,799],{"class":84},[14,1830,1831,1832,1835,1836,431,1839,1842,1843,1846],{},"This pattern ensures that SSL certificates, ",[18,1833,1834],{},"connect_args",", and custom server settings (critical for PgBouncer environments serving ",[18,1837,1838],{},"User",[18,1840,1841],{},"Order",", and ",[18,1844,1845],{},"Invoice"," tables with statement caching disabled) remain consistent between the running application and the migration process, without ever sharing an event-loop-bound engine object across boundaries.",[14,1848,1849,1850,1853,1854,1856,1857,1861,1862,1864],{},"When autogenerating migration scripts for schemas that span multiple models — for example when adding a ",[18,1851,1852],{},"tenant_id"," column to the ",[18,1855,1026],{}," table — this configuration consistency ensures Alembic compares the live schema against the same dialect options the application uses. Discrepancies between connection settings at migration time versus runtime are a common source of phantom schema diffs. For a deeper look at how ",[31,1858,1860],{"href":1859},"\u002Falembic-async-migrations-and-schema-evolution\u002Fautogenerating-and-reviewing-migration-scripts\u002F","autogenerate detects and filters schema changes",", including how to suppress false positives from server defaults on the ",[18,1863,1029],{}," table, see the dedicated guide.",[37,1866,1868],{"id":1867},"frequently-asked-questions","Frequently Asked Questions",[14,1870,1871],{},[54,1872,1873,1874,1876],{},"Why does Alembic need ",[18,1875,827],{}," if SQLAlchemy 2.0 supports async natively?",[14,1878,1879,1880,1883,1884,1887,1888,1890,1891,431,1894,1897],{},"SQLAlchemy 2.0's async layer is an opt-in surface built on top of the core synchronous engine. Alembic's migration context (",[18,1881,1882],{},"MigrationContext",") was written against the synchronous ",[18,1885,1886],{},"Connection"," interface and has not been rewritten to use coroutines internally. The ",[18,1889,827],{}," bridge hands Alembic a synchronous-looking connection object backed by the async driver, allowing Alembic to call ",[18,1892,1893],{},".execute()",[18,1895,1896],{},".fetchall()",", and DDL methods without knowing it is inside an event loop. This is the officially supported integration pattern and is not expected to change while Alembic maintains backward compatibility.",[14,1899,1900],{},[54,1901,1902,1903,951,1906,1908],{},"Can I reuse the application's ",[18,1904,1905],{},"AsyncSession",[18,1907,823],{},"?",[14,1910,1911,1912,1914,1915,1917,1918,1921,1922,951,1924,1926,1927,1930,1931,1933,1934,1936],{},"No. ",[18,1913,823],{}," receives a raw ",[18,1916,1886],{}," object, not a ",[18,1919,1920],{},"Session",". Alembic bypasses the ORM session layer entirely — it works at the Core connection level to ensure DDL statements execute in the correct transaction context. Attempting to create or use an ",[18,1923,1905],{},[18,1925,823],{}," will fail with a ",[18,1928,1929],{},"MissingGreenlet"," error because ",[18,1932,1905],{}," expects to be driven by coroutines, which cannot run inside the synchronous ",[18,1935,827],{}," callback.",[14,1938,1939],{},[54,1940,1941,1942,1944],{},"What happens if ",[18,1943,419],{}," is not set in the environment?",[14,1946,1947,1950,1951,1954,1955,1958,1959,1961],{},[18,1948,1949],{},"os.getenv(\"DATABASE_URL\", \"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost:5432\u002Fappdb\")"," falls back to a hardcoded default. In production, the fallback should either be a clearly invalid placeholder (which causes a fast, obvious failure) or absent entirely — use ",[18,1952,1953],{},"os.environ[\"DATABASE_URL\"]"," to raise ",[18,1956,1957],{},"KeyError"," immediately rather than silently connecting to a wrong database. The distinction matters when ",[18,1960,1085],{}," runs as part of a deployment script: a missing env var should halt deployment, not migrate the wrong database.",[14,1963,1964],{},[54,1965,1966,1967,1971],{},"Does this setup work with ",[31,1968,1970],{"href":1969},"\u002Falembic-async-migrations-and-schema-evolution\u002Fzero-downtime-schema-migration-strategies\u002F","zero-downtime migration strategies"," like adding nullable columns first?",[14,1973,1974,1975,1977,1978,1981,1982,1984,1985,1988,1989,1991],{},"Yes. The ",[18,1976,20],{}," configuration only controls how Alembic connects to the database and executes the migration script. It does not constrain what the migration script itself does. Zero-downtime strategies — expanding first with a nullable ",[18,1979,1980],{},"payment_method"," column on ",[18,1983,1023],{},", then backfilling, then constraining — are implemented inside the individual migration files under ",[18,1986,1987],{},"alembic\u002Fversions\u002F",". The async ",[18,1990,20],{}," is transparent to those patterns.",[37,1993,1995],{"id":1994},"related","Related",[1997,1998,1999,2008,2018,2024],"ul",{},[2000,2001,2002,2004,2005,2007],"li",{},[31,2003,34],{"href":33}," — parent guide covering the full async Alembic setup from ",[18,2006,49],{}," through first migration",[2000,2009,2010,2013,2014,2017],{},[31,2011,2012],{"href":1859},"Autogenerating and Reviewing Migration Scripts"," — how to run ",[18,2015,2016],{},"--autogenerate"," against an async engine and review the output for correctness",[2000,2019,2020,2023],{},[31,2021,2022],{"href":1969},"Zero-Downtime Schema Migration Strategies"," — sequencing migrations for live production databases without table locks",[2000,2025,2026,2029,2030,2032,2033,2036],{},[31,2027,2028],{"href":846},"Configuring Async Engines and Connection Pools"," — pool sizing, ",[18,2031,958],{}," vs ",[18,2034,2035],{},"AsyncAdaptedQueuePool",", and connection lifecycle management",[2038,2039,2040],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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":63,"searchDepth":77,"depth":77,"links":2042},[2043,2044,2045,2046,2049,2050],{"id":39,"depth":77,"text":40},{"id":802,"depth":77,"text":803},{"id":879,"depth":77,"text":880},{"id":1108,"depth":77,"text":1109,"children":2047},[2048],{"id":1113,"depth":94,"text":1114},{"id":1867,"depth":77,"text":1868},{"id":1994,"depth":77,"text":1995},"Setting up Alembic's env.py for asyncpg requires replacing the default synchronous engine with an AsyncEngine and bridging Alembic's synchronous migration context to the async runtime via conn.run_sync() — a complete walkthrough is part of the broader Configuring Alembic with Async SQLAlchemy Engines guide.","md",{"date":2054},"2026-06-18","\u002Falembic-async-migrations-and-schema-evolution\u002Fconfiguring-alembic-with-async-sqlalchemy-engines\u002Fsetting-up-alembic-env-py-for-asyncpg",{"title":5,"description":2051},"alembic-async-migrations-and-schema-evolution\u002Fconfiguring-alembic-with-async-sqlalchemy-engines\u002Fsetting-up-alembic-env-py-for-asyncpg\u002Findex","I2YOhdYcp_71MIcmsSJeMPulLxZmz5IYeZ5kHWFXMsU",1781810028981]