sqlspec 0.26.0__py3-none-any.whl → 0.27.0__py3-none-any.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.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (197) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +62 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +52 -2
  9. sqlspec/adapters/adbc/driver.py +144 -45
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +25 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +352 -144
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +138 -43
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +292 -84
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
  77. sqlspec/adapters/psqlpy/driver.py +101 -31
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +40 -11
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +77 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +231 -60
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +95 -161
  129. sqlspec/driver/_common.py +133 -80
  130. sqlspec/driver/_sync.py +95 -162
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/base.py +200 -76
  153. sqlspec/migrations/commands.py +591 -62
  154. sqlspec/migrations/context.py +6 -9
  155. sqlspec/migrations/fix.py +199 -0
  156. sqlspec/migrations/loaders.py +47 -19
  157. sqlspec/migrations/runner.py +241 -75
  158. sqlspec/migrations/tracker.py +237 -21
  159. sqlspec/migrations/utils.py +51 -3
  160. sqlspec/migrations/validation.py +177 -0
  161. sqlspec/protocols.py +66 -36
  162. sqlspec/storage/_utils.py +98 -0
  163. sqlspec/storage/backends/fsspec.py +134 -106
  164. sqlspec/storage/backends/local.py +78 -51
  165. sqlspec/storage/backends/obstore.py +278 -162
  166. sqlspec/storage/registry.py +75 -39
  167. sqlspec/typing.py +14 -84
  168. sqlspec/utils/config_resolver.py +6 -6
  169. sqlspec/utils/correlation.py +4 -5
  170. sqlspec/utils/data_transformation.py +3 -2
  171. sqlspec/utils/deprecation.py +9 -8
  172. sqlspec/utils/fixtures.py +4 -4
  173. sqlspec/utils/logging.py +46 -6
  174. sqlspec/utils/module_loader.py +2 -2
  175. sqlspec/utils/schema.py +288 -0
  176. sqlspec/utils/serializers.py +3 -3
  177. sqlspec/utils/sync_tools.py +21 -17
  178. sqlspec/utils/text.py +1 -2
  179. sqlspec/utils/type_guards.py +111 -20
  180. sqlspec/utils/version.py +433 -0
  181. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  182. sqlspec-0.27.0.dist-info/RECORD +207 -0
  183. sqlspec/builder/mixins/__init__.py +0 -55
  184. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  185. sqlspec/builder/mixins/_delete_operations.py +0 -50
  186. sqlspec/builder/mixins/_insert_operations.py +0 -282
  187. sqlspec/builder/mixins/_merge_operations.py +0 -698
  188. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  189. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  190. sqlspec/builder/mixins/_select_operations.py +0 -930
  191. sqlspec/builder/mixins/_update_operations.py +0 -199
  192. sqlspec/builder/mixins/_where_clause.py +0 -1298
  193. sqlspec-0.26.0.dist-info/RECORD +0 -157
  194. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  195. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  197. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/protocols.py CHANGED
@@ -4,7 +4,7 @@ This module provides protocols that can be used for static type checking
4
4
  and runtime isinstance() checks.
5
5
  """
6
6
 
7
- from typing import TYPE_CHECKING, Any, Optional, Protocol, Union, runtime_checkable
7
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
8
8
 
9
9
  from typing_extensions import Self
10
10
 
@@ -186,7 +186,7 @@ class ObjectStoreItemProtocol(Protocol):
186
186
  """Protocol for object store items with path/key attributes."""
187
187
 
188
188
  path: str
189
- key: "Optional[str]"
189
+ key: "str | None"
190
190
 
191
191
 
192
192
  @runtime_checkable
@@ -199,35 +199,35 @@ class ObjectStoreProtocol(Protocol):
199
199
  def __init__(self, uri: str, **kwargs: Any) -> None:
200
200
  return
201
201
 
202
- def read_bytes(self, path: "Union[str, Path]", **kwargs: Any) -> bytes:
202
+ def read_bytes(self, path: "str | Path", **kwargs: Any) -> bytes:
203
203
  """Read bytes from an object."""
204
204
  return b""
205
205
 
206
- def write_bytes(self, path: "Union[str, Path]", data: bytes, **kwargs: Any) -> None:
206
+ def write_bytes(self, path: "str | Path", data: bytes, **kwargs: Any) -> None:
207
207
  """Write bytes to an object."""
208
208
  return
209
209
 
210
- def read_text(self, path: "Union[str, Path]", encoding: str = "utf-8", **kwargs: Any) -> str:
210
+ def read_text(self, path: "str | Path", encoding: str = "utf-8", **kwargs: Any) -> str:
211
211
  """Read text from an object."""
212
212
  return ""
213
213
 
214
- def write_text(self, path: "Union[str, Path]", data: str, encoding: str = "utf-8", **kwargs: Any) -> None:
214
+ def write_text(self, path: "str | Path", data: str, encoding: str = "utf-8", **kwargs: Any) -> None:
215
215
  """Write text to an object."""
216
216
  return
217
217
 
218
- def exists(self, path: "Union[str, Path]", **kwargs: Any) -> bool:
218
+ def exists(self, path: "str | Path", **kwargs: Any) -> bool:
219
219
  """Check if an object exists."""
220
220
  return False
221
221
 
222
- def delete(self, path: "Union[str, Path]", **kwargs: Any) -> None:
222
+ def delete(self, path: "str | Path", **kwargs: Any) -> None:
223
223
  """Delete an object."""
224
224
  return
225
225
 
226
- def copy(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
226
+ def copy(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
227
227
  """Copy an object."""
228
228
  return
229
229
 
230
- def move(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
230
+ def move(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
231
231
  """Move an object."""
232
232
  return
233
233
 
@@ -239,24 +239,24 @@ class ObjectStoreProtocol(Protocol):
239
239
  """Find objects matching a glob pattern."""
240
240
  return []
241
241
 
242
- def is_object(self, path: "Union[str, Path]") -> bool:
242
+ def is_object(self, path: "str | Path") -> bool:
243
243
  """Check if path points to an object."""
244
244
  return False
245
245
 
246
- def is_path(self, path: "Union[str, Path]") -> bool:
246
+ def is_path(self, path: "str | Path") -> bool:
247
247
  """Check if path points to a prefix (directory-like)."""
248
248
  return False
249
249
 
250
- def get_metadata(self, path: "Union[str, Path]", **kwargs: Any) -> dict[str, Any]:
250
+ def get_metadata(self, path: "str | Path", **kwargs: Any) -> dict[str, Any]:
251
251
  """Get object metadata."""
252
252
  return {}
253
253
 
254
- def read_arrow(self, path: "Union[str, Path]", **kwargs: Any) -> "ArrowTable":
254
+ def read_arrow(self, path: "str | Path", **kwargs: Any) -> "ArrowTable":
255
255
  """Read an Arrow table from storage."""
256
256
  msg = "Arrow reading not implemented"
257
257
  raise NotImplementedError(msg)
258
258
 
259
- def write_arrow(self, path: "Union[str, Path]", table: "ArrowTable", **kwargs: Any) -> None:
259
+ def write_arrow(self, path: "str | Path", table: "ArrowTable", **kwargs: Any) -> None:
260
260
  """Write an Arrow table to storage."""
261
261
  msg = "Arrow writing not implemented"
262
262
  raise NotImplementedError(msg)
@@ -266,34 +266,32 @@ class ObjectStoreProtocol(Protocol):
266
266
  msg = "Arrow streaming not implemented"
267
267
  raise NotImplementedError(msg)
268
268
 
269
- async def read_bytes_async(self, path: "Union[str, Path]", **kwargs: Any) -> bytes:
269
+ async def read_bytes_async(self, path: "str | Path", **kwargs: Any) -> bytes:
270
270
  """Async read bytes from an object."""
271
271
  msg = "Async operations not implemented"
272
272
  raise NotImplementedError(msg)
273
273
 
274
- async def write_bytes_async(self, path: "Union[str, Path]", data: bytes, **kwargs: Any) -> None:
274
+ async def write_bytes_async(self, path: "str | Path", data: bytes, **kwargs: Any) -> None:
275
275
  """Async write bytes to an object."""
276
276
  msg = "Async operations not implemented"
277
277
  raise NotImplementedError(msg)
278
278
 
279
- async def read_text_async(self, path: "Union[str, Path]", encoding: str = "utf-8", **kwargs: Any) -> str:
279
+ async def read_text_async(self, path: "str | Path", encoding: str = "utf-8", **kwargs: Any) -> str:
280
280
  """Async read text from an object."""
281
281
  msg = "Async operations not implemented"
282
282
  raise NotImplementedError(msg)
283
283
 
284
- async def write_text_async(
285
- self, path: "Union[str, Path]", data: str, encoding: str = "utf-8", **kwargs: Any
286
- ) -> None:
284
+ async def write_text_async(self, path: "str | Path", data: str, encoding: str = "utf-8", **kwargs: Any) -> None:
287
285
  """Async write text to an object."""
288
286
  msg = "Async operations not implemented"
289
287
  raise NotImplementedError(msg)
290
288
 
291
- async def exists_async(self, path: "Union[str, Path]", **kwargs: Any) -> bool:
289
+ async def exists_async(self, path: "str | Path", **kwargs: Any) -> bool:
292
290
  """Async check if an object exists."""
293
291
  msg = "Async operations not implemented"
294
292
  raise NotImplementedError(msg)
295
293
 
296
- async def delete_async(self, path: "Union[str, Path]", **kwargs: Any) -> None:
294
+ async def delete_async(self, path: "str | Path", **kwargs: Any) -> None:
297
295
  """Async delete an object."""
298
296
  msg = "Async operations not implemented"
299
297
  raise NotImplementedError(msg)
@@ -303,27 +301,27 @@ class ObjectStoreProtocol(Protocol):
303
301
  msg = "Async operations not implemented"
304
302
  raise NotImplementedError(msg)
305
303
 
306
- async def copy_async(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
304
+ async def copy_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
307
305
  """Async copy an object."""
308
306
  msg = "Async operations not implemented"
309
307
  raise NotImplementedError(msg)
310
308
 
311
- async def move_async(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
309
+ async def move_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
312
310
  """Async move an object."""
313
311
  msg = "Async operations not implemented"
314
312
  raise NotImplementedError(msg)
315
313
 
316
- async def get_metadata_async(self, path: "Union[str, Path]", **kwargs: Any) -> dict[str, Any]:
314
+ async def get_metadata_async(self, path: "str | Path", **kwargs: Any) -> dict[str, Any]:
317
315
  """Async get object metadata."""
318
316
  msg = "Async operations not implemented"
319
317
  raise NotImplementedError(msg)
320
318
 
321
- async def read_arrow_async(self, path: "Union[str, Path]", **kwargs: Any) -> "ArrowTable":
319
+ async def read_arrow_async(self, path: "str | Path", **kwargs: Any) -> "ArrowTable":
322
320
  """Async read an Arrow table from storage."""
323
321
  msg = "Async arrow reading not implemented"
324
322
  raise NotImplementedError(msg)
325
323
 
326
- async def write_arrow_async(self, path: "Union[str, Path]", table: "ArrowTable", **kwargs: Any) -> None:
324
+ async def write_arrow_async(self, path: "str | Path", table: "ArrowTable", **kwargs: Any) -> None:
327
325
  """Async write an Arrow table to storage."""
328
326
  msg = "Async arrow writing not implemented"
329
327
  raise NotImplementedError(msg)
@@ -339,7 +337,7 @@ class HasSQLGlotExpressionProtocol(Protocol):
339
337
  """Protocol for objects with a sqlglot_expression property."""
340
338
 
341
339
  @property
342
- def sqlglot_expression(self) -> "Optional[exp.Expression]":
340
+ def sqlglot_expression(self) -> "exp.Expression | None":
343
341
  """Return the SQLGlot expression for this object."""
344
342
  ...
345
343
 
@@ -348,16 +346,24 @@ class HasSQLGlotExpressionProtocol(Protocol):
348
346
  class HasParameterBuilderProtocol(Protocol):
349
347
  """Protocol for objects that can add parameters."""
350
348
 
351
- def add_parameter(self, value: Any, name: "Optional[str]" = None) -> tuple[Any, str]:
349
+ def add_parameter(self, value: Any, name: "str | None" = None) -> tuple[Any, str]:
352
350
  """Add a parameter to the builder."""
353
351
  ...
354
352
 
353
+ def get_expression(self) -> "exp.Expression | None":
354
+ """Return the underlying SQLGlot expression."""
355
+ ...
356
+
357
+ def set_expression(self, expression: "exp.Expression") -> None:
358
+ """Replace the underlying SQLGlot expression."""
359
+ ...
360
+
355
361
 
356
362
  @runtime_checkable
357
363
  class HasExpressionProtocol(Protocol):
358
364
  """Protocol for objects with an _expression attribute."""
359
365
 
360
- _expression: "Optional[exp.Expression]"
366
+ _expression: "exp.Expression | None"
361
367
 
362
368
 
363
369
  @runtime_checkable
@@ -373,21 +379,21 @@ class HasToStatementProtocol(Protocol):
373
379
  class SQLBuilderProtocol(Protocol):
374
380
  """Protocol for SQL query builders."""
375
381
 
376
- _expression: "Optional[exp.Expression]"
382
+ _expression: "exp.Expression | None"
377
383
  _parameters: dict[str, Any]
378
384
  _parameter_counter: int
379
385
  _columns: Any # Optional attribute for some builders
380
386
  _table: Any # Optional attribute for some builders
381
387
  _with_ctes: Any # Optional attribute for some builders
382
388
  dialect: Any
383
- dialect_name: "Optional[str]"
389
+ dialect_name: "str | None"
384
390
 
385
391
  @property
386
392
  def parameters(self) -> dict[str, Any]:
387
393
  """Public access to query parameters."""
388
394
  ...
389
395
 
390
- def add_parameter(self, value: Any, name: "Optional[str]" = None) -> tuple[Any, str]:
396
+ def add_parameter(self, value: Any, name: "str | None" = None) -> tuple[Any, str]:
391
397
  """Add a parameter to the builder."""
392
398
  ...
393
399
 
@@ -399,14 +405,38 @@ class SQLBuilderProtocol(Protocol):
399
405
  """Replace literal values in an expression with bound parameters."""
400
406
  ...
401
407
 
402
- def build(self) -> "Union[exp.Expression, Any]":
408
+ def build(self) -> "exp.Expression | Any":
403
409
  """Build and return the final expression."""
404
410
  ...
405
411
 
412
+ def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
413
+ """Merge parameters from SQL objects into the builder."""
414
+ ...
415
+
416
+ def _build_final_expression(self, *, copy: bool = False) -> "exp.Expression":
417
+ """Return the expression with attached CTEs."""
418
+ ...
419
+
420
+ def _spawn_like_self(self) -> "Self":
421
+ """Create a new builder with matching configuration."""
422
+ ...
423
+
424
+ def get_expression(self) -> "exp.Expression | None":
425
+ """Return the underlying SQLGlot expression."""
426
+ ...
427
+
428
+ def set_expression(self, expression: "exp.Expression") -> None:
429
+ """Replace the underlying SQLGlot expression."""
430
+ ...
431
+
432
+ def generate_unique_parameter_name(self, base_name: str) -> str:
433
+ """Generate a unique parameter name exposed via public API."""
434
+ ...
435
+
406
436
 
407
437
  class SelectBuilderProtocol(SQLBuilderProtocol, Protocol):
408
438
  """Protocol for SELECT query builders."""
409
439
 
410
- def select(self, *columns: "Union[str, exp.Expression]") -> Self:
440
+ def select(self, *columns: "str | exp.Expression") -> Self:
411
441
  """Add SELECT columns to the query."""
412
442
  ...
@@ -0,0 +1,98 @@
1
+ """Shared utilities for storage backends."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from sqlspec.exceptions import MissingDependencyError
6
+ from sqlspec.typing import PYARROW_INSTALLED
7
+
8
+ if TYPE_CHECKING:
9
+ from pathlib import Path
10
+
11
+ __all__ = ("ensure_pyarrow", "resolve_storage_path")
12
+
13
+
14
+ def ensure_pyarrow() -> None:
15
+ """Ensure PyArrow is available for Arrow operations.
16
+
17
+ Raises:
18
+ MissingDependencyError: If pyarrow is not installed.
19
+ """
20
+ if not PYARROW_INSTALLED:
21
+ raise MissingDependencyError(package="pyarrow", install_package="pyarrow")
22
+
23
+
24
+ def resolve_storage_path(
25
+ path: "str | Path", base_path: str = "", protocol: str = "file", strip_file_scheme: bool = True
26
+ ) -> str:
27
+ """Resolve path relative to base_path with protocol-specific handling.
28
+
29
+ Args:
30
+ path: Path to resolve (may include file:// scheme).
31
+ base_path: Base path to prepend if path is relative.
32
+ protocol: Storage protocol (file, s3, gs, etc.).
33
+ strip_file_scheme: Whether to strip file:// prefix.
34
+
35
+ Returns:
36
+ Resolved path string suitable for the storage backend.
37
+
38
+ Examples:
39
+ >>> resolve_storage_path("/data/file.txt", protocol="file")
40
+ 'data/file.txt'
41
+
42
+ >>> resolve_storage_path(
43
+ ... "file.txt", base_path="/base", protocol="file"
44
+ ... )
45
+ 'base/file.txt'
46
+
47
+ >>> resolve_storage_path(
48
+ ... "file:///data/file.txt", strip_file_scheme=True
49
+ ... )
50
+ 'data/file.txt'
51
+
52
+ >>> resolve_storage_path(
53
+ ... "/data/subdir/file.txt",
54
+ ... base_path="/data",
55
+ ... protocol="file",
56
+ ... )
57
+ 'subdir/file.txt'
58
+ """
59
+ from pathlib import Path as PathlibPath
60
+
61
+ path_str = str(path)
62
+
63
+ if strip_file_scheme and path_str.startswith("file://"):
64
+ path_str = path_str.removeprefix("file://")
65
+
66
+ # For local file protocol
67
+ if protocol == "file":
68
+ path_obj = PathlibPath(path_str)
69
+
70
+ # Absolute path handling
71
+ if path_obj.is_absolute():
72
+ if base_path:
73
+ base_obj = PathlibPath(base_path)
74
+ # Try to make path relative to base_path
75
+ try:
76
+ relative = path_obj.relative_to(base_obj)
77
+ # Return joined path for FSSpec-style backends
78
+ return f"{base_path.rstrip('/')}/{relative}"
79
+ except ValueError:
80
+ # Path is outside base_path
81
+ return path_str.lstrip("/")
82
+ # No base_path - strip leading /
83
+ return path_str.lstrip("/")
84
+
85
+ # Relative path with base_path - join them
86
+ if base_path:
87
+ return f"{base_path.rstrip('/')}/{path_str}"
88
+
89
+ # Relative path without base_path
90
+ return path_str
91
+
92
+ # For cloud storage protocols (s3, gs, etc.), join with base_path
93
+ if not base_path:
94
+ return path_str
95
+
96
+ clean_base = base_path.rstrip("/")
97
+ clean_path = path_str.lstrip("/")
98
+ return f"{clean_base}/{clean_path}"