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.
Files changed (531) hide show
  1. ac8f31065839703b4e70__mypyc.cpython-310-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +140 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +315 -0
  6. sqlspec/_typing.py +700 -0
  7. sqlspec/adapters/__init__.py +0 -0
  8. sqlspec/adapters/adbc/__init__.py +5 -0
  9. sqlspec/adapters/adbc/_typing.py +82 -0
  10. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  11. sqlspec/adapters/adbc/adk/store.py +1273 -0
  12. sqlspec/adapters/adbc/config.py +295 -0
  13. sqlspec/adapters/adbc/core.cpython-310-aarch64-linux-gnu.so +0 -0
  14. sqlspec/adapters/adbc/core.py +735 -0
  15. sqlspec/adapters/adbc/data_dictionary.py +334 -0
  16. sqlspec/adapters/adbc/driver.py +529 -0
  17. sqlspec/adapters/adbc/events/__init__.py +5 -0
  18. sqlspec/adapters/adbc/events/store.py +285 -0
  19. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  20. sqlspec/adapters/adbc/litestar/store.py +502 -0
  21. sqlspec/adapters/adbc/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  22. sqlspec/adapters/adbc/type_converter.py +140 -0
  23. sqlspec/adapters/aiosqlite/__init__.py +25 -0
  24. sqlspec/adapters/aiosqlite/_typing.py +82 -0
  25. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  26. sqlspec/adapters/aiosqlite/adk/store.py +818 -0
  27. sqlspec/adapters/aiosqlite/config.py +334 -0
  28. sqlspec/adapters/aiosqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
  29. sqlspec/adapters/aiosqlite/core.py +315 -0
  30. sqlspec/adapters/aiosqlite/data_dictionary.py +208 -0
  31. sqlspec/adapters/aiosqlite/driver.py +313 -0
  32. sqlspec/adapters/aiosqlite/events/__init__.py +5 -0
  33. sqlspec/adapters/aiosqlite/events/store.py +20 -0
  34. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  35. sqlspec/adapters/aiosqlite/litestar/store.py +279 -0
  36. sqlspec/adapters/aiosqlite/pool.py +533 -0
  37. sqlspec/adapters/asyncmy/__init__.py +21 -0
  38. sqlspec/adapters/asyncmy/_typing.py +87 -0
  39. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  40. sqlspec/adapters/asyncmy/adk/store.py +703 -0
  41. sqlspec/adapters/asyncmy/config.py +302 -0
  42. sqlspec/adapters/asyncmy/core.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sqlspec/adapters/asyncmy/core.py +360 -0
  44. sqlspec/adapters/asyncmy/data_dictionary.py +124 -0
  45. sqlspec/adapters/asyncmy/driver.py +383 -0
  46. sqlspec/adapters/asyncmy/events/__init__.py +5 -0
  47. sqlspec/adapters/asyncmy/events/store.py +104 -0
  48. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  49. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  50. sqlspec/adapters/asyncpg/__init__.py +19 -0
  51. sqlspec/adapters/asyncpg/_typing.py +88 -0
  52. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  53. sqlspec/adapters/asyncpg/adk/store.py +748 -0
  54. sqlspec/adapters/asyncpg/config.py +569 -0
  55. sqlspec/adapters/asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  56. sqlspec/adapters/asyncpg/core.py +367 -0
  57. sqlspec/adapters/asyncpg/data_dictionary.py +162 -0
  58. sqlspec/adapters/asyncpg/driver.py +487 -0
  59. sqlspec/adapters/asyncpg/events/__init__.py +6 -0
  60. sqlspec/adapters/asyncpg/events/backend.py +286 -0
  61. sqlspec/adapters/asyncpg/events/store.py +40 -0
  62. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  63. sqlspec/adapters/asyncpg/litestar/store.py +251 -0
  64. sqlspec/adapters/bigquery/__init__.py +14 -0
  65. sqlspec/adapters/bigquery/_typing.py +86 -0
  66. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  67. sqlspec/adapters/bigquery/adk/store.py +827 -0
  68. sqlspec/adapters/bigquery/config.py +353 -0
  69. sqlspec/adapters/bigquery/core.cpython-310-aarch64-linux-gnu.so +0 -0
  70. sqlspec/adapters/bigquery/core.py +715 -0
  71. sqlspec/adapters/bigquery/data_dictionary.py +128 -0
  72. sqlspec/adapters/bigquery/driver.py +548 -0
  73. sqlspec/adapters/bigquery/events/__init__.py +5 -0
  74. sqlspec/adapters/bigquery/events/store.py +139 -0
  75. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  76. sqlspec/adapters/bigquery/litestar/store.py +325 -0
  77. sqlspec/adapters/bigquery/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  78. sqlspec/adapters/bigquery/type_converter.py +107 -0
  79. sqlspec/adapters/cockroach_asyncpg/__init__.py +24 -0
  80. sqlspec/adapters/cockroach_asyncpg/_typing.py +72 -0
  81. sqlspec/adapters/cockroach_asyncpg/adk/__init__.py +3 -0
  82. sqlspec/adapters/cockroach_asyncpg/adk/store.py +410 -0
  83. sqlspec/adapters/cockroach_asyncpg/config.py +238 -0
  84. sqlspec/adapters/cockroach_asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  85. sqlspec/adapters/cockroach_asyncpg/core.py +55 -0
  86. sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +107 -0
  87. sqlspec/adapters/cockroach_asyncpg/driver.py +144 -0
  88. sqlspec/adapters/cockroach_asyncpg/events/__init__.py +3 -0
  89. sqlspec/adapters/cockroach_asyncpg/events/store.py +20 -0
  90. sqlspec/adapters/cockroach_asyncpg/litestar/__init__.py +3 -0
  91. sqlspec/adapters/cockroach_asyncpg/litestar/store.py +142 -0
  92. sqlspec/adapters/cockroach_psycopg/__init__.py +38 -0
  93. sqlspec/adapters/cockroach_psycopg/_typing.py +129 -0
  94. sqlspec/adapters/cockroach_psycopg/adk/__init__.py +13 -0
  95. sqlspec/adapters/cockroach_psycopg/adk/store.py +868 -0
  96. sqlspec/adapters/cockroach_psycopg/config.py +484 -0
  97. sqlspec/adapters/cockroach_psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sqlspec/adapters/cockroach_psycopg/core.py +63 -0
  99. sqlspec/adapters/cockroach_psycopg/data_dictionary.py +215 -0
  100. sqlspec/adapters/cockroach_psycopg/driver.py +284 -0
  101. sqlspec/adapters/cockroach_psycopg/events/__init__.py +6 -0
  102. sqlspec/adapters/cockroach_psycopg/events/store.py +34 -0
  103. sqlspec/adapters/cockroach_psycopg/litestar/__init__.py +3 -0
  104. sqlspec/adapters/cockroach_psycopg/litestar/store.py +325 -0
  105. sqlspec/adapters/duckdb/__init__.py +25 -0
  106. sqlspec/adapters/duckdb/_typing.py +81 -0
  107. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  108. sqlspec/adapters/duckdb/adk/store.py +850 -0
  109. sqlspec/adapters/duckdb/config.py +463 -0
  110. sqlspec/adapters/duckdb/core.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sqlspec/adapters/duckdb/core.py +257 -0
  112. sqlspec/adapters/duckdb/data_dictionary.py +140 -0
  113. sqlspec/adapters/duckdb/driver.py +430 -0
  114. sqlspec/adapters/duckdb/events/__init__.py +5 -0
  115. sqlspec/adapters/duckdb/events/store.py +57 -0
  116. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  117. sqlspec/adapters/duckdb/litestar/store.py +330 -0
  118. sqlspec/adapters/duckdb/pool.py +293 -0
  119. sqlspec/adapters/duckdb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  120. sqlspec/adapters/duckdb/type_converter.py +118 -0
  121. sqlspec/adapters/mock/__init__.py +72 -0
  122. sqlspec/adapters/mock/_typing.py +147 -0
  123. sqlspec/adapters/mock/config.py +483 -0
  124. sqlspec/adapters/mock/core.py +319 -0
  125. sqlspec/adapters/mock/data_dictionary.py +366 -0
  126. sqlspec/adapters/mock/driver.py +721 -0
  127. sqlspec/adapters/mysqlconnector/__init__.py +36 -0
  128. sqlspec/adapters/mysqlconnector/_typing.py +141 -0
  129. sqlspec/adapters/mysqlconnector/adk/__init__.py +15 -0
  130. sqlspec/adapters/mysqlconnector/adk/store.py +1060 -0
  131. sqlspec/adapters/mysqlconnector/config.py +394 -0
  132. sqlspec/adapters/mysqlconnector/core.cpython-310-aarch64-linux-gnu.so +0 -0
  133. sqlspec/adapters/mysqlconnector/core.py +303 -0
  134. sqlspec/adapters/mysqlconnector/data_dictionary.py +235 -0
  135. sqlspec/adapters/mysqlconnector/driver.py +483 -0
  136. sqlspec/adapters/mysqlconnector/events/__init__.py +8 -0
  137. sqlspec/adapters/mysqlconnector/events/store.py +98 -0
  138. sqlspec/adapters/mysqlconnector/litestar/__init__.py +5 -0
  139. sqlspec/adapters/mysqlconnector/litestar/store.py +426 -0
  140. sqlspec/adapters/oracledb/__init__.py +60 -0
  141. sqlspec/adapters/oracledb/_numpy_handlers.py +141 -0
  142. sqlspec/adapters/oracledb/_typing.py +182 -0
  143. sqlspec/adapters/oracledb/_uuid_handlers.py +166 -0
  144. sqlspec/adapters/oracledb/adk/__init__.py +10 -0
  145. sqlspec/adapters/oracledb/adk/store.py +2369 -0
  146. sqlspec/adapters/oracledb/config.py +550 -0
  147. sqlspec/adapters/oracledb/core.cpython-310-aarch64-linux-gnu.so +0 -0
  148. sqlspec/adapters/oracledb/core.py +543 -0
  149. sqlspec/adapters/oracledb/data_dictionary.py +536 -0
  150. sqlspec/adapters/oracledb/driver.py +1229 -0
  151. sqlspec/adapters/oracledb/events/__init__.py +16 -0
  152. sqlspec/adapters/oracledb/events/backend.py +347 -0
  153. sqlspec/adapters/oracledb/events/store.py +420 -0
  154. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  155. sqlspec/adapters/oracledb/litestar/store.py +781 -0
  156. sqlspec/adapters/oracledb/migrations.py +535 -0
  157. sqlspec/adapters/oracledb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sqlspec/adapters/oracledb/type_converter.py +211 -0
  159. sqlspec/adapters/psqlpy/__init__.py +17 -0
  160. sqlspec/adapters/psqlpy/_typing.py +79 -0
  161. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  162. sqlspec/adapters/psqlpy/adk/store.py +766 -0
  163. sqlspec/adapters/psqlpy/config.py +304 -0
  164. sqlspec/adapters/psqlpy/core.cpython-310-aarch64-linux-gnu.so +0 -0
  165. sqlspec/adapters/psqlpy/core.py +480 -0
  166. sqlspec/adapters/psqlpy/data_dictionary.py +126 -0
  167. sqlspec/adapters/psqlpy/driver.py +438 -0
  168. sqlspec/adapters/psqlpy/events/__init__.py +6 -0
  169. sqlspec/adapters/psqlpy/events/backend.py +310 -0
  170. sqlspec/adapters/psqlpy/events/store.py +20 -0
  171. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  172. sqlspec/adapters/psqlpy/litestar/store.py +270 -0
  173. sqlspec/adapters/psqlpy/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  174. sqlspec/adapters/psqlpy/type_converter.py +113 -0
  175. sqlspec/adapters/psycopg/__init__.py +32 -0
  176. sqlspec/adapters/psycopg/_typing.py +164 -0
  177. sqlspec/adapters/psycopg/adk/__init__.py +10 -0
  178. sqlspec/adapters/psycopg/adk/store.py +1387 -0
  179. sqlspec/adapters/psycopg/config.py +576 -0
  180. sqlspec/adapters/psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  181. sqlspec/adapters/psycopg/core.py +450 -0
  182. sqlspec/adapters/psycopg/data_dictionary.py +289 -0
  183. sqlspec/adapters/psycopg/driver.py +975 -0
  184. sqlspec/adapters/psycopg/events/__init__.py +20 -0
  185. sqlspec/adapters/psycopg/events/backend.py +458 -0
  186. sqlspec/adapters/psycopg/events/store.py +42 -0
  187. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  188. sqlspec/adapters/psycopg/litestar/store.py +552 -0
  189. sqlspec/adapters/psycopg/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  190. sqlspec/adapters/psycopg/type_converter.py +93 -0
  191. sqlspec/adapters/pymysql/__init__.py +21 -0
  192. sqlspec/adapters/pymysql/_typing.py +71 -0
  193. sqlspec/adapters/pymysql/adk/__init__.py +5 -0
  194. sqlspec/adapters/pymysql/adk/store.py +540 -0
  195. sqlspec/adapters/pymysql/config.py +195 -0
  196. sqlspec/adapters/pymysql/core.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sqlspec/adapters/pymysql/core.py +299 -0
  198. sqlspec/adapters/pymysql/data_dictionary.py +122 -0
  199. sqlspec/adapters/pymysql/driver.py +259 -0
  200. sqlspec/adapters/pymysql/events/__init__.py +5 -0
  201. sqlspec/adapters/pymysql/events/store.py +50 -0
  202. sqlspec/adapters/pymysql/litestar/__init__.py +5 -0
  203. sqlspec/adapters/pymysql/litestar/store.py +232 -0
  204. sqlspec/adapters/pymysql/pool.py +137 -0
  205. sqlspec/adapters/spanner/__init__.py +40 -0
  206. sqlspec/adapters/spanner/_typing.py +86 -0
  207. sqlspec/adapters/spanner/adk/__init__.py +5 -0
  208. sqlspec/adapters/spanner/adk/store.py +732 -0
  209. sqlspec/adapters/spanner/config.py +352 -0
  210. sqlspec/adapters/spanner/core.cpython-310-aarch64-linux-gnu.so +0 -0
  211. sqlspec/adapters/spanner/core.py +188 -0
  212. sqlspec/adapters/spanner/data_dictionary.py +120 -0
  213. sqlspec/adapters/spanner/dialect/__init__.py +6 -0
  214. sqlspec/adapters/spanner/dialect/_spangres.py +57 -0
  215. sqlspec/adapters/spanner/dialect/_spanner.py +130 -0
  216. sqlspec/adapters/spanner/driver.py +373 -0
  217. sqlspec/adapters/spanner/events/__init__.py +5 -0
  218. sqlspec/adapters/spanner/events/store.py +187 -0
  219. sqlspec/adapters/spanner/litestar/__init__.py +5 -0
  220. sqlspec/adapters/spanner/litestar/store.py +291 -0
  221. sqlspec/adapters/spanner/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sqlspec/adapters/spanner/type_converter.py +331 -0
  223. sqlspec/adapters/sqlite/__init__.py +19 -0
  224. sqlspec/adapters/sqlite/_typing.py +80 -0
  225. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  226. sqlspec/adapters/sqlite/adk/store.py +958 -0
  227. sqlspec/adapters/sqlite/config.py +280 -0
  228. sqlspec/adapters/sqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
  229. sqlspec/adapters/sqlite/core.py +312 -0
  230. sqlspec/adapters/sqlite/data_dictionary.py +202 -0
  231. sqlspec/adapters/sqlite/driver.py +359 -0
  232. sqlspec/adapters/sqlite/events/__init__.py +5 -0
  233. sqlspec/adapters/sqlite/events/store.py +20 -0
  234. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  235. sqlspec/adapters/sqlite/litestar/store.py +316 -0
  236. sqlspec/adapters/sqlite/pool.py +198 -0
  237. sqlspec/adapters/sqlite/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  238. sqlspec/adapters/sqlite/type_converter.py +114 -0
  239. sqlspec/base.py +747 -0
  240. sqlspec/builder/__init__.py +179 -0
  241. sqlspec/builder/_base.cpython-310-aarch64-linux-gnu.so +0 -0
  242. sqlspec/builder/_base.py +1022 -0
  243. sqlspec/builder/_column.cpython-310-aarch64-linux-gnu.so +0 -0
  244. sqlspec/builder/_column.py +521 -0
  245. sqlspec/builder/_ddl.cpython-310-aarch64-linux-gnu.so +0 -0
  246. sqlspec/builder/_ddl.py +1642 -0
  247. sqlspec/builder/_delete.cpython-310-aarch64-linux-gnu.so +0 -0
  248. sqlspec/builder/_delete.py +95 -0
  249. sqlspec/builder/_dml.cpython-310-aarch64-linux-gnu.so +0 -0
  250. sqlspec/builder/_dml.py +365 -0
  251. sqlspec/builder/_explain.cpython-310-aarch64-linux-gnu.so +0 -0
  252. sqlspec/builder/_explain.py +579 -0
  253. sqlspec/builder/_expression_wrappers.cpython-310-aarch64-linux-gnu.so +0 -0
  254. sqlspec/builder/_expression_wrappers.py +46 -0
  255. sqlspec/builder/_factory.cpython-310-aarch64-linux-gnu.so +0 -0
  256. sqlspec/builder/_factory.py +1697 -0
  257. sqlspec/builder/_insert.cpython-310-aarch64-linux-gnu.so +0 -0
  258. sqlspec/builder/_insert.py +328 -0
  259. sqlspec/builder/_join.cpython-310-aarch64-linux-gnu.so +0 -0
  260. sqlspec/builder/_join.py +499 -0
  261. sqlspec/builder/_merge.cpython-310-aarch64-linux-gnu.so +0 -0
  262. sqlspec/builder/_merge.py +821 -0
  263. sqlspec/builder/_parsing_utils.cpython-310-aarch64-linux-gnu.so +0 -0
  264. sqlspec/builder/_parsing_utils.py +297 -0
  265. sqlspec/builder/_select.cpython-310-aarch64-linux-gnu.so +0 -0
  266. sqlspec/builder/_select.py +1660 -0
  267. sqlspec/builder/_temporal.cpython-310-aarch64-linux-gnu.so +0 -0
  268. sqlspec/builder/_temporal.py +139 -0
  269. sqlspec/builder/_update.cpython-310-aarch64-linux-gnu.so +0 -0
  270. sqlspec/builder/_update.py +173 -0
  271. sqlspec/builder/_vector_expressions.py +267 -0
  272. sqlspec/cli.py +911 -0
  273. sqlspec/config.py +1755 -0
  274. sqlspec/core/__init__.py +374 -0
  275. sqlspec/core/_correlation.cpython-310-aarch64-linux-gnu.so +0 -0
  276. sqlspec/core/_correlation.py +176 -0
  277. sqlspec/core/cache.cpython-310-aarch64-linux-gnu.so +0 -0
  278. sqlspec/core/cache.py +1069 -0
  279. sqlspec/core/compiler.cpython-310-aarch64-linux-gnu.so +0 -0
  280. sqlspec/core/compiler.py +954 -0
  281. sqlspec/core/explain.cpython-310-aarch64-linux-gnu.so +0 -0
  282. sqlspec/core/explain.py +275 -0
  283. sqlspec/core/filters.cpython-310-aarch64-linux-gnu.so +0 -0
  284. sqlspec/core/filters.py +952 -0
  285. sqlspec/core/hashing.cpython-310-aarch64-linux-gnu.so +0 -0
  286. sqlspec/core/hashing.py +262 -0
  287. sqlspec/core/metrics.cpython-310-aarch64-linux-gnu.so +0 -0
  288. sqlspec/core/metrics.py +83 -0
  289. sqlspec/core/parameters/__init__.py +71 -0
  290. sqlspec/core/parameters/_alignment.cpython-310-aarch64-linux-gnu.so +0 -0
  291. sqlspec/core/parameters/_alignment.py +270 -0
  292. sqlspec/core/parameters/_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  293. sqlspec/core/parameters/_converter.py +543 -0
  294. sqlspec/core/parameters/_processor.cpython-310-aarch64-linux-gnu.so +0 -0
  295. sqlspec/core/parameters/_processor.py +505 -0
  296. sqlspec/core/parameters/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
  297. sqlspec/core/parameters/_registry.py +206 -0
  298. sqlspec/core/parameters/_transformers.cpython-310-aarch64-linux-gnu.so +0 -0
  299. sqlspec/core/parameters/_transformers.py +292 -0
  300. sqlspec/core/parameters/_types.cpython-310-aarch64-linux-gnu.so +0 -0
  301. sqlspec/core/parameters/_types.py +499 -0
  302. sqlspec/core/parameters/_validator.cpython-310-aarch64-linux-gnu.so +0 -0
  303. sqlspec/core/parameters/_validator.py +180 -0
  304. sqlspec/core/pipeline.cpython-310-aarch64-linux-gnu.so +0 -0
  305. sqlspec/core/pipeline.py +319 -0
  306. sqlspec/core/query_modifiers.cpython-310-aarch64-linux-gnu.so +0 -0
  307. sqlspec/core/query_modifiers.py +437 -0
  308. sqlspec/core/result/__init__.py +23 -0
  309. sqlspec/core/result/_base.cpython-310-aarch64-linux-gnu.so +0 -0
  310. sqlspec/core/result/_base.py +1121 -0
  311. sqlspec/core/result/_io.cpython-310-aarch64-linux-gnu.so +0 -0
  312. sqlspec/core/result/_io.py +28 -0
  313. sqlspec/core/splitter.cpython-310-aarch64-linux-gnu.so +0 -0
  314. sqlspec/core/splitter.py +966 -0
  315. sqlspec/core/stack.cpython-310-aarch64-linux-gnu.so +0 -0
  316. sqlspec/core/stack.py +163 -0
  317. sqlspec/core/statement.cpython-310-aarch64-linux-gnu.so +0 -0
  318. sqlspec/core/statement.py +1503 -0
  319. sqlspec/core/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  320. sqlspec/core/type_converter.py +339 -0
  321. sqlspec/data_dictionary/__init__.py +22 -0
  322. sqlspec/data_dictionary/_loader.py +123 -0
  323. sqlspec/data_dictionary/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
  324. sqlspec/data_dictionary/_registry.py +74 -0
  325. sqlspec/data_dictionary/_types.cpython-310-aarch64-linux-gnu.so +0 -0
  326. sqlspec/data_dictionary/_types.py +121 -0
  327. sqlspec/data_dictionary/dialects/__init__.py +21 -0
  328. sqlspec/data_dictionary/dialects/bigquery.cpython-310-aarch64-linux-gnu.so +0 -0
  329. sqlspec/data_dictionary/dialects/bigquery.py +49 -0
  330. sqlspec/data_dictionary/dialects/cockroachdb.cpython-310-aarch64-linux-gnu.so +0 -0
  331. sqlspec/data_dictionary/dialects/cockroachdb.py +43 -0
  332. sqlspec/data_dictionary/dialects/duckdb.cpython-310-aarch64-linux-gnu.so +0 -0
  333. sqlspec/data_dictionary/dialects/duckdb.py +47 -0
  334. sqlspec/data_dictionary/dialects/mysql.cpython-310-aarch64-linux-gnu.so +0 -0
  335. sqlspec/data_dictionary/dialects/mysql.py +42 -0
  336. sqlspec/data_dictionary/dialects/oracle.cpython-310-aarch64-linux-gnu.so +0 -0
  337. sqlspec/data_dictionary/dialects/oracle.py +34 -0
  338. sqlspec/data_dictionary/dialects/postgres.cpython-310-aarch64-linux-gnu.so +0 -0
  339. sqlspec/data_dictionary/dialects/postgres.py +46 -0
  340. sqlspec/data_dictionary/dialects/spanner.cpython-310-aarch64-linux-gnu.so +0 -0
  341. sqlspec/data_dictionary/dialects/spanner.py +37 -0
  342. sqlspec/data_dictionary/dialects/sqlite.cpython-310-aarch64-linux-gnu.so +0 -0
  343. sqlspec/data_dictionary/dialects/sqlite.py +42 -0
  344. sqlspec/data_dictionary/sql/.gitkeep +0 -0
  345. sqlspec/data_dictionary/sql/bigquery/columns.sql +23 -0
  346. sqlspec/data_dictionary/sql/bigquery/foreign_keys.sql +34 -0
  347. sqlspec/data_dictionary/sql/bigquery/indexes.sql +19 -0
  348. sqlspec/data_dictionary/sql/bigquery/tables.sql +33 -0
  349. sqlspec/data_dictionary/sql/bigquery/version.sql +3 -0
  350. sqlspec/data_dictionary/sql/cockroachdb/columns.sql +34 -0
  351. sqlspec/data_dictionary/sql/cockroachdb/foreign_keys.sql +40 -0
  352. sqlspec/data_dictionary/sql/cockroachdb/indexes.sql +32 -0
  353. sqlspec/data_dictionary/sql/cockroachdb/tables.sql +44 -0
  354. sqlspec/data_dictionary/sql/cockroachdb/version.sql +3 -0
  355. sqlspec/data_dictionary/sql/duckdb/columns.sql +23 -0
  356. sqlspec/data_dictionary/sql/duckdb/foreign_keys.sql +36 -0
  357. sqlspec/data_dictionary/sql/duckdb/indexes.sql +19 -0
  358. sqlspec/data_dictionary/sql/duckdb/tables.sql +38 -0
  359. sqlspec/data_dictionary/sql/duckdb/version.sql +3 -0
  360. sqlspec/data_dictionary/sql/mysql/columns.sql +23 -0
  361. sqlspec/data_dictionary/sql/mysql/foreign_keys.sql +28 -0
  362. sqlspec/data_dictionary/sql/mysql/indexes.sql +26 -0
  363. sqlspec/data_dictionary/sql/mysql/tables.sql +33 -0
  364. sqlspec/data_dictionary/sql/mysql/version.sql +3 -0
  365. sqlspec/data_dictionary/sql/oracle/columns.sql +23 -0
  366. sqlspec/data_dictionary/sql/oracle/foreign_keys.sql +48 -0
  367. sqlspec/data_dictionary/sql/oracle/indexes.sql +44 -0
  368. sqlspec/data_dictionary/sql/oracle/tables.sql +25 -0
  369. sqlspec/data_dictionary/sql/oracle/version.sql +20 -0
  370. sqlspec/data_dictionary/sql/postgres/columns.sql +34 -0
  371. sqlspec/data_dictionary/sql/postgres/foreign_keys.sql +40 -0
  372. sqlspec/data_dictionary/sql/postgres/indexes.sql +56 -0
  373. sqlspec/data_dictionary/sql/postgres/tables.sql +44 -0
  374. sqlspec/data_dictionary/sql/postgres/version.sql +3 -0
  375. sqlspec/data_dictionary/sql/spanner/columns.sql +23 -0
  376. sqlspec/data_dictionary/sql/spanner/foreign_keys.sql +70 -0
  377. sqlspec/data_dictionary/sql/spanner/indexes.sql +30 -0
  378. sqlspec/data_dictionary/sql/spanner/tables.sql +9 -0
  379. sqlspec/data_dictionary/sql/spanner/version.sql +3 -0
  380. sqlspec/data_dictionary/sql/sqlite/columns.sql +23 -0
  381. sqlspec/data_dictionary/sql/sqlite/foreign_keys.sql +22 -0
  382. sqlspec/data_dictionary/sql/sqlite/indexes.sql +7 -0
  383. sqlspec/data_dictionary/sql/sqlite/tables.sql +28 -0
  384. sqlspec/data_dictionary/sql/sqlite/version.sql +3 -0
  385. sqlspec/driver/__init__.py +32 -0
  386. sqlspec/driver/_async.cpython-310-aarch64-linux-gnu.so +0 -0
  387. sqlspec/driver/_async.py +1737 -0
  388. sqlspec/driver/_common.cpython-310-aarch64-linux-gnu.so +0 -0
  389. sqlspec/driver/_common.py +1478 -0
  390. sqlspec/driver/_sql_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
  391. sqlspec/driver/_sql_helpers.py +148 -0
  392. sqlspec/driver/_storage_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
  393. sqlspec/driver/_storage_helpers.py +144 -0
  394. sqlspec/driver/_sync.cpython-310-aarch64-linux-gnu.so +0 -0
  395. sqlspec/driver/_sync.py +1710 -0
  396. sqlspec/exceptions.py +338 -0
  397. sqlspec/extensions/__init__.py +0 -0
  398. sqlspec/extensions/adk/__init__.py +70 -0
  399. sqlspec/extensions/adk/_types.py +51 -0
  400. sqlspec/extensions/adk/converters.py +172 -0
  401. sqlspec/extensions/adk/memory/__init__.py +69 -0
  402. sqlspec/extensions/adk/memory/_types.py +30 -0
  403. sqlspec/extensions/adk/memory/converters.py +149 -0
  404. sqlspec/extensions/adk/memory/service.py +217 -0
  405. sqlspec/extensions/adk/memory/store.py +569 -0
  406. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +246 -0
  407. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  408. sqlspec/extensions/adk/service.py +225 -0
  409. sqlspec/extensions/adk/store.py +567 -0
  410. sqlspec/extensions/events/__init__.py +51 -0
  411. sqlspec/extensions/events/_channel.py +703 -0
  412. sqlspec/extensions/events/_hints.py +45 -0
  413. sqlspec/extensions/events/_models.py +23 -0
  414. sqlspec/extensions/events/_payload.py +69 -0
  415. sqlspec/extensions/events/_protocols.py +134 -0
  416. sqlspec/extensions/events/_queue.py +461 -0
  417. sqlspec/extensions/events/_store.py +209 -0
  418. sqlspec/extensions/events/migrations/0001_create_event_queue.py +59 -0
  419. sqlspec/extensions/events/migrations/__init__.py +3 -0
  420. sqlspec/extensions/fastapi/__init__.py +19 -0
  421. sqlspec/extensions/fastapi/extension.py +351 -0
  422. sqlspec/extensions/fastapi/providers.py +607 -0
  423. sqlspec/extensions/flask/__init__.py +37 -0
  424. sqlspec/extensions/flask/_state.py +76 -0
  425. sqlspec/extensions/flask/_utils.py +71 -0
  426. sqlspec/extensions/flask/extension.py +519 -0
  427. sqlspec/extensions/litestar/__init__.py +28 -0
  428. sqlspec/extensions/litestar/_utils.py +52 -0
  429. sqlspec/extensions/litestar/channels.py +165 -0
  430. sqlspec/extensions/litestar/cli.py +102 -0
  431. sqlspec/extensions/litestar/config.py +90 -0
  432. sqlspec/extensions/litestar/handlers.py +316 -0
  433. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  434. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  435. sqlspec/extensions/litestar/plugin.py +671 -0
  436. sqlspec/extensions/litestar/providers.py +526 -0
  437. sqlspec/extensions/litestar/store.py +296 -0
  438. sqlspec/extensions/otel/__init__.py +58 -0
  439. sqlspec/extensions/prometheus/__init__.py +113 -0
  440. sqlspec/extensions/starlette/__init__.py +19 -0
  441. sqlspec/extensions/starlette/_state.py +30 -0
  442. sqlspec/extensions/starlette/_utils.py +96 -0
  443. sqlspec/extensions/starlette/extension.py +346 -0
  444. sqlspec/extensions/starlette/middleware.py +235 -0
  445. sqlspec/loader.cpython-310-aarch64-linux-gnu.so +0 -0
  446. sqlspec/loader.py +702 -0
  447. sqlspec/migrations/__init__.py +36 -0
  448. sqlspec/migrations/base.py +731 -0
  449. sqlspec/migrations/commands.py +1232 -0
  450. sqlspec/migrations/context.py +157 -0
  451. sqlspec/migrations/fix.py +204 -0
  452. sqlspec/migrations/loaders.py +443 -0
  453. sqlspec/migrations/runner.py +1172 -0
  454. sqlspec/migrations/templates.py +234 -0
  455. sqlspec/migrations/tracker.py +611 -0
  456. sqlspec/migrations/utils.py +256 -0
  457. sqlspec/migrations/validation.py +207 -0
  458. sqlspec/migrations/version.py +446 -0
  459. sqlspec/observability/__init__.py +55 -0
  460. sqlspec/observability/_common.cpython-310-aarch64-linux-gnu.so +0 -0
  461. sqlspec/observability/_common.py +77 -0
  462. sqlspec/observability/_config.cpython-310-aarch64-linux-gnu.so +0 -0
  463. sqlspec/observability/_config.py +348 -0
  464. sqlspec/observability/_diagnostics.cpython-310-aarch64-linux-gnu.so +0 -0
  465. sqlspec/observability/_diagnostics.py +74 -0
  466. sqlspec/observability/_dispatcher.cpython-310-aarch64-linux-gnu.so +0 -0
  467. sqlspec/observability/_dispatcher.py +152 -0
  468. sqlspec/observability/_formatters/__init__.py +13 -0
  469. sqlspec/observability/_formatters/_aws.cpython-310-aarch64-linux-gnu.so +0 -0
  470. sqlspec/observability/_formatters/_aws.py +102 -0
  471. sqlspec/observability/_formatters/_azure.cpython-310-aarch64-linux-gnu.so +0 -0
  472. sqlspec/observability/_formatters/_azure.py +96 -0
  473. sqlspec/observability/_formatters/_base.cpython-310-aarch64-linux-gnu.so +0 -0
  474. sqlspec/observability/_formatters/_base.py +57 -0
  475. sqlspec/observability/_formatters/_gcp.cpython-310-aarch64-linux-gnu.so +0 -0
  476. sqlspec/observability/_formatters/_gcp.py +131 -0
  477. sqlspec/observability/_formatting.py +58 -0
  478. sqlspec/observability/_observer.cpython-310-aarch64-linux-gnu.so +0 -0
  479. sqlspec/observability/_observer.py +357 -0
  480. sqlspec/observability/_runtime.cpython-310-aarch64-linux-gnu.so +0 -0
  481. sqlspec/observability/_runtime.py +420 -0
  482. sqlspec/observability/_sampling.cpython-310-aarch64-linux-gnu.so +0 -0
  483. sqlspec/observability/_sampling.py +188 -0
  484. sqlspec/observability/_spans.cpython-310-aarch64-linux-gnu.so +0 -0
  485. sqlspec/observability/_spans.py +161 -0
  486. sqlspec/protocols.py +916 -0
  487. sqlspec/py.typed +0 -0
  488. sqlspec/storage/__init__.py +48 -0
  489. sqlspec/storage/_utils.py +104 -0
  490. sqlspec/storage/backends/__init__.py +1 -0
  491. sqlspec/storage/backends/base.py +253 -0
  492. sqlspec/storage/backends/fsspec.py +529 -0
  493. sqlspec/storage/backends/local.py +441 -0
  494. sqlspec/storage/backends/obstore.py +916 -0
  495. sqlspec/storage/errors.py +104 -0
  496. sqlspec/storage/pipeline.py +582 -0
  497. sqlspec/storage/registry.py +301 -0
  498. sqlspec/typing.py +395 -0
  499. sqlspec/utils/__init__.py +7 -0
  500. sqlspec/utils/arrow_helpers.py +318 -0
  501. sqlspec/utils/config_tools.py +332 -0
  502. sqlspec/utils/correlation.cpython-310-aarch64-linux-gnu.so +0 -0
  503. sqlspec/utils/correlation.py +134 -0
  504. sqlspec/utils/deprecation.py +190 -0
  505. sqlspec/utils/fixtures.cpython-310-aarch64-linux-gnu.so +0 -0
  506. sqlspec/utils/fixtures.py +258 -0
  507. sqlspec/utils/logging.py +222 -0
  508. sqlspec/utils/module_loader.py +306 -0
  509. sqlspec/utils/portal.cpython-310-aarch64-linux-gnu.so +0 -0
  510. sqlspec/utils/portal.py +375 -0
  511. sqlspec/utils/schema.cpython-310-aarch64-linux-gnu.so +0 -0
  512. sqlspec/utils/schema.py +485 -0
  513. sqlspec/utils/serializers.cpython-310-aarch64-linux-gnu.so +0 -0
  514. sqlspec/utils/serializers.py +408 -0
  515. sqlspec/utils/singleton.cpython-310-aarch64-linux-gnu.so +0 -0
  516. sqlspec/utils/singleton.py +41 -0
  517. sqlspec/utils/sync_tools.cpython-310-aarch64-linux-gnu.so +0 -0
  518. sqlspec/utils/sync_tools.py +311 -0
  519. sqlspec/utils/text.cpython-310-aarch64-linux-gnu.so +0 -0
  520. sqlspec/utils/text.py +108 -0
  521. sqlspec/utils/type_converters.cpython-310-aarch64-linux-gnu.so +0 -0
  522. sqlspec/utils/type_converters.py +128 -0
  523. sqlspec/utils/type_guards.cpython-310-aarch64-linux-gnu.so +0 -0
  524. sqlspec/utils/type_guards.py +1360 -0
  525. sqlspec/utils/uuids.cpython-310-aarch64-linux-gnu.so +0 -0
  526. sqlspec/utils/uuids.py +225 -0
  527. sqlspec-0.36.0.dist-info/METADATA +205 -0
  528. sqlspec-0.36.0.dist-info/RECORD +531 -0
  529. sqlspec-0.36.0.dist-info/WHEEL +7 -0
  530. sqlspec-0.36.0.dist-info/entry_points.txt +2 -0
  531. 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