[{"data":1,"prerenderedAt":2856},["ShallowReactive",2],{"page-\u002Falembic-async-migrations-and-schema-evolution\u002Fautogenerating-and-reviewing-migration-scripts\u002Fexcluding-tables-and-schemas-from-alembic-autogenerate\u002F":3},{"id":4,"title":5,"body":6,"description":2848,"extension":2849,"meta":2850,"navigation":106,"path":2852,"seo":2853,"stem":2854,"__hash__":2855},"content\u002Falembic-async-migrations-and-schema-evolution\u002Fautogenerating-and-reviewing-migration-scripts\u002Fexcluding-tables-and-schemas-from-alembic-autogenerate\u002Findex.md","Excluding Tables and Schemas from Alembic Autogenerate",{"type":7,"value":8,"toc":2829},"minimark",[9,13,40,45,498,502,508,635,645,650,656,875,890,901,905,908,1203,1209,1213,1481,1485,1489,1492,1884,1888,1894,2002,2009,2013,2020,2202,2205,2209,2217,2274,2287,2301,2305,2308,2345,2348,2352,2355,2695,2698,2702,2712,2739,2770,2791,2795,2825],[10,11,5],"h1",{"id":12},"excluding-tables-and-schemas-from-alembic-autogenerate",[14,15,16,17,21,22,25,26,29,30,33,34,39],"p",{},"Use the ",[18,19,20],"code",{},"include_object"," or ",[18,23,24],{},"include_name"," hook in ",[18,27,28],{},"env.py"," to tell Alembic which tables and schemas to ignore during autogenerate — this prevents it from emitting spurious ",[18,31,32],{},"op.drop_table"," directives for tables your application does not own, and is a critical step in any ",[35,36,38],"a",{"href":37},"\u002Falembic-async-migrations-and-schema-evolution\u002Fautogenerating-and-reviewing-migration-scripts\u002F","autogenerate workflow",".",[41,42,44],"h2",{"id":43},"quick-answer","Quick Answer",[46,47,52],"pre",{"className":48,"code":49,"language":50,"meta":51,"style":51},"language-python shiki shiki-themes github-light github-dark","# BEFORE — no filter: autogenerate drops spatial_ref_sys, celery_taskmeta, etc.\ncontext.configure(\n    connection=connection,\n    target_metadata=target_metadata,\n)\n\n# AFTER — include_object hook keeps autogenerate scoped to your app's tables\nEXCLUDE_TABLES = frozenset({\n    # PostGIS geometry tables\n    \"spatial_ref_sys\",\n    \"raster_columns\",\n    \"raster_overviews\",\n    \"geometry_columns\",\n    # Celery result backend\n    \"celery_taskmeta\",\n    \"celery_tasksetmeta\",\n    # Django admin tables (shared database)\n    \"django_migrations\",\n    \"django_content_type\",\n    \"auth_user\",\n    \"auth_permission\",\n    \"auth_group\",\n})\n\nEXCLUDE_SCHEMAS = frozenset({\"tiger\", \"tiger_data\", \"topology\"})  # PostGIS\n\n\ndef include_object(object, name, type_, reflected, compare_to):\n    if type_ == \"table\":\n        if name in EXCLUDE_TABLES:\n            return False\n        # object.schema is None for the default schema\n        if getattr(object, \"schema\", None) in EXCLUDE_SCHEMAS:\n            return False\n    if type_ == \"schema\" and name in EXCLUDE_SCHEMAS:\n        return False\n    return True\n\n\ncontext.configure(\n    connection=connection,\n    target_metadata=target_metadata,\n    include_schemas=True,\n    include_object=include_object,\n)\n","python","",[18,53,54,63,70,84,95,101,108,114,130,136,146,154,162,170,176,184,192,198,206,214,222,230,238,244,249,282,287,292,305,323,340,349,355,389,396,419,427,436,441,446,451,460,469,482,493],{"__ignoreMap":51},[55,56,59],"span",{"class":57,"line":58},"line",1,[55,60,62],{"class":61},"sJ8bj","# BEFORE — no filter: autogenerate drops spatial_ref_sys, celery_taskmeta, etc.\n",[55,64,66],{"class":57,"line":65},2,[55,67,69],{"class":68},"sVt8B","context.configure(\n",[55,71,73,77,81],{"class":57,"line":72},3,[55,74,76],{"class":75},"s4XuR","    connection",[55,78,80],{"class":79},"szBVR","=",[55,82,83],{"class":68},"connection,\n",[55,85,87,90,92],{"class":57,"line":86},4,[55,88,89],{"class":75},"    target_metadata",[55,91,80],{"class":79},[55,93,94],{"class":68},"target_metadata,\n",[55,96,98],{"class":57,"line":97},5,[55,99,100],{"class":68},")\n",[55,102,104],{"class":57,"line":103},6,[55,105,107],{"emptyLinePlaceholder":106},true,"\n",[55,109,111],{"class":57,"line":110},7,[55,112,113],{"class":61},"# AFTER — include_object hook keeps autogenerate scoped to your app's tables\n",[55,115,117,121,124,127],{"class":57,"line":116},8,[55,118,120],{"class":119},"sj4cs","EXCLUDE_TABLES",[55,122,123],{"class":79}," =",[55,125,126],{"class":119}," frozenset",[55,128,129],{"class":68},"({\n",[55,131,133],{"class":57,"line":132},9,[55,134,135],{"class":61},"    # PostGIS geometry tables\n",[55,137,139,143],{"class":57,"line":138},10,[55,140,142],{"class":141},"sZZnC","    \"spatial_ref_sys\"",[55,144,145],{"class":68},",\n",[55,147,149,152],{"class":57,"line":148},11,[55,150,151],{"class":141},"    \"raster_columns\"",[55,153,145],{"class":68},[55,155,157,160],{"class":57,"line":156},12,[55,158,159],{"class":141},"    \"raster_overviews\"",[55,161,145],{"class":68},[55,163,165,168],{"class":57,"line":164},13,[55,166,167],{"class":141},"    \"geometry_columns\"",[55,169,145],{"class":68},[55,171,173],{"class":57,"line":172},14,[55,174,175],{"class":61},"    # Celery result backend\n",[55,177,179,182],{"class":57,"line":178},15,[55,180,181],{"class":141},"    \"celery_taskmeta\"",[55,183,145],{"class":68},[55,185,187,190],{"class":57,"line":186},16,[55,188,189],{"class":141},"    \"celery_tasksetmeta\"",[55,191,145],{"class":68},[55,193,195],{"class":57,"line":194},17,[55,196,197],{"class":61},"    # Django admin tables (shared database)\n",[55,199,201,204],{"class":57,"line":200},18,[55,202,203],{"class":141},"    \"django_migrations\"",[55,205,145],{"class":68},[55,207,209,212],{"class":57,"line":208},19,[55,210,211],{"class":141},"    \"django_content_type\"",[55,213,145],{"class":68},[55,215,217,220],{"class":57,"line":216},20,[55,218,219],{"class":141},"    \"auth_user\"",[55,221,145],{"class":68},[55,223,225,228],{"class":57,"line":224},21,[55,226,227],{"class":141},"    \"auth_permission\"",[55,229,145],{"class":68},[55,231,233,236],{"class":57,"line":232},22,[55,234,235],{"class":141},"    \"auth_group\"",[55,237,145],{"class":68},[55,239,241],{"class":57,"line":240},23,[55,242,243],{"class":68},"})\n",[55,245,247],{"class":57,"line":246},24,[55,248,107],{"emptyLinePlaceholder":106},[55,250,252,255,257,259,262,265,268,271,273,276,279],{"class":57,"line":251},25,[55,253,254],{"class":119},"EXCLUDE_SCHEMAS",[55,256,123],{"class":79},[55,258,126],{"class":119},[55,260,261],{"class":68},"({",[55,263,264],{"class":141},"\"tiger\"",[55,266,267],{"class":68},", ",[55,269,270],{"class":141},"\"tiger_data\"",[55,272,267],{"class":68},[55,274,275],{"class":141},"\"topology\"",[55,277,278],{"class":68},"})  ",[55,280,281],{"class":61},"# PostGIS\n",[55,283,285],{"class":57,"line":284},26,[55,286,107],{"emptyLinePlaceholder":106},[55,288,290],{"class":57,"line":289},27,[55,291,107],{"emptyLinePlaceholder":106},[55,293,295,298,302],{"class":57,"line":294},28,[55,296,297],{"class":79},"def",[55,299,301],{"class":300},"sScJk"," include_object",[55,303,304],{"class":68},"(object, name, type_, reflected, compare_to):\n",[55,306,308,311,314,317,320],{"class":57,"line":307},29,[55,309,310],{"class":79},"    if",[55,312,313],{"class":68}," type_ ",[55,315,316],{"class":79},"==",[55,318,319],{"class":141}," \"table\"",[55,321,322],{"class":68},":\n",[55,324,326,329,332,335,338],{"class":57,"line":325},30,[55,327,328],{"class":79},"        if",[55,330,331],{"class":68}," name ",[55,333,334],{"class":79},"in",[55,336,337],{"class":119}," EXCLUDE_TABLES",[55,339,322],{"class":68},[55,341,343,346],{"class":57,"line":342},31,[55,344,345],{"class":79},"            return",[55,347,348],{"class":119}," False\n",[55,350,352],{"class":57,"line":351},32,[55,353,354],{"class":61},"        # object.schema is None for the default schema\n",[55,356,358,360,363,366,369,371,374,376,379,382,384,387],{"class":57,"line":357},33,[55,359,328],{"class":79},[55,361,362],{"class":119}," getattr",[55,364,365],{"class":68},"(",[55,367,368],{"class":119},"object",[55,370,267],{"class":68},[55,372,373],{"class":141},"\"schema\"",[55,375,267],{"class":68},[55,377,378],{"class":119},"None",[55,380,381],{"class":68},") ",[55,383,334],{"class":79},[55,385,386],{"class":119}," EXCLUDE_SCHEMAS",[55,388,322],{"class":68},[55,390,392,394],{"class":57,"line":391},34,[55,393,345],{"class":79},[55,395,348],{"class":119},[55,397,399,401,403,405,408,411,413,415,417],{"class":57,"line":398},35,[55,400,310],{"class":79},[55,402,313],{"class":68},[55,404,316],{"class":79},[55,406,407],{"class":141}," \"schema\"",[55,409,410],{"class":79}," and",[55,412,331],{"class":68},[55,414,334],{"class":79},[55,416,386],{"class":119},[55,418,322],{"class":68},[55,420,422,425],{"class":57,"line":421},36,[55,423,424],{"class":79},"        return",[55,426,348],{"class":119},[55,428,430,433],{"class":57,"line":429},37,[55,431,432],{"class":79},"    return",[55,434,435],{"class":119}," True\n",[55,437,439],{"class":57,"line":438},38,[55,440,107],{"emptyLinePlaceholder":106},[55,442,444],{"class":57,"line":443},39,[55,445,107],{"emptyLinePlaceholder":106},[55,447,449],{"class":57,"line":448},40,[55,450,69],{"class":68},[55,452,454,456,458],{"class":57,"line":453},41,[55,455,76],{"class":75},[55,457,80],{"class":79},[55,459,83],{"class":68},[55,461,463,465,467],{"class":57,"line":462},42,[55,464,89],{"class":75},[55,466,80],{"class":79},[55,468,94],{"class":68},[55,470,472,475,477,480],{"class":57,"line":471},43,[55,473,474],{"class":75},"    include_schemas",[55,476,80],{"class":79},[55,478,479],{"class":119},"True",[55,481,145],{"class":68},[55,483,485,488,490],{"class":57,"line":484},44,[55,486,487],{"class":75},"    include_object",[55,489,80],{"class":79},[55,491,492],{"class":68},"include_object,\n",[55,494,496],{"class":57,"line":495},45,[55,497,100],{"class":68},[41,499,501],{"id":500},"execution-context-async-workflow-integration","Execution Context & Async Workflow Integration",[14,503,504,505,507],{},"Alembic calls ",[18,506,20],{}," during the diff phase — after reflecting the live schema but before writing any op-directives. The function receives five arguments:",[509,510,511,527],"table",{},[512,513,514],"thead",{},[515,516,517,521,524],"tr",{},[518,519,520],"th",{},"Argument",[518,522,523],{},"Type",[518,525,526],{},"Meaning",[528,529,530,555,570,600,617],"tbody",{},[515,531,532,537,542],{},[533,534,535],"td",{},[18,536,368],{},[533,538,539],{},[18,540,541],{},"SchemaItem",[533,543,544,545,267,548,267,551,554],{},"The reflected or model object (",[18,546,547],{},"Table",[18,549,550],{},"Column",[18,552,553],{},"Index",", etc.)",[515,556,557,562,567],{},[533,558,559],{},[18,560,561],{},"name",[533,563,564],{},[18,565,566],{},"str",[533,568,569],{},"The object's name",[515,571,572,577,581],{},[533,573,574],{},[18,575,576],{},"type_",[533,578,579],{},[18,580,566],{},[533,582,583,267,586,267,589,267,592,267,595,267,598],{},[18,584,585],{},"\"table\"",[18,587,588],{},"\"column\"",[18,590,591],{},"\"index\"",[18,593,594],{},"\"unique_constraint\"",[18,596,597],{},"\"foreign_key_constraint\"",[18,599,373],{},[515,601,602,607,612],{},[533,603,604],{},[18,605,606],{},"reflected",[533,608,609],{},[18,610,611],{},"bool",[533,613,614,616],{},[18,615,479],{}," if the object came from database reflection",[515,618,619,624,629],{},[533,620,621],{},[18,622,623],{},"compare_to",[533,625,626],{},[18,627,628],{},"SchemaItem | None",[533,630,631,632,634],{},"The counterpart from the other side of the diff; ",[18,633,378],{}," if no counterpart exists",[14,636,637,638,640,641,644],{},"Return ",[18,639,479],{}," to include the object in the diff, ",[18,642,643],{},"False"," to exclude it entirely. When you exclude a table, Alembic skips its columns, indexes, and constraints too — you do not need a separate column-level filter for tables you have already excluded at the table level.",[646,647,649],"h3",{"id":648},"choosing-between-include_object-and-include_name","Choosing between include_object and include_name",[14,651,652,653,655],{},"The ",[18,654,24],{}," hook fires earlier — it filters by name alone before full reflection occurs, making it more efficient for large schemas:",[46,657,659],{"className":48,"code":658,"language":50,"meta":51,"style":51},"# alembic\u002Fenv.py — include_name for prefix-based and allowlist filtering\nOWNED_SCHEMAS = {\"public\", \"app\"}\nTABLE_PREFIX_EXCLUDE = (\"celery_\", \"django_\", \"spatial_\", \"raster_\")\n\n\ndef include_name(name, type_, parent_names):\n    \"\"\"\n    Filter objects by name before reflection.\n    parent_names is a dict with keys 'schema_name' and 'table_name'\n    depending on context.\n    \"\"\"\n    if type_ == \"schema\":\n        # Only reflect schemas owned by this service\n        return name in OWNED_SCHEMAS\n    if type_ == \"table\":\n        if name.startswith(TABLE_PREFIX_EXCLUDE):\n            return False\n    return True\n\n\ncontext.configure(\n    connection=connection,\n    target_metadata=target_metadata,\n    include_schemas=True,\n    include_name=include_name,\n)\n",[18,660,661,666,687,717,721,725,735,740,745,750,755,759,771,776,787,799,811,817,823,827,831,835,843,851,861,871],{"__ignoreMap":51},[55,662,663],{"class":57,"line":58},[55,664,665],{"class":61},"# alembic\u002Fenv.py — include_name for prefix-based and allowlist filtering\n",[55,667,668,671,673,676,679,681,684],{"class":57,"line":65},[55,669,670],{"class":119},"OWNED_SCHEMAS",[55,672,123],{"class":79},[55,674,675],{"class":68}," {",[55,677,678],{"class":141},"\"public\"",[55,680,267],{"class":68},[55,682,683],{"class":141},"\"app\"",[55,685,686],{"class":68},"}\n",[55,688,689,692,694,697,700,702,705,707,710,712,715],{"class":57,"line":72},[55,690,691],{"class":119},"TABLE_PREFIX_EXCLUDE",[55,693,123],{"class":79},[55,695,696],{"class":68}," (",[55,698,699],{"class":141},"\"celery_\"",[55,701,267],{"class":68},[55,703,704],{"class":141},"\"django_\"",[55,706,267],{"class":68},[55,708,709],{"class":141},"\"spatial_\"",[55,711,267],{"class":68},[55,713,714],{"class":141},"\"raster_\"",[55,716,100],{"class":68},[55,718,719],{"class":57,"line":86},[55,720,107],{"emptyLinePlaceholder":106},[55,722,723],{"class":57,"line":97},[55,724,107],{"emptyLinePlaceholder":106},[55,726,727,729,732],{"class":57,"line":103},[55,728,297],{"class":79},[55,730,731],{"class":300}," include_name",[55,733,734],{"class":68},"(name, type_, parent_names):\n",[55,736,737],{"class":57,"line":110},[55,738,739],{"class":141},"    \"\"\"\n",[55,741,742],{"class":57,"line":116},[55,743,744],{"class":141},"    Filter objects by name before reflection.\n",[55,746,747],{"class":57,"line":132},[55,748,749],{"class":141},"    parent_names is a dict with keys 'schema_name' and 'table_name'\n",[55,751,752],{"class":57,"line":138},[55,753,754],{"class":141},"    depending on context.\n",[55,756,757],{"class":57,"line":148},[55,758,739],{"class":141},[55,760,761,763,765,767,769],{"class":57,"line":156},[55,762,310],{"class":79},[55,764,313],{"class":68},[55,766,316],{"class":79},[55,768,407],{"class":141},[55,770,322],{"class":68},[55,772,773],{"class":57,"line":164},[55,774,775],{"class":61},"        # Only reflect schemas owned by this service\n",[55,777,778,780,782,784],{"class":57,"line":172},[55,779,424],{"class":79},[55,781,331],{"class":68},[55,783,334],{"class":79},[55,785,786],{"class":119}," OWNED_SCHEMAS\n",[55,788,789,791,793,795,797],{"class":57,"line":178},[55,790,310],{"class":79},[55,792,313],{"class":68},[55,794,316],{"class":79},[55,796,319],{"class":141},[55,798,322],{"class":68},[55,800,801,803,806,808],{"class":57,"line":186},[55,802,328],{"class":79},[55,804,805],{"class":68}," name.startswith(",[55,807,691],{"class":119},[55,809,810],{"class":68},"):\n",[55,812,813,815],{"class":57,"line":194},[55,814,345],{"class":79},[55,816,348],{"class":119},[55,818,819,821],{"class":57,"line":200},[55,820,432],{"class":79},[55,822,435],{"class":119},[55,824,825],{"class":57,"line":208},[55,826,107],{"emptyLinePlaceholder":106},[55,828,829],{"class":57,"line":216},[55,830,107],{"emptyLinePlaceholder":106},[55,832,833],{"class":57,"line":224},[55,834,69],{"class":68},[55,836,837,839,841],{"class":57,"line":232},[55,838,76],{"class":75},[55,840,80],{"class":79},[55,842,83],{"class":68},[55,844,845,847,849],{"class":57,"line":240},[55,846,89],{"class":75},[55,848,80],{"class":79},[55,850,94],{"class":68},[55,852,853,855,857,859],{"class":57,"line":246},[55,854,474],{"class":75},[55,856,80],{"class":79},[55,858,479],{"class":119},[55,860,145],{"class":68},[55,862,863,866,868],{"class":57,"line":251},[55,864,865],{"class":75},"    include_name",[55,867,80],{"class":79},[55,869,870],{"class":68},"include_name,\n",[55,872,873],{"class":57,"line":284},[55,874,100],{"class":68},[14,876,877,878,880,881,883,884,886,887,889],{},"Use ",[18,879,24],{}," for simple prefix or allowlist filtering. Use ",[18,882,20],{}," when you need to inspect the actual schema object's attributes — for example, checking whether a table has a specific column, or whether a foreign key references an excluded table. The two hooks can be used together: ",[18,885,24],{}," reduces the reflection surface, then ",[18,888,20],{}," applies finer-grained logic to what remains.",[14,891,892,893,896,897,900],{},"In async projects the hook functions themselves are synchronous — they run inside ",[18,894,895],{},"connection.run_sync(do_run_migrations)",", so no event loop is available inside them. Keep hook functions pure Python with no ",[18,898,899],{},"await"," expressions and no I\u002FO.",[646,902,904],{"id":903},"scoping-autogenerate-to-tables-owned-by-your-application","Scoping Autogenerate to Tables Owned by Your Application",[14,906,907],{},"The cleanest pattern for shared-database architectures is to derive the exclusion set dynamically from your own metadata, so you never need to maintain a manual exclusion list:",[46,909,911],{"className":48,"code":910,"language":50,"meta":51,"style":51},"# alembic\u002Fenv.py — exclude everything not in our metadata\nfrom myapp.models import Base\nimport myapp.models.users     # noqa: F401\nimport myapp.models.orders    # noqa: F401\nimport myapp.models.products  # noqa: F401\nimport myapp.models.invoices  # noqa: F401\n\ntarget_metadata = Base.metadata\n\n# All table names this service owns, keyed by (schema, name) for schema-aware DBs\nOWNED_TABLES = frozenset(\n    (t.schema, t.name) for t in target_metadata.tables.values()\n)\n\n\ndef include_object(object, name, type_, reflected, compare_to):\n    if type_ == \"table\" and reflected:\n        schema = getattr(object, \"schema\", None)\n        if (schema, name) not in OWNED_TABLES:\n            # Table is in the DB but not owned by this service — skip it\n            return False\n    return True\n\n\ndef do_run_migrations(connection):\n    context.configure(\n        connection=connection,\n        target_metadata=target_metadata,\n        compare_type=True,\n        include_schemas=True,\n        include_object=include_object,\n    )\n    with context.begin_transaction():\n        context.run_migrations()\n",[18,912,913,918,932,942,951,960,969,973,983,987,992,1004,1020,1024,1028,1032,1040,1055,1078,1096,1101,1107,1113,1117,1121,1131,1136,1145,1154,1165,1176,1185,1190,1198],{"__ignoreMap":51},[55,914,915],{"class":57,"line":58},[55,916,917],{"class":61},"# alembic\u002Fenv.py — exclude everything not in our metadata\n",[55,919,920,923,926,929],{"class":57,"line":65},[55,921,922],{"class":79},"from",[55,924,925],{"class":68}," myapp.models ",[55,927,928],{"class":79},"import",[55,930,931],{"class":68}," Base\n",[55,933,934,936,939],{"class":57,"line":72},[55,935,928],{"class":79},[55,937,938],{"class":68}," myapp.models.users     ",[55,940,941],{"class":61},"# noqa: F401\n",[55,943,944,946,949],{"class":57,"line":86},[55,945,928],{"class":79},[55,947,948],{"class":68}," myapp.models.orders    ",[55,950,941],{"class":61},[55,952,953,955,958],{"class":57,"line":97},[55,954,928],{"class":79},[55,956,957],{"class":68}," myapp.models.products  ",[55,959,941],{"class":61},[55,961,962,964,967],{"class":57,"line":103},[55,963,928],{"class":79},[55,965,966],{"class":68}," myapp.models.invoices  ",[55,968,941],{"class":61},[55,970,971],{"class":57,"line":110},[55,972,107],{"emptyLinePlaceholder":106},[55,974,975,978,980],{"class":57,"line":116},[55,976,977],{"class":68},"target_metadata ",[55,979,80],{"class":79},[55,981,982],{"class":68}," Base.metadata\n",[55,984,985],{"class":57,"line":132},[55,986,107],{"emptyLinePlaceholder":106},[55,988,989],{"class":57,"line":138},[55,990,991],{"class":61},"# All table names this service owns, keyed by (schema, name) for schema-aware DBs\n",[55,993,994,997,999,1001],{"class":57,"line":148},[55,995,996],{"class":119},"OWNED_TABLES",[55,998,123],{"class":79},[55,1000,126],{"class":119},[55,1002,1003],{"class":68},"(\n",[55,1005,1006,1009,1012,1015,1017],{"class":57,"line":156},[55,1007,1008],{"class":68},"    (t.schema, t.name) ",[55,1010,1011],{"class":79},"for",[55,1013,1014],{"class":68}," t ",[55,1016,334],{"class":79},[55,1018,1019],{"class":68}," target_metadata.tables.values()\n",[55,1021,1022],{"class":57,"line":164},[55,1023,100],{"class":68},[55,1025,1026],{"class":57,"line":172},[55,1027,107],{"emptyLinePlaceholder":106},[55,1029,1030],{"class":57,"line":178},[55,1031,107],{"emptyLinePlaceholder":106},[55,1033,1034,1036,1038],{"class":57,"line":186},[55,1035,297],{"class":79},[55,1037,301],{"class":300},[55,1039,304],{"class":68},[55,1041,1042,1044,1046,1048,1050,1052],{"class":57,"line":194},[55,1043,310],{"class":79},[55,1045,313],{"class":68},[55,1047,316],{"class":79},[55,1049,319],{"class":141},[55,1051,410],{"class":79},[55,1053,1054],{"class":68}," reflected:\n",[55,1056,1057,1060,1062,1064,1066,1068,1070,1072,1074,1076],{"class":57,"line":200},[55,1058,1059],{"class":68},"        schema ",[55,1061,80],{"class":79},[55,1063,362],{"class":119},[55,1065,365],{"class":68},[55,1067,368],{"class":119},[55,1069,267],{"class":68},[55,1071,373],{"class":141},[55,1073,267],{"class":68},[55,1075,378],{"class":119},[55,1077,100],{"class":68},[55,1079,1080,1082,1085,1088,1091,1094],{"class":57,"line":208},[55,1081,328],{"class":79},[55,1083,1084],{"class":68}," (schema, name) ",[55,1086,1087],{"class":79},"not",[55,1089,1090],{"class":79}," in",[55,1092,1093],{"class":119}," OWNED_TABLES",[55,1095,322],{"class":68},[55,1097,1098],{"class":57,"line":216},[55,1099,1100],{"class":61},"            # Table is in the DB but not owned by this service — skip it\n",[55,1102,1103,1105],{"class":57,"line":224},[55,1104,345],{"class":79},[55,1106,348],{"class":119},[55,1108,1109,1111],{"class":57,"line":232},[55,1110,432],{"class":79},[55,1112,435],{"class":119},[55,1114,1115],{"class":57,"line":240},[55,1116,107],{"emptyLinePlaceholder":106},[55,1118,1119],{"class":57,"line":246},[55,1120,107],{"emptyLinePlaceholder":106},[55,1122,1123,1125,1128],{"class":57,"line":251},[55,1124,297],{"class":79},[55,1126,1127],{"class":300}," do_run_migrations",[55,1129,1130],{"class":68},"(connection):\n",[55,1132,1133],{"class":57,"line":284},[55,1134,1135],{"class":68},"    context.configure(\n",[55,1137,1138,1141,1143],{"class":57,"line":289},[55,1139,1140],{"class":75},"        connection",[55,1142,80],{"class":79},[55,1144,83],{"class":68},[55,1146,1147,1150,1152],{"class":57,"line":294},[55,1148,1149],{"class":75},"        target_metadata",[55,1151,80],{"class":79},[55,1153,94],{"class":68},[55,1155,1156,1159,1161,1163],{"class":57,"line":307},[55,1157,1158],{"class":75},"        compare_type",[55,1160,80],{"class":79},[55,1162,479],{"class":119},[55,1164,145],{"class":68},[55,1166,1167,1170,1172,1174],{"class":57,"line":325},[55,1168,1169],{"class":75},"        include_schemas",[55,1171,80],{"class":79},[55,1173,479],{"class":119},[55,1175,145],{"class":68},[55,1177,1178,1181,1183],{"class":57,"line":342},[55,1179,1180],{"class":75},"        include_object",[55,1182,80],{"class":79},[55,1184,492],{"class":68},[55,1186,1187],{"class":57,"line":351},[55,1188,1189],{"class":68},"    )\n",[55,1191,1192,1195],{"class":57,"line":357},[55,1193,1194],{"class":79},"    with",[55,1196,1197],{"class":68}," context.begin_transaction():\n",[55,1199,1200],{"class":57,"line":391},[55,1201,1202],{"class":68},"        context.run_migrations()\n",[14,1204,1205,1206,1208],{},"This approach is self-maintaining: when you add a new model, ",[18,1207,996],{}," automatically expands to include it. No manual updates to the exclusion set are needed.",[41,1210,1212],{"id":1211},"resolving-warnings-errors-common-mistakes","Resolving Warnings, Errors & Common Mistakes",[509,1214,1215,1228],{},[512,1216,1217],{},[515,1218,1219,1222,1225],{},[518,1220,1221],{},"Warning \u002F Error",[518,1223,1224],{},"Root Cause",[518,1226,1227],{},"Production Fix",[528,1229,1230,1259,1276,1309,1331,1350,1377,1400,1430,1454],{},[515,1231,1232,1237,1243],{},[533,1233,1234],{},[18,1235,1236],{},"autogenerate produces op.drop_table(\"spatial_ref_sys\")",[533,1238,1239,1240],{},"PostGIS geometry tables visible in reflected schema but absent from ",[18,1241,1242],{},"target_metadata",[533,1244,1245,1246,267,1249,267,1252,267,1255,1258],{},"Add ",[18,1247,1248],{},"spatial_ref_sys",[18,1250,1251],{},"raster_columns",[18,1253,1254],{},"raster_overviews",[18,1256,1257],{},"geometry_columns"," to the exclusion set",[515,1260,1261,1266,1269],{},[533,1262,1263],{},[18,1264,1265],{},"autogenerate produces op.drop_table(\"celery_taskmeta\")",[533,1267,1268],{},"Celery result backend tables in same DB; not in model metadata",[533,1270,1271,1272,1275],{},"Add all ",[18,1273,1274],{},"celery_*"," tables to exclusion set, or migrate Celery to a dedicated database",[515,1277,1278,1283,1293],{},[533,1279,1280],{},[18,1281,1282],{},"autogenerate produces op.drop_schema(\"tiger\")",[533,1284,1285,1286,1289,1290],{},"PostGIS ",[18,1287,1288],{},"tiger"," schema visible when ",[18,1291,1292],{},"include_schemas=True",[533,1294,1245,1295,267,1297,267,1300,1303,1304,1306,1307],{},[18,1296,1288],{},[18,1298,1299],{},"tiger_data",[18,1301,1302],{},"topology"," to ",[18,1305,254],{},"; also handle in ",[18,1308,24],{},[515,1310,1311,1316,1322],{},[533,1312,1313],{},[18,1314,1315],{},"include_object not called for reflected schema objects",[533,1317,1318,1321],{},[18,1319,1320],{},"include_schemas=False"," (the default); schema-level objects not reflected at all",[533,1323,1324,1325,1327,1328],{},"Set ",[18,1326,1292],{}," in ",[18,1329,1330],{},"context.configure",[515,1332,1333,1338,1344],{},[533,1334,1335],{},[18,1336,1337],{},"TypeError: include_object() takes 4 positional arguments but 5 were given",[533,1339,1340,1341,1343],{},"Old four-argument hook signature missing the ",[18,1342,623],{}," parameter",[533,1345,1346,1347],{},"Update the signature to ",[18,1348,1349],{},"(object, name, type_, reflected, compare_to)",[515,1351,1352,1357,1368],{},[533,1353,1354],{},[18,1355,1356],{},"autogenerate still emits changes for excluded table",[533,1358,1359,1361,1362,1364,1365,1367],{},[18,1360,24],{}," returned ",[18,1363,479],{}," but ",[18,1366,20],{}," was not wired; Alembic reflected it fully",[533,1369,1370,1371,1373,1374,1376],{},"Ensure both hooks are passed to ",[18,1372,1330],{},"; ",[18,1375,24],{}," alone does not block object-level diffs if the table was already reflected",[515,1378,1379,1384,1395],{},[533,1380,1381],{},[18,1382,1383],{},"autogenerate drops the alembic_version table",[533,1385,1386,1387,1390,1391,1394],{},"Custom ",[18,1388,1389],{},"version_table"," name is not the default ",[18,1392,1393],{},"alembic_version"," so Alembic's internal skip logic does not trigger",[533,1396,1397,1398],{},"Add the custom version table name to ",[18,1399,120],{},[515,1401,1402,1407,1416],{},[533,1403,1404],{},[18,1405,1406],{},"AttributeError: 'NoneType' object has no attribute 'schema'",[533,1408,1409,1411,1412,1415],{},[18,1410,20],{}," accesses ",[18,1413,1414],{},"object.schema"," on a non-Table object such as an Index",[533,1417,1418,1419,1422,1423,1426,1427],{},"Guard with ",[18,1420,1421],{},"if type_ == \"table\":"," before accessing ",[18,1424,1425],{},".schema","; use ",[18,1428,1429],{},"getattr(object, \"schema\", None)",[515,1431,1432,1437,1443],{},[533,1433,1434],{},[18,1435,1436],{},"autogenerate emits spurious op.alter_column for unchanged columns",[533,1438,1439,1442],{},[18,1440,1441],{},"compare_server_default=True"," combined with dialect rendering differences",[533,1444,1445,1446,1449,1450,1453],{},"Add a ",[18,1447,1448],{},"process_revision_directives"," hook to filter out no-op server-default changes, or disable ",[18,1451,1452],{},"compare_server_default"," for known-noisy columns",[515,1455,1456,1461,1474],{},[533,1457,1458],{},[18,1459,1460],{},"autogenerate emits op.drop_table for alembic_version in tenant schema",[533,1462,1463,1464,1466,1467,1470,1471,1473],{},"Running ",[18,1465,1292],{}," without ",[18,1468,1469],{},"version_table_schema"," causes Alembic to create ",[18,1472,1393],{}," in every schema it encounters",[533,1475,1324,1476,1327,1479],{},[18,1477,1478],{},"version_table_schema=\"public\"",[18,1480,1330],{},[41,1482,1484],{"id":1483},"advanced-exclusion-optimization","Advanced Exclusion Optimization",[646,1486,1488],{"id":1487},"regex-based-exclusion-for-dynamic-table-names","Regex-Based Exclusion for Dynamic Table Names",[14,1490,1491],{},"For projects with many third-party integrations or dynamically created tables (per-tenant partitions, time-sharded tables), a regex-based approach is more maintainable than a static set:",[46,1493,1495],{"className":48,"code":1494,"language":50,"meta":51,"style":51},"# alembic\u002Fenv.py — regex exclusion pattern\nimport re\nfrom myapp.models import Base\n\ntarget_metadata = Base.metadata\n\nEXCLUDE_PATTERNS = [\n    re.compile(r\"^celery_\"),\n    re.compile(r\"^django_\"),\n    re.compile(r\"^spatial_\"),\n    re.compile(r\"^raster_\"),\n    re.compile(r\"^topology\\.\"),          # PostGIS topology schema tables\n    re.compile(r\"^tenant_\\d+_archive$\"), # archived tenant shards\n    re.compile(r\"^_timescaledb_\"),       # TimescaleDB internal tables\n]\n\nOWNED_SCHEMAS = {\"public\", \"app\", \"reporting\"}\n\n\ndef include_object(object, name, type_, reflected, compare_to):\n    if type_ == \"table\":\n        # Reject tables in schemas we do not own\n        schema = getattr(object, \"schema\", None)\n        if schema is not None and schema not in OWNED_SCHEMAS:\n            return False\n        # Reject tables matching the exclusion patterns\n        if any(p.search(name) for p in EXCLUDE_PATTERNS):\n            return False\n    if type_ == \"schema\" and name not in OWNED_SCHEMAS:\n        return False\n    return True\n",[18,1496,1497,1502,1509,1519,1523,1531,1535,1545,1568,1585,1602,1619,1643,1676,1697,1702,1706,1727,1731,1735,1743,1755,1760,1782,1811,1817,1822,1844,1850,1872,1878],{"__ignoreMap":51},[55,1498,1499],{"class":57,"line":58},[55,1500,1501],{"class":61},"# alembic\u002Fenv.py — regex exclusion pattern\n",[55,1503,1504,1506],{"class":57,"line":65},[55,1505,928],{"class":79},[55,1507,1508],{"class":68}," re\n",[55,1510,1511,1513,1515,1517],{"class":57,"line":72},[55,1512,922],{"class":79},[55,1514,925],{"class":68},[55,1516,928],{"class":79},[55,1518,931],{"class":68},[55,1520,1521],{"class":57,"line":86},[55,1522,107],{"emptyLinePlaceholder":106},[55,1524,1525,1527,1529],{"class":57,"line":97},[55,1526,977],{"class":68},[55,1528,80],{"class":79},[55,1530,982],{"class":68},[55,1532,1533],{"class":57,"line":103},[55,1534,107],{"emptyLinePlaceholder":106},[55,1536,1537,1540,1542],{"class":57,"line":110},[55,1538,1539],{"class":119},"EXCLUDE_PATTERNS",[55,1541,123],{"class":79},[55,1543,1544],{"class":68}," [\n",[55,1546,1547,1550,1553,1556,1559,1563,1565],{"class":57,"line":116},[55,1548,1549],{"class":68},"    re.compile(",[55,1551,1552],{"class":79},"r",[55,1554,1555],{"class":141},"\"",[55,1557,1558],{"class":119},"^",[55,1560,1562],{"class":1561},"sA_wV","celery_",[55,1564,1555],{"class":141},[55,1566,1567],{"class":68},"),\n",[55,1569,1570,1572,1574,1576,1578,1581,1583],{"class":57,"line":132},[55,1571,1549],{"class":68},[55,1573,1552],{"class":79},[55,1575,1555],{"class":141},[55,1577,1558],{"class":119},[55,1579,1580],{"class":1561},"django_",[55,1582,1555],{"class":141},[55,1584,1567],{"class":68},[55,1586,1587,1589,1591,1593,1595,1598,1600],{"class":57,"line":138},[55,1588,1549],{"class":68},[55,1590,1552],{"class":79},[55,1592,1555],{"class":141},[55,1594,1558],{"class":119},[55,1596,1597],{"class":1561},"spatial_",[55,1599,1555],{"class":141},[55,1601,1567],{"class":68},[55,1603,1604,1606,1608,1610,1612,1615,1617],{"class":57,"line":148},[55,1605,1549],{"class":68},[55,1607,1552],{"class":79},[55,1609,1555],{"class":141},[55,1611,1558],{"class":119},[55,1613,1614],{"class":1561},"raster_",[55,1616,1555],{"class":141},[55,1618,1567],{"class":68},[55,1620,1621,1623,1625,1627,1629,1631,1635,1637,1640],{"class":57,"line":156},[55,1622,1549],{"class":68},[55,1624,1552],{"class":79},[55,1626,1555],{"class":141},[55,1628,1558],{"class":119},[55,1630,1302],{"class":1561},[55,1632,1634],{"class":1633},"snhLl","\\.",[55,1636,1555],{"class":141},[55,1638,1639],{"class":68},"),          ",[55,1641,1642],{"class":61},"# PostGIS topology schema tables\n",[55,1644,1645,1647,1649,1651,1653,1656,1659,1662,1665,1668,1670,1673],{"class":57,"line":164},[55,1646,1549],{"class":68},[55,1648,1552],{"class":79},[55,1650,1555],{"class":141},[55,1652,1558],{"class":119},[55,1654,1655],{"class":1561},"tenant_",[55,1657,1658],{"class":119},"\\d",[55,1660,1661],{"class":79},"+",[55,1663,1664],{"class":1561},"_archive",[55,1666,1667],{"class":119},"$",[55,1669,1555],{"class":141},[55,1671,1672],{"class":68},"), ",[55,1674,1675],{"class":61},"# archived tenant shards\n",[55,1677,1678,1680,1682,1684,1686,1689,1691,1694],{"class":57,"line":172},[55,1679,1549],{"class":68},[55,1681,1552],{"class":79},[55,1683,1555],{"class":141},[55,1685,1558],{"class":119},[55,1687,1688],{"class":1561},"_timescaledb_",[55,1690,1555],{"class":141},[55,1692,1693],{"class":68},"),       ",[55,1695,1696],{"class":61},"# TimescaleDB internal tables\n",[55,1698,1699],{"class":57,"line":178},[55,1700,1701],{"class":68},"]\n",[55,1703,1704],{"class":57,"line":186},[55,1705,107],{"emptyLinePlaceholder":106},[55,1707,1708,1710,1712,1714,1716,1718,1720,1722,1725],{"class":57,"line":194},[55,1709,670],{"class":119},[55,1711,123],{"class":79},[55,1713,675],{"class":68},[55,1715,678],{"class":141},[55,1717,267],{"class":68},[55,1719,683],{"class":141},[55,1721,267],{"class":68},[55,1723,1724],{"class":141},"\"reporting\"",[55,1726,686],{"class":68},[55,1728,1729],{"class":57,"line":200},[55,1730,107],{"emptyLinePlaceholder":106},[55,1732,1733],{"class":57,"line":208},[55,1734,107],{"emptyLinePlaceholder":106},[55,1736,1737,1739,1741],{"class":57,"line":216},[55,1738,297],{"class":79},[55,1740,301],{"class":300},[55,1742,304],{"class":68},[55,1744,1745,1747,1749,1751,1753],{"class":57,"line":224},[55,1746,310],{"class":79},[55,1748,313],{"class":68},[55,1750,316],{"class":79},[55,1752,319],{"class":141},[55,1754,322],{"class":68},[55,1756,1757],{"class":57,"line":232},[55,1758,1759],{"class":61},"        # Reject tables in schemas we do not own\n",[55,1761,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780],{"class":57,"line":240},[55,1763,1059],{"class":68},[55,1765,80],{"class":79},[55,1767,362],{"class":119},[55,1769,365],{"class":68},[55,1771,368],{"class":119},[55,1773,267],{"class":68},[55,1775,373],{"class":141},[55,1777,267],{"class":68},[55,1779,378],{"class":119},[55,1781,100],{"class":68},[55,1783,1784,1786,1789,1792,1795,1798,1800,1802,1804,1806,1809],{"class":57,"line":246},[55,1785,328],{"class":79},[55,1787,1788],{"class":68}," schema ",[55,1790,1791],{"class":79},"is",[55,1793,1794],{"class":79}," not",[55,1796,1797],{"class":119}," None",[55,1799,410],{"class":79},[55,1801,1788],{"class":68},[55,1803,1087],{"class":79},[55,1805,1090],{"class":79},[55,1807,1808],{"class":119}," OWNED_SCHEMAS",[55,1810,322],{"class":68},[55,1812,1813,1815],{"class":57,"line":251},[55,1814,345],{"class":79},[55,1816,348],{"class":119},[55,1818,1819],{"class":57,"line":284},[55,1820,1821],{"class":61},"        # Reject tables matching the exclusion patterns\n",[55,1823,1824,1826,1829,1832,1834,1837,1839,1842],{"class":57,"line":289},[55,1825,328],{"class":79},[55,1827,1828],{"class":119}," any",[55,1830,1831],{"class":68},"(p.search(name) ",[55,1833,1011],{"class":79},[55,1835,1836],{"class":68}," p ",[55,1838,334],{"class":79},[55,1840,1841],{"class":119}," EXCLUDE_PATTERNS",[55,1843,810],{"class":68},[55,1845,1846,1848],{"class":57,"line":294},[55,1847,345],{"class":79},[55,1849,348],{"class":119},[55,1851,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870],{"class":57,"line":307},[55,1853,310],{"class":79},[55,1855,313],{"class":68},[55,1857,316],{"class":79},[55,1859,407],{"class":141},[55,1861,410],{"class":79},[55,1863,331],{"class":68},[55,1865,1087],{"class":79},[55,1867,1090],{"class":79},[55,1869,1808],{"class":119},[55,1871,322],{"class":68},[55,1873,1874,1876],{"class":57,"line":325},[55,1875,424],{"class":79},[55,1877,348],{"class":119},[55,1879,1880,1882],{"class":57,"line":342},[55,1881,432],{"class":79},[55,1883,435],{"class":119},[646,1885,1887],{"id":1886},"reading-exclusions-from-environment-variables","Reading Exclusions from Environment Variables",[14,1889,1890,1891,1893],{},"For CI pipelines that need to vary exclusion lists without modifying ",[18,1892,28],{},", read the list from an environment variable:",[46,1895,1897],{"className":48,"code":1896,"language":50,"meta":51,"style":51},"# alembic\u002Fenv.py — environment-variable-driven exclusion\nimport os\n\n_extra_excludes = os.environ.get(\"ALEMBIC_EXCLUDE_TABLES\", \"\")\nEXCLUDE_TABLES = frozenset(\n    t.strip() for t in _extra_excludes.split(\",\") if t.strip()\n) | {\n    \"spatial_ref_sys\",\n    \"celery_taskmeta\",\n    \"celery_tasksetmeta\",\n}\n",[18,1898,1899,1904,1911,1915,1935,1945,1970,1980,1986,1992,1998],{"__ignoreMap":51},[55,1900,1901],{"class":57,"line":58},[55,1902,1903],{"class":61},"# alembic\u002Fenv.py — environment-variable-driven exclusion\n",[55,1905,1906,1908],{"class":57,"line":65},[55,1907,928],{"class":79},[55,1909,1910],{"class":68}," os\n",[55,1912,1913],{"class":57,"line":72},[55,1914,107],{"emptyLinePlaceholder":106},[55,1916,1917,1920,1922,1925,1928,1930,1933],{"class":57,"line":86},[55,1918,1919],{"class":68},"_extra_excludes ",[55,1921,80],{"class":79},[55,1923,1924],{"class":68}," os.environ.get(",[55,1926,1927],{"class":141},"\"ALEMBIC_EXCLUDE_TABLES\"",[55,1929,267],{"class":68},[55,1931,1932],{"class":141},"\"\"",[55,1934,100],{"class":68},[55,1936,1937,1939,1941,1943],{"class":57,"line":97},[55,1938,120],{"class":119},[55,1940,123],{"class":79},[55,1942,126],{"class":119},[55,1944,1003],{"class":68},[55,1946,1947,1950,1952,1954,1956,1959,1962,1964,1967],{"class":57,"line":103},[55,1948,1949],{"class":68},"    t.strip() ",[55,1951,1011],{"class":79},[55,1953,1014],{"class":68},[55,1955,334],{"class":79},[55,1957,1958],{"class":68}," _extra_excludes.split(",[55,1960,1961],{"class":141},"\",\"",[55,1963,381],{"class":68},[55,1965,1966],{"class":79},"if",[55,1968,1969],{"class":68}," t.strip()\n",[55,1971,1972,1974,1977],{"class":57,"line":110},[55,1973,381],{"class":68},[55,1975,1976],{"class":79},"|",[55,1978,1979],{"class":68}," {\n",[55,1981,1982,1984],{"class":57,"line":116},[55,1983,142],{"class":141},[55,1985,145],{"class":68},[55,1987,1988,1990],{"class":57,"line":132},[55,1989,181],{"class":141},[55,1991,145],{"class":68},[55,1993,1994,1996],{"class":57,"line":138},[55,1995,189],{"class":141},[55,1997,145],{"class":68},[55,1999,2000],{"class":57,"line":148},[55,2001,686],{"class":68},[14,2003,2004,2005,2008],{},"With this pattern, a CI job running against a shared staging environment can pass ",[18,2006,2007],{},"ALEMBIC_EXCLUDE_TABLES=legacy_product_catalog,old_sessions"," without touching the codebase.",[646,2010,2012],{"id":2011},"column-level-exclusion-within-managed-tables","Column-Level Exclusion within Managed Tables",[14,2014,2015,2016,2019],{},"To exclude a specific column from comparison within an otherwise managed table — for example, a ",[18,2017,2018],{},"search_vector"," column managed by a PostgreSQL trigger rather than SQLAlchemy:",[46,2021,2023],{"className":48,"code":2022,"language":50,"meta":51,"style":51},"EXCLUDE_COLUMNS = {\n    (\"products\", \"search_vector\"),   # tsvector maintained by trigger\n    (\"orders\", \"tsv_description\"),\n}\n\n\ndef include_object(object, name, type_, reflected, compare_to):\n    if type_ == \"column\":\n        table_name = getattr(object, \"table\", None)\n        if table_name is not None:\n            key = (table_name.name, name)\n            if key in EXCLUDE_COLUMNS:\n                return False\n    if type_ == \"table\" and name in EXCLUDE_TABLES:\n        return False\n    return True\n",[18,2024,2025,2034,2053,2067,2071,2075,2079,2087,2100,2123,2138,2148,2163,2170,2190,2196],{"__ignoreMap":51},[55,2026,2027,2030,2032],{"class":57,"line":58},[55,2028,2029],{"class":119},"EXCLUDE_COLUMNS",[55,2031,123],{"class":79},[55,2033,1979],{"class":68},[55,2035,2036,2039,2042,2044,2047,2050],{"class":57,"line":65},[55,2037,2038],{"class":68},"    (",[55,2040,2041],{"class":141},"\"products\"",[55,2043,267],{"class":68},[55,2045,2046],{"class":141},"\"search_vector\"",[55,2048,2049],{"class":68},"),   ",[55,2051,2052],{"class":61},"# tsvector maintained by trigger\n",[55,2054,2055,2057,2060,2062,2065],{"class":57,"line":72},[55,2056,2038],{"class":68},[55,2058,2059],{"class":141},"\"orders\"",[55,2061,267],{"class":68},[55,2063,2064],{"class":141},"\"tsv_description\"",[55,2066,1567],{"class":68},[55,2068,2069],{"class":57,"line":86},[55,2070,686],{"class":68},[55,2072,2073],{"class":57,"line":97},[55,2074,107],{"emptyLinePlaceholder":106},[55,2076,2077],{"class":57,"line":103},[55,2078,107],{"emptyLinePlaceholder":106},[55,2080,2081,2083,2085],{"class":57,"line":110},[55,2082,297],{"class":79},[55,2084,301],{"class":300},[55,2086,304],{"class":68},[55,2088,2089,2091,2093,2095,2098],{"class":57,"line":116},[55,2090,310],{"class":79},[55,2092,313],{"class":68},[55,2094,316],{"class":79},[55,2096,2097],{"class":141}," \"column\"",[55,2099,322],{"class":68},[55,2101,2102,2105,2107,2109,2111,2113,2115,2117,2119,2121],{"class":57,"line":132},[55,2103,2104],{"class":68},"        table_name ",[55,2106,80],{"class":79},[55,2108,362],{"class":119},[55,2110,365],{"class":68},[55,2112,368],{"class":119},[55,2114,267],{"class":68},[55,2116,585],{"class":141},[55,2118,267],{"class":68},[55,2120,378],{"class":119},[55,2122,100],{"class":68},[55,2124,2125,2127,2130,2132,2134,2136],{"class":57,"line":138},[55,2126,328],{"class":79},[55,2128,2129],{"class":68}," table_name ",[55,2131,1791],{"class":79},[55,2133,1794],{"class":79},[55,2135,1797],{"class":119},[55,2137,322],{"class":68},[55,2139,2140,2143,2145],{"class":57,"line":148},[55,2141,2142],{"class":68},"            key ",[55,2144,80],{"class":79},[55,2146,2147],{"class":68}," (table_name.name, name)\n",[55,2149,2150,2153,2156,2158,2161],{"class":57,"line":156},[55,2151,2152],{"class":79},"            if",[55,2154,2155],{"class":68}," key ",[55,2157,334],{"class":79},[55,2159,2160],{"class":119}," EXCLUDE_COLUMNS",[55,2162,322],{"class":68},[55,2164,2165,2168],{"class":57,"line":164},[55,2166,2167],{"class":79},"                return",[55,2169,348],{"class":119},[55,2171,2172,2174,2176,2178,2180,2182,2184,2186,2188],{"class":57,"line":172},[55,2173,310],{"class":79},[55,2175,313],{"class":68},[55,2177,316],{"class":79},[55,2179,319],{"class":141},[55,2181,410],{"class":79},[55,2183,331],{"class":68},[55,2185,334],{"class":79},[55,2187,337],{"class":119},[55,2189,322],{"class":68},[55,2191,2192,2194],{"class":57,"line":178},[55,2193,424],{"class":79},[55,2195,348],{"class":119},[55,2197,2198,2200],{"class":57,"line":186},[55,2199,432],{"class":79},[55,2201,435],{"class":119},[14,2203,2204],{},"Note that excluding a column does not automatically exclude foreign key constraints or indexes that reference it. Add those to the exclusion logic separately if needed.",[41,2206,2208],{"id":2207},"verifying-that-exclusions-work-correctly","Verifying That Exclusions Work Correctly",[14,2210,2211,2212,21,2214,2216],{},"After wiring ",[18,2213,20],{},[18,2215,24],{},", verify the hooks are effective by running autogenerate against a database that contains the excluded tables and checking the output:",[46,2218,2222],{"className":2219,"code":2220,"language":2221,"meta":51,"style":51},"language-bash shiki shiki-themes github-light github-dark","# Run autogenerate and inspect what Alembic proposes\nalembic revision --autogenerate -m \"verify_exclusions_test\"\n\n# Open the generated file and confirm excluded tables are absent\ncat alembic\u002Fversions\u002F*verify_exclusions_test*.py\n","bash",[18,2223,2224,2229,2246,2250,2255],{"__ignoreMap":51},[55,2225,2226],{"class":57,"line":58},[55,2227,2228],{"class":61},"# Run autogenerate and inspect what Alembic proposes\n",[55,2230,2231,2234,2237,2240,2243],{"class":57,"line":65},[55,2232,2233],{"class":300},"alembic",[55,2235,2236],{"class":141}," revision",[55,2238,2239],{"class":119}," --autogenerate",[55,2241,2242],{"class":119}," -m",[55,2244,2245],{"class":141}," \"verify_exclusions_test\"\n",[55,2247,2248],{"class":57,"line":72},[55,2249,107],{"emptyLinePlaceholder":106},[55,2251,2252],{"class":57,"line":86},[55,2253,2254],{"class":61},"# Open the generated file and confirm excluded tables are absent\n",[55,2256,2257,2260,2263,2266,2269,2271],{"class":57,"line":97},[55,2258,2259],{"class":300},"cat",[55,2261,2262],{"class":141}," alembic\u002Fversions\u002F",[55,2264,2265],{"class":119},"*",[55,2267,2268],{"class":141},"verify_exclusions_test",[55,2270,2265],{"class":119},[55,2272,2273],{"class":141},".py\n",[14,2275,2276,2277,2280,2281,2283,2284,2286],{},"The generated file should not contain any ",[18,2278,2279],{},"op.drop_table(\"spatial_ref_sys\")"," or similar directives. If it does, the hook is not being called — which most commonly means ",[18,2282,1292],{}," is missing from ",[18,2285,1330],{},", so Alembic is not reflecting those schemas at all and the tables appear via a different path.",[14,2288,2289,2290,2293,2294,2297,2298,2300],{},"A second verification approach: run autogenerate twice in a row without making any model changes. If both runs produce revision files with no ops inside ",[18,2291,2292],{},"upgrade()"," and ",[18,2295,2296],{},"downgrade()",", the exclusion is stable and idempotent. If the second run still proposes changes, there is a spurious diff loop — typically caused by ",[18,2299,1441],{}," combined with dialect rendering differences for columns that were not excluded.",[646,2302,2304],{"id":2303},"auditing-hook-coverage-with-logging","Auditing Hook Coverage with Logging",[14,2306,2307],{},"Enable Alembic's diff logging to see which objects are being considered and whether hooks are firing:",[46,2309,2311],{"className":48,"code":2310,"language":50,"meta":51,"style":51},"# alembic\u002Fenv.py — debug logging for hook troubleshooting\nimport logging\n\nlogging.getLogger(\"alembic.autogenerate.compare\").setLevel(logging.DEBUG)\n",[18,2312,2313,2318,2325,2329],{"__ignoreMap":51},[55,2314,2315],{"class":57,"line":58},[55,2316,2317],{"class":61},"# alembic\u002Fenv.py — debug logging for hook troubleshooting\n",[55,2319,2320,2322],{"class":57,"line":65},[55,2321,928],{"class":79},[55,2323,2324],{"class":68}," logging\n",[55,2326,2327],{"class":57,"line":72},[55,2328,107],{"emptyLinePlaceholder":106},[55,2330,2331,2334,2337,2340,2343],{"class":57,"line":86},[55,2332,2333],{"class":68},"logging.getLogger(",[55,2335,2336],{"class":141},"\"alembic.autogenerate.compare\"",[55,2338,2339],{"class":68},").setLevel(logging.",[55,2341,2342],{"class":119},"DEBUG",[55,2344,100],{"class":68},[14,2346,2347],{},"With this set, Alembic logs each object it compares. You can grep the output for the table names you expect to be excluded and confirm they are absent from the comparison log.",[646,2349,2351],{"id":2350},"testing-include_object-in-isolation","Testing include_object in Isolation",[14,2353,2354],{},"For larger projects, unit-test the hook function directly to avoid running a full migration cycle during CI:",[46,2356,2358],{"className":48,"code":2357,"language":50,"meta":51,"style":51},"# tests\u002Ftest_alembic_env.py\nfrom unittest.mock import MagicMock\nfrom alembic.env import include_object  # import the hook function directly\n\n\ndef make_table_mock(name, schema=None):\n    obj = MagicMock()\n    obj.schema = schema\n    return obj\n\n\ndef test_excludes_spatial_ref_sys():\n    obj = make_table_mock(\"spatial_ref_sys\")\n    assert include_object(obj, \"spatial_ref_sys\", \"table\", True, None) is False\n\n\ndef test_excludes_tiger_schema_tables():\n    obj = make_table_mock(\"counties\", schema=\"tiger\")\n    assert include_object(obj, \"counties\", \"table\", True, None) is False\n\n\ndef test_includes_owned_table():\n    obj = make_table_mock(\"orders\")\n    assert include_object(obj, \"orders\", \"table\", True, None) is True\n\n\ndef test_includes_columns_in_owned_table():\n    # Column objects do not have schema on them directly\n    col = MagicMock()\n    col.table = make_table_mock(\"products\")\n    assert include_object(col, \"price\", \"column\", True, None) is True\n",[18,2359,2360,2365,2377,2392,2396,2400,2416,2426,2436,2443,2447,2451,2461,2475,2503,2507,2511,2520,2542,2568,2572,2576,2585,2597,2623,2627,2631,2640,2645,2654,2667],{"__ignoreMap":51},[55,2361,2362],{"class":57,"line":58},[55,2363,2364],{"class":61},"# tests\u002Ftest_alembic_env.py\n",[55,2366,2367,2369,2372,2374],{"class":57,"line":65},[55,2368,922],{"class":79},[55,2370,2371],{"class":68}," unittest.mock ",[55,2373,928],{"class":79},[55,2375,2376],{"class":68}," MagicMock\n",[55,2378,2379,2381,2384,2386,2389],{"class":57,"line":72},[55,2380,922],{"class":79},[55,2382,2383],{"class":68}," alembic.env ",[55,2385,928],{"class":79},[55,2387,2388],{"class":68}," include_object  ",[55,2390,2391],{"class":61},"# import the hook function directly\n",[55,2393,2394],{"class":57,"line":86},[55,2395,107],{"emptyLinePlaceholder":106},[55,2397,2398],{"class":57,"line":97},[55,2399,107],{"emptyLinePlaceholder":106},[55,2401,2402,2404,2407,2410,2412,2414],{"class":57,"line":103},[55,2403,297],{"class":79},[55,2405,2406],{"class":300}," make_table_mock",[55,2408,2409],{"class":68},"(name, schema",[55,2411,80],{"class":79},[55,2413,378],{"class":119},[55,2415,810],{"class":68},[55,2417,2418,2421,2423],{"class":57,"line":110},[55,2419,2420],{"class":68},"    obj ",[55,2422,80],{"class":79},[55,2424,2425],{"class":68}," MagicMock()\n",[55,2427,2428,2431,2433],{"class":57,"line":116},[55,2429,2430],{"class":68},"    obj.schema ",[55,2432,80],{"class":79},[55,2434,2435],{"class":68}," schema\n",[55,2437,2438,2440],{"class":57,"line":132},[55,2439,432],{"class":79},[55,2441,2442],{"class":68}," obj\n",[55,2444,2445],{"class":57,"line":138},[55,2446,107],{"emptyLinePlaceholder":106},[55,2448,2449],{"class":57,"line":148},[55,2450,107],{"emptyLinePlaceholder":106},[55,2452,2453,2455,2458],{"class":57,"line":156},[55,2454,297],{"class":79},[55,2456,2457],{"class":300}," test_excludes_spatial_ref_sys",[55,2459,2460],{"class":68},"():\n",[55,2462,2463,2465,2467,2470,2473],{"class":57,"line":164},[55,2464,2420],{"class":68},[55,2466,80],{"class":79},[55,2468,2469],{"class":68}," make_table_mock(",[55,2471,2472],{"class":141},"\"spatial_ref_sys\"",[55,2474,100],{"class":68},[55,2476,2477,2480,2483,2485,2487,2489,2491,2493,2495,2497,2499,2501],{"class":57,"line":172},[55,2478,2479],{"class":79},"    assert",[55,2481,2482],{"class":68}," include_object(obj, ",[55,2484,2472],{"class":141},[55,2486,267],{"class":68},[55,2488,585],{"class":141},[55,2490,267],{"class":68},[55,2492,479],{"class":119},[55,2494,267],{"class":68},[55,2496,378],{"class":119},[55,2498,381],{"class":68},[55,2500,1791],{"class":79},[55,2502,348],{"class":119},[55,2504,2505],{"class":57,"line":178},[55,2506,107],{"emptyLinePlaceholder":106},[55,2508,2509],{"class":57,"line":186},[55,2510,107],{"emptyLinePlaceholder":106},[55,2512,2513,2515,2518],{"class":57,"line":194},[55,2514,297],{"class":79},[55,2516,2517],{"class":300}," test_excludes_tiger_schema_tables",[55,2519,2460],{"class":68},[55,2521,2522,2524,2526,2528,2531,2533,2536,2538,2540],{"class":57,"line":200},[55,2523,2420],{"class":68},[55,2525,80],{"class":79},[55,2527,2469],{"class":68},[55,2529,2530],{"class":141},"\"counties\"",[55,2532,267],{"class":68},[55,2534,2535],{"class":75},"schema",[55,2537,80],{"class":79},[55,2539,264],{"class":141},[55,2541,100],{"class":68},[55,2543,2544,2546,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566],{"class":57,"line":208},[55,2545,2479],{"class":79},[55,2547,2482],{"class":68},[55,2549,2530],{"class":141},[55,2551,267],{"class":68},[55,2553,585],{"class":141},[55,2555,267],{"class":68},[55,2557,479],{"class":119},[55,2559,267],{"class":68},[55,2561,378],{"class":119},[55,2563,381],{"class":68},[55,2565,1791],{"class":79},[55,2567,348],{"class":119},[55,2569,2570],{"class":57,"line":216},[55,2571,107],{"emptyLinePlaceholder":106},[55,2573,2574],{"class":57,"line":224},[55,2575,107],{"emptyLinePlaceholder":106},[55,2577,2578,2580,2583],{"class":57,"line":232},[55,2579,297],{"class":79},[55,2581,2582],{"class":300}," test_includes_owned_table",[55,2584,2460],{"class":68},[55,2586,2587,2589,2591,2593,2595],{"class":57,"line":240},[55,2588,2420],{"class":68},[55,2590,80],{"class":79},[55,2592,2469],{"class":68},[55,2594,2059],{"class":141},[55,2596,100],{"class":68},[55,2598,2599,2601,2603,2605,2607,2609,2611,2613,2615,2617,2619,2621],{"class":57,"line":246},[55,2600,2479],{"class":79},[55,2602,2482],{"class":68},[55,2604,2059],{"class":141},[55,2606,267],{"class":68},[55,2608,585],{"class":141},[55,2610,267],{"class":68},[55,2612,479],{"class":119},[55,2614,267],{"class":68},[55,2616,378],{"class":119},[55,2618,381],{"class":68},[55,2620,1791],{"class":79},[55,2622,435],{"class":119},[55,2624,2625],{"class":57,"line":251},[55,2626,107],{"emptyLinePlaceholder":106},[55,2628,2629],{"class":57,"line":284},[55,2630,107],{"emptyLinePlaceholder":106},[55,2632,2633,2635,2638],{"class":57,"line":289},[55,2634,297],{"class":79},[55,2636,2637],{"class":300}," test_includes_columns_in_owned_table",[55,2639,2460],{"class":68},[55,2641,2642],{"class":57,"line":294},[55,2643,2644],{"class":61},"    # Column objects do not have schema on them directly\n",[55,2646,2647,2650,2652],{"class":57,"line":307},[55,2648,2649],{"class":68},"    col ",[55,2651,80],{"class":79},[55,2653,2425],{"class":68},[55,2655,2656,2659,2661,2663,2665],{"class":57,"line":325},[55,2657,2658],{"class":68},"    col.table ",[55,2660,80],{"class":79},[55,2662,2469],{"class":68},[55,2664,2041],{"class":141},[55,2666,100],{"class":68},[55,2668,2669,2671,2674,2677,2679,2681,2683,2685,2687,2689,2691,2693],{"class":57,"line":342},[55,2670,2479],{"class":79},[55,2672,2673],{"class":68}," include_object(col, ",[55,2675,2676],{"class":141},"\"price\"",[55,2678,267],{"class":68},[55,2680,588],{"class":141},[55,2682,267],{"class":68},[55,2684,479],{"class":119},[55,2686,267],{"class":68},[55,2688,378],{"class":119},[55,2690,381],{"class":68},[55,2692,1791],{"class":79},[55,2694,435],{"class":119},[14,2696,2697],{},"Running these as standard pytest tests catches hook regressions without needing a live database.",[41,2699,2701],{"id":2700},"frequently-asked-questions","Frequently Asked Questions",[14,2703,2704,2711],{},[2705,2706,2707,2708,2710],"strong",{},"Does ",[18,2709,20],{}," prevent autogenerate from detecting new columns in excluded tables?","\nYes. Once a table is excluded at the table level, Alembic skips all of its columns, indexes, and constraints. You do not need column-level filters for excluded tables.",[14,2713,2714,2717,2718,2721,2722,2725,2726,2728,2729,2731,2732,21,2735,2738],{},[2705,2715,2716],{},"Can I exclude a single column within an otherwise managed table?","\nYes. Check ",[18,2719,2720],{},"type_ == \"column\""," and inspect the parent table name via ",[18,2723,2724],{},"object.table.name"," inside ",[18,2727,20],{},". Return ",[18,2730,643],{}," for the specific column. Be aware that foreign keys and indexes referencing that column may also need to be excluded; handle them by checking ",[18,2733,2734],{},"type_ == \"foreign_key_constraint\"",[18,2736,2737],{},"type_ == \"index\""," and inspecting the referenced column names.",[14,2740,2741,2747,2749,2750,2753,2754,2756,2757,2759,2760,2763,2764,2766,2767,2769],{},[2705,2742,2743,2744,2746],{},"Why does setting ",[18,2745,1320],{}," still show foreign-schema tables?",[18,2748,1320],{}," means Alembic only reflects the default schema (usually ",[18,2751,2752],{},"public"," in PostgreSQL). But if your ",[18,2755,1242],{}," contains ",[18,2758,547],{}," objects with an explicit ",[18,2761,2762],{},"schema="," argument, Alembic reflects those schemas regardless of this flag. Set ",[18,2765,1292],{}," and use ",[18,2768,20],{}," to control which schemas are diffed.",[14,2771,2772,2778,2779,2782,2783,2293,2785,2787,2788,2790],{},[2705,2773,2774,2775,2777],{},"Is there a way to exclude tables without editing ",[18,2776,28],{},"?","\nAlembic does not support exclusion via ",[18,2780,2781],{},"alembic.ini"," keys. The ",[18,2784,20],{},[18,2786,24],{}," hooks in ",[18,2789,28],{}," are the only supported mechanism. To allow runtime overrides without code changes, read the exclusion list from an environment variable as shown in the advanced optimization section above.",[41,2792,2794],{"id":2793},"related","Related",[2796,2797,2798,2805,2818],"ul",{},[2799,2800,2801,2804],"li",{},[35,2802,2803],{"href":37},"Autogenerating and reviewing migration scripts"," — parent guide: how autogenerate works end-to-end, naming conventions, editing revisions, and branch\u002Fmerge workflows.",[2799,2806,2807,2811,2812,2814,2815,2817],{},[35,2808,2810],{"href":2809},"\u002Falembic-async-migrations-and-schema-evolution\u002Fconfiguring-alembic-with-async-sqlalchemy-engines\u002F","Configuring Alembic with async SQLAlchemy engines"," — wiring ",[18,2813,28],{}," for asyncpg, which is required before ",[18,2816,20],{}," hooks can run.",[2799,2819,2820,2824],{},[35,2821,2823],{"href":2822},"\u002Falembic-async-migrations-and-schema-evolution\u002Fzero-downtime-schema-migration-strategies\u002F","Zero-downtime schema migration strategies"," — safely applying migrations to live systems where table exclusion matters most for avoiding accidental data loss.",[2826,2827,2828],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}",{"title":51,"searchDepth":65,"depth":65,"links":2830},[2831,2832,2836,2837,2842,2846,2847],{"id":43,"depth":65,"text":44},{"id":500,"depth":65,"text":501,"children":2833},[2834,2835],{"id":648,"depth":72,"text":649},{"id":903,"depth":72,"text":904},{"id":1211,"depth":65,"text":1212},{"id":1483,"depth":65,"text":1484,"children":2838},[2839,2840,2841],{"id":1487,"depth":72,"text":1488},{"id":1886,"depth":72,"text":1887},{"id":2011,"depth":72,"text":2012},{"id":2207,"depth":65,"text":2208,"children":2843},[2844,2845],{"id":2303,"depth":72,"text":2304},{"id":2350,"depth":72,"text":2351},{"id":2700,"depth":65,"text":2701},{"id":2793,"depth":65,"text":2794},"Use the include_object or include_name hook in env.py to tell Alembic which tables and schemas to ignore during autogenerate — this prevents it from emitting spurious op.drop_table directives for tables your application does not own, and is a critical step in any autogenerate workflow.","md",{"date":2851},"2026-06-18","\u002Falembic-async-migrations-and-schema-evolution\u002Fautogenerating-and-reviewing-migration-scripts\u002Fexcluding-tables-and-schemas-from-alembic-autogenerate",{"title":5,"description":2848},"alembic-async-migrations-and-schema-evolution\u002Fautogenerating-and-reviewing-migration-scripts\u002Fexcluding-tables-and-schemas-from-alembic-autogenerate\u002Findex","Nb8k3_-mzsJxphAFNGsH1e6N_8W79glNbwZb0oqF0OM",1781810028981]