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,916 @@
|
|
|
1
|
+
"""Object storage backend using obstore.
|
|
2
|
+
|
|
3
|
+
Implements the ObjectStoreProtocol using obstore for S3, GCS, Azure,
|
|
4
|
+
and local file storage.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import fnmatch
|
|
8
|
+
import io
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
from collections.abc import AsyncIterator, Iterator
|
|
12
|
+
from functools import partial
|
|
13
|
+
from pathlib import Path, PurePosixPath
|
|
14
|
+
from typing import Any, Final, cast, overload
|
|
15
|
+
from urllib.parse import urlparse
|
|
16
|
+
|
|
17
|
+
from mypy_extensions import mypyc_attr
|
|
18
|
+
|
|
19
|
+
from sqlspec.exceptions import StorageOperationFailedError
|
|
20
|
+
from sqlspec.storage._utils import import_pyarrow, import_pyarrow_parquet, resolve_storage_path
|
|
21
|
+
from sqlspec.storage.errors import execute_sync_storage_operation
|
|
22
|
+
from sqlspec.typing import ArrowRecordBatch, ArrowTable
|
|
23
|
+
from sqlspec.utils.logging import get_logger, log_with_context
|
|
24
|
+
from sqlspec.utils.module_loader import ensure_obstore
|
|
25
|
+
|
|
26
|
+
__all__ = ("ObStoreBackend",)
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
DEFAULT_OPTIONS: Final[dict[str, Any]] = {"connect_timeout": "30s", "request_timeout": "60s"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _log_storage_event(
|
|
35
|
+
event: str,
|
|
36
|
+
*,
|
|
37
|
+
backend_type: str,
|
|
38
|
+
protocol: str,
|
|
39
|
+
operation: str | None = None,
|
|
40
|
+
mode: str | None = "sync",
|
|
41
|
+
path: str | None = None,
|
|
42
|
+
source_path: str | None = None,
|
|
43
|
+
destination_path: str | None = None,
|
|
44
|
+
count: int | None = None,
|
|
45
|
+
exists: bool | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
fields: dict[str, Any] = {
|
|
48
|
+
"backend_type": backend_type,
|
|
49
|
+
"protocol": protocol,
|
|
50
|
+
"mode": mode,
|
|
51
|
+
"path": path,
|
|
52
|
+
"source_path": source_path,
|
|
53
|
+
"destination_path": destination_path,
|
|
54
|
+
"count": count,
|
|
55
|
+
"exists": exists,
|
|
56
|
+
}
|
|
57
|
+
if operation is not None:
|
|
58
|
+
fields["operation"] = operation
|
|
59
|
+
log_with_context(logger, logging.DEBUG, event, **fields)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _read_obstore_bytes(store: Any, resolved_path: str) -> bytes:
|
|
63
|
+
"""Read bytes via obstore."""
|
|
64
|
+
result = store.get(resolved_path)
|
|
65
|
+
return cast("bytes", result.bytes().to_bytes())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@mypyc_attr(allow_interpreted_subclasses=True)
|
|
69
|
+
class ObStoreBackend:
|
|
70
|
+
"""Object storage backend using obstore.
|
|
71
|
+
|
|
72
|
+
Implements ObjectStoreProtocol using obstore's Rust-based implementation
|
|
73
|
+
for storage operations. Supports AWS S3, Google Cloud Storage, Azure Blob Storage,
|
|
74
|
+
local filesystem, and HTTP endpoints.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
__slots__ = (
|
|
78
|
+
"_is_local_store",
|
|
79
|
+
"_local_store_root",
|
|
80
|
+
"_path_cache",
|
|
81
|
+
"backend_type",
|
|
82
|
+
"base_path",
|
|
83
|
+
"protocol",
|
|
84
|
+
"store",
|
|
85
|
+
"store_options",
|
|
86
|
+
"store_uri",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def __init__(self, uri: str, **kwargs: Any) -> None:
|
|
90
|
+
"""Initialize obstore backend.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
uri: Storage URI (e.g., 's3://bucket', 'file:///path', 'gs://bucket')
|
|
94
|
+
**kwargs: Additional options including base_path and obstore configuration
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
ensure_obstore()
|
|
98
|
+
base_path = kwargs.pop("base_path", "")
|
|
99
|
+
|
|
100
|
+
self.store_uri = uri
|
|
101
|
+
self.base_path = base_path.rstrip("/") if base_path else ""
|
|
102
|
+
self.store_options = kwargs
|
|
103
|
+
self.store: Any
|
|
104
|
+
self._path_cache: dict[str, str] = {}
|
|
105
|
+
self._is_local_store = False
|
|
106
|
+
self._local_store_root = ""
|
|
107
|
+
self.protocol = uri.split("://", 1)[0] if "://" in uri else "file"
|
|
108
|
+
self.backend_type = "obstore"
|
|
109
|
+
try:
|
|
110
|
+
if uri.startswith("memory://"):
|
|
111
|
+
from obstore.store import MemoryStore
|
|
112
|
+
|
|
113
|
+
self.store = MemoryStore()
|
|
114
|
+
elif uri.startswith("file://"):
|
|
115
|
+
from obstore.store import LocalStore
|
|
116
|
+
|
|
117
|
+
parsed = urlparse(uri)
|
|
118
|
+
path_str = parsed.path or "/"
|
|
119
|
+
if parsed.fragment:
|
|
120
|
+
path_str = f"{path_str}#{parsed.fragment}"
|
|
121
|
+
path_obj = Path(path_str)
|
|
122
|
+
|
|
123
|
+
if path_obj.is_file():
|
|
124
|
+
path_str = str(path_obj.parent)
|
|
125
|
+
|
|
126
|
+
local_store_root = self.base_path or path_str
|
|
127
|
+
|
|
128
|
+
self._is_local_store = True
|
|
129
|
+
self._local_store_root = local_store_root
|
|
130
|
+
self.store = LocalStore(local_store_root, mkdir=True)
|
|
131
|
+
else:
|
|
132
|
+
from obstore.store import from_url
|
|
133
|
+
|
|
134
|
+
self.store = from_url(uri, **kwargs) # pyright: ignore[reportAttributeAccessIssue]
|
|
135
|
+
|
|
136
|
+
_log_storage_event(
|
|
137
|
+
"storage.backend.ready",
|
|
138
|
+
backend_type=self.backend_type,
|
|
139
|
+
protocol=self.protocol,
|
|
140
|
+
operation="init",
|
|
141
|
+
path=uri,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
except Exception as exc:
|
|
145
|
+
msg = f"Failed to initialize obstore backend for {uri}"
|
|
146
|
+
raise StorageOperationFailedError(msg) from exc
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def is_local_store(self) -> bool:
|
|
150
|
+
"""Return whether the backend uses local storage."""
|
|
151
|
+
return self._is_local_store
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_config(cls, config: "dict[str, Any]") -> "ObStoreBackend":
|
|
155
|
+
"""Create backend from configuration dictionary."""
|
|
156
|
+
store_uri = config["store_uri"]
|
|
157
|
+
base_path = config.get("base_path", "")
|
|
158
|
+
store_options = config.get("store_options", {})
|
|
159
|
+
|
|
160
|
+
kwargs = dict(store_options)
|
|
161
|
+
if base_path:
|
|
162
|
+
kwargs["base_path"] = base_path
|
|
163
|
+
|
|
164
|
+
return cls(uri=store_uri, **kwargs)
|
|
165
|
+
|
|
166
|
+
def _resolve_path(self, path: "str | Path") -> str:
|
|
167
|
+
if self._is_local_store:
|
|
168
|
+
return self._resolve_path_for_local_store(path)
|
|
169
|
+
return resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
170
|
+
|
|
171
|
+
def _resolve_path_for_local_store(self, path: "str | Path") -> str:
|
|
172
|
+
"""Resolve path for LocalStore which expects relative paths from its root."""
|
|
173
|
+
|
|
174
|
+
path_obj = Path(str(path))
|
|
175
|
+
|
|
176
|
+
if path_obj.is_absolute() and self._local_store_root:
|
|
177
|
+
try:
|
|
178
|
+
return str(path_obj.relative_to(self._local_store_root))
|
|
179
|
+
except ValueError:
|
|
180
|
+
return str(path).lstrip("/")
|
|
181
|
+
|
|
182
|
+
return str(path)
|
|
183
|
+
|
|
184
|
+
def read_bytes(self, path: "str | Path", **kwargs: Any) -> bytes: # pyright: ignore[reportUnusedParameter]
|
|
185
|
+
"""Read bytes using obstore."""
|
|
186
|
+
resolved_path = self._resolve_path(path)
|
|
187
|
+
|
|
188
|
+
result = execute_sync_storage_operation(
|
|
189
|
+
partial(_read_obstore_bytes, self.store, resolved_path),
|
|
190
|
+
backend=self.backend_type,
|
|
191
|
+
operation="read_bytes",
|
|
192
|
+
path=resolved_path,
|
|
193
|
+
)
|
|
194
|
+
_log_storage_event(
|
|
195
|
+
"storage.read",
|
|
196
|
+
backend_type=self.backend_type,
|
|
197
|
+
protocol=self.protocol,
|
|
198
|
+
operation="read_bytes",
|
|
199
|
+
path=resolved_path,
|
|
200
|
+
)
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
def write_bytes(self, path: "str | Path", data: bytes, **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
204
|
+
"""Write bytes using obstore."""
|
|
205
|
+
resolved_path = self._resolve_path(path)
|
|
206
|
+
|
|
207
|
+
execute_sync_storage_operation(
|
|
208
|
+
partial(self.store.put, resolved_path, data),
|
|
209
|
+
backend=self.backend_type,
|
|
210
|
+
operation="write_bytes",
|
|
211
|
+
path=resolved_path,
|
|
212
|
+
)
|
|
213
|
+
_log_storage_event(
|
|
214
|
+
"storage.write",
|
|
215
|
+
backend_type=self.backend_type,
|
|
216
|
+
protocol=self.protocol,
|
|
217
|
+
operation="write_bytes",
|
|
218
|
+
path=resolved_path,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def read_text(self, path: "str | Path", encoding: str = "utf-8", **kwargs: Any) -> str:
|
|
222
|
+
"""Read text using obstore."""
|
|
223
|
+
return self.read_bytes(path, **kwargs).decode(encoding)
|
|
224
|
+
|
|
225
|
+
def write_text(self, path: "str | Path", data: str, encoding: str = "utf-8", **kwargs: Any) -> None:
|
|
226
|
+
"""Write text using obstore."""
|
|
227
|
+
self.write_bytes(path, data.encode(encoding), **kwargs)
|
|
228
|
+
|
|
229
|
+
def list_objects(self, prefix: str = "", recursive: bool = True, **kwargs: Any) -> "list[str]": # pyright: ignore[reportUnusedParameter]
|
|
230
|
+
"""List objects using obstore."""
|
|
231
|
+
resolved_prefix = (
|
|
232
|
+
resolve_storage_path(prefix, self.base_path, self.protocol, strip_file_scheme=True)
|
|
233
|
+
if prefix
|
|
234
|
+
else self.base_path or ""
|
|
235
|
+
)
|
|
236
|
+
items = self.store.list_with_delimiter(resolved_prefix) if not recursive else self.store.list(resolved_prefix)
|
|
237
|
+
paths = sorted(item["path"] for batch in items for item in batch)
|
|
238
|
+
_log_storage_event(
|
|
239
|
+
"storage.list",
|
|
240
|
+
backend_type=self.backend_type,
|
|
241
|
+
protocol=self.protocol,
|
|
242
|
+
operation="list_objects",
|
|
243
|
+
path=resolved_prefix,
|
|
244
|
+
count=len(paths),
|
|
245
|
+
)
|
|
246
|
+
return paths
|
|
247
|
+
|
|
248
|
+
def exists(self, path: "str | Path", **kwargs: Any) -> bool: # pyright: ignore[reportUnusedParameter]
|
|
249
|
+
"""Check if object exists using obstore."""
|
|
250
|
+
try:
|
|
251
|
+
resolved_path = self._resolve_path(path)
|
|
252
|
+
self.store.head(resolved_path) # pyright: ignore[reportUnknownMemberType]
|
|
253
|
+
except Exception:
|
|
254
|
+
_log_storage_event(
|
|
255
|
+
"storage.read",
|
|
256
|
+
backend_type=self.backend_type,
|
|
257
|
+
protocol=self.protocol,
|
|
258
|
+
operation="exists",
|
|
259
|
+
path=str(path),
|
|
260
|
+
exists=False,
|
|
261
|
+
)
|
|
262
|
+
return False
|
|
263
|
+
_log_storage_event(
|
|
264
|
+
"storage.read",
|
|
265
|
+
backend_type=self.backend_type,
|
|
266
|
+
protocol=self.protocol,
|
|
267
|
+
operation="exists",
|
|
268
|
+
path=resolved_path,
|
|
269
|
+
exists=True,
|
|
270
|
+
)
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
def delete(self, path: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
274
|
+
"""Delete object using obstore."""
|
|
275
|
+
resolved_path = self._resolve_path(path)
|
|
276
|
+
execute_sync_storage_operation(
|
|
277
|
+
partial(self.store.delete, resolved_path), backend=self.backend_type, operation="delete", path=resolved_path
|
|
278
|
+
)
|
|
279
|
+
_log_storage_event(
|
|
280
|
+
"storage.write",
|
|
281
|
+
backend_type=self.backend_type,
|
|
282
|
+
protocol=self.protocol,
|
|
283
|
+
operation="delete",
|
|
284
|
+
path=resolved_path,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def copy(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
288
|
+
"""Copy object using obstore."""
|
|
289
|
+
source_path = self._resolve_path(source)
|
|
290
|
+
dest_path = self._resolve_path(destination)
|
|
291
|
+
execute_sync_storage_operation(
|
|
292
|
+
partial(self.store.copy, source_path, dest_path),
|
|
293
|
+
backend=self.backend_type,
|
|
294
|
+
operation="copy",
|
|
295
|
+
path=f"{source_path}->{dest_path}",
|
|
296
|
+
)
|
|
297
|
+
_log_storage_event(
|
|
298
|
+
"storage.write",
|
|
299
|
+
backend_type=self.backend_type,
|
|
300
|
+
protocol=self.protocol,
|
|
301
|
+
operation="copy",
|
|
302
|
+
source_path=source_path,
|
|
303
|
+
destination_path=dest_path,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def move(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
307
|
+
"""Move object using obstore."""
|
|
308
|
+
source_path = self._resolve_path(source)
|
|
309
|
+
dest_path = self._resolve_path(destination)
|
|
310
|
+
execute_sync_storage_operation(
|
|
311
|
+
partial(self.store.rename, source_path, dest_path),
|
|
312
|
+
backend=self.backend_type,
|
|
313
|
+
operation="move",
|
|
314
|
+
path=f"{source_path}->{dest_path}",
|
|
315
|
+
)
|
|
316
|
+
_log_storage_event(
|
|
317
|
+
"storage.write",
|
|
318
|
+
backend_type=self.backend_type,
|
|
319
|
+
protocol=self.protocol,
|
|
320
|
+
operation="move",
|
|
321
|
+
source_path=source_path,
|
|
322
|
+
destination_path=dest_path,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def glob(self, pattern: str, **kwargs: Any) -> "list[str]":
|
|
326
|
+
"""Find objects matching pattern.
|
|
327
|
+
|
|
328
|
+
Lists all objects and filters them client-side using the pattern.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
resolved_pattern = resolve_storage_path(pattern, self.base_path, self.protocol, strip_file_scheme=True)
|
|
332
|
+
all_objects = self.list_objects(recursive=True, **kwargs)
|
|
333
|
+
|
|
334
|
+
if "**" in pattern:
|
|
335
|
+
matching_objects = []
|
|
336
|
+
|
|
337
|
+
if pattern.startswith("**/"):
|
|
338
|
+
suffix_pattern = pattern[3:]
|
|
339
|
+
|
|
340
|
+
for obj in all_objects:
|
|
341
|
+
obj_path = PurePosixPath(obj)
|
|
342
|
+
if obj_path.match(resolved_pattern) or obj_path.match(suffix_pattern):
|
|
343
|
+
matching_objects.append(obj)
|
|
344
|
+
else:
|
|
345
|
+
for obj in all_objects:
|
|
346
|
+
obj_path = PurePosixPath(obj)
|
|
347
|
+
if obj_path.match(resolved_pattern):
|
|
348
|
+
matching_objects.append(obj)
|
|
349
|
+
results = matching_objects
|
|
350
|
+
else:
|
|
351
|
+
results = [obj for obj in all_objects if fnmatch.fnmatch(obj, resolved_pattern)]
|
|
352
|
+
_log_storage_event(
|
|
353
|
+
"storage.list",
|
|
354
|
+
backend_type=self.backend_type,
|
|
355
|
+
protocol=self.protocol,
|
|
356
|
+
operation="glob",
|
|
357
|
+
path=resolved_pattern,
|
|
358
|
+
count=len(results),
|
|
359
|
+
)
|
|
360
|
+
return results
|
|
361
|
+
|
|
362
|
+
def get_metadata(self, path: "str | Path", **kwargs: Any) -> "dict[str, object]": # pyright: ignore[reportUnusedParameter]
|
|
363
|
+
"""Get object metadata using obstore."""
|
|
364
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
metadata = self.store.head(resolved_path)
|
|
368
|
+
except Exception:
|
|
369
|
+
return {"path": resolved_path, "exists": False}
|
|
370
|
+
else:
|
|
371
|
+
if isinstance(metadata, dict):
|
|
372
|
+
result = {
|
|
373
|
+
"path": resolved_path,
|
|
374
|
+
"exists": True,
|
|
375
|
+
"size": metadata.get("size"),
|
|
376
|
+
"last_modified": metadata.get("last_modified"),
|
|
377
|
+
"e_tag": metadata.get("e_tag"),
|
|
378
|
+
"version": metadata.get("version"),
|
|
379
|
+
}
|
|
380
|
+
if metadata.get("metadata"):
|
|
381
|
+
result["custom_metadata"] = metadata["metadata"]
|
|
382
|
+
return result
|
|
383
|
+
|
|
384
|
+
result = {
|
|
385
|
+
"path": resolved_path,
|
|
386
|
+
"exists": True,
|
|
387
|
+
"size": metadata.size,
|
|
388
|
+
"last_modified": metadata.last_modified,
|
|
389
|
+
"e_tag": metadata.e_tag,
|
|
390
|
+
"version": metadata.version,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if metadata.metadata:
|
|
394
|
+
result["custom_metadata"] = metadata.metadata
|
|
395
|
+
|
|
396
|
+
return result
|
|
397
|
+
|
|
398
|
+
def is_object(self, path: "str | Path") -> bool:
|
|
399
|
+
"""Check if path is an object using obstore."""
|
|
400
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
401
|
+
return self.exists(path) and not resolved_path.endswith("/")
|
|
402
|
+
|
|
403
|
+
def is_path(self, path: "str | Path") -> bool:
|
|
404
|
+
"""Check if path is a prefix/directory using obstore."""
|
|
405
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
406
|
+
|
|
407
|
+
if resolved_path.endswith("/"):
|
|
408
|
+
return True
|
|
409
|
+
|
|
410
|
+
try:
|
|
411
|
+
objects = self.list_objects(prefix=str(path), recursive=True)
|
|
412
|
+
return len(objects) > 0
|
|
413
|
+
except Exception:
|
|
414
|
+
return False
|
|
415
|
+
|
|
416
|
+
def read_arrow(self, path: "str | Path", **kwargs: Any) -> ArrowTable:
|
|
417
|
+
"""Read Arrow table using obstore."""
|
|
418
|
+
pq = import_pyarrow_parquet()
|
|
419
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
420
|
+
data = self.read_bytes(resolved_path)
|
|
421
|
+
result = cast(
|
|
422
|
+
"ArrowTable",
|
|
423
|
+
execute_sync_storage_operation(
|
|
424
|
+
partial(pq.read_table, io.BytesIO(data), **kwargs),
|
|
425
|
+
backend=self.backend_type,
|
|
426
|
+
operation="read_arrow",
|
|
427
|
+
path=resolved_path,
|
|
428
|
+
),
|
|
429
|
+
)
|
|
430
|
+
_log_storage_event(
|
|
431
|
+
"storage.read",
|
|
432
|
+
backend_type=self.backend_type,
|
|
433
|
+
protocol=self.protocol,
|
|
434
|
+
operation="read_arrow",
|
|
435
|
+
path=resolved_path,
|
|
436
|
+
)
|
|
437
|
+
return result
|
|
438
|
+
|
|
439
|
+
def write_arrow(self, path: "str | Path", table: ArrowTable, **kwargs: Any) -> None:
|
|
440
|
+
"""Write Arrow table using obstore."""
|
|
441
|
+
pa = import_pyarrow()
|
|
442
|
+
pq = import_pyarrow_parquet()
|
|
443
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
444
|
+
|
|
445
|
+
schema = table.schema
|
|
446
|
+
if any(str(f.type).startswith("decimal64") for f in schema):
|
|
447
|
+
new_fields = []
|
|
448
|
+
for field in schema:
|
|
449
|
+
if str(field.type).startswith("decimal64"):
|
|
450
|
+
match = re.match(r"decimal64\((\d+),\s*(\d+)\)", str(field.type))
|
|
451
|
+
if match:
|
|
452
|
+
precision, scale = int(match.group(1)), int(match.group(2))
|
|
453
|
+
new_fields.append(pa.field(field.name, pa.decimal128(precision, scale)))
|
|
454
|
+
else:
|
|
455
|
+
new_fields.append(field)
|
|
456
|
+
else:
|
|
457
|
+
new_fields.append(field)
|
|
458
|
+
table = table.cast(pa.schema(new_fields))
|
|
459
|
+
|
|
460
|
+
buffer = io.BytesIO()
|
|
461
|
+
execute_sync_storage_operation(
|
|
462
|
+
partial(pq.write_table, table, buffer, **kwargs),
|
|
463
|
+
backend=self.backend_type,
|
|
464
|
+
operation="write_arrow",
|
|
465
|
+
path=resolved_path,
|
|
466
|
+
)
|
|
467
|
+
buffer.seek(0)
|
|
468
|
+
self.write_bytes(resolved_path, buffer.read())
|
|
469
|
+
_log_storage_event(
|
|
470
|
+
"storage.write",
|
|
471
|
+
backend_type=self.backend_type,
|
|
472
|
+
protocol=self.protocol,
|
|
473
|
+
operation="write_arrow",
|
|
474
|
+
path=resolved_path,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
def stream_read(self, path: "str | Path", chunk_size: "int | None" = None, **kwargs: Any) -> Iterator[bytes]:
|
|
478
|
+
"""Stream bytes using obstore.
|
|
479
|
+
|
|
480
|
+
Note:
|
|
481
|
+
For remote backends, this currently performs a full read and yields chunks
|
|
482
|
+
as obstore's sync client doesn't expose a streaming iterator.
|
|
483
|
+
Use stream_read_async for true streaming.
|
|
484
|
+
"""
|
|
485
|
+
resolved_path = self._resolve_path(path)
|
|
486
|
+
data = self.read_bytes(resolved_path)
|
|
487
|
+
|
|
488
|
+
if chunk_size:
|
|
489
|
+
for i in range(0, len(data), chunk_size):
|
|
490
|
+
yield data[i : i + chunk_size]
|
|
491
|
+
else:
|
|
492
|
+
yield data
|
|
493
|
+
|
|
494
|
+
def stream_arrow(self, pattern: str, **kwargs: Any) -> Iterator[ArrowRecordBatch]:
|
|
495
|
+
"""Stream Arrow record batches.
|
|
496
|
+
|
|
497
|
+
Yields:
|
|
498
|
+
Iterator of Arrow record batches from matching objects.
|
|
499
|
+
"""
|
|
500
|
+
pq = import_pyarrow_parquet()
|
|
501
|
+
for obj_path in self.glob(pattern, **kwargs):
|
|
502
|
+
resolved_path = resolve_storage_path(obj_path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
503
|
+
result = execute_sync_storage_operation(
|
|
504
|
+
partial(self.store.get, resolved_path),
|
|
505
|
+
backend=self.backend_type,
|
|
506
|
+
operation="stream_read",
|
|
507
|
+
path=resolved_path,
|
|
508
|
+
)
|
|
509
|
+
bytes_obj = result.bytes()
|
|
510
|
+
data = bytes_obj.to_bytes()
|
|
511
|
+
buffer = io.BytesIO(data)
|
|
512
|
+
parquet_file = execute_sync_storage_operation(
|
|
513
|
+
partial(pq.ParquetFile, buffer), backend=self.backend_type, operation="stream_arrow", path=resolved_path
|
|
514
|
+
)
|
|
515
|
+
yield from parquet_file.iter_batches()
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def supports_signing(self) -> bool:
|
|
519
|
+
"""Whether this backend supports URL signing.
|
|
520
|
+
|
|
521
|
+
Only S3, GCS, and Azure backends support pre-signed URLs.
|
|
522
|
+
Local file storage does not support URL signing.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
True if the protocol supports signing, False otherwise.
|
|
526
|
+
"""
|
|
527
|
+
signable_protocols = {"s3", "gs", "gcs", "az", "azure"}
|
|
528
|
+
return self.protocol in signable_protocols
|
|
529
|
+
|
|
530
|
+
@overload
|
|
531
|
+
def sign_sync(self, paths: str, expires_in: int = 3600, for_upload: bool = False) -> str: ...
|
|
532
|
+
|
|
533
|
+
@overload
|
|
534
|
+
def sign_sync(self, paths: "list[str]", expires_in: int = 3600, for_upload: bool = False) -> "list[str]": ...
|
|
535
|
+
|
|
536
|
+
def sign_sync(
|
|
537
|
+
self, paths: "str | list[str]", expires_in: int = 3600, for_upload: bool = False
|
|
538
|
+
) -> "str | list[str]":
|
|
539
|
+
"""Generate signed URL(s) for the object(s).
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
paths: Single object path or list of paths to sign.
|
|
543
|
+
expires_in: URL expiration time in seconds (default: 3600, max: 604800 = 7 days).
|
|
544
|
+
for_upload: Whether the URL is for upload (PUT) vs download (GET).
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
Single signed URL string if paths is a string, or list of signed URLs
|
|
548
|
+
if paths is a list. Preserves input type for convenience.
|
|
549
|
+
|
|
550
|
+
Raises:
|
|
551
|
+
NotImplementedError: If the backend protocol does not support signing.
|
|
552
|
+
ValueError: If expires_in exceeds maximum (604800 seconds).
|
|
553
|
+
"""
|
|
554
|
+
import obstore as obs
|
|
555
|
+
|
|
556
|
+
signable_protocols = {"s3", "gs", "gcs", "az", "azure"}
|
|
557
|
+
if self.protocol not in signable_protocols:
|
|
558
|
+
msg = (
|
|
559
|
+
f"URL signing is not supported for protocol '{self.protocol}'. "
|
|
560
|
+
f"Only S3, GCS, and Azure backends support pre-signed URLs."
|
|
561
|
+
)
|
|
562
|
+
raise NotImplementedError(msg)
|
|
563
|
+
|
|
564
|
+
max_expires = 604800 # 7 days max per obstore/object_store limits
|
|
565
|
+
if expires_in > max_expires:
|
|
566
|
+
msg = f"expires_in cannot exceed {max_expires} seconds (7 days), got {expires_in}"
|
|
567
|
+
raise ValueError(msg)
|
|
568
|
+
|
|
569
|
+
from datetime import timedelta
|
|
570
|
+
|
|
571
|
+
method = "PUT" if for_upload else "GET"
|
|
572
|
+
expires_delta = timedelta(seconds=expires_in)
|
|
573
|
+
|
|
574
|
+
if isinstance(paths, str):
|
|
575
|
+
path_list = [paths]
|
|
576
|
+
is_single = True
|
|
577
|
+
else:
|
|
578
|
+
path_list = list(paths)
|
|
579
|
+
is_single = False
|
|
580
|
+
|
|
581
|
+
resolved_paths = [
|
|
582
|
+
resolve_storage_path(p, self.base_path, self.protocol, strip_file_scheme=True) for p in path_list
|
|
583
|
+
]
|
|
584
|
+
|
|
585
|
+
try:
|
|
586
|
+
signed_urls: list[str] = obs.sign(self.store, method, resolved_paths, expires_delta) # type: ignore[call-overload]
|
|
587
|
+
return signed_urls[0] if is_single else signed_urls
|
|
588
|
+
except Exception as exc:
|
|
589
|
+
msg = f"Failed to generate signed URL(s) for {resolved_paths}"
|
|
590
|
+
raise StorageOperationFailedError(msg) from exc
|
|
591
|
+
|
|
592
|
+
async def read_bytes_async(self, path: "str | Path", **kwargs: Any) -> bytes: # pyright: ignore[reportUnusedParameter]
|
|
593
|
+
"""Read bytes from storage asynchronously."""
|
|
594
|
+
if self._is_local_store:
|
|
595
|
+
resolved_path = self._resolve_path_for_local_store(path)
|
|
596
|
+
else:
|
|
597
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
598
|
+
|
|
599
|
+
result = await self.store.get_async(resolved_path)
|
|
600
|
+
bytes_obj = await result.bytes_async() # pyright: ignore[reportAttributeAccessIssue]
|
|
601
|
+
data = cast("bytes", bytes_obj.to_bytes())
|
|
602
|
+
_log_storage_event(
|
|
603
|
+
"storage.read",
|
|
604
|
+
backend_type=self.backend_type,
|
|
605
|
+
protocol=self.protocol,
|
|
606
|
+
operation="read_bytes",
|
|
607
|
+
mode="async",
|
|
608
|
+
path=resolved_path,
|
|
609
|
+
)
|
|
610
|
+
return data
|
|
611
|
+
|
|
612
|
+
async def write_bytes_async(self, path: "str | Path", data: bytes, **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
613
|
+
"""Write bytes to storage asynchronously."""
|
|
614
|
+
if self._is_local_store:
|
|
615
|
+
resolved_path = self._resolve_path_for_local_store(path)
|
|
616
|
+
else:
|
|
617
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
618
|
+
|
|
619
|
+
await self.store.put_async(resolved_path, data)
|
|
620
|
+
_log_storage_event(
|
|
621
|
+
"storage.write",
|
|
622
|
+
backend_type=self.backend_type,
|
|
623
|
+
protocol=self.protocol,
|
|
624
|
+
operation="write_bytes",
|
|
625
|
+
mode="async",
|
|
626
|
+
path=resolved_path,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
async def stream_read_async(
|
|
630
|
+
self, path: "str | Path", chunk_size: "int | None" = None, **kwargs: Any
|
|
631
|
+
) -> AsyncIterator[bytes]:
|
|
632
|
+
"""Stream bytes from storage asynchronously."""
|
|
633
|
+
if self._is_local_store:
|
|
634
|
+
resolved_path = self._resolve_path_for_local_store(path)
|
|
635
|
+
else:
|
|
636
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
637
|
+
|
|
638
|
+
result = await self.store.get_async(resolved_path)
|
|
639
|
+
stream = result.stream()
|
|
640
|
+
|
|
641
|
+
async def _generator() -> AsyncIterator[bytes]:
|
|
642
|
+
async for chunk in stream:
|
|
643
|
+
yield bytes(chunk)
|
|
644
|
+
|
|
645
|
+
_log_storage_event(
|
|
646
|
+
"storage.read",
|
|
647
|
+
backend_type=self.backend_type,
|
|
648
|
+
protocol=self.protocol,
|
|
649
|
+
operation="stream_read",
|
|
650
|
+
mode="async",
|
|
651
|
+
path=resolved_path,
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
return _generator()
|
|
655
|
+
|
|
656
|
+
async def list_objects_async(self, prefix: str = "", recursive: bool = True, **kwargs: Any) -> "list[str]": # pyright: ignore[reportUnusedParameter]
|
|
657
|
+
"""List objects in storage asynchronously."""
|
|
658
|
+
resolved_prefix = (
|
|
659
|
+
resolve_storage_path(prefix, self.base_path, self.protocol, strip_file_scheme=True)
|
|
660
|
+
if prefix
|
|
661
|
+
else self.base_path or ""
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
objects: list[str] = []
|
|
665
|
+
async for batch in self.store.list_async(resolved_prefix): # pyright: ignore[reportAttributeAccessIssue]
|
|
666
|
+
objects.extend(item["path"] for item in batch)
|
|
667
|
+
|
|
668
|
+
if not recursive and resolved_prefix:
|
|
669
|
+
base_depth = resolved_prefix.count("/")
|
|
670
|
+
objects = [obj for obj in objects if obj.count("/") <= base_depth + 1]
|
|
671
|
+
|
|
672
|
+
results = sorted(objects)
|
|
673
|
+
_log_storage_event(
|
|
674
|
+
"storage.list",
|
|
675
|
+
backend_type=self.backend_type,
|
|
676
|
+
protocol=self.protocol,
|
|
677
|
+
operation="list_objects",
|
|
678
|
+
mode="async",
|
|
679
|
+
path=resolved_prefix,
|
|
680
|
+
count=len(results),
|
|
681
|
+
)
|
|
682
|
+
return results
|
|
683
|
+
|
|
684
|
+
async def read_text_async(self, path: "str | Path", encoding: str = "utf-8", **kwargs: Any) -> str:
|
|
685
|
+
"""Read text from storage asynchronously."""
|
|
686
|
+
data = await self.read_bytes_async(path, **kwargs)
|
|
687
|
+
return data.decode(encoding)
|
|
688
|
+
|
|
689
|
+
async def write_text_async(self, path: "str | Path", data: str, encoding: str = "utf-8", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
690
|
+
"""Write text to storage asynchronously."""
|
|
691
|
+
encoded_data = data.encode(encoding)
|
|
692
|
+
await self.write_bytes_async(path, encoded_data, **kwargs)
|
|
693
|
+
|
|
694
|
+
async def exists_async(self, path: "str | Path", **kwargs: Any) -> bool: # pyright: ignore[reportUnusedParameter]
|
|
695
|
+
"""Check if object exists in storage asynchronously."""
|
|
696
|
+
if self._is_local_store:
|
|
697
|
+
resolved_path = self._resolve_path_for_local_store(path)
|
|
698
|
+
else:
|
|
699
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
700
|
+
|
|
701
|
+
try:
|
|
702
|
+
await self.store.head_async(resolved_path)
|
|
703
|
+
except Exception:
|
|
704
|
+
_log_storage_event(
|
|
705
|
+
"storage.read",
|
|
706
|
+
backend_type=self.backend_type,
|
|
707
|
+
protocol=self.protocol,
|
|
708
|
+
operation="exists",
|
|
709
|
+
mode="async",
|
|
710
|
+
path=str(path),
|
|
711
|
+
exists=False,
|
|
712
|
+
)
|
|
713
|
+
return False
|
|
714
|
+
_log_storage_event(
|
|
715
|
+
"storage.read",
|
|
716
|
+
backend_type=self.backend_type,
|
|
717
|
+
protocol=self.protocol,
|
|
718
|
+
operation="exists",
|
|
719
|
+
mode="async",
|
|
720
|
+
path=resolved_path,
|
|
721
|
+
exists=True,
|
|
722
|
+
)
|
|
723
|
+
return True
|
|
724
|
+
|
|
725
|
+
async def delete_async(self, path: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
726
|
+
"""Delete object from storage asynchronously."""
|
|
727
|
+
if self._is_local_store:
|
|
728
|
+
resolved_path = self._resolve_path_for_local_store(path)
|
|
729
|
+
else:
|
|
730
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
731
|
+
|
|
732
|
+
await self.store.delete_async(resolved_path)
|
|
733
|
+
_log_storage_event(
|
|
734
|
+
"storage.write",
|
|
735
|
+
backend_type=self.backend_type,
|
|
736
|
+
protocol=self.protocol,
|
|
737
|
+
operation="delete",
|
|
738
|
+
mode="async",
|
|
739
|
+
path=resolved_path,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
async def copy_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
743
|
+
"""Copy object in storage asynchronously."""
|
|
744
|
+
if self._is_local_store:
|
|
745
|
+
source_path = self._resolve_path_for_local_store(source)
|
|
746
|
+
dest_path = self._resolve_path_for_local_store(destination)
|
|
747
|
+
else:
|
|
748
|
+
source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=True)
|
|
749
|
+
dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=True)
|
|
750
|
+
|
|
751
|
+
await self.store.copy_async(source_path, dest_path)
|
|
752
|
+
_log_storage_event(
|
|
753
|
+
"storage.write",
|
|
754
|
+
backend_type=self.backend_type,
|
|
755
|
+
protocol=self.protocol,
|
|
756
|
+
operation="copy",
|
|
757
|
+
mode="async",
|
|
758
|
+
source_path=source_path,
|
|
759
|
+
destination_path=dest_path,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
async def move_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter]
|
|
763
|
+
"""Move object in storage asynchronously."""
|
|
764
|
+
if self._is_local_store:
|
|
765
|
+
source_path = self._resolve_path_for_local_store(source)
|
|
766
|
+
dest_path = self._resolve_path_for_local_store(destination)
|
|
767
|
+
else:
|
|
768
|
+
source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=True)
|
|
769
|
+
dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=True)
|
|
770
|
+
|
|
771
|
+
await self.store.rename_async(source_path, dest_path)
|
|
772
|
+
_log_storage_event(
|
|
773
|
+
"storage.write",
|
|
774
|
+
backend_type=self.backend_type,
|
|
775
|
+
protocol=self.protocol,
|
|
776
|
+
operation="move",
|
|
777
|
+
mode="async",
|
|
778
|
+
source_path=source_path,
|
|
779
|
+
destination_path=dest_path,
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
async def get_metadata_async(self, path: "str | Path", **kwargs: Any) -> "dict[str, object]": # pyright: ignore[reportUnusedParameter]
|
|
783
|
+
"""Get object metadata from storage asynchronously."""
|
|
784
|
+
if self._is_local_store:
|
|
785
|
+
resolved_path = self._resolve_path_for_local_store(path)
|
|
786
|
+
else:
|
|
787
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
788
|
+
|
|
789
|
+
result: dict[str, object] = {}
|
|
790
|
+
try:
|
|
791
|
+
metadata = await self.store.head_async(resolved_path)
|
|
792
|
+
result.update({
|
|
793
|
+
"path": resolved_path,
|
|
794
|
+
"exists": True,
|
|
795
|
+
"size": metadata.get("size"),
|
|
796
|
+
"last_modified": metadata.get("last_modified"),
|
|
797
|
+
"e_tag": metadata.get("e_tag"),
|
|
798
|
+
"version": metadata.get("version"),
|
|
799
|
+
})
|
|
800
|
+
if metadata.get("metadata"):
|
|
801
|
+
result["custom_metadata"] = metadata["metadata"]
|
|
802
|
+
|
|
803
|
+
except Exception:
|
|
804
|
+
return {"path": resolved_path, "exists": False}
|
|
805
|
+
else:
|
|
806
|
+
return result
|
|
807
|
+
|
|
808
|
+
async def read_arrow_async(self, path: "str | Path", **kwargs: Any) -> ArrowTable:
|
|
809
|
+
"""Read Arrow table from storage asynchronously."""
|
|
810
|
+
pq = import_pyarrow_parquet()
|
|
811
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
812
|
+
data = await self.read_bytes_async(resolved_path)
|
|
813
|
+
result = cast("ArrowTable", pq.read_table(io.BytesIO(data), **kwargs))
|
|
814
|
+
_log_storage_event(
|
|
815
|
+
"storage.read",
|
|
816
|
+
backend_type=self.backend_type,
|
|
817
|
+
protocol=self.protocol,
|
|
818
|
+
operation="read_arrow",
|
|
819
|
+
mode="async",
|
|
820
|
+
path=resolved_path,
|
|
821
|
+
)
|
|
822
|
+
return result
|
|
823
|
+
|
|
824
|
+
async def write_arrow_async(self, path: "str | Path", table: ArrowTable, **kwargs: Any) -> None:
|
|
825
|
+
"""Write Arrow table to storage asynchronously."""
|
|
826
|
+
pq = import_pyarrow_parquet()
|
|
827
|
+
resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True)
|
|
828
|
+
buffer = io.BytesIO()
|
|
829
|
+
pq.write_table(table, buffer, **kwargs)
|
|
830
|
+
buffer.seek(0)
|
|
831
|
+
await self.write_bytes_async(resolved_path, buffer.read())
|
|
832
|
+
_log_storage_event(
|
|
833
|
+
"storage.write",
|
|
834
|
+
backend_type=self.backend_type,
|
|
835
|
+
protocol=self.protocol,
|
|
836
|
+
operation="write_arrow",
|
|
837
|
+
mode="async",
|
|
838
|
+
path=resolved_path,
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
def stream_arrow_async(self, pattern: str, **kwargs: Any) -> AsyncIterator["ArrowRecordBatch"]:
|
|
842
|
+
"""Stream Arrow record batches from storage asynchronously.
|
|
843
|
+
|
|
844
|
+
Args:
|
|
845
|
+
pattern: Glob pattern to match files.
|
|
846
|
+
**kwargs: Additional arguments passed to stream_arrow().
|
|
847
|
+
|
|
848
|
+
Returns:
|
|
849
|
+
AsyncIterator yielding Arrow record batches.
|
|
850
|
+
"""
|
|
851
|
+
from sqlspec.storage.backends.base import AsyncArrowBatchIterator
|
|
852
|
+
|
|
853
|
+
resolved_pattern = resolve_storage_path(pattern, self.base_path, self.protocol, strip_file_scheme=True)
|
|
854
|
+
return AsyncArrowBatchIterator(self.stream_arrow(resolved_pattern, **kwargs))
|
|
855
|
+
|
|
856
|
+
@overload
|
|
857
|
+
async def sign_async(self, paths: str, expires_in: int = 3600, for_upload: bool = False) -> str: ...
|
|
858
|
+
|
|
859
|
+
@overload
|
|
860
|
+
async def sign_async(self, paths: "list[str]", expires_in: int = 3600, for_upload: bool = False) -> "list[str]": ...
|
|
861
|
+
|
|
862
|
+
async def sign_async(
|
|
863
|
+
self, paths: "str | list[str]", expires_in: int = 3600, for_upload: bool = False
|
|
864
|
+
) -> "str | list[str]":
|
|
865
|
+
"""Generate signed URL(s) asynchronously.
|
|
866
|
+
|
|
867
|
+
Args:
|
|
868
|
+
paths: Single object path or list of paths to sign.
|
|
869
|
+
expires_in: URL expiration time in seconds (default: 3600, max: 604800 = 7 days).
|
|
870
|
+
for_upload: Whether the URL is for upload (PUT) vs download (GET).
|
|
871
|
+
|
|
872
|
+
Returns:
|
|
873
|
+
Single signed URL string if paths is a string, or list of signed URLs
|
|
874
|
+
if paths is a list. Preserves input type for convenience.
|
|
875
|
+
|
|
876
|
+
Raises:
|
|
877
|
+
NotImplementedError: If the backend protocol does not support signing.
|
|
878
|
+
ValueError: If expires_in exceeds maximum (604800 seconds).
|
|
879
|
+
"""
|
|
880
|
+
import obstore as obs
|
|
881
|
+
|
|
882
|
+
signable_protocols = {"s3", "gs", "gcs", "az", "azure"}
|
|
883
|
+
if self.protocol not in signable_protocols:
|
|
884
|
+
msg = (
|
|
885
|
+
f"URL signing is not supported for protocol '{self.protocol}'. "
|
|
886
|
+
f"Only S3, GCS, and Azure backends support pre-signed URLs."
|
|
887
|
+
)
|
|
888
|
+
raise NotImplementedError(msg)
|
|
889
|
+
|
|
890
|
+
max_expires = 604800 # 7 days max per obstore/object_store limits
|
|
891
|
+
if expires_in > max_expires:
|
|
892
|
+
msg = f"expires_in cannot exceed {max_expires} seconds (7 days), got {expires_in}"
|
|
893
|
+
raise ValueError(msg)
|
|
894
|
+
|
|
895
|
+
from datetime import timedelta
|
|
896
|
+
|
|
897
|
+
method = "PUT" if for_upload else "GET"
|
|
898
|
+
expires_delta = timedelta(seconds=expires_in)
|
|
899
|
+
|
|
900
|
+
if isinstance(paths, str):
|
|
901
|
+
path_list = [paths]
|
|
902
|
+
is_single = True
|
|
903
|
+
else:
|
|
904
|
+
path_list = list(paths)
|
|
905
|
+
is_single = False
|
|
906
|
+
|
|
907
|
+
resolved_paths = [
|
|
908
|
+
resolve_storage_path(p, self.base_path, self.protocol, strip_file_scheme=True) for p in path_list
|
|
909
|
+
]
|
|
910
|
+
|
|
911
|
+
try:
|
|
912
|
+
signed_urls: list[str] = await obs.sign_async(self.store, method, resolved_paths, expires_delta) # type: ignore[call-overload]
|
|
913
|
+
return signed_urls[0] if is_single else signed_urls
|
|
914
|
+
except Exception as exc:
|
|
915
|
+
msg = f"Failed to generate signed URL(s) for {resolved_paths}"
|
|
916
|
+
raise StorageOperationFailedError(msg) from exc
|