[{"data":1,"prerenderedAt":2338},["ShallowReactive",2],{"page-\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002Fsetting-up-an-async-engine-from-scratch\u002F":3},{"id":4,"title":5,"body":6,"description":2330,"extension":2331,"meta":2332,"navigation":122,"path":2334,"seo":2335,"stem":2336,"__hash__":2337},"content\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002Fsetting-up-an-async-engine-from-scratch\u002Findex.md","Setting Up an Async Engine from Scratch",{"type":7,"value":8,"toc":2313},"minimark",[9,13,48,53,59,64,92,101,105,1251,1254,1258,1262,1269,1289,1293,1314,1328,1332,1364,1375,1383,1387,1569,1573,1577,1587,1973,1979,1989,1993,2002,2024,2029,2035,2198,2212,2220,2246,2255,2277,2281,2309],[10,11,5],"h1",{"id":12},"setting-up-an-async-engine-from-scratch",[14,15,16,17,21,22,25,26,29,30,33,34,37,38,41,42,47],"p",{},"To set up a SQLAlchemy async engine from scratch, install ",[18,19,20],"code",{},"sqlalchemy"," and ",[18,23,24],{},"asyncpg",", build a ",[18,27,28],{},"postgresql+asyncpg:\u002F\u002F"," URL, call ",[18,31,32],{},"create_async_engine()",", wrap it with ",[18,35,36],{},"async_sessionmaker",", run your first query, and call ",[18,39,40],{},"await engine.dispose()"," on shutdown — all covered in ",[43,44,46],"a",{"href":45},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002F","Configuring Async Engines and Connection Pools",".",[49,50,52],"h2",{"id":51},"quick-answer","Quick Answer",[14,54,55,56,47],{},"The following script is a complete, runnable example. It covers every step from installation through graceful shutdown. Copy it, substitute your credentials, and run it with ",[18,57,58],{},"python engine_setup.py",[60,61,63],"h3",{"id":62},"step-1-install-dependencies","Step 1 — Install dependencies",[65,66,71],"pre",{"className":67,"code":68,"language":69,"meta":70,"style":70},"language-bash shiki shiki-themes github-light github-dark","pip install sqlalchemy asyncpg\n","bash","",[18,72,73],{"__ignoreMap":70},[74,75,78,82,86,89],"span",{"class":76,"line":77},"line",1,[74,79,81],{"class":80},"sScJk","pip",[74,83,85],{"class":84},"sZZnC"," install",[74,87,88],{"class":84}," sqlalchemy",[74,90,91],{"class":84}," asyncpg\n",[14,93,94,96,97,100],{},[18,95,24],{}," is a pure-async PostgreSQL driver written in Cython. SQLAlchemy's async layer knows how to wrap it through the ",[18,98,99],{},"postgresql+asyncpg"," dialect string, so no other adapter is needed.",[60,102,104],{"id":103},"step-2-complete-runnable-script","Step 2 — Complete runnable script",[65,106,110],{"className":107,"code":108,"language":109,"meta":70,"style":70},"language-python shiki shiki-themes github-light github-dark","\"\"\"engine_setup.py — SQLAlchemy 2.0 async engine, session, query, and shutdown.\"\"\"\n\nimport asyncio\nimport os\nfrom urllib.parse import quote_plus\n\nfrom sqlalchemy import Integer, String, select\nfrom sqlalchemy.ext.asyncio import (\n    AsyncSession,\n    async_sessionmaker,\n    create_async_engine,\n)\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\n\n\n# ---------------------------------------------------------------------------\n# 1. Build the connection URL from environment variables.\n#    quote_plus() handles special characters in passwords (@, #, %, etc.).\n# ---------------------------------------------------------------------------\nDB_HOST = os.environ.get(\"DB_HOST\", \"localhost\")\nDB_PORT = os.environ.get(\"DB_PORT\", \"5432\")\nDB_NAME = os.environ.get(\"DB_NAME\", \"storefront\")\nDB_USER = os.environ.get(\"DB_USER\", \"appuser\")\nDB_PASS = quote_plus(os.environ.get(\"DB_PASS\", \"s3cr3t!\"))\n\nDATABASE_URL = (\n    f\"postgresql+asyncpg:\u002F\u002F{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}\u002F{DB_NAME}\"\n)\n\n\n# ---------------------------------------------------------------------------\n# 2. Create the async engine.\n#    pool_size    — number of persistent connections kept open.\n#    max_overflow — extra connections allowed above pool_size during spikes.\n#    pool_pre_ping — sends a lightweight \"SELECT 1\" before each checkout\n#                    so stale connections are recycled automatically.\n#    echo         — set True during development to log every SQL statement.\n# ---------------------------------------------------------------------------\nengine = create_async_engine(\n    DATABASE_URL,\n    pool_size=10,\n    max_overflow=5,\n    pool_timeout=30,\n    pool_recycle=1800,\n    pool_pre_ping=True,\n    echo=False,\n)\n\n\n# ---------------------------------------------------------------------------\n# 3. Create the session factory.\n#    expire_on_commit=False keeps ORM attributes readable after commit\n#    without needing a second round-trip to the database.\n#    class_=AsyncSession is the default but shown here for clarity.\n# ---------------------------------------------------------------------------\nAsyncSessionLocal = async_sessionmaker(\n    bind=engine,\n    class_=AsyncSession,\n    expire_on_commit=False,\n)\n\n\n# ---------------------------------------------------------------------------\n# 4. Define a minimal ORM model.\n# ---------------------------------------------------------------------------\nclass Base(DeclarativeBase):\n    pass\n\n\nclass Order(Base):\n    __tablename__ = \"orders\"\n\n    id: Mapped[int] = mapped_column(Integer, primary_key=True)\n    customer_name: Mapped[str] = mapped_column(String(120))\n    status: Mapped[str] = mapped_column(String(32), default=\"pending\")\n\n\n# ---------------------------------------------------------------------------\n# 5. A realistic query — fetch an order by primary key.\n# ---------------------------------------------------------------------------\nasync def get_order(order_id: int) -> Order | None:\n    async with AsyncSessionLocal() as session:\n        result = await session.execute(\n            select(Order).where(Order.id == order_id)\n        )\n        return result.scalars().first()\n\n\n# ---------------------------------------------------------------------------\n# 6. Graceful shutdown — dispose() closes every connection in the pool\n#    and cancels any pending checkouts.  Call this in your lifespan handler\n#    or atexit hook so PostgreSQL does not see abrupt disconnects.\n# ---------------------------------------------------------------------------\nasync def shutdown() -> None:\n    await engine.dispose()\n\n\n# ---------------------------------------------------------------------------\n# 7. Entry-point for local testing.\n# ---------------------------------------------------------------------------\nasync def main() -> None:\n    # Create tables (dev\u002Ftest only — use Alembic migrations in production).\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n    order = await get_order(order_id=1)\n    if order:\n        print(f\"Order {order.id}: {order.customer_name} — {order.status}\")\n    else:\n        print(\"Order not found.\")\n\n    await shutdown()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n","python",[18,111,112,117,124,135,143,157,162,175,188,194,200,206,212,225,230,235,242,248,254,259,283,303,323,343,365,370,380,418,423,428,433,438,444,450,456,462,468,474,479,491,500,514,527,540,553,566,579,584,589,594,599,605,611,617,623,628,639,650,661,673,678,683,688,693,699,704,722,728,733,738,753,764,769,798,819,849,854,859,864,870,875,904,922,936,948,954,963,968,973,978,984,990,996,1001,1019,1028,1033,1038,1043,1049,1054,1070,1076,1091,1100,1105,1128,1137,1185,1193,1205,1210,1218,1223,1228,1245],{"__ignoreMap":70},[74,113,114],{"class":76,"line":77},[74,115,116],{"class":84},"\"\"\"engine_setup.py — SQLAlchemy 2.0 async engine, session, query, and shutdown.\"\"\"\n",[74,118,120],{"class":76,"line":119},2,[74,121,123],{"emptyLinePlaceholder":122},true,"\n",[74,125,127,131],{"class":76,"line":126},3,[74,128,130],{"class":129},"szBVR","import",[74,132,134],{"class":133},"sVt8B"," asyncio\n",[74,136,138,140],{"class":76,"line":137},4,[74,139,130],{"class":129},[74,141,142],{"class":133}," os\n",[74,144,146,149,152,154],{"class":76,"line":145},5,[74,147,148],{"class":129},"from",[74,150,151],{"class":133}," urllib.parse ",[74,153,130],{"class":129},[74,155,156],{"class":133}," quote_plus\n",[74,158,160],{"class":76,"line":159},6,[74,161,123],{"emptyLinePlaceholder":122},[74,163,165,167,170,172],{"class":76,"line":164},7,[74,166,148],{"class":129},[74,168,169],{"class":133}," sqlalchemy ",[74,171,130],{"class":129},[74,173,174],{"class":133}," Integer, String, select\n",[74,176,178,180,183,185],{"class":76,"line":177},8,[74,179,148],{"class":129},[74,181,182],{"class":133}," sqlalchemy.ext.asyncio ",[74,184,130],{"class":129},[74,186,187],{"class":133}," (\n",[74,189,191],{"class":76,"line":190},9,[74,192,193],{"class":133},"    AsyncSession,\n",[74,195,197],{"class":76,"line":196},10,[74,198,199],{"class":133},"    async_sessionmaker,\n",[74,201,203],{"class":76,"line":202},11,[74,204,205],{"class":133},"    create_async_engine,\n",[74,207,209],{"class":76,"line":208},12,[74,210,211],{"class":133},")\n",[74,213,215,217,220,222],{"class":76,"line":214},13,[74,216,148],{"class":129},[74,218,219],{"class":133}," sqlalchemy.orm ",[74,221,130],{"class":129},[74,223,224],{"class":133}," DeclarativeBase, Mapped, mapped_column\n",[74,226,228],{"class":76,"line":227},14,[74,229,123],{"emptyLinePlaceholder":122},[74,231,233],{"class":76,"line":232},15,[74,234,123],{"emptyLinePlaceholder":122},[74,236,238],{"class":76,"line":237},16,[74,239,241],{"class":240},"sJ8bj","# ---------------------------------------------------------------------------\n",[74,243,245],{"class":76,"line":244},17,[74,246,247],{"class":240},"# 1. Build the connection URL from environment variables.\n",[74,249,251],{"class":76,"line":250},18,[74,252,253],{"class":240},"#    quote_plus() handles special characters in passwords (@, #, %, etc.).\n",[74,255,257],{"class":76,"line":256},19,[74,258,241],{"class":240},[74,260,262,266,269,272,275,278,281],{"class":76,"line":261},20,[74,263,265],{"class":264},"sj4cs","DB_HOST",[74,267,268],{"class":129}," =",[74,270,271],{"class":133}," os.environ.get(",[74,273,274],{"class":84},"\"DB_HOST\"",[74,276,277],{"class":133},", ",[74,279,280],{"class":84},"\"localhost\"",[74,282,211],{"class":133},[74,284,286,289,291,293,296,298,301],{"class":76,"line":285},21,[74,287,288],{"class":264},"DB_PORT",[74,290,268],{"class":129},[74,292,271],{"class":133},[74,294,295],{"class":84},"\"DB_PORT\"",[74,297,277],{"class":133},[74,299,300],{"class":84},"\"5432\"",[74,302,211],{"class":133},[74,304,306,309,311,313,316,318,321],{"class":76,"line":305},22,[74,307,308],{"class":264},"DB_NAME",[74,310,268],{"class":129},[74,312,271],{"class":133},[74,314,315],{"class":84},"\"DB_NAME\"",[74,317,277],{"class":133},[74,319,320],{"class":84},"\"storefront\"",[74,322,211],{"class":133},[74,324,326,329,331,333,336,338,341],{"class":76,"line":325},23,[74,327,328],{"class":264},"DB_USER",[74,330,268],{"class":129},[74,332,271],{"class":133},[74,334,335],{"class":84},"\"DB_USER\"",[74,337,277],{"class":133},[74,339,340],{"class":84},"\"appuser\"",[74,342,211],{"class":133},[74,344,346,349,351,354,357,359,362],{"class":76,"line":345},24,[74,347,348],{"class":264},"DB_PASS",[74,350,268],{"class":129},[74,352,353],{"class":133}," quote_plus(os.environ.get(",[74,355,356],{"class":84},"\"DB_PASS\"",[74,358,277],{"class":133},[74,360,361],{"class":84},"\"s3cr3t!\"",[74,363,364],{"class":133},"))\n",[74,366,368],{"class":76,"line":367},25,[74,369,123],{"emptyLinePlaceholder":122},[74,371,373,376,378],{"class":76,"line":372},26,[74,374,375],{"class":264},"DATABASE_URL",[74,377,268],{"class":129},[74,379,187],{"class":133},[74,381,383,386,389,392,395,398,401,404,406,409,412,415],{"class":76,"line":382},27,[74,384,385],{"class":129},"    f",[74,387,388],{"class":84},"\"postgresql+asyncpg:\u002F\u002F",[74,390,391],{"class":264},"{DB_USER}",[74,393,394],{"class":84},":",[74,396,397],{"class":264},"{DB_PASS}",[74,399,400],{"class":84},"@",[74,402,403],{"class":264},"{DB_HOST}",[74,405,394],{"class":84},[74,407,408],{"class":264},"{DB_PORT}",[74,410,411],{"class":84},"\u002F",[74,413,414],{"class":264},"{DB_NAME}",[74,416,417],{"class":84},"\"\n",[74,419,421],{"class":76,"line":420},28,[74,422,211],{"class":133},[74,424,426],{"class":76,"line":425},29,[74,427,123],{"emptyLinePlaceholder":122},[74,429,431],{"class":76,"line":430},30,[74,432,123],{"emptyLinePlaceholder":122},[74,434,436],{"class":76,"line":435},31,[74,437,241],{"class":240},[74,439,441],{"class":76,"line":440},32,[74,442,443],{"class":240},"# 2. Create the async engine.\n",[74,445,447],{"class":76,"line":446},33,[74,448,449],{"class":240},"#    pool_size    — number of persistent connections kept open.\n",[74,451,453],{"class":76,"line":452},34,[74,454,455],{"class":240},"#    max_overflow — extra connections allowed above pool_size during spikes.\n",[74,457,459],{"class":76,"line":458},35,[74,460,461],{"class":240},"#    pool_pre_ping — sends a lightweight \"SELECT 1\" before each checkout\n",[74,463,465],{"class":76,"line":464},36,[74,466,467],{"class":240},"#                    so stale connections are recycled automatically.\n",[74,469,471],{"class":76,"line":470},37,[74,472,473],{"class":240},"#    echo         — set True during development to log every SQL statement.\n",[74,475,477],{"class":76,"line":476},38,[74,478,241],{"class":240},[74,480,482,485,488],{"class":76,"line":481},39,[74,483,484],{"class":133},"engine ",[74,486,487],{"class":129},"=",[74,489,490],{"class":133}," create_async_engine(\n",[74,492,494,497],{"class":76,"line":493},40,[74,495,496],{"class":264},"    DATABASE_URL",[74,498,499],{"class":133},",\n",[74,501,503,507,509,512],{"class":76,"line":502},41,[74,504,506],{"class":505},"s4XuR","    pool_size",[74,508,487],{"class":129},[74,510,511],{"class":264},"10",[74,513,499],{"class":133},[74,515,517,520,522,525],{"class":76,"line":516},42,[74,518,519],{"class":505},"    max_overflow",[74,521,487],{"class":129},[74,523,524],{"class":264},"5",[74,526,499],{"class":133},[74,528,530,533,535,538],{"class":76,"line":529},43,[74,531,532],{"class":505},"    pool_timeout",[74,534,487],{"class":129},[74,536,537],{"class":264},"30",[74,539,499],{"class":133},[74,541,543,546,548,551],{"class":76,"line":542},44,[74,544,545],{"class":505},"    pool_recycle",[74,547,487],{"class":129},[74,549,550],{"class":264},"1800",[74,552,499],{"class":133},[74,554,556,559,561,564],{"class":76,"line":555},45,[74,557,558],{"class":505},"    pool_pre_ping",[74,560,487],{"class":129},[74,562,563],{"class":264},"True",[74,565,499],{"class":133},[74,567,569,572,574,577],{"class":76,"line":568},46,[74,570,571],{"class":505},"    echo",[74,573,487],{"class":129},[74,575,576],{"class":264},"False",[74,578,499],{"class":133},[74,580,582],{"class":76,"line":581},47,[74,583,211],{"class":133},[74,585,587],{"class":76,"line":586},48,[74,588,123],{"emptyLinePlaceholder":122},[74,590,592],{"class":76,"line":591},49,[74,593,123],{"emptyLinePlaceholder":122},[74,595,597],{"class":76,"line":596},50,[74,598,241],{"class":240},[74,600,602],{"class":76,"line":601},51,[74,603,604],{"class":240},"# 3. Create the session factory.\n",[74,606,608],{"class":76,"line":607},52,[74,609,610],{"class":240},"#    expire_on_commit=False keeps ORM attributes readable after commit\n",[74,612,614],{"class":76,"line":613},53,[74,615,616],{"class":240},"#    without needing a second round-trip to the database.\n",[74,618,620],{"class":76,"line":619},54,[74,621,622],{"class":240},"#    class_=AsyncSession is the default but shown here for clarity.\n",[74,624,626],{"class":76,"line":625},55,[74,627,241],{"class":240},[74,629,631,634,636],{"class":76,"line":630},56,[74,632,633],{"class":133},"AsyncSessionLocal ",[74,635,487],{"class":129},[74,637,638],{"class":133}," async_sessionmaker(\n",[74,640,642,645,647],{"class":76,"line":641},57,[74,643,644],{"class":505},"    bind",[74,646,487],{"class":129},[74,648,649],{"class":133},"engine,\n",[74,651,653,656,658],{"class":76,"line":652},58,[74,654,655],{"class":505},"    class_",[74,657,487],{"class":129},[74,659,660],{"class":133},"AsyncSession,\n",[74,662,664,667,669,671],{"class":76,"line":663},59,[74,665,666],{"class":505},"    expire_on_commit",[74,668,487],{"class":129},[74,670,576],{"class":264},[74,672,499],{"class":133},[74,674,676],{"class":76,"line":675},60,[74,677,211],{"class":133},[74,679,681],{"class":76,"line":680},61,[74,682,123],{"emptyLinePlaceholder":122},[74,684,686],{"class":76,"line":685},62,[74,687,123],{"emptyLinePlaceholder":122},[74,689,691],{"class":76,"line":690},63,[74,692,241],{"class":240},[74,694,696],{"class":76,"line":695},64,[74,697,698],{"class":240},"# 4. Define a minimal ORM model.\n",[74,700,702],{"class":76,"line":701},65,[74,703,241],{"class":240},[74,705,707,710,713,716,719],{"class":76,"line":706},66,[74,708,709],{"class":129},"class",[74,711,712],{"class":80}," Base",[74,714,715],{"class":133},"(",[74,717,718],{"class":80},"DeclarativeBase",[74,720,721],{"class":133},"):\n",[74,723,725],{"class":76,"line":724},67,[74,726,727],{"class":129},"    pass\n",[74,729,731],{"class":76,"line":730},68,[74,732,123],{"emptyLinePlaceholder":122},[74,734,736],{"class":76,"line":735},69,[74,737,123],{"emptyLinePlaceholder":122},[74,739,741,743,746,748,751],{"class":76,"line":740},70,[74,742,709],{"class":129},[74,744,745],{"class":80}," Order",[74,747,715],{"class":133},[74,749,750],{"class":80},"Base",[74,752,721],{"class":133},[74,754,756,759,761],{"class":76,"line":755},71,[74,757,758],{"class":133},"    __tablename__ ",[74,760,487],{"class":129},[74,762,763],{"class":84}," \"orders\"\n",[74,765,767],{"class":76,"line":766},72,[74,768,123],{"emptyLinePlaceholder":122},[74,770,772,775,778,781,784,786,789,792,794,796],{"class":76,"line":771},73,[74,773,774],{"class":264},"    id",[74,776,777],{"class":133},": Mapped[",[74,779,780],{"class":264},"int",[74,782,783],{"class":133},"] ",[74,785,487],{"class":129},[74,787,788],{"class":133}," mapped_column(Integer, ",[74,790,791],{"class":505},"primary_key",[74,793,487],{"class":129},[74,795,563],{"class":264},[74,797,211],{"class":133},[74,799,801,804,807,809,811,814,817],{"class":76,"line":800},74,[74,802,803],{"class":133},"    customer_name: Mapped[",[74,805,806],{"class":264},"str",[74,808,783],{"class":133},[74,810,487],{"class":129},[74,812,813],{"class":133}," mapped_column(String(",[74,815,816],{"class":264},"120",[74,818,364],{"class":133},[74,820,822,825,827,829,831,833,836,839,842,844,847],{"class":76,"line":821},75,[74,823,824],{"class":133},"    status: Mapped[",[74,826,806],{"class":264},[74,828,783],{"class":133},[74,830,487],{"class":129},[74,832,813],{"class":133},[74,834,835],{"class":264},"32",[74,837,838],{"class":133},"), ",[74,840,841],{"class":505},"default",[74,843,487],{"class":129},[74,845,846],{"class":84},"\"pending\"",[74,848,211],{"class":133},[74,850,852],{"class":76,"line":851},76,[74,853,123],{"emptyLinePlaceholder":122},[74,855,857],{"class":76,"line":856},77,[74,858,123],{"emptyLinePlaceholder":122},[74,860,862],{"class":76,"line":861},78,[74,863,241],{"class":240},[74,865,867],{"class":76,"line":866},79,[74,868,869],{"class":240},"# 5. A realistic query — fetch an order by primary key.\n",[74,871,873],{"class":76,"line":872},80,[74,874,241],{"class":240},[74,876,878,881,884,887,890,892,895,898,901],{"class":76,"line":877},81,[74,879,880],{"class":129},"async",[74,882,883],{"class":129}," def",[74,885,886],{"class":80}," get_order",[74,888,889],{"class":133},"(order_id: ",[74,891,780],{"class":264},[74,893,894],{"class":133},") -> Order ",[74,896,897],{"class":129},"|",[74,899,900],{"class":264}," None",[74,902,903],{"class":133},":\n",[74,905,907,910,913,916,919],{"class":76,"line":906},82,[74,908,909],{"class":129},"    async",[74,911,912],{"class":129}," with",[74,914,915],{"class":133}," AsyncSessionLocal() ",[74,917,918],{"class":129},"as",[74,920,921],{"class":133}," session:\n",[74,923,925,928,930,933],{"class":76,"line":924},83,[74,926,927],{"class":133},"        result ",[74,929,487],{"class":129},[74,931,932],{"class":129}," await",[74,934,935],{"class":133}," session.execute(\n",[74,937,939,942,945],{"class":76,"line":938},84,[74,940,941],{"class":133},"            select(Order).where(Order.id ",[74,943,944],{"class":129},"==",[74,946,947],{"class":133}," order_id)\n",[74,949,951],{"class":76,"line":950},85,[74,952,953],{"class":133},"        )\n",[74,955,957,960],{"class":76,"line":956},86,[74,958,959],{"class":129},"        return",[74,961,962],{"class":133}," result.scalars().first()\n",[74,964,966],{"class":76,"line":965},87,[74,967,123],{"emptyLinePlaceholder":122},[74,969,971],{"class":76,"line":970},88,[74,972,123],{"emptyLinePlaceholder":122},[74,974,976],{"class":76,"line":975},89,[74,977,241],{"class":240},[74,979,981],{"class":76,"line":980},90,[74,982,983],{"class":240},"# 6. Graceful shutdown — dispose() closes every connection in the pool\n",[74,985,987],{"class":76,"line":986},91,[74,988,989],{"class":240},"#    and cancels any pending checkouts.  Call this in your lifespan handler\n",[74,991,993],{"class":76,"line":992},92,[74,994,995],{"class":240},"#    or atexit hook so PostgreSQL does not see abrupt disconnects.\n",[74,997,999],{"class":76,"line":998},93,[74,1000,241],{"class":240},[74,1002,1004,1006,1008,1011,1014,1017],{"class":76,"line":1003},94,[74,1005,880],{"class":129},[74,1007,883],{"class":129},[74,1009,1010],{"class":80}," shutdown",[74,1012,1013],{"class":133},"() -> ",[74,1015,1016],{"class":264},"None",[74,1018,903],{"class":133},[74,1020,1022,1025],{"class":76,"line":1021},95,[74,1023,1024],{"class":129},"    await",[74,1026,1027],{"class":133}," engine.dispose()\n",[74,1029,1031],{"class":76,"line":1030},96,[74,1032,123],{"emptyLinePlaceholder":122},[74,1034,1036],{"class":76,"line":1035},97,[74,1037,123],{"emptyLinePlaceholder":122},[74,1039,1041],{"class":76,"line":1040},98,[74,1042,241],{"class":240},[74,1044,1046],{"class":76,"line":1045},99,[74,1047,1048],{"class":240},"# 7. Entry-point for local testing.\n",[74,1050,1052],{"class":76,"line":1051},100,[74,1053,241],{"class":240},[74,1055,1057,1059,1061,1064,1066,1068],{"class":76,"line":1056},101,[74,1058,880],{"class":129},[74,1060,883],{"class":129},[74,1062,1063],{"class":80}," main",[74,1065,1013],{"class":133},[74,1067,1016],{"class":264},[74,1069,903],{"class":133},[74,1071,1073],{"class":76,"line":1072},102,[74,1074,1075],{"class":240},"    # Create tables (dev\u002Ftest only — use Alembic migrations in production).\n",[74,1077,1079,1081,1083,1086,1088],{"class":76,"line":1078},103,[74,1080,909],{"class":129},[74,1082,912],{"class":129},[74,1084,1085],{"class":133}," engine.begin() ",[74,1087,918],{"class":129},[74,1089,1090],{"class":133}," conn:\n",[74,1092,1094,1097],{"class":76,"line":1093},104,[74,1095,1096],{"class":129},"        await",[74,1098,1099],{"class":133}," conn.run_sync(Base.metadata.create_all)\n",[74,1101,1103],{"class":76,"line":1102},105,[74,1104,123],{"emptyLinePlaceholder":122},[74,1106,1108,1111,1113,1115,1118,1121,1123,1126],{"class":76,"line":1107},106,[74,1109,1110],{"class":133},"    order ",[74,1112,487],{"class":129},[74,1114,932],{"class":129},[74,1116,1117],{"class":133}," get_order(",[74,1119,1120],{"class":505},"order_id",[74,1122,487],{"class":129},[74,1124,1125],{"class":264},"1",[74,1127,211],{"class":133},[74,1129,1131,1134],{"class":76,"line":1130},107,[74,1132,1133],{"class":129},"    if",[74,1135,1136],{"class":133}," order:\n",[74,1138,1140,1143,1145,1148,1151,1154,1157,1160,1163,1165,1168,1170,1173,1175,1178,1180,1183],{"class":76,"line":1139},108,[74,1141,1142],{"class":264},"        print",[74,1144,715],{"class":133},[74,1146,1147],{"class":129},"f",[74,1149,1150],{"class":84},"\"Order ",[74,1152,1153],{"class":264},"{",[74,1155,1156],{"class":133},"order.id",[74,1158,1159],{"class":264},"}",[74,1161,1162],{"class":84},": ",[74,1164,1153],{"class":264},[74,1166,1167],{"class":133},"order.customer_name",[74,1169,1159],{"class":264},[74,1171,1172],{"class":84}," — ",[74,1174,1153],{"class":264},[74,1176,1177],{"class":133},"order.status",[74,1179,1159],{"class":264},[74,1181,1182],{"class":84},"\"",[74,1184,211],{"class":133},[74,1186,1188,1191],{"class":76,"line":1187},109,[74,1189,1190],{"class":129},"    else",[74,1192,903],{"class":133},[74,1194,1196,1198,1200,1203],{"class":76,"line":1195},110,[74,1197,1142],{"class":264},[74,1199,715],{"class":133},[74,1201,1202],{"class":84},"\"Order not found.\"",[74,1204,211],{"class":133},[74,1206,1208],{"class":76,"line":1207},111,[74,1209,123],{"emptyLinePlaceholder":122},[74,1211,1213,1215],{"class":76,"line":1212},112,[74,1214,1024],{"class":129},[74,1216,1217],{"class":133}," shutdown()\n",[74,1219,1221],{"class":76,"line":1220},113,[74,1222,123],{"emptyLinePlaceholder":122},[74,1224,1226],{"class":76,"line":1225},114,[74,1227,123],{"emptyLinePlaceholder":122},[74,1229,1231,1234,1237,1240,1243],{"class":76,"line":1230},115,[74,1232,1233],{"class":129},"if",[74,1235,1236],{"class":264}," __name__",[74,1238,1239],{"class":129}," ==",[74,1241,1242],{"class":84}," \"__main__\"",[74,1244,903],{"class":133},[74,1246,1248],{"class":76,"line":1247},116,[74,1249,1250],{"class":133},"    asyncio.run(main())\n",[14,1252,1253],{},"Every import is explicit, the URL is built from environment variables, passwords are percent-encoded, and the engine is disposed before the process exits.",[49,1255,1257],{"id":1256},"execution-context-async-workflow-integration","Execution Context & Async Workflow Integration",[60,1259,1261],{"id":1260},"why-asyncpg-sqlalchemy-async-works-without-greenlets","Why asyncpg + SQLAlchemy async works without greenlets",[14,1263,1264,1265,1268],{},"Traditional SQLAlchemy sync drivers (psycopg2, cx_Oracle) are blocking: each call occupies the OS thread until the database responds. To use them in async frameworks, you would need ",[18,1266,1267],{},"run_in_executor"," to move the blocking call off the event loop thread, or a greenlet-based shim.",[14,1270,1271,1273,1274,1277,1278,277,1281,1284,1285,1288],{},[18,1272,24],{}," is different. It implements the PostgreSQL wire protocol using Python's native ",[18,1275,1276],{},"asyncio"," primitives — ",[18,1279,1280],{},"asyncio.StreamReader",[18,1282,1283],{},"asyncio.StreamWriter",", and ",[18,1286,1287],{},"asyncio.Queue",". When your coroutine awaits a query, control returns to the event loop, which can service other coroutines (handling HTTP requests, processing messages, etc.) while the network round-trip completes. No threads, no greenlets.",[60,1290,1292],{"id":1291},"asyncadaptedqueuepool","AsyncAdaptedQueuePool",[14,1294,1295,1296,1298,1299,1302,1303,1306,1307,21,1310,1313],{},"SQLAlchemy's async layer uses ",[18,1297,1292],{}," rather than the synchronous ",[18,1300,1301],{},"QueuePool",". It wraps the same queue-based pool management logic but replaces blocking ",[18,1304,1305],{},"threading.Condition"," calls with ",[18,1308,1309],{},"asyncio.Event",[18,1311,1312],{},"asyncio.Lock",". The result is that connection borrowing and returning are themselves awaitable operations that yield control to the event loop instead of blocking a thread.",[14,1315,1316,1317,1320,1321,1324,1325,1327],{},"When ",[18,1318,1319],{},"pool_pre_ping=True"," is set, the pool calls ",[18,1322,1323],{},"SELECT 1"," on each connection before handing it to your code. If the connection is stale (the database restarted, a firewall dropped the TCP session, etc.), ",[18,1326,24],{}," raises an error, the pool discards that connection, opens a fresh one, and retries — transparently to your application code.",[60,1329,1331],{"id":1330},"how-the-session-fits-in","How the session fits in",[14,1333,1334,1336,1337,1340,1341,1344,1345,1348,1349,1352,1353,1356,1357,1360,1361,47],{},[18,1335,36],{}," creates a factory that produces ",[18,1338,1339],{},"AsyncSession"," objects. Each session borrows a connection from the pool on first use and returns it when the ",[18,1342,1343],{},"async with"," block exits. Setting ",[18,1346,1347],{},"expire_on_commit=False"," is especially important in async code: with the default ",[18,1350,1351],{},"expire_on_commit=True",", accessing an ORM attribute after ",[18,1354,1355],{},"commit()"," would trigger a lazy load, which is not allowed in async sessions and raises ",[18,1358,1359],{},"MissingGreenlet",". Disabling expiry means attributes retain their in-memory values after commit so you can safely return domain objects from service functions without an extra ",[18,1362,1363],{},"await session.refresh(obj)",[14,1365,1366,1367,1370,1371,47],{},"For further tuning options, including ",[18,1368,1369],{},"pool_size"," guidance for high-traffic services, see ",[43,1372,1374],{"href":1373},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002Fsetting-up-asyncpg-connection-pool-size-for-high-concurrency\u002F","Setting Up asyncpg Connection Pool Size for High Concurrency",[14,1376,1377,1378,1382],{},"The broader async engine and dialect landscape — including when to choose asyncpg versus psycopg3's async mode — is covered in the ",[43,1379,1381],{"href":1380},"\u002Fasync-engines-dialects-and-connection-pooling\u002F","Async Engines, Dialects, and Connection Pooling"," section.",[49,1384,1386],{"id":1385},"resolving-warnings-errors-common-mistakes","Resolving Warnings, Errors & Common Mistakes",[1388,1389,1390,1406],"table",{},[1391,1392,1393],"thead",{},[1394,1395,1396,1400,1403],"tr",{},[1397,1398,1399],"th",{},"Exact error string",[1397,1401,1402],{},"Root cause",[1397,1404,1405],{},"Production fix",[1407,1408,1409,1440,1476,1509,1541],"tbody",{},[1394,1410,1411,1417,1422],{},[1412,1413,1414],"td",{},[18,1415,1416],{},"ModuleNotFoundError: No module named 'asyncpg'",[1412,1418,1419,1421],{},[18,1420,24],{}," is not installed in the active virtual environment. This is the most common first-run error.",[1412,1423,1424,1425,1428,1429,1432,1433,1436,1437,47],{},"Run ",[18,1426,1427],{},"pip install asyncpg"," inside the correct venv. Verify with ",[18,1430,1431],{},"pip show asyncpg",". Pin the version in ",[18,1434,1435],{},"requirements.txt"," or ",[18,1438,1439],{},"pyproject.toml",[1394,1441,1442,1447,1453],{},[1412,1443,1444],{},[18,1445,1446],{},"sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called",[1412,1448,1449,1450,1452],{},"ORM lazy-loading was triggered inside an async context. Typically caused by accessing a relationship attribute after commit without ",[18,1451,1347],{},", or by calling a sync SQLAlchemy function that internally tries to emit SQL.",[1412,1454,1455,1456,1458,1459,1461,1462,1436,1465,1468,1469,277,1472,1475],{},"Set ",[18,1457,1347],{}," on ",[18,1460,36],{},". Use ",[18,1463,1464],{},"selectinload()",[18,1466,1467],{},"joinedload()"," for relationships instead of relying on lazy loads. Never call sync SQLAlchemy APIs (",[18,1470,1471],{},"Session.execute",[18,1473,1474],{},"session.get",") inside async code.",[1394,1477,1478,1486,1496],{},[1412,1479,1480,1436,1483],{},[18,1481,1482],{},"ValueError: Could not parse rfc1738 URL from string",[18,1484,1485],{},"ArgumentError: Could not parse SQLAlchemy URL",[1412,1487,1488,1489,1492,1493,1495],{},"The dialect string is malformed — often ",[18,1490,1491],{},"postgresql:\u002F\u002F"," instead of ",[18,1494,28],{},", or a special character in the password is not percent-encoded.",[1412,1497,1498,1499,1502,1503,1505,1506,47],{},"Use ",[18,1500,1501],{},"urllib.parse.quote_plus()"," to encode the password component. Use the exact dialect string ",[18,1504,99],{},". Validate by printing the URL before passing it to ",[18,1507,1508],{},"create_async_engine",[1394,1510,1511,1519,1526],{},[1412,1512,1513,1436,1516],{},[18,1514,1515],{},"asyncio.TimeoutError",[18,1517,1518],{},"sqlalchemy.exc.TimeoutError: QueuePool limit … overflow … timed out",[1412,1520,1521,1522,1525],{},"All connections in the pool (including overflow slots) are checked out, and no connection became available within ",[18,1523,1524],{},"pool_timeout"," seconds. Common under sustained high concurrency or when sessions are not closed promptly.",[1412,1527,1528,1529,1531,1532,1534,1535,21,1537,1540],{},"Ensure every ",[18,1530,1339],{}," is used inside ",[18,1533,1343],{},". Increase ",[18,1536,1369],{},[18,1538,1539],{},"max_overflow"," proportionally to the number of concurrent requests. Add connection lifecycle monitoring (see pool event hooks).",[1394,1542,1543,1551,1554],{},[1412,1544,1545,1436,1548],{},[18,1546,1547],{},"sqlalchemy.exc.OperationalError: (asyncpg.exceptions.ConnectionDoesNotExistError) connection was closed",[18,1549,1550],{},"connection refused",[1412,1552,1553],{},"The database is unreachable — wrong host\u002Fport, PostgreSQL not running, or a firewall rule blocking the connection.",[1412,1555,1556,1557,21,1559,1561,1562,1565,1566,1568],{},"Verify ",[18,1558,265],{},[18,1560,288],{}," environment variables. Confirm PostgreSQL is running (",[18,1563,1564],{},"pg_isready -h $DB_HOST -p $DB_PORT","). Enable ",[18,1567,1319],{}," so the pool recovers from transient outages without propagating stale-connection errors to application code.",[49,1570,1572],{"id":1571},"advanced-engine-initialization-optimization","Advanced Engine Initialization Optimization",[60,1574,1576],{"id":1575},"pre-warming-the-connection-pool-on-startup","Pre-warming the connection pool on startup",[14,1578,1579,1580,21,1583,1586],{},"By default, SQLAlchemy opens connections lazily — the first requests after startup compete for fresh connection slots, which can add noticeable latency to your cold-start response times. In production services where latency SLOs are tight (for example, a B2B tenant management API serving ",[18,1581,1582],{},"Invoice",[18,1584,1585],{},"Tenant"," queries), pre-warming the pool during application startup eliminates that first-request spike.",[65,1588,1590],{"className":107,"code":1589,"language":109,"meta":70,"style":70},"\"\"\"pool_warmup.py — pre-warm the asyncpg connection pool at application startup.\"\"\"\n\nimport asyncio\nfrom sqlalchemy import text\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncConnection\nfrom typing import Sequence\n\nengine = create_async_engine(\n    \"postgresql+asyncpg:\u002F\u002Fappuser:s3cr3t@localhost:5432\u002Fstorefront\",\n    pool_size=10,\n    max_overflow=5,\n    pool_pre_ping=True,\n)\n\n\nasync def _ping(conn: AsyncConnection) -> None:\n    \"\"\"Validate a single connection with a lightweight query.\"\"\"\n    await conn.execute(text(\"SELECT 1\"))\n\n\nasync def prewarm_pool(pool_size: int = 10) -> None:\n    \"\"\"\n    Open `pool_size` connections concurrently so the pool is fully\n    populated before the first real request arrives.\n    \"\"\"\n    async with asyncio.TaskGroup() as tg:\n        conns: list[AsyncConnection] = []\n        for _ in range(pool_size):\n            conn = await engine.connect()\n            conns.append(conn)\n            tg.create_task(_ping(conn))\n\n    # Return all connections to the pool.\n    for conn in conns:\n        await conn.close()\n\n    print(f\"Pool pre-warmed: {pool_size} connections ready.\")\n\n\n# FastAPI lifespan example:\n#\n# from contextlib import asynccontextmanager\n# from fastapi import FastAPI\n#\n# @asynccontextmanager\n# async def lifespan(app: FastAPI):\n#     await prewarm_pool(pool_size=10)\n#     yield\n#     await engine.dispose()\n#\n# app = FastAPI(lifespan=lifespan)\n",[18,1591,1592,1597,1601,1607,1618,1629,1641,1645,1653,1660,1670,1680,1690,1694,1698,1702,1718,1723,1735,1739,1743,1769,1774,1779,1784,1788,1802,1812,1829,1841,1846,1851,1855,1860,1873,1880,1884,1907,1911,1915,1920,1925,1930,1935,1939,1944,1949,1954,1959,1964,1968],{"__ignoreMap":70},[74,1593,1594],{"class":76,"line":77},[74,1595,1596],{"class":84},"\"\"\"pool_warmup.py — pre-warm the asyncpg connection pool at application startup.\"\"\"\n",[74,1598,1599],{"class":76,"line":119},[74,1600,123],{"emptyLinePlaceholder":122},[74,1602,1603,1605],{"class":76,"line":126},[74,1604,130],{"class":129},[74,1606,134],{"class":133},[74,1608,1609,1611,1613,1615],{"class":76,"line":137},[74,1610,148],{"class":129},[74,1612,169],{"class":133},[74,1614,130],{"class":129},[74,1616,1617],{"class":133}," text\n",[74,1619,1620,1622,1624,1626],{"class":76,"line":145},[74,1621,148],{"class":129},[74,1623,182],{"class":133},[74,1625,130],{"class":129},[74,1627,1628],{"class":133}," create_async_engine, AsyncConnection\n",[74,1630,1631,1633,1636,1638],{"class":76,"line":159},[74,1632,148],{"class":129},[74,1634,1635],{"class":133}," typing ",[74,1637,130],{"class":129},[74,1639,1640],{"class":133}," Sequence\n",[74,1642,1643],{"class":76,"line":164},[74,1644,123],{"emptyLinePlaceholder":122},[74,1646,1647,1649,1651],{"class":76,"line":177},[74,1648,484],{"class":133},[74,1650,487],{"class":129},[74,1652,490],{"class":133},[74,1654,1655,1658],{"class":76,"line":190},[74,1656,1657],{"class":84},"    \"postgresql+asyncpg:\u002F\u002Fappuser:s3cr3t@localhost:5432\u002Fstorefront\"",[74,1659,499],{"class":133},[74,1661,1662,1664,1666,1668],{"class":76,"line":196},[74,1663,506],{"class":505},[74,1665,487],{"class":129},[74,1667,511],{"class":264},[74,1669,499],{"class":133},[74,1671,1672,1674,1676,1678],{"class":76,"line":202},[74,1673,519],{"class":505},[74,1675,487],{"class":129},[74,1677,524],{"class":264},[74,1679,499],{"class":133},[74,1681,1682,1684,1686,1688],{"class":76,"line":208},[74,1683,558],{"class":505},[74,1685,487],{"class":129},[74,1687,563],{"class":264},[74,1689,499],{"class":133},[74,1691,1692],{"class":76,"line":214},[74,1693,211],{"class":133},[74,1695,1696],{"class":76,"line":227},[74,1697,123],{"emptyLinePlaceholder":122},[74,1699,1700],{"class":76,"line":232},[74,1701,123],{"emptyLinePlaceholder":122},[74,1703,1704,1706,1708,1711,1714,1716],{"class":76,"line":237},[74,1705,880],{"class":129},[74,1707,883],{"class":129},[74,1709,1710],{"class":80}," _ping",[74,1712,1713],{"class":133},"(conn: AsyncConnection) -> ",[74,1715,1016],{"class":264},[74,1717,903],{"class":133},[74,1719,1720],{"class":76,"line":244},[74,1721,1722],{"class":84},"    \"\"\"Validate a single connection with a lightweight query.\"\"\"\n",[74,1724,1725,1727,1730,1733],{"class":76,"line":250},[74,1726,1024],{"class":129},[74,1728,1729],{"class":133}," conn.execute(text(",[74,1731,1732],{"class":84},"\"SELECT 1\"",[74,1734,364],{"class":133},[74,1736,1737],{"class":76,"line":256},[74,1738,123],{"emptyLinePlaceholder":122},[74,1740,1741],{"class":76,"line":261},[74,1742,123],{"emptyLinePlaceholder":122},[74,1744,1745,1747,1749,1752,1755,1757,1759,1762,1765,1767],{"class":76,"line":285},[74,1746,880],{"class":129},[74,1748,883],{"class":129},[74,1750,1751],{"class":80}," prewarm_pool",[74,1753,1754],{"class":133},"(pool_size: ",[74,1756,780],{"class":264},[74,1758,268],{"class":129},[74,1760,1761],{"class":264}," 10",[74,1763,1764],{"class":133},") -> ",[74,1766,1016],{"class":264},[74,1768,903],{"class":133},[74,1770,1771],{"class":76,"line":305},[74,1772,1773],{"class":84},"    \"\"\"\n",[74,1775,1776],{"class":76,"line":325},[74,1777,1778],{"class":84},"    Open `pool_size` connections concurrently so the pool is fully\n",[74,1780,1781],{"class":76,"line":345},[74,1782,1783],{"class":84},"    populated before the first real request arrives.\n",[74,1785,1786],{"class":76,"line":367},[74,1787,1773],{"class":84},[74,1789,1790,1792,1794,1797,1799],{"class":76,"line":372},[74,1791,909],{"class":129},[74,1793,912],{"class":129},[74,1795,1796],{"class":133}," asyncio.TaskGroup() ",[74,1798,918],{"class":129},[74,1800,1801],{"class":133}," tg:\n",[74,1803,1804,1807,1809],{"class":76,"line":382},[74,1805,1806],{"class":133},"        conns: list[AsyncConnection] ",[74,1808,487],{"class":129},[74,1810,1811],{"class":133}," []\n",[74,1813,1814,1817,1820,1823,1826],{"class":76,"line":420},[74,1815,1816],{"class":129},"        for",[74,1818,1819],{"class":133}," _ ",[74,1821,1822],{"class":129},"in",[74,1824,1825],{"class":264}," range",[74,1827,1828],{"class":133},"(pool_size):\n",[74,1830,1831,1834,1836,1838],{"class":76,"line":425},[74,1832,1833],{"class":133},"            conn ",[74,1835,487],{"class":129},[74,1837,932],{"class":129},[74,1839,1840],{"class":133}," engine.connect()\n",[74,1842,1843],{"class":76,"line":430},[74,1844,1845],{"class":133},"            conns.append(conn)\n",[74,1847,1848],{"class":76,"line":435},[74,1849,1850],{"class":133},"            tg.create_task(_ping(conn))\n",[74,1852,1853],{"class":76,"line":440},[74,1854,123],{"emptyLinePlaceholder":122},[74,1856,1857],{"class":76,"line":446},[74,1858,1859],{"class":240},"    # Return all connections to the pool.\n",[74,1861,1862,1865,1868,1870],{"class":76,"line":452},[74,1863,1864],{"class":129},"    for",[74,1866,1867],{"class":133}," conn ",[74,1869,1822],{"class":129},[74,1871,1872],{"class":133}," conns:\n",[74,1874,1875,1877],{"class":76,"line":458},[74,1876,1096],{"class":129},[74,1878,1879],{"class":133}," conn.close()\n",[74,1881,1882],{"class":76,"line":464},[74,1883,123],{"emptyLinePlaceholder":122},[74,1885,1886,1889,1891,1893,1896,1898,1900,1902,1905],{"class":76,"line":470},[74,1887,1888],{"class":264},"    print",[74,1890,715],{"class":133},[74,1892,1147],{"class":129},[74,1894,1895],{"class":84},"\"Pool pre-warmed: ",[74,1897,1153],{"class":264},[74,1899,1369],{"class":133},[74,1901,1159],{"class":264},[74,1903,1904],{"class":84}," connections ready.\"",[74,1906,211],{"class":133},[74,1908,1909],{"class":76,"line":476},[74,1910,123],{"emptyLinePlaceholder":122},[74,1912,1913],{"class":76,"line":481},[74,1914,123],{"emptyLinePlaceholder":122},[74,1916,1917],{"class":76,"line":493},[74,1918,1919],{"class":240},"# FastAPI lifespan example:\n",[74,1921,1922],{"class":76,"line":502},[74,1923,1924],{"class":240},"#\n",[74,1926,1927],{"class":76,"line":516},[74,1928,1929],{"class":240},"# from contextlib import asynccontextmanager\n",[74,1931,1932],{"class":76,"line":529},[74,1933,1934],{"class":240},"# from fastapi import FastAPI\n",[74,1936,1937],{"class":76,"line":542},[74,1938,1924],{"class":240},[74,1940,1941],{"class":76,"line":555},[74,1942,1943],{"class":240},"# @asynccontextmanager\n",[74,1945,1946],{"class":76,"line":568},[74,1947,1948],{"class":240},"# async def lifespan(app: FastAPI):\n",[74,1950,1951],{"class":76,"line":581},[74,1952,1953],{"class":240},"#     await prewarm_pool(pool_size=10)\n",[74,1955,1956],{"class":76,"line":586},[74,1957,1958],{"class":240},"#     yield\n",[74,1960,1961],{"class":76,"line":591},[74,1962,1963],{"class":240},"#     await engine.dispose()\n",[74,1965,1966],{"class":76,"line":596},[74,1967,1924],{"class":240},[74,1969,1970],{"class":76,"line":601},[74,1971,1972],{"class":240},"# app = FastAPI(lifespan=lifespan)\n",[14,1974,1975,1978],{},[18,1976,1977],{},"asyncio.TaskGroup"," (Python 3.11+) runs all pings concurrently and raises the first exception if any connection fails, giving you a fast fail-safe during startup rather than a slow cascade of errors under load.",[14,1980,1981,1982,1985,1986,1988],{},"An alternative to explicit pre-warming is the ",[18,1983,1984],{},"creator="," parameter, which lets you supply a factory function that ",[18,1987,24],{}," calls when it needs a new raw connection. This is useful when connecting through a proxy that requires a per-connection handshake (for example, Cloud SQL Auth Proxy or PgBouncer with SCRAM authentication), where the default connection string approach is insufficient.",[49,1990,1992],{"id":1991},"frequently-asked-questions","Frequently Asked Questions",[14,1994,1995],{},[1996,1997,1998,1999,2001],"strong",{},"Can I reuse the same ",[18,2000,1508],{}," engine for synchronous SQLAlchemy code?",[14,2003,2004,2005,2008,2009,1436,2012,2015,2016,2019,2020,2023],{},"No. ",[18,2006,2007],{},"AsyncEngine"," wraps an async-only connection pool and cannot be passed to synchronous ",[18,2010,2011],{},"Session",[18,2013,2014],{},"Connection"," objects. If you need both sync and async access in the same application (for example, during a migration period), create two separate engines — one with ",[18,2017,2018],{},"create_engine(\"postgresql+psycopg2:\u002F\u002F...\")"," for sync code and one with ",[18,2021,2022],{},"create_async_engine(\"postgresql+asyncpg:\u002F\u002F...\")"," for async code. They can point at the same database but manage independent connection pools.",[14,2025,2026],{},[1996,2027,2028],{},"How do I write tests for async database code without a real PostgreSQL instance?",[14,2030,1498,2031,2034],{},[18,2032,2033],{},"aiosqlite"," as an in-process SQLite driver for unit and integration tests:",[65,2036,2038],{"className":107,"code":2037,"language":109,"meta":70,"style":70},"# conftest.py\nimport pytest_asyncio\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\n\n@pytest_asyncio.fixture\nasync def db_session():\n    test_engine = create_async_engine(\n        \"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\",\n        echo=False,\n    )\n    async with test_engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n    factory = async_sessionmaker(bind=test_engine, expire_on_commit=False)\n    async with factory() as session:\n        yield session\n\n    await test_engine.dispose()\n",[18,2039,2040,2045,2052,2063,2067,2072,2084,2093,2100,2111,2116,2129,2135,2139,2166,2179,2187,2191],{"__ignoreMap":70},[74,2041,2042],{"class":76,"line":77},[74,2043,2044],{"class":240},"# conftest.py\n",[74,2046,2047,2049],{"class":76,"line":119},[74,2048,130],{"class":129},[74,2050,2051],{"class":133}," pytest_asyncio\n",[74,2053,2054,2056,2058,2060],{"class":76,"line":126},[74,2055,148],{"class":129},[74,2057,182],{"class":133},[74,2059,130],{"class":129},[74,2061,2062],{"class":133}," create_async_engine, async_sessionmaker, AsyncSession\n",[74,2064,2065],{"class":76,"line":137},[74,2066,123],{"emptyLinePlaceholder":122},[74,2068,2069],{"class":76,"line":145},[74,2070,2071],{"class":80},"@pytest_asyncio.fixture\n",[74,2073,2074,2076,2078,2081],{"class":76,"line":159},[74,2075,880],{"class":129},[74,2077,883],{"class":129},[74,2079,2080],{"class":80}," db_session",[74,2082,2083],{"class":133},"():\n",[74,2085,2086,2089,2091],{"class":76,"line":164},[74,2087,2088],{"class":133},"    test_engine ",[74,2090,487],{"class":129},[74,2092,490],{"class":133},[74,2094,2095,2098],{"class":76,"line":177},[74,2096,2097],{"class":84},"        \"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\"",[74,2099,499],{"class":133},[74,2101,2102,2105,2107,2109],{"class":76,"line":190},[74,2103,2104],{"class":505},"        echo",[74,2106,487],{"class":129},[74,2108,576],{"class":264},[74,2110,499],{"class":133},[74,2112,2113],{"class":76,"line":196},[74,2114,2115],{"class":133},"    )\n",[74,2117,2118,2120,2122,2125,2127],{"class":76,"line":202},[74,2119,909],{"class":129},[74,2121,912],{"class":129},[74,2123,2124],{"class":133}," test_engine.begin() ",[74,2126,918],{"class":129},[74,2128,1090],{"class":133},[74,2130,2131,2133],{"class":76,"line":208},[74,2132,1096],{"class":129},[74,2134,1099],{"class":133},[74,2136,2137],{"class":76,"line":214},[74,2138,123],{"emptyLinePlaceholder":122},[74,2140,2141,2144,2146,2149,2152,2154,2157,2160,2162,2164],{"class":76,"line":227},[74,2142,2143],{"class":133},"    factory ",[74,2145,487],{"class":129},[74,2147,2148],{"class":133}," async_sessionmaker(",[74,2150,2151],{"class":505},"bind",[74,2153,487],{"class":129},[74,2155,2156],{"class":133},"test_engine, ",[74,2158,2159],{"class":505},"expire_on_commit",[74,2161,487],{"class":129},[74,2163,576],{"class":264},[74,2165,211],{"class":133},[74,2167,2168,2170,2172,2175,2177],{"class":76,"line":232},[74,2169,909],{"class":129},[74,2171,912],{"class":129},[74,2173,2174],{"class":133}," factory() ",[74,2176,918],{"class":129},[74,2178,921],{"class":133},[74,2180,2181,2184],{"class":76,"line":237},[74,2182,2183],{"class":129},"        yield",[74,2185,2186],{"class":133}," session\n",[74,2188,2189],{"class":76,"line":244},[74,2190,123],{"emptyLinePlaceholder":122},[74,2192,2193,2195],{"class":76,"line":250},[74,2194,1024],{"class":129},[74,2196,2197],{"class":133}," test_engine.dispose()\n",[14,2199,2200,2201,2203,2204,2207,2208,2211],{},"Install ",[18,2202,2033],{}," alongside ",[18,2205,2206],{},"pytest-asyncio"," (",[18,2209,2210],{},"pip install aiosqlite pytest-asyncio","). Be aware that SQLite does not support all PostgreSQL-specific types and functions; for dialect-specific queries, prefer testing against a real PostgreSQL instance running in Docker.",[14,2213,2214],{},[1996,2215,2216,2217,2219],{},"What happens if I forget to call ",[18,2218,40],{}," on shutdown?",[14,2221,2222,2223,2226,2227,2230,2231,2234,2235,2238,2239,2241,2242,2245],{},"The OS will reclaim all open file descriptors when the process exits, so your application will not hang indefinitely. However, PostgreSQL will see abrupt TCP disconnects rather than clean ",[18,2224,2225],{},"CLOSE"," messages. This leaves connections in the ",[18,2228,2229],{},"idle"," state on the server side until ",[18,2232,2233],{},"tcp_keepalives_idle"," expires (often minutes), which temporarily consumes ",[18,2236,2237],{},"max_connections"," slots. In high-restart-frequency environments (Kubernetes rolling deploys, Lambda cold starts) this can cause connection exhaustion on the database server. Always call ",[18,2240,40],{}," in a lifespan handler, ",[18,2243,2244],{},"atexit"," hook, or signal handler to send clean disconnects.",[14,2247,2248],{},[1996,2249,2250,2251,2254],{},"Is ",[18,2252,2253],{},"echo=True"," safe in production for debugging a specific query?",[14,2256,2257,2258,2260,2261,2264,2265,2268,2269,2272,2273,2276],{},"Enabling ",[18,2259,2253],{}," writes every SQL statement and its parameters to the Python logging system at the ",[18,2262,2263],{},"INFO"," level. This is fine for short debugging sessions but carries two risks in production: it can expose sensitive data (customer names, invoice amounts, authentication tokens) in log aggregation systems, and it adds measurable I\u002FO overhead under high query volume. Instead, configure the ",[18,2266,2267],{},"sqlalchemy.engine"," logger at ",[18,2270,2271],{},"DEBUG"," level only for the specific request or task you are investigating, then revert. SQLAlchemy also supports ",[18,2274,2275],{},"echo=\"debug\""," which additionally logs result rows — avoid this in production entirely.",[49,2278,2280],{"id":2279},"related","Related",[2282,2283,2284,2290,2295,2302],"ul",{},[2285,2286,2287,2289],"li",{},[43,2288,46],{"href":45}," — parent guide covering the full engine configuration surface",[2285,2291,2292,2294],{},[43,2293,1374],{"href":1373}," — production pool sizing formulas and overflow tuning",[2285,2296,2297,2301],{},[43,2298,2300],{"href":2299},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fhandling-connection-leaks-and-pool-exhaustion\u002F","Handling Connection Leaks and Pool Exhaustion"," — diagnosing and fixing pool exhaustion in running services",[2285,2303,2304,2308],{},[43,2305,2307],{"href":2306},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fchoosing-between-asyncpg-and-psycopg-async-drivers\u002F","Choosing Between asyncpg and psycopg Async Drivers"," — when to switch from asyncpg to psycopg3's async mode",[2310,2311,2312],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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);}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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}",{"title":70,"searchDepth":119,"depth":119,"links":2314},[2315,2319,2324,2325,2328,2329],{"id":51,"depth":119,"text":52,"children":2316},[2317,2318],{"id":62,"depth":126,"text":63},{"id":103,"depth":126,"text":104},{"id":1256,"depth":119,"text":1257,"children":2320},[2321,2322,2323],{"id":1260,"depth":126,"text":1261},{"id":1291,"depth":126,"text":1292},{"id":1330,"depth":126,"text":1331},{"id":1385,"depth":119,"text":1386},{"id":1571,"depth":119,"text":1572,"children":2326},[2327],{"id":1575,"depth":126,"text":1576},{"id":1991,"depth":119,"text":1992},{"id":2279,"depth":119,"text":2280},"To set up a SQLAlchemy async engine from scratch, install sqlalchemy and asyncpg, build a postgresql+asyncpg:\u002F\u002F URL, call create_async_engine(), wrap it with async_sessionmaker, run your first query, and call await engine.dispose() on shutdown — all covered in Configuring Async Engines and Connection Pools.","md",{"date":2333},"2026-06-18","\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002Fsetting-up-an-async-engine-from-scratch",{"title":5,"description":2330},"async-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002Fsetting-up-an-async-engine-from-scratch\u002Findex","85Q7e3F3D42cUZeF1Ar8DeNsas2x3f69QUP403I0UQs",1781810028982]