sqlspec 0.36.0__cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ac8f31065839703b4e70__mypyc.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/__init__.py +140 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +315 -0
- sqlspec/_typing.py +700 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_typing.py +82 -0
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +1273 -0
- sqlspec/adapters/adbc/config.py +295 -0
- sqlspec/adapters/adbc/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/adbc/core.py +735 -0
- sqlspec/adapters/adbc/data_dictionary.py +334 -0
- sqlspec/adapters/adbc/driver.py +529 -0
- sqlspec/adapters/adbc/events/__init__.py +5 -0
- sqlspec/adapters/adbc/events/store.py +285 -0
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +502 -0
- sqlspec/adapters/adbc/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/adbc/type_converter.py +140 -0
- sqlspec/adapters/aiosqlite/__init__.py +25 -0
- sqlspec/adapters/aiosqlite/_typing.py +82 -0
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +818 -0
- sqlspec/adapters/aiosqlite/config.py +334 -0
- sqlspec/adapters/aiosqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/aiosqlite/core.py +315 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +208 -0
- sqlspec/adapters/aiosqlite/driver.py +313 -0
- sqlspec/adapters/aiosqlite/events/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/events/store.py +20 -0
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +279 -0
- sqlspec/adapters/aiosqlite/pool.py +533 -0
- sqlspec/adapters/asyncmy/__init__.py +21 -0
- sqlspec/adapters/asyncmy/_typing.py +87 -0
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +703 -0
- sqlspec/adapters/asyncmy/config.py +302 -0
- sqlspec/adapters/asyncmy/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/asyncmy/core.py +360 -0
- sqlspec/adapters/asyncmy/data_dictionary.py +124 -0
- sqlspec/adapters/asyncmy/driver.py +383 -0
- sqlspec/adapters/asyncmy/events/__init__.py +5 -0
- sqlspec/adapters/asyncmy/events/store.py +104 -0
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +19 -0
- sqlspec/adapters/asyncpg/_typing.py +88 -0
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +748 -0
- sqlspec/adapters/asyncpg/config.py +569 -0
- sqlspec/adapters/asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/asyncpg/core.py +367 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +162 -0
- sqlspec/adapters/asyncpg/driver.py +487 -0
- sqlspec/adapters/asyncpg/events/__init__.py +6 -0
- sqlspec/adapters/asyncpg/events/backend.py +286 -0
- sqlspec/adapters/asyncpg/events/store.py +40 -0
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +251 -0
- sqlspec/adapters/bigquery/__init__.py +14 -0
- sqlspec/adapters/bigquery/_typing.py +86 -0
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +827 -0
- sqlspec/adapters/bigquery/config.py +353 -0
- sqlspec/adapters/bigquery/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/bigquery/core.py +715 -0
- sqlspec/adapters/bigquery/data_dictionary.py +128 -0
- sqlspec/adapters/bigquery/driver.py +548 -0
- sqlspec/adapters/bigquery/events/__init__.py +5 -0
- sqlspec/adapters/bigquery/events/store.py +139 -0
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +325 -0
- sqlspec/adapters/bigquery/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/bigquery/type_converter.py +107 -0
- sqlspec/adapters/cockroach_asyncpg/__init__.py +24 -0
- sqlspec/adapters/cockroach_asyncpg/_typing.py +72 -0
- sqlspec/adapters/cockroach_asyncpg/adk/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/adk/store.py +410 -0
- sqlspec/adapters/cockroach_asyncpg/config.py +238 -0
- sqlspec/adapters/cockroach_asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/cockroach_asyncpg/core.py +55 -0
- sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +107 -0
- sqlspec/adapters/cockroach_asyncpg/driver.py +144 -0
- sqlspec/adapters/cockroach_asyncpg/events/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/events/store.py +20 -0
- sqlspec/adapters/cockroach_asyncpg/litestar/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/litestar/store.py +142 -0
- sqlspec/adapters/cockroach_psycopg/__init__.py +38 -0
- sqlspec/adapters/cockroach_psycopg/_typing.py +129 -0
- sqlspec/adapters/cockroach_psycopg/adk/__init__.py +13 -0
- sqlspec/adapters/cockroach_psycopg/adk/store.py +868 -0
- sqlspec/adapters/cockroach_psycopg/config.py +484 -0
- sqlspec/adapters/cockroach_psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/cockroach_psycopg/core.py +63 -0
- sqlspec/adapters/cockroach_psycopg/data_dictionary.py +215 -0
- sqlspec/adapters/cockroach_psycopg/driver.py +284 -0
- sqlspec/adapters/cockroach_psycopg/events/__init__.py +6 -0
- sqlspec/adapters/cockroach_psycopg/events/store.py +34 -0
- sqlspec/adapters/cockroach_psycopg/litestar/__init__.py +3 -0
- sqlspec/adapters/cockroach_psycopg/litestar/store.py +325 -0
- sqlspec/adapters/duckdb/__init__.py +25 -0
- sqlspec/adapters/duckdb/_typing.py +81 -0
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +850 -0
- sqlspec/adapters/duckdb/config.py +463 -0
- sqlspec/adapters/duckdb/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/duckdb/core.py +257 -0
- sqlspec/adapters/duckdb/data_dictionary.py +140 -0
- sqlspec/adapters/duckdb/driver.py +430 -0
- sqlspec/adapters/duckdb/events/__init__.py +5 -0
- sqlspec/adapters/duckdb/events/store.py +57 -0
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +330 -0
- sqlspec/adapters/duckdb/pool.py +293 -0
- sqlspec/adapters/duckdb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/duckdb/type_converter.py +118 -0
- sqlspec/adapters/mock/__init__.py +72 -0
- sqlspec/adapters/mock/_typing.py +147 -0
- sqlspec/adapters/mock/config.py +483 -0
- sqlspec/adapters/mock/core.py +319 -0
- sqlspec/adapters/mock/data_dictionary.py +366 -0
- sqlspec/adapters/mock/driver.py +721 -0
- sqlspec/adapters/mysqlconnector/__init__.py +36 -0
- sqlspec/adapters/mysqlconnector/_typing.py +141 -0
- sqlspec/adapters/mysqlconnector/adk/__init__.py +15 -0
- sqlspec/adapters/mysqlconnector/adk/store.py +1060 -0
- sqlspec/adapters/mysqlconnector/config.py +394 -0
- sqlspec/adapters/mysqlconnector/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/mysqlconnector/core.py +303 -0
- sqlspec/adapters/mysqlconnector/data_dictionary.py +235 -0
- sqlspec/adapters/mysqlconnector/driver.py +483 -0
- sqlspec/adapters/mysqlconnector/events/__init__.py +8 -0
- sqlspec/adapters/mysqlconnector/events/store.py +98 -0
- sqlspec/adapters/mysqlconnector/litestar/__init__.py +5 -0
- sqlspec/adapters/mysqlconnector/litestar/store.py +426 -0
- sqlspec/adapters/oracledb/__init__.py +60 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +141 -0
- sqlspec/adapters/oracledb/_typing.py +182 -0
- sqlspec/adapters/oracledb/_uuid_handlers.py +166 -0
- sqlspec/adapters/oracledb/adk/__init__.py +10 -0
- sqlspec/adapters/oracledb/adk/store.py +2369 -0
- sqlspec/adapters/oracledb/config.py +550 -0
- sqlspec/adapters/oracledb/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/oracledb/core.py +543 -0
- sqlspec/adapters/oracledb/data_dictionary.py +536 -0
- sqlspec/adapters/oracledb/driver.py +1229 -0
- sqlspec/adapters/oracledb/events/__init__.py +16 -0
- sqlspec/adapters/oracledb/events/backend.py +347 -0
- sqlspec/adapters/oracledb/events/store.py +420 -0
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +781 -0
- sqlspec/adapters/oracledb/migrations.py +535 -0
- sqlspec/adapters/oracledb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/oracledb/type_converter.py +211 -0
- sqlspec/adapters/psqlpy/__init__.py +17 -0
- sqlspec/adapters/psqlpy/_typing.py +79 -0
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +766 -0
- sqlspec/adapters/psqlpy/config.py +304 -0
- sqlspec/adapters/psqlpy/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psqlpy/core.py +480 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +126 -0
- sqlspec/adapters/psqlpy/driver.py +438 -0
- sqlspec/adapters/psqlpy/events/__init__.py +6 -0
- sqlspec/adapters/psqlpy/events/backend.py +310 -0
- sqlspec/adapters/psqlpy/events/store.py +20 -0
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +270 -0
- sqlspec/adapters/psqlpy/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psqlpy/type_converter.py +113 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_typing.py +164 -0
- sqlspec/adapters/psycopg/adk/__init__.py +10 -0
- sqlspec/adapters/psycopg/adk/store.py +1387 -0
- sqlspec/adapters/psycopg/config.py +576 -0
- sqlspec/adapters/psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psycopg/core.py +450 -0
- sqlspec/adapters/psycopg/data_dictionary.py +289 -0
- sqlspec/adapters/psycopg/driver.py +975 -0
- sqlspec/adapters/psycopg/events/__init__.py +20 -0
- sqlspec/adapters/psycopg/events/backend.py +458 -0
- sqlspec/adapters/psycopg/events/store.py +42 -0
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +552 -0
- sqlspec/adapters/psycopg/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psycopg/type_converter.py +93 -0
- sqlspec/adapters/pymysql/__init__.py +21 -0
- sqlspec/adapters/pymysql/_typing.py +71 -0
- sqlspec/adapters/pymysql/adk/__init__.py +5 -0
- sqlspec/adapters/pymysql/adk/store.py +540 -0
- sqlspec/adapters/pymysql/config.py +195 -0
- sqlspec/adapters/pymysql/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/pymysql/core.py +299 -0
- sqlspec/adapters/pymysql/data_dictionary.py +122 -0
- sqlspec/adapters/pymysql/driver.py +259 -0
- sqlspec/adapters/pymysql/events/__init__.py +5 -0
- sqlspec/adapters/pymysql/events/store.py +50 -0
- sqlspec/adapters/pymysql/litestar/__init__.py +5 -0
- sqlspec/adapters/pymysql/litestar/store.py +232 -0
- sqlspec/adapters/pymysql/pool.py +137 -0
- sqlspec/adapters/spanner/__init__.py +40 -0
- sqlspec/adapters/spanner/_typing.py +86 -0
- sqlspec/adapters/spanner/adk/__init__.py +5 -0
- sqlspec/adapters/spanner/adk/store.py +732 -0
- sqlspec/adapters/spanner/config.py +352 -0
- sqlspec/adapters/spanner/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/spanner/core.py +188 -0
- sqlspec/adapters/spanner/data_dictionary.py +120 -0
- sqlspec/adapters/spanner/dialect/__init__.py +6 -0
- sqlspec/adapters/spanner/dialect/_spangres.py +57 -0
- sqlspec/adapters/spanner/dialect/_spanner.py +130 -0
- sqlspec/adapters/spanner/driver.py +373 -0
- sqlspec/adapters/spanner/events/__init__.py +5 -0
- sqlspec/adapters/spanner/events/store.py +187 -0
- sqlspec/adapters/spanner/litestar/__init__.py +5 -0
- sqlspec/adapters/spanner/litestar/store.py +291 -0
- sqlspec/adapters/spanner/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/spanner/type_converter.py +331 -0
- sqlspec/adapters/sqlite/__init__.py +19 -0
- sqlspec/adapters/sqlite/_typing.py +80 -0
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +958 -0
- sqlspec/adapters/sqlite/config.py +280 -0
- sqlspec/adapters/sqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/sqlite/core.py +312 -0
- sqlspec/adapters/sqlite/data_dictionary.py +202 -0
- sqlspec/adapters/sqlite/driver.py +359 -0
- sqlspec/adapters/sqlite/events/__init__.py +5 -0
- sqlspec/adapters/sqlite/events/store.py +20 -0
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +316 -0
- sqlspec/adapters/sqlite/pool.py +198 -0
- sqlspec/adapters/sqlite/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/sqlite/type_converter.py +114 -0
- sqlspec/base.py +747 -0
- sqlspec/builder/__init__.py +179 -0
- sqlspec/builder/_base.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_base.py +1022 -0
- sqlspec/builder/_column.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_column.py +521 -0
- sqlspec/builder/_ddl.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_ddl.py +1642 -0
- sqlspec/builder/_delete.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_delete.py +95 -0
- sqlspec/builder/_dml.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_dml.py +365 -0
- sqlspec/builder/_explain.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_explain.py +579 -0
- sqlspec/builder/_expression_wrappers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_factory.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_factory.py +1697 -0
- sqlspec/builder/_insert.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_insert.py +328 -0
- sqlspec/builder/_join.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_join.py +499 -0
- sqlspec/builder/_merge.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_merge.py +821 -0
- sqlspec/builder/_parsing_utils.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_parsing_utils.py +297 -0
- sqlspec/builder/_select.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_select.py +1660 -0
- sqlspec/builder/_temporal.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_temporal.py +139 -0
- sqlspec/builder/_update.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_update.py +173 -0
- sqlspec/builder/_vector_expressions.py +267 -0
- sqlspec/cli.py +911 -0
- sqlspec/config.py +1755 -0
- sqlspec/core/__init__.py +374 -0
- sqlspec/core/_correlation.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/_correlation.py +176 -0
- sqlspec/core/cache.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/cache.py +1069 -0
- sqlspec/core/compiler.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/compiler.py +954 -0
- sqlspec/core/explain.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/explain.py +275 -0
- sqlspec/core/filters.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/filters.py +952 -0
- sqlspec/core/hashing.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/hashing.py +262 -0
- sqlspec/core/metrics.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/metrics.py +83 -0
- sqlspec/core/parameters/__init__.py +71 -0
- sqlspec/core/parameters/_alignment.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_alignment.py +270 -0
- sqlspec/core/parameters/_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_converter.py +543 -0
- sqlspec/core/parameters/_processor.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_processor.py +505 -0
- sqlspec/core/parameters/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_registry.py +206 -0
- sqlspec/core/parameters/_transformers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_transformers.py +292 -0
- sqlspec/core/parameters/_types.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_types.py +499 -0
- sqlspec/core/parameters/_validator.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_validator.py +180 -0
- sqlspec/core/pipeline.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/pipeline.py +319 -0
- sqlspec/core/query_modifiers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/query_modifiers.py +437 -0
- sqlspec/core/result/__init__.py +23 -0
- sqlspec/core/result/_base.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result/_base.py +1121 -0
- sqlspec/core/result/_io.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result/_io.py +28 -0
- sqlspec/core/splitter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/splitter.py +966 -0
- sqlspec/core/stack.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/stack.py +163 -0
- sqlspec/core/statement.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/statement.py +1503 -0
- sqlspec/core/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/type_converter.py +339 -0
- sqlspec/data_dictionary/__init__.py +22 -0
- sqlspec/data_dictionary/_loader.py +123 -0
- sqlspec/data_dictionary/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/_registry.py +74 -0
- sqlspec/data_dictionary/_types.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/_types.py +121 -0
- sqlspec/data_dictionary/dialects/__init__.py +21 -0
- sqlspec/data_dictionary/dialects/bigquery.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/bigquery.py +49 -0
- sqlspec/data_dictionary/dialects/cockroachdb.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/cockroachdb.py +43 -0
- sqlspec/data_dictionary/dialects/duckdb.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/duckdb.py +47 -0
- sqlspec/data_dictionary/dialects/mysql.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/mysql.py +42 -0
- sqlspec/data_dictionary/dialects/oracle.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/oracle.py +34 -0
- sqlspec/data_dictionary/dialects/postgres.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/postgres.py +46 -0
- sqlspec/data_dictionary/dialects/spanner.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/spanner.py +37 -0
- sqlspec/data_dictionary/dialects/sqlite.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/sqlite.py +42 -0
- sqlspec/data_dictionary/sql/.gitkeep +0 -0
- sqlspec/data_dictionary/sql/bigquery/columns.sql +23 -0
- sqlspec/data_dictionary/sql/bigquery/foreign_keys.sql +34 -0
- sqlspec/data_dictionary/sql/bigquery/indexes.sql +19 -0
- sqlspec/data_dictionary/sql/bigquery/tables.sql +33 -0
- sqlspec/data_dictionary/sql/bigquery/version.sql +3 -0
- sqlspec/data_dictionary/sql/cockroachdb/columns.sql +34 -0
- sqlspec/data_dictionary/sql/cockroachdb/foreign_keys.sql +40 -0
- sqlspec/data_dictionary/sql/cockroachdb/indexes.sql +32 -0
- sqlspec/data_dictionary/sql/cockroachdb/tables.sql +44 -0
- sqlspec/data_dictionary/sql/cockroachdb/version.sql +3 -0
- sqlspec/data_dictionary/sql/duckdb/columns.sql +23 -0
- sqlspec/data_dictionary/sql/duckdb/foreign_keys.sql +36 -0
- sqlspec/data_dictionary/sql/duckdb/indexes.sql +19 -0
- sqlspec/data_dictionary/sql/duckdb/tables.sql +38 -0
- sqlspec/data_dictionary/sql/duckdb/version.sql +3 -0
- sqlspec/data_dictionary/sql/mysql/columns.sql +23 -0
- sqlspec/data_dictionary/sql/mysql/foreign_keys.sql +28 -0
- sqlspec/data_dictionary/sql/mysql/indexes.sql +26 -0
- sqlspec/data_dictionary/sql/mysql/tables.sql +33 -0
- sqlspec/data_dictionary/sql/mysql/version.sql +3 -0
- sqlspec/data_dictionary/sql/oracle/columns.sql +23 -0
- sqlspec/data_dictionary/sql/oracle/foreign_keys.sql +48 -0
- sqlspec/data_dictionary/sql/oracle/indexes.sql +44 -0
- sqlspec/data_dictionary/sql/oracle/tables.sql +25 -0
- sqlspec/data_dictionary/sql/oracle/version.sql +20 -0
- sqlspec/data_dictionary/sql/postgres/columns.sql +34 -0
- sqlspec/data_dictionary/sql/postgres/foreign_keys.sql +40 -0
- sqlspec/data_dictionary/sql/postgres/indexes.sql +56 -0
- sqlspec/data_dictionary/sql/postgres/tables.sql +44 -0
- sqlspec/data_dictionary/sql/postgres/version.sql +3 -0
- sqlspec/data_dictionary/sql/spanner/columns.sql +23 -0
- sqlspec/data_dictionary/sql/spanner/foreign_keys.sql +70 -0
- sqlspec/data_dictionary/sql/spanner/indexes.sql +30 -0
- sqlspec/data_dictionary/sql/spanner/tables.sql +9 -0
- sqlspec/data_dictionary/sql/spanner/version.sql +3 -0
- sqlspec/data_dictionary/sql/sqlite/columns.sql +23 -0
- sqlspec/data_dictionary/sql/sqlite/foreign_keys.sql +22 -0
- sqlspec/data_dictionary/sql/sqlite/indexes.sql +7 -0
- sqlspec/data_dictionary/sql/sqlite/tables.sql +28 -0
- sqlspec/data_dictionary/sql/sqlite/version.sql +3 -0
- sqlspec/driver/__init__.py +32 -0
- sqlspec/driver/_async.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_async.py +1737 -0
- sqlspec/driver/_common.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_common.py +1478 -0
- sqlspec/driver/_sql_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_sql_helpers.py +148 -0
- sqlspec/driver/_storage_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_storage_helpers.py +144 -0
- sqlspec/driver/_sync.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_sync.py +1710 -0
- sqlspec/exceptions.py +338 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/adk/__init__.py +70 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/memory/__init__.py +69 -0
- sqlspec/extensions/adk/memory/_types.py +30 -0
- sqlspec/extensions/adk/memory/converters.py +149 -0
- sqlspec/extensions/adk/memory/service.py +217 -0
- sqlspec/extensions/adk/memory/store.py +569 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +246 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +225 -0
- sqlspec/extensions/adk/store.py +567 -0
- sqlspec/extensions/events/__init__.py +51 -0
- sqlspec/extensions/events/_channel.py +703 -0
- sqlspec/extensions/events/_hints.py +45 -0
- sqlspec/extensions/events/_models.py +23 -0
- sqlspec/extensions/events/_payload.py +69 -0
- sqlspec/extensions/events/_protocols.py +134 -0
- sqlspec/extensions/events/_queue.py +461 -0
- sqlspec/extensions/events/_store.py +209 -0
- sqlspec/extensions/events/migrations/0001_create_event_queue.py +59 -0
- sqlspec/extensions/events/migrations/__init__.py +3 -0
- sqlspec/extensions/fastapi/__init__.py +19 -0
- sqlspec/extensions/fastapi/extension.py +351 -0
- sqlspec/extensions/fastapi/providers.py +607 -0
- sqlspec/extensions/flask/__init__.py +37 -0
- sqlspec/extensions/flask/_state.py +76 -0
- sqlspec/extensions/flask/_utils.py +71 -0
- sqlspec/extensions/flask/extension.py +519 -0
- sqlspec/extensions/litestar/__init__.py +28 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/channels.py +165 -0
- sqlspec/extensions/litestar/cli.py +102 -0
- sqlspec/extensions/litestar/config.py +90 -0
- sqlspec/extensions/litestar/handlers.py +316 -0
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +671 -0
- sqlspec/extensions/litestar/providers.py +526 -0
- sqlspec/extensions/litestar/store.py +296 -0
- sqlspec/extensions/otel/__init__.py +58 -0
- sqlspec/extensions/prometheus/__init__.py +113 -0
- sqlspec/extensions/starlette/__init__.py +19 -0
- sqlspec/extensions/starlette/_state.py +30 -0
- sqlspec/extensions/starlette/_utils.py +96 -0
- sqlspec/extensions/starlette/extension.py +346 -0
- sqlspec/extensions/starlette/middleware.py +235 -0
- sqlspec/loader.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/loader.py +702 -0
- sqlspec/migrations/__init__.py +36 -0
- sqlspec/migrations/base.py +731 -0
- sqlspec/migrations/commands.py +1232 -0
- sqlspec/migrations/context.py +157 -0
- sqlspec/migrations/fix.py +204 -0
- sqlspec/migrations/loaders.py +443 -0
- sqlspec/migrations/runner.py +1172 -0
- sqlspec/migrations/templates.py +234 -0
- sqlspec/migrations/tracker.py +611 -0
- sqlspec/migrations/utils.py +256 -0
- sqlspec/migrations/validation.py +207 -0
- sqlspec/migrations/version.py +446 -0
- sqlspec/observability/__init__.py +55 -0
- sqlspec/observability/_common.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_common.py +77 -0
- sqlspec/observability/_config.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_config.py +348 -0
- sqlspec/observability/_diagnostics.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_diagnostics.py +74 -0
- sqlspec/observability/_dispatcher.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_dispatcher.py +152 -0
- sqlspec/observability/_formatters/__init__.py +13 -0
- sqlspec/observability/_formatters/_aws.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_aws.py +102 -0
- sqlspec/observability/_formatters/_azure.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_azure.py +96 -0
- sqlspec/observability/_formatters/_base.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_base.py +57 -0
- sqlspec/observability/_formatters/_gcp.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_gcp.py +131 -0
- sqlspec/observability/_formatting.py +58 -0
- sqlspec/observability/_observer.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_observer.py +357 -0
- sqlspec/observability/_runtime.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_runtime.py +420 -0
- sqlspec/observability/_sampling.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_sampling.py +188 -0
- sqlspec/observability/_spans.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_spans.py +161 -0
- sqlspec/protocols.py +916 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +48 -0
- sqlspec/storage/_utils.py +104 -0
- sqlspec/storage/backends/__init__.py +1 -0
- sqlspec/storage/backends/base.py +253 -0
- sqlspec/storage/backends/fsspec.py +529 -0
- sqlspec/storage/backends/local.py +441 -0
- sqlspec/storage/backends/obstore.py +916 -0
- sqlspec/storage/errors.py +104 -0
- sqlspec/storage/pipeline.py +582 -0
- sqlspec/storage/registry.py +301 -0
- sqlspec/typing.py +395 -0
- sqlspec/utils/__init__.py +7 -0
- sqlspec/utils/arrow_helpers.py +318 -0
- sqlspec/utils/config_tools.py +332 -0
- sqlspec/utils/correlation.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/correlation.py +134 -0
- sqlspec/utils/deprecation.py +190 -0
- sqlspec/utils/fixtures.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/fixtures.py +258 -0
- sqlspec/utils/logging.py +222 -0
- sqlspec/utils/module_loader.py +306 -0
- sqlspec/utils/portal.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/portal.py +375 -0
- sqlspec/utils/schema.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/schema.py +485 -0
- sqlspec/utils/serializers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/serializers.py +408 -0
- sqlspec/utils/singleton.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/singleton.py +41 -0
- sqlspec/utils/sync_tools.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/sync_tools.py +311 -0
- sqlspec/utils/text.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/text.py +108 -0
- sqlspec/utils/type_converters.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_converters.py +128 -0
- sqlspec/utils/type_guards.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_guards.py +1360 -0
- sqlspec/utils/uuids.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/uuids.py +225 -0
- sqlspec-0.36.0.dist-info/METADATA +205 -0
- sqlspec-0.36.0.dist-info/RECORD +531 -0
- sqlspec-0.36.0.dist-info/WHEEL +7 -0
- sqlspec-0.36.0.dist-info/entry_points.txt +2 -0
- sqlspec-0.36.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1172 @@
|
|
|
1
|
+
"""Migration execution engine for SQLSpec."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import hashlib
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Literal, Union, cast, overload
|
|
12
|
+
|
|
13
|
+
from sqlspec.core import SQL
|
|
14
|
+
from sqlspec.loader import SQLFileLoader
|
|
15
|
+
from sqlspec.migrations.context import MigrationContext
|
|
16
|
+
from sqlspec.migrations.loaders import get_migration_loader
|
|
17
|
+
from sqlspec.migrations.templates import TemplateDescriptionHints
|
|
18
|
+
from sqlspec.migrations.version import parse_version
|
|
19
|
+
from sqlspec.observability import resolve_db_system
|
|
20
|
+
from sqlspec.utils.logging import get_logger, log_with_context
|
|
21
|
+
from sqlspec.utils.sync_tools import async_, await_
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Awaitable, Callable, Coroutine
|
|
25
|
+
|
|
26
|
+
from sqlspec.config import DatabaseConfigProtocol
|
|
27
|
+
from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
|
|
28
|
+
from sqlspec.observability import ObservabilityRuntime
|
|
29
|
+
|
|
30
|
+
__all__ = ("AsyncMigrationRunner", "SyncMigrationRunner", "create_migration_runner")
|
|
31
|
+
|
|
32
|
+
logger = get_logger("sqlspec.migrations.runner")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _CachedMigrationMetadata:
|
|
36
|
+
"""Cached migration metadata keyed by file path."""
|
|
37
|
+
|
|
38
|
+
__slots__ = ("metadata", "mtime_ns", "size")
|
|
39
|
+
|
|
40
|
+
def __init__(self, metadata: "dict[str, Any]", mtime_ns: int, size: int) -> None:
|
|
41
|
+
self.metadata = metadata
|
|
42
|
+
self.mtime_ns = mtime_ns
|
|
43
|
+
self.size = size
|
|
44
|
+
|
|
45
|
+
def clone(self) -> "dict[str, Any]":
|
|
46
|
+
return dict(self.metadata)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _MigrationFileEntry:
|
|
50
|
+
"""Represents a migration file discovered during directory scanning."""
|
|
51
|
+
|
|
52
|
+
__slots__ = ("extension_name", "path")
|
|
53
|
+
|
|
54
|
+
def __init__(self, path: Path, extension_name: "str | None") -> None:
|
|
55
|
+
self.path = path
|
|
56
|
+
self.extension_name = extension_name
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class BaseMigrationRunner(ABC):
|
|
60
|
+
"""Base migration runner with common functionality shared between sync and async implementations."""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
migrations_path: Path,
|
|
65
|
+
extension_migrations: "dict[str, Path] | None" = None,
|
|
66
|
+
context: "MigrationContext | None" = None,
|
|
67
|
+
extension_configs: "dict[str, dict[str, Any]] | None" = None,
|
|
68
|
+
runtime: "ObservabilityRuntime | None" = None,
|
|
69
|
+
description_hints: "TemplateDescriptionHints | None" = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Initialize the migration runner.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
migrations_path: Path to the directory containing migration files.
|
|
75
|
+
extension_migrations: Optional mapping of extension names to their migration paths.
|
|
76
|
+
context: Optional migration context for Python migrations.
|
|
77
|
+
extension_configs: Optional mapping of extension names to their configurations.
|
|
78
|
+
runtime: Observability runtime shared with command/context consumers.
|
|
79
|
+
description_hints: Hints for extracting migration descriptions.
|
|
80
|
+
"""
|
|
81
|
+
self.migrations_path = migrations_path
|
|
82
|
+
self.extension_migrations = extension_migrations or {}
|
|
83
|
+
self.runtime = runtime
|
|
84
|
+
self.loader = SQLFileLoader(runtime=runtime)
|
|
85
|
+
self.project_root: Path | None = None
|
|
86
|
+
self.context = context
|
|
87
|
+
self.extension_configs = extension_configs or {}
|
|
88
|
+
self._listing_digest: str | None = None
|
|
89
|
+
self._listing_cache: list[tuple[str, Path]] | None = None
|
|
90
|
+
self._listing_signatures: dict[str, tuple[int, int]] = {}
|
|
91
|
+
self._metadata_cache: dict[str, _CachedMigrationMetadata] = {}
|
|
92
|
+
self.description_hints = description_hints or TemplateDescriptionHints()
|
|
93
|
+
|
|
94
|
+
def _metric(self, name: str, amount: float = 1.0) -> None:
|
|
95
|
+
if self.runtime is None:
|
|
96
|
+
return
|
|
97
|
+
self.runtime.increment_metric(name, amount)
|
|
98
|
+
|
|
99
|
+
def _iter_directory_entries(self, base_path: Path, extension_name: "str | None") -> "list[_MigrationFileEntry]":
|
|
100
|
+
"""Collect migration files discovered under a base path."""
|
|
101
|
+
|
|
102
|
+
if not base_path.exists():
|
|
103
|
+
return []
|
|
104
|
+
|
|
105
|
+
entries: list[_MigrationFileEntry] = []
|
|
106
|
+
for pattern in ("*.sql", "*.py"):
|
|
107
|
+
for file_path in sorted(base_path.glob(pattern)):
|
|
108
|
+
if file_path.name.startswith("."):
|
|
109
|
+
continue
|
|
110
|
+
entries.append(_MigrationFileEntry(path=file_path, extension_name=extension_name))
|
|
111
|
+
return entries
|
|
112
|
+
|
|
113
|
+
def _collect_listing_entries(self) -> "tuple[list[_MigrationFileEntry], dict[str, tuple[int, int]], str]":
|
|
114
|
+
"""Gather migration files, stat signatures, and digest for cache validation."""
|
|
115
|
+
|
|
116
|
+
entries: list[_MigrationFileEntry] = []
|
|
117
|
+
signatures: dict[str, tuple[int, int]] = {}
|
|
118
|
+
digest_source = hashlib.md5(usedforsecurity=False)
|
|
119
|
+
|
|
120
|
+
for entry in self._iter_directory_entries(self.migrations_path, None):
|
|
121
|
+
self._record_entry(entry, entries, signatures, digest_source)
|
|
122
|
+
|
|
123
|
+
for ext_name, ext_path in self.extension_migrations.items():
|
|
124
|
+
for entry in self._iter_directory_entries(ext_path, ext_name):
|
|
125
|
+
self._record_entry(entry, entries, signatures, digest_source)
|
|
126
|
+
|
|
127
|
+
return entries, signatures, digest_source.hexdigest()
|
|
128
|
+
|
|
129
|
+
def _record_entry(
|
|
130
|
+
self,
|
|
131
|
+
entry: _MigrationFileEntry,
|
|
132
|
+
entries: "list[_MigrationFileEntry]",
|
|
133
|
+
signatures: "dict[str, tuple[int, int]]",
|
|
134
|
+
digest_source: Any,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Record entry metadata for cache decisions."""
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
stat_result = entry.path.stat()
|
|
140
|
+
except FileNotFoundError:
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
path_str = str(entry.path)
|
|
144
|
+
token = (stat_result.st_mtime_ns, stat_result.st_size)
|
|
145
|
+
signatures[path_str] = token
|
|
146
|
+
digest_source.update(path_str.encode("utf-8"))
|
|
147
|
+
digest_source.update(f"{token[0]}:{token[1]}".encode())
|
|
148
|
+
entries.append(entry)
|
|
149
|
+
|
|
150
|
+
def _build_sorted_listing(self, entries: "list[_MigrationFileEntry]") -> "list[tuple[str, Path]]":
|
|
151
|
+
"""Construct sorted migration listing from directory entries."""
|
|
152
|
+
|
|
153
|
+
migrations: list[tuple[str, Path]] = []
|
|
154
|
+
|
|
155
|
+
for entry in entries:
|
|
156
|
+
version = self._extract_version(entry.path.name)
|
|
157
|
+
if not version:
|
|
158
|
+
continue
|
|
159
|
+
if entry.extension_name:
|
|
160
|
+
version = f"ext_{entry.extension_name}_{version}"
|
|
161
|
+
migrations.append((version, entry.path))
|
|
162
|
+
|
|
163
|
+
def version_sort_key(migration_tuple: "tuple[str, Path]") -> "Any":
|
|
164
|
+
version_str = migration_tuple[0]
|
|
165
|
+
try:
|
|
166
|
+
return parse_version(version_str)
|
|
167
|
+
except ValueError:
|
|
168
|
+
return version_str
|
|
169
|
+
|
|
170
|
+
return sorted(migrations, key=version_sort_key)
|
|
171
|
+
|
|
172
|
+
def _log_listing_invalidation(
|
|
173
|
+
self, previous: "dict[str, tuple[int, int]]", current: "dict[str, tuple[int, int]]"
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Log cache invalidation details at INFO level."""
|
|
176
|
+
|
|
177
|
+
prev_keys = set(previous)
|
|
178
|
+
curr_keys = set(current)
|
|
179
|
+
added = curr_keys - prev_keys
|
|
180
|
+
removed = prev_keys - curr_keys
|
|
181
|
+
modified = {key for key in prev_keys & curr_keys if previous[key] != current[key]}
|
|
182
|
+
logger.info(
|
|
183
|
+
"Migration listing cache invalidated (added=%d, removed=%d, modified=%d)",
|
|
184
|
+
len(added),
|
|
185
|
+
len(removed),
|
|
186
|
+
len(modified),
|
|
187
|
+
)
|
|
188
|
+
self._metric("migrations.listing.cache_invalidations")
|
|
189
|
+
if added:
|
|
190
|
+
self._metric("migrations.listing.added", float(len(added)))
|
|
191
|
+
if removed:
|
|
192
|
+
self._metric("migrations.listing.removed", float(len(removed)))
|
|
193
|
+
if modified:
|
|
194
|
+
self._metric("migrations.listing.modified", float(len(modified)))
|
|
195
|
+
|
|
196
|
+
def _extract_version(self, filename: str) -> "str | None":
|
|
197
|
+
"""Extract version from filename.
|
|
198
|
+
|
|
199
|
+
Supports sequential (0001), timestamp (20251011120000), and extension-prefixed
|
|
200
|
+
(ext_litestar_0001) version formats.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
filename: The migration filename.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
The extracted version string or None.
|
|
207
|
+
"""
|
|
208
|
+
extension_version_parts = 3
|
|
209
|
+
timestamp_min_length = 4
|
|
210
|
+
|
|
211
|
+
name_without_ext = filename.rsplit(".", 1)[0]
|
|
212
|
+
|
|
213
|
+
if name_without_ext.startswith("ext_"):
|
|
214
|
+
parts = name_without_ext.split("_", 3)
|
|
215
|
+
if len(parts) >= extension_version_parts:
|
|
216
|
+
return f"{parts[0]}_{parts[1]}_{parts[2]}"
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
parts = name_without_ext.split("_", 1)
|
|
220
|
+
if parts and parts[0].isdigit():
|
|
221
|
+
return parts[0] if len(parts[0]) > timestamp_min_length else parts[0].zfill(4)
|
|
222
|
+
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
def _calculate_checksum(self, content: str) -> str:
|
|
226
|
+
"""Calculate MD5 checksum of migration content.
|
|
227
|
+
|
|
228
|
+
Canonicalizes content by excluding query name headers that change during
|
|
229
|
+
fix command (migrate-{version}-up/down). This ensures checksums remain
|
|
230
|
+
stable when converting timestamp versions to sequential format.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
content: The migration file content.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
MD5 checksum hex string.
|
|
237
|
+
"""
|
|
238
|
+
canonical_content = re.sub(r"^--\s*name:\s*migrate-[^-]+-(?:up|down)\s*$", "", content, flags=re.MULTILINE)
|
|
239
|
+
|
|
240
|
+
return hashlib.md5(canonical_content.encode()).hexdigest() # noqa: S324
|
|
241
|
+
|
|
242
|
+
@abstractmethod
|
|
243
|
+
def load_migration(self, file_path: Path) -> Union["dict[str, Any]", "Coroutine[Any, Any, dict[str, Any]]"]:
|
|
244
|
+
"""Load a migration file and extract its components.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
file_path: Path to the migration file.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Dictionary containing migration metadata and queries.
|
|
251
|
+
For async implementations, returns a coroutine.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def _load_migration_listing(self) -> "list[tuple[str, Path]]":
|
|
255
|
+
"""Build the cached migration listing shared by sync/async runners."""
|
|
256
|
+
entries, signatures, digest = self._collect_listing_entries()
|
|
257
|
+
cached_listing = self._listing_cache
|
|
258
|
+
|
|
259
|
+
if cached_listing is not None and self._listing_digest == digest:
|
|
260
|
+
self._metric("migrations.listing.cache_hit")
|
|
261
|
+
self._metric("migrations.listing.files_cached", float(len(cached_listing)))
|
|
262
|
+
logger.debug("Migration listing cache hit (%d files)", len(cached_listing))
|
|
263
|
+
return cached_listing
|
|
264
|
+
|
|
265
|
+
files = self._build_sorted_listing(entries)
|
|
266
|
+
previous_digest = self._listing_digest
|
|
267
|
+
previous_signatures = self._listing_signatures
|
|
268
|
+
|
|
269
|
+
self._metric("migrations.listing.cache_miss")
|
|
270
|
+
self._metric("migrations.listing.files_scanned", float(len(files)))
|
|
271
|
+
|
|
272
|
+
self._listing_cache = files
|
|
273
|
+
self._listing_signatures = signatures
|
|
274
|
+
self._listing_digest = digest
|
|
275
|
+
|
|
276
|
+
if previous_digest is None:
|
|
277
|
+
logger.debug("Primed migration listing cache with %d files", len(files))
|
|
278
|
+
else:
|
|
279
|
+
self._log_listing_invalidation(previous_signatures, signatures)
|
|
280
|
+
|
|
281
|
+
return files
|
|
282
|
+
|
|
283
|
+
@abstractmethod
|
|
284
|
+
def get_migration_files(self) -> "list[tuple[str, Path]] | Awaitable[list[tuple[str, Path]]]":
|
|
285
|
+
"""Get all migration files sorted by version."""
|
|
286
|
+
|
|
287
|
+
def _load_migration_metadata_common(self, file_path: Path, version: "str | None" = None) -> "dict[str, Any]":
|
|
288
|
+
"""Load common migration metadata that doesn't require async operations.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
file_path: Path to the migration file.
|
|
292
|
+
version: Optional pre-extracted version (preserves prefixes like ext_adk_0001).
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Partial migration metadata dictionary.
|
|
296
|
+
"""
|
|
297
|
+
cache_key = str(file_path)
|
|
298
|
+
stat_result = file_path.stat()
|
|
299
|
+
cached_metadata = self._metadata_cache.get(cache_key)
|
|
300
|
+
if (
|
|
301
|
+
cached_metadata
|
|
302
|
+
and cached_metadata.mtime_ns == stat_result.st_mtime_ns
|
|
303
|
+
and cached_metadata.size == stat_result.st_size
|
|
304
|
+
):
|
|
305
|
+
self._metric("migrations.metadata.cache_hit")
|
|
306
|
+
logger.debug("Migration metadata cache hit: %s", cache_key)
|
|
307
|
+
metadata = cached_metadata.clone()
|
|
308
|
+
metadata["file_path"] = file_path
|
|
309
|
+
return metadata
|
|
310
|
+
|
|
311
|
+
self._metric("migrations.metadata.cache_miss")
|
|
312
|
+
self._metric("migrations.metadata.bytes", float(stat_result.st_size))
|
|
313
|
+
|
|
314
|
+
content = file_path.read_text(encoding="utf-8")
|
|
315
|
+
checksum = self._calculate_checksum(content)
|
|
316
|
+
if version is None:
|
|
317
|
+
version = self._extract_version(file_path.name)
|
|
318
|
+
description = self._extract_description(content, file_path)
|
|
319
|
+
if not description:
|
|
320
|
+
description = file_path.stem.split("_", 1)[1] if "_" in file_path.stem else ""
|
|
321
|
+
|
|
322
|
+
transactional_match = re.search(
|
|
323
|
+
r"^--\s*transactional:\s*(true|false)\s*$", content, re.MULTILINE | re.IGNORECASE
|
|
324
|
+
)
|
|
325
|
+
transactional = None
|
|
326
|
+
if transactional_match:
|
|
327
|
+
transactional = transactional_match.group(1).lower() == "true"
|
|
328
|
+
|
|
329
|
+
metadata = {
|
|
330
|
+
"version": version,
|
|
331
|
+
"description": description,
|
|
332
|
+
"file_path": file_path,
|
|
333
|
+
"checksum": checksum,
|
|
334
|
+
"content": content,
|
|
335
|
+
"transactional": transactional,
|
|
336
|
+
}
|
|
337
|
+
self._metadata_cache[cache_key] = _CachedMigrationMetadata(
|
|
338
|
+
metadata=dict(metadata), mtime_ns=stat_result.st_mtime_ns, size=stat_result.st_size
|
|
339
|
+
)
|
|
340
|
+
if cached_metadata:
|
|
341
|
+
logger.debug("Migration metadata cache invalidated: %s", cache_key)
|
|
342
|
+
else:
|
|
343
|
+
logger.debug("Cached migration metadata: %s", cache_key)
|
|
344
|
+
return metadata
|
|
345
|
+
|
|
346
|
+
def _extract_description(self, content: str, file_path: Path) -> str:
|
|
347
|
+
if file_path.suffix == ".sql":
|
|
348
|
+
return self._extract_sql_description(content)
|
|
349
|
+
if file_path.suffix == ".py":
|
|
350
|
+
return self._extract_python_description(content)
|
|
351
|
+
return ""
|
|
352
|
+
|
|
353
|
+
def _extract_sql_description(self, content: str) -> str:
|
|
354
|
+
keys = self.description_hints.sql_keys
|
|
355
|
+
for line in content.splitlines():
|
|
356
|
+
stripped = line.strip()
|
|
357
|
+
if not stripped:
|
|
358
|
+
continue
|
|
359
|
+
if stripped.startswith("--"):
|
|
360
|
+
body = stripped.lstrip("-").strip()
|
|
361
|
+
if not body:
|
|
362
|
+
continue
|
|
363
|
+
if ":" in body:
|
|
364
|
+
key, value = body.split(":", 1)
|
|
365
|
+
if key.strip() in keys:
|
|
366
|
+
return value.strip()
|
|
367
|
+
continue
|
|
368
|
+
break
|
|
369
|
+
return ""
|
|
370
|
+
|
|
371
|
+
def _extract_python_description(self, content: str) -> str:
|
|
372
|
+
try:
|
|
373
|
+
module = ast.parse(content)
|
|
374
|
+
except SyntaxError:
|
|
375
|
+
return ""
|
|
376
|
+
docstring = ast.get_docstring(module) or ""
|
|
377
|
+
keys = self.description_hints.python_keys
|
|
378
|
+
for line in docstring.splitlines():
|
|
379
|
+
stripped = line.strip()
|
|
380
|
+
if not stripped:
|
|
381
|
+
continue
|
|
382
|
+
if ":" in stripped:
|
|
383
|
+
key, value = stripped.split(":", 1)
|
|
384
|
+
if key.strip() in keys:
|
|
385
|
+
return value.strip()
|
|
386
|
+
return stripped
|
|
387
|
+
return ""
|
|
388
|
+
|
|
389
|
+
def _get_context_for_migration(self, file_path: Path) -> "MigrationContext | None":
|
|
390
|
+
"""Get the appropriate context for a migration file.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
file_path: Path to the migration file.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Migration context to use, or None to use default.
|
|
397
|
+
"""
|
|
398
|
+
context_to_use = self.context
|
|
399
|
+
if context_to_use and file_path.name.startswith("ext_"):
|
|
400
|
+
version = self._extract_version(file_path.name)
|
|
401
|
+
if version and version.startswith("ext_"):
|
|
402
|
+
min_extension_version_parts = 3
|
|
403
|
+
parts = version.split("_", 2)
|
|
404
|
+
if len(parts) >= min_extension_version_parts:
|
|
405
|
+
ext_name = parts[1]
|
|
406
|
+
if ext_name in self.extension_configs:
|
|
407
|
+
context_to_use = MigrationContext(
|
|
408
|
+
dialect=self.context.dialect if self.context else None,
|
|
409
|
+
config=self.context.config if self.context else None,
|
|
410
|
+
driver=self.context.driver if self.context else None,
|
|
411
|
+
metadata=self.context.metadata.copy() if self.context and self.context.metadata else {},
|
|
412
|
+
extension_config=self.extension_configs[ext_name],
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
for ext_name, ext_path in self.extension_migrations.items():
|
|
416
|
+
if file_path.parent == ext_path:
|
|
417
|
+
if ext_name in self.extension_configs and self.context:
|
|
418
|
+
context_to_use = MigrationContext(
|
|
419
|
+
config=self.context.config,
|
|
420
|
+
dialect=self.context.dialect,
|
|
421
|
+
driver=self.context.driver,
|
|
422
|
+
metadata=self.context.metadata.copy() if self.context.metadata else {},
|
|
423
|
+
extension_config=self.extension_configs[ext_name],
|
|
424
|
+
)
|
|
425
|
+
break
|
|
426
|
+
|
|
427
|
+
return context_to_use
|
|
428
|
+
|
|
429
|
+
def should_use_transaction(
|
|
430
|
+
self, migration: "dict[str, Any]", config: "DatabaseConfigProtocol[Any, Any, Any]"
|
|
431
|
+
) -> bool:
|
|
432
|
+
"""Determine if migration should run in a transaction.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
migration: Migration metadata dictionary.
|
|
436
|
+
config: The database configuration instance.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
True if migration should be wrapped in a transaction.
|
|
440
|
+
"""
|
|
441
|
+
if not config.supports_transactional_ddl:
|
|
442
|
+
return False
|
|
443
|
+
|
|
444
|
+
if migration.get("transactional") is not None:
|
|
445
|
+
return bool(migration["transactional"])
|
|
446
|
+
|
|
447
|
+
migration_config = cast("dict[str, Any]", config.migration_config) or {}
|
|
448
|
+
return bool(migration_config.get("transactional", True))
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class SyncMigrationRunner(BaseMigrationRunner):
|
|
452
|
+
"""Synchronous migration runner with pure sync methods."""
|
|
453
|
+
|
|
454
|
+
def get_migration_files(self) -> "list[tuple[str, Path]]":
|
|
455
|
+
"""Get all migration files sorted by version.
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
List of (version, path) tuples sorted by version.
|
|
459
|
+
"""
|
|
460
|
+
return self._load_migration_listing()
|
|
461
|
+
|
|
462
|
+
def load_migration(self, file_path: Path, version: "str | None" = None) -> "dict[str, Any]":
|
|
463
|
+
"""Load a migration file and extract its components.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
file_path: Path to the migration file.
|
|
467
|
+
version: Optional pre-extracted version (preserves prefixes like ext_adk_0001).
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Dictionary containing migration metadata and queries.
|
|
471
|
+
"""
|
|
472
|
+
metadata = self._load_migration_metadata_common(file_path, version)
|
|
473
|
+
context_to_use = self._get_context_for_migration(file_path)
|
|
474
|
+
|
|
475
|
+
loader = get_migration_loader(file_path, self.migrations_path, self.project_root, context_to_use, self.loader)
|
|
476
|
+
loader.validate_migration_file(file_path)
|
|
477
|
+
|
|
478
|
+
has_upgrade, has_downgrade = True, False
|
|
479
|
+
|
|
480
|
+
if file_path.suffix == ".sql":
|
|
481
|
+
version = metadata["version"]
|
|
482
|
+
up_query, down_query = f"migrate-{version}-up", f"migrate-{version}-down"
|
|
483
|
+
has_upgrade, has_downgrade = self.loader.has_query(up_query), self.loader.has_query(down_query)
|
|
484
|
+
else:
|
|
485
|
+
try:
|
|
486
|
+
has_downgrade = bool(self._get_migration_sql({"loader": loader, "file_path": file_path}, "down"))
|
|
487
|
+
except Exception:
|
|
488
|
+
has_downgrade = False
|
|
489
|
+
|
|
490
|
+
metadata.update({"has_upgrade": has_upgrade, "has_downgrade": has_downgrade, "loader": loader})
|
|
491
|
+
return metadata
|
|
492
|
+
|
|
493
|
+
def execute_upgrade(
|
|
494
|
+
self,
|
|
495
|
+
driver: "SyncDriverAdapterBase",
|
|
496
|
+
migration: "dict[str, Any]",
|
|
497
|
+
*,
|
|
498
|
+
use_transaction: "bool | None" = None,
|
|
499
|
+
on_success: "Callable[[int], None] | None" = None,
|
|
500
|
+
) -> "tuple[str | None, int]":
|
|
501
|
+
"""Execute an upgrade migration.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
driver: The sync database driver to use.
|
|
505
|
+
migration: Migration metadata dictionary.
|
|
506
|
+
use_transaction: Override transaction behavior. If None, uses should_use_transaction logic.
|
|
507
|
+
on_success: Callback invoked with execution_time_ms before commit (for version tracking).
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
Tuple of (sql_content, execution_time_ms).
|
|
511
|
+
"""
|
|
512
|
+
upgrade_sql_list = self._get_migration_sql(migration, "up")
|
|
513
|
+
if upgrade_sql_list is None:
|
|
514
|
+
self._metric("migrations.upgrade.skipped")
|
|
515
|
+
log_with_context(
|
|
516
|
+
logger,
|
|
517
|
+
logging.WARNING,
|
|
518
|
+
"migration.apply",
|
|
519
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
520
|
+
version=migration.get("version"),
|
|
521
|
+
status="missing",
|
|
522
|
+
)
|
|
523
|
+
return None, 0
|
|
524
|
+
|
|
525
|
+
if use_transaction is None:
|
|
526
|
+
config = self.context.config if self.context else None
|
|
527
|
+
use_transaction = self.should_use_transaction(migration, config) if config else False
|
|
528
|
+
|
|
529
|
+
runtime = self.runtime
|
|
530
|
+
span = None
|
|
531
|
+
if runtime is not None:
|
|
532
|
+
version = cast("str | None", migration.get("version"))
|
|
533
|
+
span = runtime.start_migration_span("upgrade", version=version)
|
|
534
|
+
runtime.increment_metric("migrations.upgrade.invocations")
|
|
535
|
+
log_with_context(
|
|
536
|
+
logger,
|
|
537
|
+
logging.INFO,
|
|
538
|
+
"migration.apply",
|
|
539
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
540
|
+
version=migration.get("version"),
|
|
541
|
+
use_transaction=use_transaction,
|
|
542
|
+
status="start",
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
start_time = time.perf_counter()
|
|
546
|
+
execution_time = 0
|
|
547
|
+
|
|
548
|
+
try:
|
|
549
|
+
if use_transaction:
|
|
550
|
+
driver.begin()
|
|
551
|
+
for sql_statement in upgrade_sql_list:
|
|
552
|
+
if sql_statement.strip():
|
|
553
|
+
driver.execute_script(sql_statement)
|
|
554
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
555
|
+
if on_success:
|
|
556
|
+
on_success(execution_time)
|
|
557
|
+
driver.commit()
|
|
558
|
+
else:
|
|
559
|
+
for sql_statement in upgrade_sql_list:
|
|
560
|
+
if sql_statement.strip():
|
|
561
|
+
driver.execute_script(sql_statement)
|
|
562
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
563
|
+
if on_success:
|
|
564
|
+
on_success(execution_time)
|
|
565
|
+
except Exception as exc:
|
|
566
|
+
if use_transaction:
|
|
567
|
+
driver.rollback()
|
|
568
|
+
if runtime is not None:
|
|
569
|
+
duration_ms = int((time.perf_counter() - start_time) * 1000)
|
|
570
|
+
runtime.increment_metric("migrations.upgrade.errors")
|
|
571
|
+
runtime.end_migration_span(span, duration_ms=duration_ms, error=exc)
|
|
572
|
+
log_with_context(
|
|
573
|
+
logger,
|
|
574
|
+
logging.ERROR,
|
|
575
|
+
"migration.apply",
|
|
576
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
577
|
+
version=migration.get("version"),
|
|
578
|
+
duration_ms=int((time.perf_counter() - start_time) * 1000),
|
|
579
|
+
error_type=type(exc).__name__,
|
|
580
|
+
status="failed",
|
|
581
|
+
)
|
|
582
|
+
raise
|
|
583
|
+
|
|
584
|
+
if runtime is not None:
|
|
585
|
+
runtime.increment_metric("migrations.upgrade.applied")
|
|
586
|
+
runtime.increment_metric("migrations.upgrade.duration_ms", float(execution_time))
|
|
587
|
+
runtime.end_migration_span(span, duration_ms=execution_time)
|
|
588
|
+
log_with_context(
|
|
589
|
+
logger,
|
|
590
|
+
logging.INFO,
|
|
591
|
+
"migration.apply",
|
|
592
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
593
|
+
version=migration.get("version"),
|
|
594
|
+
duration_ms=execution_time,
|
|
595
|
+
status="complete",
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
return None, execution_time
|
|
599
|
+
|
|
600
|
+
def execute_downgrade(
|
|
601
|
+
self,
|
|
602
|
+
driver: "SyncDriverAdapterBase",
|
|
603
|
+
migration: "dict[str, Any]",
|
|
604
|
+
*,
|
|
605
|
+
use_transaction: "bool | None" = None,
|
|
606
|
+
on_success: "Callable[[int], None] | None" = None,
|
|
607
|
+
) -> "tuple[str | None, int]":
|
|
608
|
+
"""Execute a downgrade migration.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
driver: The sync database driver to use.
|
|
612
|
+
migration: Migration metadata dictionary.
|
|
613
|
+
use_transaction: Override transaction behavior. If None, uses should_use_transaction logic.
|
|
614
|
+
on_success: Callback invoked with execution_time_ms before commit (for version tracking).
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
Tuple of (sql_content, execution_time_ms).
|
|
618
|
+
"""
|
|
619
|
+
downgrade_sql_list = self._get_migration_sql(migration, "down")
|
|
620
|
+
if downgrade_sql_list is None:
|
|
621
|
+
self._metric("migrations.downgrade.skipped")
|
|
622
|
+
log_with_context(
|
|
623
|
+
logger,
|
|
624
|
+
logging.WARNING,
|
|
625
|
+
"migration.rollback",
|
|
626
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
627
|
+
version=migration.get("version"),
|
|
628
|
+
status="missing",
|
|
629
|
+
)
|
|
630
|
+
return None, 0
|
|
631
|
+
|
|
632
|
+
if use_transaction is None:
|
|
633
|
+
config = self.context.config if self.context else None
|
|
634
|
+
use_transaction = self.should_use_transaction(migration, config) if config else False
|
|
635
|
+
|
|
636
|
+
runtime = self.runtime
|
|
637
|
+
span = None
|
|
638
|
+
if runtime is not None:
|
|
639
|
+
version = cast("str | None", migration.get("version"))
|
|
640
|
+
span = runtime.start_migration_span("downgrade", version=version)
|
|
641
|
+
runtime.increment_metric("migrations.downgrade.invocations")
|
|
642
|
+
log_with_context(
|
|
643
|
+
logger,
|
|
644
|
+
logging.INFO,
|
|
645
|
+
"migration.rollback",
|
|
646
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
647
|
+
version=migration.get("version"),
|
|
648
|
+
use_transaction=use_transaction,
|
|
649
|
+
status="start",
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
start_time = time.perf_counter()
|
|
653
|
+
execution_time = 0
|
|
654
|
+
|
|
655
|
+
try:
|
|
656
|
+
if use_transaction:
|
|
657
|
+
driver.begin()
|
|
658
|
+
for sql_statement in downgrade_sql_list:
|
|
659
|
+
if sql_statement.strip():
|
|
660
|
+
driver.execute_script(sql_statement)
|
|
661
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
662
|
+
if on_success:
|
|
663
|
+
on_success(execution_time)
|
|
664
|
+
driver.commit()
|
|
665
|
+
else:
|
|
666
|
+
for sql_statement in downgrade_sql_list:
|
|
667
|
+
if sql_statement.strip():
|
|
668
|
+
driver.execute_script(sql_statement)
|
|
669
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
670
|
+
if on_success:
|
|
671
|
+
on_success(execution_time)
|
|
672
|
+
except Exception as exc:
|
|
673
|
+
if use_transaction:
|
|
674
|
+
driver.rollback()
|
|
675
|
+
if runtime is not None:
|
|
676
|
+
duration_ms = int((time.perf_counter() - start_time) * 1000)
|
|
677
|
+
runtime.increment_metric("migrations.downgrade.errors")
|
|
678
|
+
runtime.end_migration_span(span, duration_ms=duration_ms, error=exc)
|
|
679
|
+
log_with_context(
|
|
680
|
+
logger,
|
|
681
|
+
logging.ERROR,
|
|
682
|
+
"migration.rollback",
|
|
683
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
684
|
+
version=migration.get("version"),
|
|
685
|
+
duration_ms=int((time.perf_counter() - start_time) * 1000),
|
|
686
|
+
error_type=type(exc).__name__,
|
|
687
|
+
status="failed",
|
|
688
|
+
)
|
|
689
|
+
raise
|
|
690
|
+
|
|
691
|
+
if runtime is not None:
|
|
692
|
+
runtime.increment_metric("migrations.downgrade.applied")
|
|
693
|
+
runtime.increment_metric("migrations.downgrade.duration_ms", float(execution_time))
|
|
694
|
+
runtime.end_migration_span(span, duration_ms=execution_time)
|
|
695
|
+
log_with_context(
|
|
696
|
+
logger,
|
|
697
|
+
logging.INFO,
|
|
698
|
+
"migration.rollback",
|
|
699
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
700
|
+
version=migration.get("version"),
|
|
701
|
+
duration_ms=execution_time,
|
|
702
|
+
status="complete",
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
return None, execution_time
|
|
706
|
+
|
|
707
|
+
def _get_migration_sql(self, migration: "dict[str, Any]", direction: str) -> "list[str] | None":
|
|
708
|
+
"""Get migration SQL for given direction (sync version).
|
|
709
|
+
|
|
710
|
+
Args:
|
|
711
|
+
migration: Migration metadata.
|
|
712
|
+
direction: Either 'up' or 'down'.
|
|
713
|
+
|
|
714
|
+
Returns:
|
|
715
|
+
SQL statements for the migration.
|
|
716
|
+
"""
|
|
717
|
+
# If this is being called during migration loading (no has_*grade field yet),
|
|
718
|
+
# don't raise/warn - just proceed to check if the method exists
|
|
719
|
+
if f"has_{direction}grade" in migration and not migration.get(f"has_{direction}grade"):
|
|
720
|
+
if direction == "down":
|
|
721
|
+
logger.warning("Migration %s has no downgrade query", migration.get("version"))
|
|
722
|
+
return None
|
|
723
|
+
msg = f"Migration {migration.get('version')} has no upgrade query"
|
|
724
|
+
raise ValueError(msg)
|
|
725
|
+
|
|
726
|
+
file_path, loader = migration["file_path"], migration["loader"]
|
|
727
|
+
|
|
728
|
+
try:
|
|
729
|
+
method = loader.get_up_sql if direction == "up" else loader.get_down_sql
|
|
730
|
+
sql_statements = (
|
|
731
|
+
await_(method, raise_sync_error=False)(file_path)
|
|
732
|
+
if inspect.iscoroutinefunction(method)
|
|
733
|
+
else method(file_path)
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
except Exception as e:
|
|
737
|
+
if direction == "down":
|
|
738
|
+
logger.warning("Failed to load downgrade for migration %s: %s", migration.get("version"), e)
|
|
739
|
+
return None
|
|
740
|
+
msg = f"Failed to load upgrade for migration {migration.get('version')}: {e}"
|
|
741
|
+
raise ValueError(msg) from e
|
|
742
|
+
else:
|
|
743
|
+
if sql_statements:
|
|
744
|
+
return cast("list[str]", sql_statements)
|
|
745
|
+
return None
|
|
746
|
+
|
|
747
|
+
def load_all_migrations(self) -> "dict[str, SQL]":
|
|
748
|
+
"""Load all migrations into a single namespace for bulk operations.
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
Dictionary mapping query names to SQL objects.
|
|
752
|
+
"""
|
|
753
|
+
all_queries = {}
|
|
754
|
+
migrations = self.get_migration_files()
|
|
755
|
+
|
|
756
|
+
for version, file_path in migrations:
|
|
757
|
+
if file_path.suffix == ".sql":
|
|
758
|
+
self.loader.load_sql(file_path)
|
|
759
|
+
for query_name in self.loader.list_queries():
|
|
760
|
+
all_queries[query_name] = self.loader.get_sql(query_name)
|
|
761
|
+
else:
|
|
762
|
+
loader = get_migration_loader(
|
|
763
|
+
file_path, self.migrations_path, self.project_root, self.context, self.loader
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
try:
|
|
767
|
+
up_sql = await_(loader.get_up_sql, raise_sync_error=False)(file_path)
|
|
768
|
+
down_sql = await_(loader.get_down_sql, raise_sync_error=False)(file_path)
|
|
769
|
+
|
|
770
|
+
if up_sql:
|
|
771
|
+
all_queries[f"migrate-{version}-up"] = SQL(up_sql[0])
|
|
772
|
+
if down_sql:
|
|
773
|
+
all_queries[f"migrate-{version}-down"] = SQL(down_sql[0])
|
|
774
|
+
|
|
775
|
+
except Exception as e:
|
|
776
|
+
logger.debug("Failed to load Python migration %s: %s", file_path, e)
|
|
777
|
+
|
|
778
|
+
return all_queries
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
class AsyncMigrationRunner(BaseMigrationRunner):
|
|
782
|
+
"""Asynchronous migration runner with pure async methods."""
|
|
783
|
+
|
|
784
|
+
async def get_migration_files(self) -> "list[tuple[str, Path]]":
|
|
785
|
+
"""Get all migration files sorted by version.
|
|
786
|
+
|
|
787
|
+
Returns:
|
|
788
|
+
List of (version, path) tuples sorted by version.
|
|
789
|
+
"""
|
|
790
|
+
return await async_(self._load_migration_listing)()
|
|
791
|
+
|
|
792
|
+
async def load_migration(self, file_path: Path, version: "str | None" = None) -> "dict[str, Any]":
|
|
793
|
+
"""Load a migration file and extract its components.
|
|
794
|
+
|
|
795
|
+
Args:
|
|
796
|
+
file_path: Path to the migration file.
|
|
797
|
+
version: Optional pre-extracted version (preserves prefixes like ext_adk_0001).
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
Dictionary containing migration metadata and queries.
|
|
801
|
+
"""
|
|
802
|
+
metadata = self._load_migration_metadata_common(file_path, version)
|
|
803
|
+
context_to_use = self._get_context_for_migration(file_path)
|
|
804
|
+
|
|
805
|
+
loader = get_migration_loader(file_path, self.migrations_path, self.project_root, context_to_use, self.loader)
|
|
806
|
+
loader.validate_migration_file(file_path)
|
|
807
|
+
|
|
808
|
+
has_upgrade, has_downgrade = True, False
|
|
809
|
+
|
|
810
|
+
if file_path.suffix == ".sql":
|
|
811
|
+
version = metadata["version"]
|
|
812
|
+
up_query, down_query = f"migrate-{version}-up", f"migrate-{version}-down"
|
|
813
|
+
has_upgrade, has_downgrade = self.loader.has_query(up_query), self.loader.has_query(down_query)
|
|
814
|
+
else:
|
|
815
|
+
try:
|
|
816
|
+
has_downgrade = bool(
|
|
817
|
+
await self._get_migration_sql_async({"loader": loader, "file_path": file_path}, "down")
|
|
818
|
+
)
|
|
819
|
+
except Exception:
|
|
820
|
+
has_downgrade = False
|
|
821
|
+
|
|
822
|
+
metadata.update({"has_upgrade": has_upgrade, "has_downgrade": has_downgrade, "loader": loader})
|
|
823
|
+
return metadata
|
|
824
|
+
|
|
825
|
+
async def execute_upgrade(
|
|
826
|
+
self,
|
|
827
|
+
driver: "AsyncDriverAdapterBase",
|
|
828
|
+
migration: "dict[str, Any]",
|
|
829
|
+
*,
|
|
830
|
+
use_transaction: "bool | None" = None,
|
|
831
|
+
on_success: "Callable[[int], Awaitable[None]] | None" = None,
|
|
832
|
+
) -> "tuple[str | None, int]":
|
|
833
|
+
"""Execute an upgrade migration.
|
|
834
|
+
|
|
835
|
+
Args:
|
|
836
|
+
driver: The async database driver to use.
|
|
837
|
+
migration: Migration metadata dictionary.
|
|
838
|
+
use_transaction: Override transaction behavior. If None, uses should_use_transaction logic.
|
|
839
|
+
on_success: Async callback invoked with execution_time_ms before commit (for version tracking).
|
|
840
|
+
|
|
841
|
+
Returns:
|
|
842
|
+
Tuple of (sql_content, execution_time_ms).
|
|
843
|
+
"""
|
|
844
|
+
upgrade_sql_list = await self._get_migration_sql_async(migration, "up")
|
|
845
|
+
if upgrade_sql_list is None:
|
|
846
|
+
self._metric("migrations.upgrade.skipped")
|
|
847
|
+
log_with_context(
|
|
848
|
+
logger,
|
|
849
|
+
logging.WARNING,
|
|
850
|
+
"migration.apply",
|
|
851
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
852
|
+
version=migration.get("version"),
|
|
853
|
+
status="missing",
|
|
854
|
+
)
|
|
855
|
+
return None, 0
|
|
856
|
+
|
|
857
|
+
if use_transaction is None:
|
|
858
|
+
config = self.context.config if self.context else None
|
|
859
|
+
use_transaction = self.should_use_transaction(migration, config) if config else False
|
|
860
|
+
|
|
861
|
+
runtime = self.runtime
|
|
862
|
+
span = None
|
|
863
|
+
if runtime is not None:
|
|
864
|
+
version = cast("str | None", migration.get("version"))
|
|
865
|
+
span = runtime.start_migration_span("upgrade", version=version)
|
|
866
|
+
runtime.increment_metric("migrations.upgrade.invocations")
|
|
867
|
+
log_with_context(
|
|
868
|
+
logger,
|
|
869
|
+
logging.INFO,
|
|
870
|
+
"migration.apply",
|
|
871
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
872
|
+
version=migration.get("version"),
|
|
873
|
+
use_transaction=use_transaction,
|
|
874
|
+
status="start",
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
start_time = time.perf_counter()
|
|
878
|
+
execution_time = 0
|
|
879
|
+
|
|
880
|
+
try:
|
|
881
|
+
if use_transaction:
|
|
882
|
+
await driver.begin()
|
|
883
|
+
for sql_statement in upgrade_sql_list:
|
|
884
|
+
if sql_statement.strip():
|
|
885
|
+
await driver.execute_script(sql_statement)
|
|
886
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
887
|
+
if on_success:
|
|
888
|
+
await on_success(execution_time)
|
|
889
|
+
await driver.commit()
|
|
890
|
+
else:
|
|
891
|
+
for sql_statement in upgrade_sql_list:
|
|
892
|
+
if sql_statement.strip():
|
|
893
|
+
await driver.execute_script(sql_statement)
|
|
894
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
895
|
+
if on_success:
|
|
896
|
+
await on_success(execution_time)
|
|
897
|
+
except Exception as exc:
|
|
898
|
+
if use_transaction:
|
|
899
|
+
await driver.rollback()
|
|
900
|
+
if runtime is not None:
|
|
901
|
+
duration_ms = int((time.perf_counter() - start_time) * 1000)
|
|
902
|
+
runtime.increment_metric("migrations.upgrade.errors")
|
|
903
|
+
runtime.end_migration_span(span, duration_ms=duration_ms, error=exc)
|
|
904
|
+
log_with_context(
|
|
905
|
+
logger,
|
|
906
|
+
logging.ERROR,
|
|
907
|
+
"migration.apply",
|
|
908
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
909
|
+
version=migration.get("version"),
|
|
910
|
+
duration_ms=int((time.perf_counter() - start_time) * 1000),
|
|
911
|
+
error_type=type(exc).__name__,
|
|
912
|
+
status="failed",
|
|
913
|
+
)
|
|
914
|
+
raise
|
|
915
|
+
|
|
916
|
+
if runtime is not None:
|
|
917
|
+
runtime.increment_metric("migrations.upgrade.applied")
|
|
918
|
+
runtime.increment_metric("migrations.upgrade.duration_ms", float(execution_time))
|
|
919
|
+
runtime.end_migration_span(span, duration_ms=execution_time)
|
|
920
|
+
log_with_context(
|
|
921
|
+
logger,
|
|
922
|
+
logging.INFO,
|
|
923
|
+
"migration.apply",
|
|
924
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
925
|
+
version=migration.get("version"),
|
|
926
|
+
duration_ms=execution_time,
|
|
927
|
+
status="complete",
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
return None, execution_time
|
|
931
|
+
|
|
932
|
+
async def execute_downgrade(
|
|
933
|
+
self,
|
|
934
|
+
driver: "AsyncDriverAdapterBase",
|
|
935
|
+
migration: "dict[str, Any]",
|
|
936
|
+
*,
|
|
937
|
+
use_transaction: "bool | None" = None,
|
|
938
|
+
on_success: "Callable[[int], Awaitable[None]] | None" = None,
|
|
939
|
+
) -> "tuple[str | None, int]":
|
|
940
|
+
"""Execute a downgrade migration.
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
driver: The async database driver to use.
|
|
944
|
+
migration: Migration metadata dictionary.
|
|
945
|
+
use_transaction: Override transaction behavior. If None, uses should_use_transaction logic.
|
|
946
|
+
on_success: Async callback invoked with execution_time_ms before commit (for version tracking).
|
|
947
|
+
|
|
948
|
+
Returns:
|
|
949
|
+
Tuple of (sql_content, execution_time_ms).
|
|
950
|
+
"""
|
|
951
|
+
downgrade_sql_list = await self._get_migration_sql_async(migration, "down")
|
|
952
|
+
if downgrade_sql_list is None:
|
|
953
|
+
self._metric("migrations.downgrade.skipped")
|
|
954
|
+
log_with_context(
|
|
955
|
+
logger,
|
|
956
|
+
logging.WARNING,
|
|
957
|
+
"migration.rollback",
|
|
958
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
959
|
+
version=migration.get("version"),
|
|
960
|
+
status="missing",
|
|
961
|
+
)
|
|
962
|
+
return None, 0
|
|
963
|
+
|
|
964
|
+
if use_transaction is None:
|
|
965
|
+
config = self.context.config if self.context else None
|
|
966
|
+
use_transaction = self.should_use_transaction(migration, config) if config else False
|
|
967
|
+
|
|
968
|
+
runtime = self.runtime
|
|
969
|
+
span = None
|
|
970
|
+
if runtime is not None:
|
|
971
|
+
version = cast("str | None", migration.get("version"))
|
|
972
|
+
span = runtime.start_migration_span("downgrade", version=version)
|
|
973
|
+
runtime.increment_metric("migrations.downgrade.invocations")
|
|
974
|
+
log_with_context(
|
|
975
|
+
logger,
|
|
976
|
+
logging.INFO,
|
|
977
|
+
"migration.rollback",
|
|
978
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
979
|
+
version=migration.get("version"),
|
|
980
|
+
use_transaction=use_transaction,
|
|
981
|
+
status="start",
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
start_time = time.perf_counter()
|
|
985
|
+
execution_time = 0
|
|
986
|
+
|
|
987
|
+
try:
|
|
988
|
+
if use_transaction:
|
|
989
|
+
await driver.begin()
|
|
990
|
+
for sql_statement in downgrade_sql_list:
|
|
991
|
+
if sql_statement.strip():
|
|
992
|
+
await driver.execute_script(sql_statement)
|
|
993
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
994
|
+
if on_success:
|
|
995
|
+
await on_success(execution_time)
|
|
996
|
+
await driver.commit()
|
|
997
|
+
else:
|
|
998
|
+
for sql_statement in downgrade_sql_list:
|
|
999
|
+
if sql_statement.strip():
|
|
1000
|
+
await driver.execute_script(sql_statement)
|
|
1001
|
+
execution_time = int((time.perf_counter() - start_time) * 1000)
|
|
1002
|
+
if on_success:
|
|
1003
|
+
await on_success(execution_time)
|
|
1004
|
+
except Exception as exc:
|
|
1005
|
+
if use_transaction:
|
|
1006
|
+
await driver.rollback()
|
|
1007
|
+
if runtime is not None:
|
|
1008
|
+
duration_ms = int((time.perf_counter() - start_time) * 1000)
|
|
1009
|
+
runtime.increment_metric("migrations.downgrade.errors")
|
|
1010
|
+
runtime.end_migration_span(span, duration_ms=duration_ms, error=exc)
|
|
1011
|
+
log_with_context(
|
|
1012
|
+
logger,
|
|
1013
|
+
logging.ERROR,
|
|
1014
|
+
"migration.rollback",
|
|
1015
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
1016
|
+
version=migration.get("version"),
|
|
1017
|
+
duration_ms=int((time.perf_counter() - start_time) * 1000),
|
|
1018
|
+
error_type=type(exc).__name__,
|
|
1019
|
+
status="failed",
|
|
1020
|
+
)
|
|
1021
|
+
raise
|
|
1022
|
+
|
|
1023
|
+
if runtime is not None:
|
|
1024
|
+
runtime.increment_metric("migrations.downgrade.applied")
|
|
1025
|
+
runtime.increment_metric("migrations.downgrade.duration_ms", float(execution_time))
|
|
1026
|
+
runtime.end_migration_span(span, duration_ms=execution_time)
|
|
1027
|
+
log_with_context(
|
|
1028
|
+
logger,
|
|
1029
|
+
logging.INFO,
|
|
1030
|
+
"migration.rollback",
|
|
1031
|
+
db_system=resolve_db_system(type(driver).__name__),
|
|
1032
|
+
version=migration.get("version"),
|
|
1033
|
+
duration_ms=execution_time,
|
|
1034
|
+
status="complete",
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
return None, execution_time
|
|
1038
|
+
|
|
1039
|
+
async def _get_migration_sql_async(self, migration: "dict[str, Any]", direction: str) -> "list[str] | None":
|
|
1040
|
+
"""Get migration SQL for given direction (async version).
|
|
1041
|
+
|
|
1042
|
+
Args:
|
|
1043
|
+
migration: Migration metadata.
|
|
1044
|
+
direction: Either 'up' or 'down'.
|
|
1045
|
+
|
|
1046
|
+
Returns:
|
|
1047
|
+
SQL statements for the migration.
|
|
1048
|
+
"""
|
|
1049
|
+
# If this is being called during migration loading (no has_*grade field yet),
|
|
1050
|
+
# don't raise/warn - just proceed to check if the method exists
|
|
1051
|
+
if f"has_{direction}grade" in migration and not migration.get(f"has_{direction}grade"):
|
|
1052
|
+
if direction == "down":
|
|
1053
|
+
logger.warning("Migration %s has no downgrade query", migration.get("version"))
|
|
1054
|
+
return None
|
|
1055
|
+
msg = f"Migration {migration.get('version')} has no upgrade query"
|
|
1056
|
+
raise ValueError(msg)
|
|
1057
|
+
|
|
1058
|
+
file_path, loader = migration["file_path"], migration["loader"]
|
|
1059
|
+
|
|
1060
|
+
try:
|
|
1061
|
+
method = loader.get_up_sql if direction == "up" else loader.get_down_sql
|
|
1062
|
+
sql_statements = await method(file_path)
|
|
1063
|
+
|
|
1064
|
+
except Exception as e:
|
|
1065
|
+
if direction == "down":
|
|
1066
|
+
logger.warning("Failed to load downgrade for migration %s: %s", migration.get("version"), e)
|
|
1067
|
+
return None
|
|
1068
|
+
msg = f"Failed to load upgrade for migration {migration.get('version')}: {e}"
|
|
1069
|
+
raise ValueError(msg) from e
|
|
1070
|
+
else:
|
|
1071
|
+
if sql_statements:
|
|
1072
|
+
return cast("list[str]", sql_statements)
|
|
1073
|
+
return None
|
|
1074
|
+
|
|
1075
|
+
async def load_all_migrations(self) -> "dict[str, SQL]":
|
|
1076
|
+
"""Load all migrations into a single namespace for bulk operations.
|
|
1077
|
+
|
|
1078
|
+
Returns:
|
|
1079
|
+
Dictionary mapping query names to SQL objects.
|
|
1080
|
+
"""
|
|
1081
|
+
all_queries = {}
|
|
1082
|
+
migrations = await self.get_migration_files()
|
|
1083
|
+
|
|
1084
|
+
for version, file_path in migrations:
|
|
1085
|
+
if file_path.suffix == ".sql":
|
|
1086
|
+
await async_(self.loader.load_sql)(file_path)
|
|
1087
|
+
for query_name in self.loader.list_queries():
|
|
1088
|
+
all_queries[query_name] = self.loader.get_sql(query_name)
|
|
1089
|
+
else:
|
|
1090
|
+
loader = get_migration_loader(
|
|
1091
|
+
file_path, self.migrations_path, self.project_root, self.context, self.loader
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
try:
|
|
1095
|
+
up_sql = await loader.get_up_sql(file_path)
|
|
1096
|
+
down_sql = await loader.get_down_sql(file_path)
|
|
1097
|
+
|
|
1098
|
+
if up_sql:
|
|
1099
|
+
all_queries[f"migrate-{version}-up"] = SQL(up_sql[0])
|
|
1100
|
+
if down_sql:
|
|
1101
|
+
all_queries[f"migrate-{version}-down"] = SQL(down_sql[0])
|
|
1102
|
+
|
|
1103
|
+
except Exception as e:
|
|
1104
|
+
logger.debug("Failed to load Python migration %s: %s", file_path, e)
|
|
1105
|
+
|
|
1106
|
+
return all_queries
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
@overload
|
|
1110
|
+
def create_migration_runner(
|
|
1111
|
+
migrations_path: Path,
|
|
1112
|
+
extension_migrations: "dict[str, Path]",
|
|
1113
|
+
context: "MigrationContext | None",
|
|
1114
|
+
extension_configs: "dict[str, Any]",
|
|
1115
|
+
is_async: "Literal[False]" = False,
|
|
1116
|
+
runtime: "ObservabilityRuntime | None" = None,
|
|
1117
|
+
description_hints: "TemplateDescriptionHints | None" = None,
|
|
1118
|
+
) -> SyncMigrationRunner: ...
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
@overload
|
|
1122
|
+
def create_migration_runner(
|
|
1123
|
+
migrations_path: Path,
|
|
1124
|
+
extension_migrations: "dict[str, Path]",
|
|
1125
|
+
context: "MigrationContext | None",
|
|
1126
|
+
extension_configs: "dict[str, Any]",
|
|
1127
|
+
is_async: "Literal[True]",
|
|
1128
|
+
runtime: "ObservabilityRuntime | None" = None,
|
|
1129
|
+
description_hints: "TemplateDescriptionHints | None" = None,
|
|
1130
|
+
) -> AsyncMigrationRunner: ...
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
def create_migration_runner(
|
|
1134
|
+
migrations_path: Path,
|
|
1135
|
+
extension_migrations: "dict[str, Path]",
|
|
1136
|
+
context: "MigrationContext | None",
|
|
1137
|
+
extension_configs: "dict[str, Any]",
|
|
1138
|
+
is_async: bool = False,
|
|
1139
|
+
runtime: "ObservabilityRuntime | None" = None,
|
|
1140
|
+
description_hints: "TemplateDescriptionHints | None" = None,
|
|
1141
|
+
) -> "SyncMigrationRunner | AsyncMigrationRunner":
|
|
1142
|
+
"""Factory function to create the appropriate migration runner.
|
|
1143
|
+
|
|
1144
|
+
Args:
|
|
1145
|
+
migrations_path: Path to migrations directory.
|
|
1146
|
+
extension_migrations: Extension migration paths.
|
|
1147
|
+
context: Migration context.
|
|
1148
|
+
extension_configs: Extension configurations.
|
|
1149
|
+
is_async: Whether to create async or sync runner.
|
|
1150
|
+
runtime: Observability runtime shared with loaders and execution steps.
|
|
1151
|
+
description_hints: Optional description extraction hints from template profiles.
|
|
1152
|
+
|
|
1153
|
+
Returns:
|
|
1154
|
+
Appropriate migration runner instance.
|
|
1155
|
+
"""
|
|
1156
|
+
if is_async:
|
|
1157
|
+
return AsyncMigrationRunner(
|
|
1158
|
+
migrations_path,
|
|
1159
|
+
extension_migrations,
|
|
1160
|
+
context,
|
|
1161
|
+
extension_configs,
|
|
1162
|
+
runtime=runtime,
|
|
1163
|
+
description_hints=description_hints,
|
|
1164
|
+
)
|
|
1165
|
+
return SyncMigrationRunner(
|
|
1166
|
+
migrations_path,
|
|
1167
|
+
extension_migrations,
|
|
1168
|
+
context,
|
|
1169
|
+
extension_configs,
|
|
1170
|
+
runtime=runtime,
|
|
1171
|
+
description_hints=description_hints,
|
|
1172
|
+
)
|