sqlspec 0.47.0__cp314-cp314-win_amd64.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.
- f68e0789eb443ecb1c2c__mypyc.cp314-win_amd64.pyd +0 -0
- sqlspec/__init__.py +167 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_typing.py +714 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +13 -0
- sqlspec/adapters/adbc/_typing.py +106 -0
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +1280 -0
- sqlspec/adapters/adbc/config.py +378 -0
- sqlspec/adapters/adbc/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/adbc/core.py +922 -0
- sqlspec/adapters/adbc/data_dictionary.py +339 -0
- sqlspec/adapters/adbc/driver.py +534 -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 +534 -0
- sqlspec/adapters/adbc/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/adbc/type_converter.py +142 -0
- sqlspec/adapters/aiomysql/__init__.py +21 -0
- sqlspec/adapters/aiomysql/_typing.py +137 -0
- sqlspec/adapters/aiomysql/adk/__init__.py +5 -0
- sqlspec/adapters/aiomysql/adk/store.py +678 -0
- sqlspec/adapters/aiomysql/config.py +305 -0
- sqlspec/adapters/aiomysql/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/aiomysql/core.py +536 -0
- sqlspec/adapters/aiomysql/data_dictionary.py +121 -0
- sqlspec/adapters/aiomysql/driver.py +386 -0
- sqlspec/adapters/aiomysql/events/__init__.py +5 -0
- sqlspec/adapters/aiomysql/events/store.py +104 -0
- sqlspec/adapters/aiomysql/litestar/__init__.py +5 -0
- sqlspec/adapters/aiomysql/litestar/store.py +314 -0
- sqlspec/adapters/aiosqlite/__init__.py +26 -0
- sqlspec/adapters/aiosqlite/_typing.py +109 -0
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +829 -0
- sqlspec/adapters/aiosqlite/config.py +315 -0
- sqlspec/adapters/aiosqlite/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/aiosqlite/core.py +315 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +202 -0
- sqlspec/adapters/aiosqlite/driver.py +311 -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.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/aiosqlite/pool.py +734 -0
- sqlspec/adapters/asyncmy/__init__.py +21 -0
- sqlspec/adapters/asyncmy/_typing.py +113 -0
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +644 -0
- sqlspec/adapters/asyncmy/config.py +307 -0
- sqlspec/adapters/asyncmy/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/asyncmy/core.py +538 -0
- sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
- sqlspec/adapters/asyncmy/driver.py +391 -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 +26 -0
- sqlspec/adapters/asyncpg/_typing.py +103 -0
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +483 -0
- sqlspec/adapters/asyncpg/config.py +575 -0
- sqlspec/adapters/asyncpg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/asyncpg/core.py +480 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +157 -0
- sqlspec/adapters/asyncpg/driver.py +487 -0
- sqlspec/adapters/asyncpg/events/__init__.py +6 -0
- sqlspec/adapters/asyncpg/events/_hub.py +181 -0
- sqlspec/adapters/asyncpg/events/backend.py +210 -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 +15 -0
- sqlspec/adapters/bigquery/_typing.py +108 -0
- sqlspec/adapters/bigquery/config.py +362 -0
- sqlspec/adapters/bigquery/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/bigquery/core.py +768 -0
- sqlspec/adapters/bigquery/data_dictionary.py +120 -0
- sqlspec/adapters/bigquery/driver.py +542 -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.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/bigquery/type_converter.py +107 -0
- sqlspec/adapters/cockroach_asyncpg/__init__.py +26 -0
- sqlspec/adapters/cockroach_asyncpg/_typing.py +73 -0
- sqlspec/adapters/cockroach_asyncpg/adk/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/adk/store.py +465 -0
- sqlspec/adapters/cockroach_asyncpg/config.py +248 -0
- sqlspec/adapters/cockroach_asyncpg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/cockroach_asyncpg/core.py +55 -0
- sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +110 -0
- sqlspec/adapters/cockroach_asyncpg/driver.py +142 -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 +39 -0
- sqlspec/adapters/cockroach_psycopg/_typing.py +137 -0
- sqlspec/adapters/cockroach_psycopg/adk/__init__.py +13 -0
- sqlspec/adapters/cockroach_psycopg/adk/store.py +1039 -0
- sqlspec/adapters/cockroach_psycopg/config.py +511 -0
- sqlspec/adapters/cockroach_psycopg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/cockroach_psycopg/core.py +63 -0
- sqlspec/adapters/cockroach_psycopg/data_dictionary.py +220 -0
- sqlspec/adapters/cockroach_psycopg/driver.py +273 -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 +327 -0
- sqlspec/adapters/duckdb/__init__.py +29 -0
- sqlspec/adapters/duckdb/_typing.py +104 -0
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +935 -0
- sqlspec/adapters/duckdb/config.py +386 -0
- sqlspec/adapters/duckdb/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/duckdb/core.py +332 -0
- sqlspec/adapters/duckdb/data_dictionary.py +140 -0
- sqlspec/adapters/duckdb/driver.py +426 -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.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/duckdb/pool.py +350 -0
- sqlspec/adapters/duckdb/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/duckdb/type_converter.py +118 -0
- sqlspec/adapters/mysqlconnector/__init__.py +39 -0
- sqlspec/adapters/mysqlconnector/_typing.py +186 -0
- sqlspec/adapters/mysqlconnector/adk/__init__.py +15 -0
- sqlspec/adapters/mysqlconnector/adk/store.py +1183 -0
- sqlspec/adapters/mysqlconnector/config.py +421 -0
- sqlspec/adapters/mysqlconnector/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/mysqlconnector/core.py +472 -0
- sqlspec/adapters/mysqlconnector/data_dictionary.py +230 -0
- sqlspec/adapters/mysqlconnector/driver.py +516 -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 +39 -0
- sqlspec/adapters/oracledb/_json_handlers.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_json_handlers.py +196 -0
- sqlspec/adapters/oracledb/_param_types.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_param_types.py +46 -0
- sqlspec/adapters/oracledb/_typing.py +258 -0
- sqlspec/adapters/oracledb/_uuid_handlers.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_uuid_handlers.py +163 -0
- sqlspec/adapters/oracledb/_vector_handlers.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_vector_handlers.py +228 -0
- sqlspec/adapters/oracledb/adk/__init__.py +21 -0
- sqlspec/adapters/oracledb/adk/store.py +2453 -0
- sqlspec/adapters/oracledb/config.py +575 -0
- sqlspec/adapters/oracledb/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/core.py +820 -0
- sqlspec/adapters/oracledb/data_dictionary.py +404 -0
- sqlspec/adapters/oracledb/driver.py +1277 -0
- sqlspec/adapters/oracledb/events/__init__.py +16 -0
- sqlspec/adapters/oracledb/events/_hub.py +345 -0
- sqlspec/adapters/oracledb/events/backend.py +300 -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 +539 -0
- sqlspec/adapters/oracledb/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/type_converter.py +211 -0
- sqlspec/adapters/psqlpy/__init__.py +18 -0
- sqlspec/adapters/psqlpy/_typing.py +121 -0
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +591 -0
- sqlspec/adapters/psqlpy/config.py +376 -0
- sqlspec/adapters/psqlpy/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psqlpy/core.py +694 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +121 -0
- sqlspec/adapters/psqlpy/driver.py +411 -0
- sqlspec/adapters/psqlpy/events/__init__.py +6 -0
- sqlspec/adapters/psqlpy/events/_hub.py +204 -0
- sqlspec/adapters/psqlpy/events/backend.py +210 -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.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psqlpy/type_converter.py +113 -0
- sqlspec/adapters/psycopg/__init__.py +38 -0
- sqlspec/adapters/psycopg/_typing.py +218 -0
- sqlspec/adapters/psycopg/adk/__init__.py +10 -0
- sqlspec/adapters/psycopg/adk/store.py +1106 -0
- sqlspec/adapters/psycopg/config.py +695 -0
- sqlspec/adapters/psycopg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psycopg/core.py +520 -0
- sqlspec/adapters/psycopg/data_dictionary.py +278 -0
- sqlspec/adapters/psycopg/driver.py +1033 -0
- sqlspec/adapters/psycopg/events/__init__.py +20 -0
- sqlspec/adapters/psycopg/events/_hub.py +388 -0
- sqlspec/adapters/psycopg/events/backend.py +398 -0
- sqlspec/adapters/psycopg/events/store.py +42 -0
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/psycopg/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psycopg/type_converter.py +93 -0
- sqlspec/adapters/pymysql/__init__.py +21 -0
- sqlspec/adapters/pymysql/_typing.py +92 -0
- sqlspec/adapters/pymysql/adk/__init__.py +5 -0
- sqlspec/adapters/pymysql/adk/store.py +657 -0
- sqlspec/adapters/pymysql/config.py +176 -0
- sqlspec/adapters/pymysql/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/pymysql/core.py +469 -0
- sqlspec/adapters/pymysql/data_dictionary.py +120 -0
- sqlspec/adapters/pymysql/driver.py +271 -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.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/pymysql/pool.py +184 -0
- sqlspec/adapters/spanner/__init__.py +33 -0
- sqlspec/adapters/spanner/_typing.py +102 -0
- sqlspec/adapters/spanner/adk/__init__.py +5 -0
- sqlspec/adapters/spanner/adk/store.py +758 -0
- sqlspec/adapters/spanner/config.py +355 -0
- sqlspec/adapters/spanner/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/spanner/core.py +263 -0
- sqlspec/adapters/spanner/data_dictionary.py +120 -0
- sqlspec/adapters/spanner/driver.py +407 -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.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/spanner/type_converter.py +342 -0
- sqlspec/adapters/sqlite/__init__.py +19 -0
- sqlspec/adapters/sqlite/_typing.py +123 -0
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +992 -0
- sqlspec/adapters/sqlite/config.py +240 -0
- sqlspec/adapters/sqlite/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/sqlite/core.py +357 -0
- sqlspec/adapters/sqlite/data_dictionary.py +198 -0
- sqlspec/adapters/sqlite/driver.py +527 -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.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/sqlite/pool.py +237 -0
- sqlspec/adapters/sqlite/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/sqlite/type_converter.py +114 -0
- sqlspec/base.py +832 -0
- sqlspec/builder/__init__.py +181 -0
- sqlspec/builder/_base.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_base.py +1071 -0
- sqlspec/builder/_column.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_column.py +521 -0
- sqlspec/builder/_ddl.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_ddl.py +1691 -0
- sqlspec/builder/_delete.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_delete.py +95 -0
- sqlspec/builder/_dml.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_dml.py +386 -0
- sqlspec/builder/_explain.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_explain.py +579 -0
- sqlspec/builder/_expression_wrappers.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_factory.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_factory.py +1884 -0
- sqlspec/builder/_insert.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_insert.py +405 -0
- sqlspec/builder/_join.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_join.py +489 -0
- sqlspec/builder/_merge.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_merge.py +823 -0
- sqlspec/builder/_parsing_utils.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_parsing_utils.py +295 -0
- sqlspec/builder/_select.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_select.py +1666 -0
- sqlspec/builder/_temporal.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_temporal.py +167 -0
- sqlspec/builder/_update.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_update.py +173 -0
- sqlspec/builder/_vector_distance.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_vector_distance.py +330 -0
- sqlspec/cli.py +1095 -0
- sqlspec/config.py +2383 -0
- sqlspec/core/__init__.py +372 -0
- sqlspec/core/_correlation.cp314-win_amd64.pyd +0 -0
- sqlspec/core/_correlation.py +176 -0
- sqlspec/core/_pagination.py +42 -0
- sqlspec/core/_pool.cp314-win_amd64.pyd +0 -0
- sqlspec/core/_pool.py +76 -0
- sqlspec/core/cache.cp314-win_amd64.pyd +0 -0
- sqlspec/core/cache.py +1085 -0
- sqlspec/core/compiler.cp314-win_amd64.pyd +0 -0
- sqlspec/core/compiler.py +1090 -0
- sqlspec/core/config_runtime.cp314-win_amd64.pyd +0 -0
- sqlspec/core/config_runtime.py +174 -0
- sqlspec/core/explain.cp314-win_amd64.pyd +0 -0
- sqlspec/core/explain.py +275 -0
- sqlspec/core/filters.cp314-win_amd64.pyd +0 -0
- sqlspec/core/filters.py +969 -0
- sqlspec/core/hashing.cp314-win_amd64.pyd +0 -0
- sqlspec/core/hashing.py +266 -0
- sqlspec/core/metrics.cp314-win_amd64.pyd +0 -0
- sqlspec/core/metrics.py +83 -0
- sqlspec/core/parameters/__init__.py +72 -0
- sqlspec/core/parameters/_alignment.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_alignment.py +283 -0
- sqlspec/core/parameters/_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_converter.py +554 -0
- sqlspec/core/parameters/_processor.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_processor.py +1182 -0
- sqlspec/core/parameters/_registry.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_registry.py +206 -0
- sqlspec/core/parameters/_transformers.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_transformers.py +324 -0
- sqlspec/core/parameters/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_types.py +536 -0
- sqlspec/core/parameters/_validator.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_validator.py +171 -0
- sqlspec/core/pipeline.cp314-win_amd64.pyd +0 -0
- sqlspec/core/pipeline.py +333 -0
- sqlspec/core/query_modifiers.cp314-win_amd64.pyd +0 -0
- sqlspec/core/query_modifiers.py +508 -0
- sqlspec/core/result/__init__.py +25 -0
- sqlspec/core/result/_base.cp314-win_amd64.pyd +0 -0
- sqlspec/core/result/_base.py +1232 -0
- sqlspec/core/result/_io.cp314-win_amd64.pyd +0 -0
- sqlspec/core/result/_io.py +28 -0
- sqlspec/core/splitter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/splitter.py +1021 -0
- sqlspec/core/sqlcommenter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/sqlcommenter.py +249 -0
- sqlspec/core/stack.cp314-win_amd64.pyd +0 -0
- sqlspec/core/stack.py +163 -0
- sqlspec/core/statement.cp314-win_amd64.pyd +0 -0
- sqlspec/core/statement.py +1865 -0
- sqlspec/core/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/type_converter.py +340 -0
- sqlspec/data_dictionary/__init__.py +22 -0
- sqlspec/data_dictionary/_loader.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/_loader.py +138 -0
- sqlspec/data_dictionary/_registry.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/_registry.py +74 -0
- sqlspec/data_dictionary/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/_types.py +121 -0
- sqlspec/data_dictionary/dialects/__init__.py +21 -0
- sqlspec/data_dictionary/dialects/bigquery.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/bigquery.py +81 -0
- sqlspec/data_dictionary/dialects/cockroachdb.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/cockroachdb.py +54 -0
- sqlspec/data_dictionary/dialects/duckdb.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/duckdb.py +47 -0
- sqlspec/data_dictionary/dialects/mysql.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/mysql.py +53 -0
- sqlspec/data_dictionary/dialects/oracle.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/oracle.py +197 -0
- sqlspec/data_dictionary/dialects/postgres.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/postgres.py +69 -0
- sqlspec/data_dictionary/dialects/spanner.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/spanner.py +37 -0
- sqlspec/data_dictionary/dialects/sqlite.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/sqlite.py +59 -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/dialects/__init__.py +22 -0
- sqlspec/dialects/_compat.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/_compat.py +14 -0
- sqlspec/dialects/postgres/__init__.py +9 -0
- sqlspec/dialects/postgres/_generators.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/postgres/_generators.py +57 -0
- sqlspec/dialects/postgres/_operators.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/postgres/_operators.py +81 -0
- sqlspec/dialects/postgres/_paradedb.py +50 -0
- sqlspec/dialects/postgres/_pgvector.py +36 -0
- sqlspec/dialects/spanner/__init__.py +6 -0
- sqlspec/dialects/spanner/_generators.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/spanner/_generators.py +206 -0
- sqlspec/dialects/spanner/_spangres.py +77 -0
- sqlspec/dialects/spanner/_spanner.py +179 -0
- sqlspec/driver/__init__.py +49 -0
- sqlspec/driver/_async.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_async.py +1830 -0
- sqlspec/driver/_common.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_common.py +2292 -0
- sqlspec/driver/_exception_handler.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_exception_handler.py +108 -0
- sqlspec/driver/_query_cache.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_query_cache.py +96 -0
- sqlspec/driver/_sql_helpers.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_sql_helpers.py +139 -0
- sqlspec/driver/_storage_helpers.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_storage_helpers.py +153 -0
- sqlspec/driver/_sync.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_sync.py +1817 -0
- sqlspec/exceptions.cp314-win_amd64.pyd +0 -0
- sqlspec/exceptions.py +480 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/adk/__init__.py +84 -0
- sqlspec/extensions/adk/_config_utils.py +199 -0
- sqlspec/extensions/adk/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/adk/_types.py +41 -0
- sqlspec/extensions/adk/artifact/__init__.py +57 -0
- sqlspec/extensions/adk/artifact/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/adk/artifact/_types.py +32 -0
- sqlspec/extensions/adk/artifact/service.py +508 -0
- sqlspec/extensions/adk/artifact/store.py +361 -0
- sqlspec/extensions/adk/converters.py +212 -0
- sqlspec/extensions/adk/memory/__init__.py +69 -0
- sqlspec/extensions/adk/memory/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/adk/memory/_types.py +30 -0
- sqlspec/extensions/adk/memory/converters.py +225 -0
- sqlspec/extensions/adk/memory/service.py +316 -0
- sqlspec/extensions/adk/memory/store.py +525 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +184 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +279 -0
- sqlspec/extensions/adk/store.py +590 -0
- sqlspec/extensions/events/__init__.py +51 -0
- sqlspec/extensions/events/_channel.py +703 -0
- sqlspec/extensions/events/_hints.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/events/_hints.py +45 -0
- sqlspec/extensions/events/_models.py +23 -0
- sqlspec/extensions/events/_payload.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/events/_payload.py +69 -0
- sqlspec/extensions/events/_protocols.py +134 -0
- sqlspec/extensions/events/_queue.py +462 -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 +22 -0
- sqlspec/extensions/fastapi/extension.py +391 -0
- sqlspec/extensions/fastapi/providers.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/fastapi/providers.py +712 -0
- sqlspec/extensions/flask/__init__.py +38 -0
- sqlspec/extensions/flask/_state.py +87 -0
- sqlspec/extensions/flask/_utils.py +71 -0
- sqlspec/extensions/flask/extension.py +539 -0
- sqlspec/extensions/litestar/__init__.py +31 -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 +1066 -0
- sqlspec/extensions/litestar/providers.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/litestar/providers.py +784 -0
- sqlspec/extensions/litestar/store.py +298 -0
- sqlspec/extensions/otel/__init__.py +58 -0
- sqlspec/extensions/prometheus/__init__.py +113 -0
- sqlspec/extensions/sanic/__init__.py +19 -0
- sqlspec/extensions/sanic/_state.py +43 -0
- sqlspec/extensions/sanic/_utils.py +127 -0
- sqlspec/extensions/sanic/extension.py +647 -0
- sqlspec/extensions/starlette/__init__.py +22 -0
- sqlspec/extensions/starlette/_state.py +42 -0
- sqlspec/extensions/starlette/_utils.py +96 -0
- sqlspec/extensions/starlette/extension.py +374 -0
- sqlspec/extensions/starlette/middleware.py +281 -0
- sqlspec/loader.cp314-win_amd64.pyd +0 -0
- sqlspec/loader.py +727 -0
- sqlspec/migrations/__init__.py +39 -0
- sqlspec/migrations/base.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/base.py +862 -0
- sqlspec/migrations/commands.py +2151 -0
- sqlspec/migrations/context.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/context.py +157 -0
- sqlspec/migrations/fix.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/fix.py +204 -0
- sqlspec/migrations/loaders.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/loaders.py +443 -0
- sqlspec/migrations/runner.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/runner.py +1195 -0
- sqlspec/migrations/squash.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/squash.py +490 -0
- sqlspec/migrations/templates.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/templates.py +234 -0
- sqlspec/migrations/tracker.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/tracker.py +792 -0
- sqlspec/migrations/utils.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/utils.py +256 -0
- sqlspec/migrations/validation.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/validation.py +359 -0
- sqlspec/migrations/version.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/version.py +446 -0
- sqlspec/observability/__init__.py +57 -0
- sqlspec/observability/_common.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_common.py +77 -0
- sqlspec/observability/_config.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_config.py +364 -0
- sqlspec/observability/_diagnostics.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_diagnostics.py +74 -0
- sqlspec/observability/_dispatcher.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_dispatcher.py +200 -0
- sqlspec/observability/_formatters/__init__.py +13 -0
- sqlspec/observability/_formatters/_aws.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_aws.py +102 -0
- sqlspec/observability/_formatters/_azure.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_azure.py +96 -0
- sqlspec/observability/_formatters/_base.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_base.py +57 -0
- sqlspec/observability/_formatters/_gcp.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_gcp.py +131 -0
- sqlspec/observability/_formatting.py +58 -0
- sqlspec/observability/_observer.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_observer.py +361 -0
- sqlspec/observability/_runtime.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_runtime.py +461 -0
- sqlspec/observability/_sampling.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_sampling.py +188 -0
- sqlspec/observability/_spans.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_spans.py +161 -0
- sqlspec/protocols.py +955 -0
- sqlspec/py.typed +0 -0
- sqlspec/service.py +433 -0
- sqlspec/storage/__init__.py +48 -0
- sqlspec/storage/_arrow_payload.py +68 -0
- sqlspec/storage/_paths.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/_paths.py +58 -0
- sqlspec/storage/_utils.py +46 -0
- sqlspec/storage/backends/__init__.py +1 -0
- sqlspec/storage/backends/base.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/backends/base.py +374 -0
- sqlspec/storage/backends/fsspec.py +574 -0
- sqlspec/storage/backends/local.py +468 -0
- sqlspec/storage/backends/obstore.py +956 -0
- sqlspec/storage/errors.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/errors.py +102 -0
- sqlspec/storage/pipeline.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/pipeline.py +628 -0
- sqlspec/storage/registry.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/registry.py +329 -0
- sqlspec/typing.py +405 -0
- sqlspec/utils/__init__.py +7 -0
- sqlspec/utils/arrow_helpers.py +384 -0
- sqlspec/utils/config_tools.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/config_tools.py +314 -0
- sqlspec/utils/correlation.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/correlation.py +134 -0
- sqlspec/utils/deprecation.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/deprecation.py +157 -0
- sqlspec/utils/dispatch.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/dispatch.py +101 -0
- sqlspec/utils/fixtures.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/fixtures.py +260 -0
- sqlspec/utils/logging.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/logging.py +251 -0
- sqlspec/utils/module_loader.py +306 -0
- sqlspec/utils/portal.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/portal.py +377 -0
- sqlspec/utils/schema.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/schema.py +1040 -0
- sqlspec/utils/serializers/__init__.py +30 -0
- sqlspec/utils/serializers/_json.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/serializers/_json.py +415 -0
- sqlspec/utils/serializers/_numpy.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/serializers/_numpy.py +65 -0
- sqlspec/utils/serializers/_schema.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/serializers/_schema.py +285 -0
- sqlspec/utils/singleton.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/singleton.py +41 -0
- sqlspec/utils/sync_tools.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/sync_tools.py +316 -0
- sqlspec/utils/text.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/text.py +109 -0
- sqlspec/utils/type_converters.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/type_converters.py +216 -0
- sqlspec/utils/type_guards.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/type_guards.py +1508 -0
- sqlspec/utils/uuids.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/uuids.py +241 -0
- sqlspec-0.47.0.dist-info/METADATA +202 -0
- sqlspec-0.47.0.dist-info/RECORD +621 -0
- sqlspec-0.47.0.dist-info/WHEEL +4 -0
- sqlspec-0.47.0.dist-info/entry_points.txt +6 -0
- sqlspec-0.47.0.dist-info/licenses/LICENSE +21 -0
sqlspec/utils/schema.py
ADDED
|
@@ -0,0 +1,1040 @@
|
|
|
1
|
+
"""Schema transformation utilities for converting data to various schema types."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
|
+
from decimal import Decimal, InvalidOperation
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from functools import lru_cache, partial
|
|
8
|
+
from pathlib import Path, PurePath
|
|
9
|
+
from typing import Any, Final, TypeGuard, cast, overload
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
|
|
12
|
+
from typing_extensions import TypeVar
|
|
13
|
+
|
|
14
|
+
from sqlspec.exceptions import SQLSpecError
|
|
15
|
+
from sqlspec.typing import (
|
|
16
|
+
CATTRS_INSTALLED,
|
|
17
|
+
NUMPY_INSTALLED,
|
|
18
|
+
SchemaT,
|
|
19
|
+
attrs_asdict,
|
|
20
|
+
cattrs_structure,
|
|
21
|
+
cattrs_unstructure,
|
|
22
|
+
convert,
|
|
23
|
+
get_type_adapter,
|
|
24
|
+
)
|
|
25
|
+
from sqlspec.utils.dispatch import TypeDispatcher
|
|
26
|
+
from sqlspec.utils.logging import get_logger
|
|
27
|
+
from sqlspec.utils.serializers import from_json
|
|
28
|
+
from sqlspec.utils.text import camelize, kebabize, pascalize
|
|
29
|
+
from sqlspec.utils.type_guards import (
|
|
30
|
+
get_msgspec_rename_config,
|
|
31
|
+
is_attrs_instance,
|
|
32
|
+
is_attrs_schema,
|
|
33
|
+
is_dataclass,
|
|
34
|
+
is_dict,
|
|
35
|
+
is_msgspec_struct,
|
|
36
|
+
is_pydantic_model,
|
|
37
|
+
is_typed_dict,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = (
|
|
41
|
+
"_DEFAULT_TYPE_DECODERS",
|
|
42
|
+
"DataT",
|
|
43
|
+
"ValueT",
|
|
44
|
+
"_convert_numpy_recursive",
|
|
45
|
+
"_convert_numpy_to_list",
|
|
46
|
+
"_default_msgspec_deserializer",
|
|
47
|
+
"_is_list_type_target",
|
|
48
|
+
"to_schema",
|
|
49
|
+
"to_value_type",
|
|
50
|
+
"transform_dict_keys",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
DataT = TypeVar("DataT", default=dict[str, Any])
|
|
54
|
+
ValueT = TypeVar("ValueT")
|
|
55
|
+
|
|
56
|
+
logger = get_logger(__name__)
|
|
57
|
+
|
|
58
|
+
_DATETIME_TYPES: Final[set[type]] = {datetime.datetime, datetime.date, datetime.time}
|
|
59
|
+
_DATETIME_TYPE_TUPLE: Final[tuple[type, ...]] = (datetime.datetime, datetime.date, datetime.time)
|
|
60
|
+
_MSGSPEC_RENAME_CONVERTERS: Final[dict[str, Callable[[str], str]]] = {
|
|
61
|
+
"camel": camelize,
|
|
62
|
+
"kebab": kebabize,
|
|
63
|
+
"pascal": pascalize,
|
|
64
|
+
}
|
|
65
|
+
_NUMPY_RECURSIVE_DISPATCHER: "TypeDispatcher[Callable[[Any], Any]] | None" = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# =============================================================================
|
|
69
|
+
# Dict Key Transformation
|
|
70
|
+
# =============================================================================
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _safe_convert_key(key: Any, converter: Callable[[str], str]) -> Any:
|
|
74
|
+
"""Safely convert a key using the converter function.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
key: Key to convert (may not be a string).
|
|
78
|
+
converter: Function to convert string keys.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Converted key if conversion succeeds, original key otherwise.
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
if not isinstance(key, str):
|
|
85
|
+
return key
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
return converter(key)
|
|
89
|
+
except (TypeError, ValueError, AttributeError):
|
|
90
|
+
return key
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def transform_dict_keys(data: dict | list | Any, converter: Callable[[str], str]) -> dict | list | Any:
|
|
94
|
+
"""Transform dictionary keys using the provided converter function.
|
|
95
|
+
|
|
96
|
+
Recursively transforms all dictionary keys in a data structure using
|
|
97
|
+
the provided converter function. Handles nested dictionaries, lists
|
|
98
|
+
of dictionaries, and preserves non-dict values unchanged.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
data: The data structure to transform. Can be a dict, list, or any other type.
|
|
102
|
+
converter: Function to convert string keys (e.g., camelize, kebabize).
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The transformed data structure with converted keys. Non-dict values
|
|
106
|
+
are returned unchanged.
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
Transform snake_case keys to camelCase:
|
|
110
|
+
|
|
111
|
+
>>> from sqlspec.utils.text import camelize
|
|
112
|
+
>>> data = {"user_id": 123, "created_at": "2024-01-01"}
|
|
113
|
+
>>> transform_dict_keys(data, camelize)
|
|
114
|
+
{"userId": 123, "createdAt": "2024-01-01"}
|
|
115
|
+
|
|
116
|
+
Transform nested structures:
|
|
117
|
+
|
|
118
|
+
>>> nested = {
|
|
119
|
+
... "user_data": {"first_name": "John", "last_name": "Doe"},
|
|
120
|
+
... "order_items": [
|
|
121
|
+
... {"item_id": 1, "item_name": "Product A"},
|
|
122
|
+
... {"item_id": 2, "item_name": "Product B"},
|
|
123
|
+
... ],
|
|
124
|
+
... }
|
|
125
|
+
>>> transform_dict_keys(nested, camelize)
|
|
126
|
+
{
|
|
127
|
+
"userData": {
|
|
128
|
+
"firstName": "John",
|
|
129
|
+
"lastName": "Doe"
|
|
130
|
+
},
|
|
131
|
+
"orderItems": [
|
|
132
|
+
{"itemId": 1, "itemName": "Product A"},
|
|
133
|
+
{"itemId": 2, "itemName": "Product B"}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
if isinstance(data, dict):
|
|
139
|
+
return _transform_dict(data, converter)
|
|
140
|
+
if isinstance(data, list):
|
|
141
|
+
return _transform_list(data, converter)
|
|
142
|
+
return data
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _transform_dict(data: dict, converter: Callable[[str], str]) -> dict:
|
|
146
|
+
"""Transform a dictionary's keys recursively.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
data: Dictionary to transform.
|
|
150
|
+
converter: Function to convert string keys.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary with transformed keys and recursively transformed values.
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
transformed = {}
|
|
157
|
+
|
|
158
|
+
for key, value in data.items():
|
|
159
|
+
converted_key = _safe_convert_key(key, converter)
|
|
160
|
+
transformed_value = transform_dict_keys(value, converter)
|
|
161
|
+
transformed[converted_key] = transformed_value
|
|
162
|
+
|
|
163
|
+
return transformed
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _transform_list(data: list, converter: Callable[[str], str]) -> list:
|
|
167
|
+
"""Transform a list's elements recursively.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
data: List to transform.
|
|
171
|
+
converter: Function to convert string keys in nested structures.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
List with recursively transformed elements.
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
return [transform_dict_keys(item, converter) for item in data]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# =============================================================================
|
|
181
|
+
# Schema Type Detection
|
|
182
|
+
# =============================================================================
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _is_list_type_target(target_type: Any) -> "TypeGuard[list[object]]":
|
|
186
|
+
"""Check if target type is a list type (e.g., list[float])."""
|
|
187
|
+
try:
|
|
188
|
+
origin = target_type.__origin__
|
|
189
|
+
except (AttributeError, TypeError):
|
|
190
|
+
return False
|
|
191
|
+
return origin is list
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _convert_numpy_to_list(target_type: Any, value: Any) -> Any:
|
|
195
|
+
"""Convert numpy array to list if target is a list type."""
|
|
196
|
+
if not NUMPY_INSTALLED:
|
|
197
|
+
return value
|
|
198
|
+
|
|
199
|
+
import numpy as np
|
|
200
|
+
|
|
201
|
+
if isinstance(value, np.ndarray) and _is_list_type_target(target_type):
|
|
202
|
+
return value.tolist()
|
|
203
|
+
|
|
204
|
+
return value
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@lru_cache(maxsize=128)
|
|
208
|
+
def _detect_schema_type(schema_type: type) -> "str | None":
|
|
209
|
+
"""Detect schema type with LRU caching.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
schema_type: Type to detect
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Type identifier string or None if unsupported
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
return (
|
|
219
|
+
"typed_dict"
|
|
220
|
+
if is_typed_dict(schema_type)
|
|
221
|
+
else "dataclass"
|
|
222
|
+
if is_dataclass(schema_type)
|
|
223
|
+
else "msgspec"
|
|
224
|
+
if is_msgspec_struct(schema_type)
|
|
225
|
+
else "pydantic"
|
|
226
|
+
if is_pydantic_model(schema_type)
|
|
227
|
+
else "attrs"
|
|
228
|
+
if is_attrs_schema(schema_type)
|
|
229
|
+
else None
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _is_foreign_key_metadata_type(schema_type: type) -> bool:
|
|
234
|
+
if schema_type.__name__ != "ForeignKeyMetadata":
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
# Check module for stronger guarantee without importing
|
|
238
|
+
module = getattr(schema_type, "__module__", "")
|
|
239
|
+
if "sqlspec" in module and ("driver" in module or "data_dictionary" in module):
|
|
240
|
+
return True
|
|
241
|
+
|
|
242
|
+
slots = getattr(schema_type, "__slots__", None)
|
|
243
|
+
if not slots:
|
|
244
|
+
return False
|
|
245
|
+
return {"table_name", "column_name", "referenced_table", "referenced_column"}.issubset(set(slots))
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _convert_foreign_key_metadata(data: Any, schema_type: Any) -> Any:
|
|
249
|
+
"""Convert data to ForeignKeyMetadata schema type.
|
|
250
|
+
|
|
251
|
+
Handles both single items and lists for consistency with other converters.
|
|
252
|
+
"""
|
|
253
|
+
if isinstance(data, list):
|
|
254
|
+
return [_convert_single_foreign_key_metadata(item, schema_type) for item in data]
|
|
255
|
+
return _convert_single_foreign_key_metadata(data, schema_type)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _convert_single_foreign_key_metadata(data: Any, schema_type: Any) -> Any:
|
|
259
|
+
"""Convert a single item to ForeignKeyMetadata."""
|
|
260
|
+
if not is_dict(data):
|
|
261
|
+
return data
|
|
262
|
+
payload = {
|
|
263
|
+
"table_name": data.get("table_name") or data.get("table"),
|
|
264
|
+
"column_name": data.get("column_name") or data.get("column"),
|
|
265
|
+
"referenced_table": data.get("referenced_table") or data.get("referenced_table_name"),
|
|
266
|
+
"referenced_column": data.get("referenced_column") or data.get("referenced_column_name"),
|
|
267
|
+
"constraint_name": data.get("constraint_name"),
|
|
268
|
+
"schema": data.get("schema") or data.get("table_schema"),
|
|
269
|
+
"referenced_schema": data.get("referenced_schema") or data.get("referenced_table_schema"),
|
|
270
|
+
}
|
|
271
|
+
return schema_type(**payload)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _convert_typed_dict(data: Any, schema_type: Any) -> Any:
|
|
275
|
+
"""Convert data to TypedDict."""
|
|
276
|
+
return [item for item in data if is_dict(item)] if isinstance(data, list) else data
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _convert_dataclass(data: Any, schema_type: Any) -> Any:
|
|
280
|
+
"""Convert data to dataclass."""
|
|
281
|
+
if isinstance(data, list):
|
|
282
|
+
return [schema_type(**dict(item)) if is_dict(item) else item for item in data]
|
|
283
|
+
return schema_type(**dict(data)) if is_dict(data) else (schema_type(**data) if isinstance(data, dict) else data)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class _IsTypePredicate:
|
|
287
|
+
"""Callable predicate to check if a type matches a target type."""
|
|
288
|
+
|
|
289
|
+
__slots__ = ("_type",)
|
|
290
|
+
|
|
291
|
+
def __init__(self, target_type: type) -> None:
|
|
292
|
+
self._type = target_type
|
|
293
|
+
|
|
294
|
+
def __call__(self, x: Any) -> bool:
|
|
295
|
+
return x is self._type
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class _UUIDDecoder:
|
|
299
|
+
"""Decoder for UUID types."""
|
|
300
|
+
|
|
301
|
+
__slots__ = ()
|
|
302
|
+
|
|
303
|
+
def __call__(self, t: type, v: Any) -> Any:
|
|
304
|
+
return t(v.hex)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class _ISOFormatDecoder:
|
|
308
|
+
"""Decoder for types with isoformat() method (datetime, date, time)."""
|
|
309
|
+
|
|
310
|
+
__slots__ = ()
|
|
311
|
+
|
|
312
|
+
def __call__(self, t: type, v: Any) -> Any:
|
|
313
|
+
return t(v.isoformat())
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class _EnumDecoder:
|
|
317
|
+
"""Decoder for Enum types."""
|
|
318
|
+
|
|
319
|
+
__slots__ = ()
|
|
320
|
+
|
|
321
|
+
def __call__(self, t: type, v: Any) -> Any:
|
|
322
|
+
return t(v.value)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
_DEFAULT_TYPE_DECODERS: Final[list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]]] = [
|
|
326
|
+
(_IsTypePredicate(UUID), _UUIDDecoder()),
|
|
327
|
+
(_IsTypePredicate(datetime.datetime), _ISOFormatDecoder()),
|
|
328
|
+
(_IsTypePredicate(datetime.date), _ISOFormatDecoder()),
|
|
329
|
+
(_IsTypePredicate(datetime.time), _ISOFormatDecoder()),
|
|
330
|
+
(_IsTypePredicate(Enum), _EnumDecoder()),
|
|
331
|
+
(_is_list_type_target, _convert_numpy_to_list),
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _default_msgspec_deserializer(
|
|
336
|
+
target_type: Any, value: Any, type_decoders: "Sequence[tuple[Any, Any]] | None" = None
|
|
337
|
+
) -> Any:
|
|
338
|
+
"""Convert msgspec types with type decoder support.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
target_type: Type to convert to
|
|
342
|
+
value: Value to convert
|
|
343
|
+
type_decoders: Optional sequence of (predicate, decoder) pairs
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Converted value or original value if conversion not applicable
|
|
347
|
+
|
|
348
|
+
"""
|
|
349
|
+
if NUMPY_INSTALLED:
|
|
350
|
+
import numpy as np
|
|
351
|
+
|
|
352
|
+
if isinstance(value, np.ndarray) and _is_list_type_target(target_type):
|
|
353
|
+
return value.tolist()
|
|
354
|
+
|
|
355
|
+
if type_decoders:
|
|
356
|
+
for predicate, decoder in type_decoders:
|
|
357
|
+
if predicate(target_type):
|
|
358
|
+
return decoder(target_type, value)
|
|
359
|
+
|
|
360
|
+
if target_type is UUID and isinstance(value, UUID):
|
|
361
|
+
return value.hex
|
|
362
|
+
|
|
363
|
+
if target_type in _DATETIME_TYPES and isinstance(value, _DATETIME_TYPE_TUPLE):
|
|
364
|
+
datetime_value = cast("datetime.datetime | datetime.date | datetime.time", value)
|
|
365
|
+
return datetime_value.isoformat()
|
|
366
|
+
|
|
367
|
+
if isinstance(target_type, type) and issubclass(target_type, Enum) and isinstance(value, Enum):
|
|
368
|
+
return value.value
|
|
369
|
+
|
|
370
|
+
try:
|
|
371
|
+
if isinstance(target_type, type) and isinstance(value, target_type):
|
|
372
|
+
return value
|
|
373
|
+
except TypeError:
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
if isinstance(target_type, type):
|
|
377
|
+
try:
|
|
378
|
+
if issubclass(target_type, (Path, PurePath)) or issubclass(target_type, UUID):
|
|
379
|
+
return target_type(str(value))
|
|
380
|
+
except (TypeError, ValueError):
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
return value
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
_DEFAULT_MSGSPEC_DESERIALIZER: Final[Callable[[Any, Any], Any]] = partial(
|
|
387
|
+
_default_msgspec_deserializer, type_decoders=_DEFAULT_TYPE_DECODERS
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _convert_numpy_recursive(obj: Any) -> Any:
|
|
392
|
+
"""Recursively convert numpy arrays to lists.
|
|
393
|
+
|
|
394
|
+
This is a module-level function to avoid nested function definitions
|
|
395
|
+
which are problematic for mypyc compilation.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
obj: Object to convert (may contain numpy arrays nested in dicts/lists)
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Object with all numpy arrays converted to lists
|
|
402
|
+
|
|
403
|
+
"""
|
|
404
|
+
if not NUMPY_INSTALLED:
|
|
405
|
+
return obj
|
|
406
|
+
|
|
407
|
+
handler = _get_numpy_recursive_dispatcher().get(obj)
|
|
408
|
+
if handler is not None:
|
|
409
|
+
return handler(obj)
|
|
410
|
+
return obj
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _convert_numpy_array(obj: Any) -> Any:
|
|
414
|
+
return obj.tolist()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _convert_numpy_mapping(obj: Any) -> Any:
|
|
418
|
+
return {key: _convert_numpy_recursive(value) for key, value in obj.items()}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _convert_numpy_sequence(obj: Any) -> Any:
|
|
422
|
+
converted = [_convert_numpy_recursive(item) for item in obj]
|
|
423
|
+
return type(obj)(converted)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _get_numpy_recursive_dispatcher() -> "TypeDispatcher[Callable[[Any], Any]]":
|
|
427
|
+
global _NUMPY_RECURSIVE_DISPATCHER
|
|
428
|
+
if _NUMPY_RECURSIVE_DISPATCHER is None:
|
|
429
|
+
import numpy as np
|
|
430
|
+
|
|
431
|
+
dispatcher = TypeDispatcher["Callable[[Any], Any]"]()
|
|
432
|
+
dispatcher.register(np.ndarray, _convert_numpy_array)
|
|
433
|
+
dispatcher.register(dict, _convert_numpy_mapping)
|
|
434
|
+
dispatcher.register(list, _convert_numpy_sequence)
|
|
435
|
+
dispatcher.register(tuple, _convert_numpy_sequence)
|
|
436
|
+
_NUMPY_RECURSIVE_DISPATCHER = dispatcher
|
|
437
|
+
return _NUMPY_RECURSIVE_DISPATCHER
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _convert_msgspec(data: Any, schema_type: Any) -> Any:
|
|
441
|
+
"""Convert data to msgspec Struct."""
|
|
442
|
+
rename_config = get_msgspec_rename_config(schema_type)
|
|
443
|
+
|
|
444
|
+
transformed_data = data
|
|
445
|
+
if (rename_config and is_dict(data)) or (isinstance(data, Sequence) and data and is_dict(data[0])):
|
|
446
|
+
try:
|
|
447
|
+
converter = _MSGSPEC_RENAME_CONVERTERS.get(rename_config) if rename_config else None
|
|
448
|
+
if converter:
|
|
449
|
+
transformed_data = (
|
|
450
|
+
[transform_dict_keys(item, converter) if is_dict(item) else item for item in data]
|
|
451
|
+
if isinstance(data, Sequence)
|
|
452
|
+
else (transform_dict_keys(data, converter) if is_dict(data) else data)
|
|
453
|
+
)
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.debug("Field name transformation failed for msgspec schema: %s", e)
|
|
456
|
+
|
|
457
|
+
if NUMPY_INSTALLED:
|
|
458
|
+
transformed_data = _convert_numpy_recursive(transformed_data)
|
|
459
|
+
|
|
460
|
+
return convert(
|
|
461
|
+
obj=transformed_data,
|
|
462
|
+
type=(list[schema_type] if isinstance(transformed_data, Sequence) else schema_type),
|
|
463
|
+
from_attributes=True,
|
|
464
|
+
dec_hook=_DEFAULT_MSGSPEC_DESERIALIZER,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _convert_pydantic(data: Any, schema_type: Any) -> Any:
|
|
469
|
+
"""Convert data to Pydantic model."""
|
|
470
|
+
if isinstance(data, Sequence):
|
|
471
|
+
return get_type_adapter(list[schema_type]).validate_python(data, from_attributes=True)
|
|
472
|
+
return get_type_adapter(schema_type).validate_python(data, from_attributes=True)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _convert_attrs(data: Any, schema_type: Any) -> Any:
|
|
476
|
+
"""Convert data to attrs class."""
|
|
477
|
+
if CATTRS_INSTALLED:
|
|
478
|
+
if isinstance(data, Sequence):
|
|
479
|
+
return cattrs_structure(data, list[schema_type])
|
|
480
|
+
structured = cattrs_unstructure(data) if is_attrs_instance(data) else data
|
|
481
|
+
return cattrs_structure(structured, schema_type)
|
|
482
|
+
|
|
483
|
+
if isinstance(data, list):
|
|
484
|
+
return [schema_type(**dict(item)) if is_dict(item) else schema_type(**attrs_asdict(item)) for item in data]
|
|
485
|
+
return schema_type(**dict(data)) if is_dict(data) else data
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# Cache for schema converters - maps type directly to converter callable (or None if unsupported)
|
|
489
|
+
# Manual dict cache is faster than lru_cache for mypyc: direct dict[type] lookup vs decorated call
|
|
490
|
+
_SCHEMA_CONVERTER_CACHE: "dict[type, Callable[[Any, Any], Any] | None]" = {}
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _get_schema_converter(schema_type: type) -> "Callable[[Any, Any], Any] | None":
|
|
494
|
+
"""Get the converter function for a schema type with caching.
|
|
495
|
+
|
|
496
|
+
This is optimized for mypyc: uses a plain dict cache instead of lru_cache,
|
|
497
|
+
and caches the callable directly instead of a string key.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
schema_type: The schema type to get a converter for.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Converter function if schema_type is a supported schema, None otherwise.
|
|
504
|
+
|
|
505
|
+
"""
|
|
506
|
+
try:
|
|
507
|
+
return _SCHEMA_CONVERTER_CACHE[schema_type]
|
|
508
|
+
except KeyError:
|
|
509
|
+
# Determine converter - order by expected frequency
|
|
510
|
+
if is_typed_dict(schema_type):
|
|
511
|
+
conv: Callable[[Any, Any], Any] | None = _convert_typed_dict
|
|
512
|
+
elif is_dataclass(schema_type):
|
|
513
|
+
conv = _convert_dataclass
|
|
514
|
+
elif is_msgspec_struct(schema_type):
|
|
515
|
+
conv = _convert_msgspec
|
|
516
|
+
elif is_pydantic_model(schema_type):
|
|
517
|
+
conv = _convert_pydantic
|
|
518
|
+
elif is_attrs_schema(schema_type):
|
|
519
|
+
conv = _convert_attrs
|
|
520
|
+
elif _is_foreign_key_metadata_type(schema_type):
|
|
521
|
+
conv = _convert_foreign_key_metadata
|
|
522
|
+
else:
|
|
523
|
+
conv = None
|
|
524
|
+
_SCHEMA_CONVERTER_CACHE[schema_type] = conv # pyright: ignore[reportArgumentType]
|
|
525
|
+
return conv
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
@overload
|
|
529
|
+
def to_schema(data: "list[DataT]", *, schema_type: "type[SchemaT]") -> "list[SchemaT]": ...
|
|
530
|
+
@overload
|
|
531
|
+
def to_schema(data: "list[DataT]", *, schema_type: None = None) -> "list[DataT]": ...
|
|
532
|
+
@overload
|
|
533
|
+
def to_schema(data: "DataT", *, schema_type: "type[SchemaT]") -> "SchemaT": ...
|
|
534
|
+
@overload
|
|
535
|
+
def to_schema(data: "DataT", *, schema_type: None = None) -> "DataT": ...
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def to_schema(data: Any, *, schema_type: Any = None) -> Any:
|
|
539
|
+
"""Convert data to a specified schema type.
|
|
540
|
+
|
|
541
|
+
Supports transformation to various schema types including:
|
|
542
|
+
- TypedDict
|
|
543
|
+
- dataclasses
|
|
544
|
+
- msgspec Structs
|
|
545
|
+
- Pydantic models
|
|
546
|
+
- attrs classes
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
data: Input data to convert (dict, list of dicts, or other)
|
|
550
|
+
schema_type: Target schema type for conversion. If None, returns data unchanged.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Converted data in the specified schema type, or original data if schema_type is None
|
|
554
|
+
|
|
555
|
+
Raises:
|
|
556
|
+
SQLSpecError: If schema_type is not a supported type
|
|
557
|
+
|
|
558
|
+
"""
|
|
559
|
+
if schema_type is None:
|
|
560
|
+
return data
|
|
561
|
+
|
|
562
|
+
# Get cached converter - single dict lookup, no string indirection
|
|
563
|
+
conv = _get_schema_converter(schema_type)
|
|
564
|
+
if conv is None:
|
|
565
|
+
msg = "`schema_type` should be a valid Dataclass, Pydantic model, Msgspec struct, Attrs class, or TypedDict"
|
|
566
|
+
raise SQLSpecError(msg)
|
|
567
|
+
|
|
568
|
+
return conv(data, schema_type)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
# =============================================================================
|
|
572
|
+
# Scalar Type Conversion
|
|
573
|
+
# =============================================================================
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _ensure_json_parsed(value: Any) -> Any:
|
|
577
|
+
"""Parse JSON string if needed, otherwise return as-is.
|
|
578
|
+
|
|
579
|
+
This helper is used when converting database values (potentially JSON strings
|
|
580
|
+
from JSONB columns) to schema types like Pydantic models or dataclasses.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
value: The value to potentially parse. If it's a string, attempts JSON parsing.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Parsed JSON object if value was a valid JSON string, otherwise the original value.
|
|
587
|
+
|
|
588
|
+
"""
|
|
589
|
+
if isinstance(value, str):
|
|
590
|
+
try:
|
|
591
|
+
return from_json(value)
|
|
592
|
+
except Exception:
|
|
593
|
+
return value
|
|
594
|
+
return value
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _try_parse_json(value: str) -> Any:
|
|
598
|
+
"""Attempt to parse a JSON string, returning None on failure.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
value: JSON string to parse.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Parsed JSON value, or None if parsing fails.
|
|
605
|
+
|
|
606
|
+
"""
|
|
607
|
+
try:
|
|
608
|
+
return from_json(value)
|
|
609
|
+
except Exception:
|
|
610
|
+
return None
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
# Boolean true values for string conversion
|
|
614
|
+
_BOOL_TRUE_VALUES: Final[frozenset[str]] = frozenset({"true", "1", "yes", "y", "t", "on"})
|
|
615
|
+
|
|
616
|
+
# Types requiring strict type() identity check due to subclass gotchas:
|
|
617
|
+
# - bool is subclass of int (isinstance(True, int) is True)
|
|
618
|
+
# - datetime is subclass of date (isinstance(datetime(...), date) is True)
|
|
619
|
+
_STRICT_IDENTITY_TYPES: Final[tuple[type, ...]] = (int, bool, datetime.date, datetime.time)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _convert_to_int(value: Any) -> int:
|
|
623
|
+
"""Convert a value to int.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
value: Value to convert.
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
Converted integer value.
|
|
630
|
+
|
|
631
|
+
Raises:
|
|
632
|
+
TypeError: If value cannot be converted to int.
|
|
633
|
+
|
|
634
|
+
"""
|
|
635
|
+
if isinstance(value, bool):
|
|
636
|
+
return int(value)
|
|
637
|
+
if isinstance(value, (int, float, Decimal)):
|
|
638
|
+
return int(value)
|
|
639
|
+
if isinstance(value, str):
|
|
640
|
+
try:
|
|
641
|
+
return int(value)
|
|
642
|
+
except ValueError:
|
|
643
|
+
# Try parsing as float first for values like "42.0"
|
|
644
|
+
try:
|
|
645
|
+
return int(float(value))
|
|
646
|
+
except ValueError:
|
|
647
|
+
pass
|
|
648
|
+
msg = f"Cannot convert {type(value).__name__} to int"
|
|
649
|
+
raise TypeError(msg)
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _convert_to_float(value: Any) -> float:
|
|
653
|
+
"""Convert a value to float.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
value: Value to convert.
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
Converted float value.
|
|
660
|
+
|
|
661
|
+
Raises:
|
|
662
|
+
TypeError: If value cannot be converted to float.
|
|
663
|
+
|
|
664
|
+
"""
|
|
665
|
+
if isinstance(value, bool):
|
|
666
|
+
return float(value)
|
|
667
|
+
if isinstance(value, (int, float, Decimal)):
|
|
668
|
+
return float(value)
|
|
669
|
+
if isinstance(value, str):
|
|
670
|
+
try:
|
|
671
|
+
return float(value)
|
|
672
|
+
except ValueError:
|
|
673
|
+
pass
|
|
674
|
+
msg = f"Cannot convert {type(value).__name__} to float"
|
|
675
|
+
raise TypeError(msg)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def _convert_to_bool(value: Any) -> bool:
|
|
679
|
+
"""Convert a value to bool.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
value: Value to convert.
|
|
683
|
+
|
|
684
|
+
Returns:
|
|
685
|
+
Converted boolean value.
|
|
686
|
+
|
|
687
|
+
Raises:
|
|
688
|
+
TypeError: If value cannot be converted to bool.
|
|
689
|
+
|
|
690
|
+
"""
|
|
691
|
+
if isinstance(value, bool):
|
|
692
|
+
return value
|
|
693
|
+
if isinstance(value, (int, float)):
|
|
694
|
+
return bool(value)
|
|
695
|
+
if isinstance(value, str):
|
|
696
|
+
return value.lower() in _BOOL_TRUE_VALUES
|
|
697
|
+
msg = f"Cannot convert {type(value).__name__} to bool"
|
|
698
|
+
raise TypeError(msg)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def _convert_to_datetime(value: Any) -> datetime.datetime:
|
|
702
|
+
"""Convert a value to datetime.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
value: Value to convert.
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
Converted datetime value.
|
|
709
|
+
|
|
710
|
+
Raises:
|
|
711
|
+
TypeError: If value cannot be converted to datetime.
|
|
712
|
+
|
|
713
|
+
"""
|
|
714
|
+
if isinstance(value, datetime.datetime):
|
|
715
|
+
return value
|
|
716
|
+
if isinstance(value, str):
|
|
717
|
+
try:
|
|
718
|
+
return datetime.datetime.fromisoformat(value)
|
|
719
|
+
except ValueError:
|
|
720
|
+
pass
|
|
721
|
+
if isinstance(value, datetime.date) and not isinstance(value, datetime.datetime):
|
|
722
|
+
return datetime.datetime.combine(value, datetime.time.min)
|
|
723
|
+
msg = f"Cannot convert {type(value).__name__} to datetime"
|
|
724
|
+
raise TypeError(msg)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def _convert_to_date(value: Any) -> datetime.date:
|
|
728
|
+
"""Convert a value to date.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
value: Value to convert.
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
Converted date value.
|
|
735
|
+
|
|
736
|
+
Raises:
|
|
737
|
+
TypeError: If value cannot be converted to date.
|
|
738
|
+
|
|
739
|
+
"""
|
|
740
|
+
if isinstance(value, datetime.datetime):
|
|
741
|
+
return value.date()
|
|
742
|
+
if isinstance(value, datetime.date):
|
|
743
|
+
return value
|
|
744
|
+
if isinstance(value, str):
|
|
745
|
+
try:
|
|
746
|
+
# Try ISO format first
|
|
747
|
+
return datetime.date.fromisoformat(value)
|
|
748
|
+
except ValueError:
|
|
749
|
+
# Try parsing as datetime and extracting date
|
|
750
|
+
try:
|
|
751
|
+
return datetime.datetime.fromisoformat(value).date()
|
|
752
|
+
except ValueError:
|
|
753
|
+
pass
|
|
754
|
+
msg = f"Cannot convert {type(value).__name__} to date"
|
|
755
|
+
raise TypeError(msg)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def _convert_to_time(value: Any) -> datetime.time:
|
|
759
|
+
"""Convert a value to time.
|
|
760
|
+
|
|
761
|
+
Args:
|
|
762
|
+
value: Value to convert.
|
|
763
|
+
|
|
764
|
+
Returns:
|
|
765
|
+
Converted time value.
|
|
766
|
+
|
|
767
|
+
Raises:
|
|
768
|
+
TypeError: If value cannot be converted to time.
|
|
769
|
+
|
|
770
|
+
"""
|
|
771
|
+
if isinstance(value, datetime.datetime):
|
|
772
|
+
return value.time()
|
|
773
|
+
if isinstance(value, datetime.time):
|
|
774
|
+
return value
|
|
775
|
+
if isinstance(value, str):
|
|
776
|
+
try:
|
|
777
|
+
return datetime.time.fromisoformat(value)
|
|
778
|
+
except ValueError:
|
|
779
|
+
pass
|
|
780
|
+
msg = f"Cannot convert {type(value).__name__} to time"
|
|
781
|
+
raise TypeError(msg)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def _convert_to_decimal(value: Any) -> Decimal:
|
|
785
|
+
"""Convert a value to Decimal.
|
|
786
|
+
|
|
787
|
+
Args:
|
|
788
|
+
value: Value to convert.
|
|
789
|
+
|
|
790
|
+
Returns:
|
|
791
|
+
Converted Decimal value.
|
|
792
|
+
|
|
793
|
+
Raises:
|
|
794
|
+
TypeError: If value cannot be converted to Decimal.
|
|
795
|
+
|
|
796
|
+
"""
|
|
797
|
+
if isinstance(value, Decimal):
|
|
798
|
+
return value
|
|
799
|
+
if isinstance(value, (int, float, str)):
|
|
800
|
+
try:
|
|
801
|
+
return Decimal(str(value))
|
|
802
|
+
except InvalidOperation:
|
|
803
|
+
pass
|
|
804
|
+
msg = f"Cannot convert {type(value).__name__} to Decimal"
|
|
805
|
+
raise TypeError(msg)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def _convert_to_uuid(value: Any) -> UUID:
|
|
809
|
+
"""Convert a value to UUID.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
value: Value to convert.
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
Converted UUID value.
|
|
816
|
+
|
|
817
|
+
Raises:
|
|
818
|
+
TypeError: If value cannot be converted to UUID.
|
|
819
|
+
|
|
820
|
+
"""
|
|
821
|
+
if isinstance(value, UUID):
|
|
822
|
+
return value
|
|
823
|
+
if isinstance(value, str):
|
|
824
|
+
try:
|
|
825
|
+
return UUID(value)
|
|
826
|
+
except ValueError:
|
|
827
|
+
pass
|
|
828
|
+
if isinstance(value, bytes):
|
|
829
|
+
try:
|
|
830
|
+
return UUID(bytes=value)
|
|
831
|
+
except ValueError:
|
|
832
|
+
pass
|
|
833
|
+
msg = f"Cannot convert {type(value).__name__} to UUID"
|
|
834
|
+
raise TypeError(msg)
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def _convert_to_path(value: Any) -> Path:
|
|
838
|
+
"""Convert a value to Path.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
value: Value to convert.
|
|
842
|
+
|
|
843
|
+
Returns:
|
|
844
|
+
Converted Path value.
|
|
845
|
+
|
|
846
|
+
Raises:
|
|
847
|
+
TypeError: If value cannot be converted to Path.
|
|
848
|
+
|
|
849
|
+
"""
|
|
850
|
+
if isinstance(value, Path):
|
|
851
|
+
return value
|
|
852
|
+
if isinstance(value, (str, PurePath)):
|
|
853
|
+
return Path(value)
|
|
854
|
+
msg = f"Cannot convert {type(value).__name__} to Path"
|
|
855
|
+
raise TypeError(msg)
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _convert_to_dict(value: Any) -> dict[str, Any]:
|
|
859
|
+
"""Convert a value to dict.
|
|
860
|
+
|
|
861
|
+
This is useful for JSON/JSONB database columns where the driver may return
|
|
862
|
+
either a dict (already parsed) or a string (needs parsing).
|
|
863
|
+
|
|
864
|
+
Args:
|
|
865
|
+
value: Value to convert.
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
Converted dict value.
|
|
869
|
+
|
|
870
|
+
Raises:
|
|
871
|
+
TypeError: If value cannot be converted to dict.
|
|
872
|
+
|
|
873
|
+
"""
|
|
874
|
+
if isinstance(value, dict):
|
|
875
|
+
return value
|
|
876
|
+
if isinstance(value, str):
|
|
877
|
+
parsed = _try_parse_json(value)
|
|
878
|
+
if parsed is not None:
|
|
879
|
+
if isinstance(parsed, dict):
|
|
880
|
+
return parsed
|
|
881
|
+
msg = f"JSON string did not parse to dict, got {type(parsed).__name__}"
|
|
882
|
+
raise TypeError(msg)
|
|
883
|
+
msg = f"Cannot convert {type(value).__name__} to dict"
|
|
884
|
+
raise TypeError(msg)
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
def _convert_to_list(value: Any) -> list[Any]:
|
|
888
|
+
"""Convert a value to list.
|
|
889
|
+
|
|
890
|
+
This is useful for JSON array database columns where the driver may return
|
|
891
|
+
either a list (already parsed) or a string (needs parsing).
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
value: Value to convert.
|
|
895
|
+
|
|
896
|
+
Returns:
|
|
897
|
+
Converted list value.
|
|
898
|
+
|
|
899
|
+
Raises:
|
|
900
|
+
TypeError: If value cannot be converted to list.
|
|
901
|
+
|
|
902
|
+
"""
|
|
903
|
+
if isinstance(value, list):
|
|
904
|
+
return value
|
|
905
|
+
if isinstance(value, str):
|
|
906
|
+
parsed = _try_parse_json(value)
|
|
907
|
+
if parsed is not None:
|
|
908
|
+
if isinstance(parsed, list):
|
|
909
|
+
return parsed
|
|
910
|
+
msg = f"JSON string did not parse to list, got {type(parsed).__name__}"
|
|
911
|
+
raise TypeError(msg)
|
|
912
|
+
if isinstance(value, (tuple, set, frozenset)):
|
|
913
|
+
return list(value)
|
|
914
|
+
msg = f"Cannot convert {type(value).__name__} to list"
|
|
915
|
+
raise TypeError(msg)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def to_value_type(value: Any, value_type: "type[ValueT]") -> "ValueT":
|
|
919
|
+
"""Convert a database value to the specified Python type.
|
|
920
|
+
|
|
921
|
+
This function handles type conversion for common database return values,
|
|
922
|
+
providing runtime type safety for scalar queries. When the value is already
|
|
923
|
+
the correct type, it is returned as-is without conversion overhead.
|
|
924
|
+
|
|
925
|
+
Also supports schema types (Pydantic models, dataclasses, msgspec Structs,
|
|
926
|
+
attrs classes, and TypedDict). For schema types, JSON strings are automatically
|
|
927
|
+
parsed before conversion.
|
|
928
|
+
|
|
929
|
+
Args:
|
|
930
|
+
value: The value to convert.
|
|
931
|
+
value_type: The target Python type. Supported types include:
|
|
932
|
+
- Primitives: int, float, str, bool
|
|
933
|
+
- Temporal: datetime, date, time
|
|
934
|
+
- Numeric: Decimal
|
|
935
|
+
- Identifiers: UUID, Path
|
|
936
|
+
- Collections: dict, list (for JSON/JSONB columns)
|
|
937
|
+
- Schema types: Pydantic models, dataclasses, msgspec Structs,
|
|
938
|
+
attrs classes, TypedDict (for JSONB columns)
|
|
939
|
+
|
|
940
|
+
Returns:
|
|
941
|
+
The converted value of the specified type.
|
|
942
|
+
|
|
943
|
+
Raises:
|
|
944
|
+
TypeError: If the value cannot be converted to the specified type.
|
|
945
|
+
|
|
946
|
+
Examples:
|
|
947
|
+
Convert string to int:
|
|
948
|
+
|
|
949
|
+
>>> to_value_type("42", int)
|
|
950
|
+
42
|
|
951
|
+
|
|
952
|
+
Convert Decimal to float:
|
|
953
|
+
|
|
954
|
+
>>> from decimal import Decimal
|
|
955
|
+
>>> to_value_type(Decimal("3.14"), float)
|
|
956
|
+
3.14
|
|
957
|
+
|
|
958
|
+
Convert string to UUID:
|
|
959
|
+
|
|
960
|
+
>>> from uuid import UUID
|
|
961
|
+
>>> to_value_type("550e8400-e29b-41d4-a716-446655440000", UUID)
|
|
962
|
+
UUID('550e8400-e29b-41d4-a716-446655440000')
|
|
963
|
+
|
|
964
|
+
Convert JSON string to dict:
|
|
965
|
+
|
|
966
|
+
>>> to_value_type('{"key": "value"}', dict)
|
|
967
|
+
{'key': 'value'}
|
|
968
|
+
|
|
969
|
+
Convert JSONB to Pydantic model:
|
|
970
|
+
|
|
971
|
+
>>> from pydantic import BaseModel
|
|
972
|
+
>>> class User(BaseModel):
|
|
973
|
+
... name: str
|
|
974
|
+
... email: str
|
|
975
|
+
>>> to_value_type(
|
|
976
|
+
... '{"name": "Alice", "email": "alice@example.com"}', User
|
|
977
|
+
... )
|
|
978
|
+
User(name='Alice', email='alice@example.com')
|
|
979
|
+
|
|
980
|
+
Identity conversion (no overhead when type matches exactly):
|
|
981
|
+
|
|
982
|
+
>>> value = 42
|
|
983
|
+
>>> result = to_value_type(value, int)
|
|
984
|
+
>>> result is value
|
|
985
|
+
True
|
|
986
|
+
|
|
987
|
+
Bool to int conversion (bool is a subclass of int, but converts correctly):
|
|
988
|
+
|
|
989
|
+
>>> to_value_type(True, int)
|
|
990
|
+
1
|
|
991
|
+
>>> to_value_type(False, int)
|
|
992
|
+
0
|
|
993
|
+
|
|
994
|
+
"""
|
|
995
|
+
# Fast path: already correct type (handles ~90% of cases)
|
|
996
|
+
# Uses strict type() identity which correctly handles subclass gotchas:
|
|
997
|
+
# - type(True) is int → False (bool is subclass of int)
|
|
998
|
+
# - type(datetime(...)) is date → False (datetime is subclass of date)
|
|
999
|
+
if type(value) is value_type:
|
|
1000
|
+
return value
|
|
1001
|
+
|
|
1002
|
+
# Scalar type conversions - most common cases, fast pointer comparisons under mypyc
|
|
1003
|
+
if value_type is int:
|
|
1004
|
+
return cast("ValueT", _convert_to_int(value))
|
|
1005
|
+
if value_type is str:
|
|
1006
|
+
return cast("ValueT", str(value))
|
|
1007
|
+
if value_type is float:
|
|
1008
|
+
return cast("ValueT", _convert_to_float(value))
|
|
1009
|
+
if value_type is bool:
|
|
1010
|
+
return cast("ValueT", _convert_to_bool(value))
|
|
1011
|
+
if value_type is datetime.datetime:
|
|
1012
|
+
return cast("ValueT", _convert_to_datetime(value))
|
|
1013
|
+
if value_type is datetime.date:
|
|
1014
|
+
return cast("ValueT", _convert_to_date(value))
|
|
1015
|
+
if value_type is datetime.time:
|
|
1016
|
+
return cast("ValueT", _convert_to_time(value))
|
|
1017
|
+
if value_type is Decimal:
|
|
1018
|
+
return cast("ValueT", _convert_to_decimal(value))
|
|
1019
|
+
if value_type is UUID:
|
|
1020
|
+
return cast("ValueT", _convert_to_uuid(value))
|
|
1021
|
+
if value_type is Path:
|
|
1022
|
+
return cast("ValueT", _convert_to_path(value))
|
|
1023
|
+
if value_type is dict:
|
|
1024
|
+
return cast("ValueT", _convert_to_dict(value))
|
|
1025
|
+
if value_type is list:
|
|
1026
|
+
return cast("ValueT", _convert_to_list(value))
|
|
1027
|
+
|
|
1028
|
+
# Schema types (Pydantic, dataclass, msgspec, attrs, TypedDict)
|
|
1029
|
+
# Deferred after scalar checks to avoid overhead for common scalar queries
|
|
1030
|
+
schema_converter = _get_schema_converter(value_type)
|
|
1031
|
+
if schema_converter is not None:
|
|
1032
|
+
parsed = _ensure_json_parsed(value)
|
|
1033
|
+
return cast("ValueT", schema_converter(parsed, value_type))
|
|
1034
|
+
|
|
1035
|
+
# Fallback: try direct construction for custom types
|
|
1036
|
+
try:
|
|
1037
|
+
return value_type(value) # type: ignore[call-arg]
|
|
1038
|
+
except (TypeError, ValueError) as e:
|
|
1039
|
+
msg = f"Cannot convert {type(value).__name__} to {value_type.__name__}"
|
|
1040
|
+
raise TypeError(msg) from e
|