sqlspec 0.26.0__py3-none-any.whl → 0.28.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 (212) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +155 -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 +880 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +74 -2
  9. sqlspec/adapters/adbc/driver.py +226 -58
  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 +536 -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 +503 -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 +460 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +48 -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 +585 -0
  44. sqlspec/adapters/bigquery/config.py +36 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +489 -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 +563 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +225 -44
  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 +1628 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +475 -86
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +765 -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 +483 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
  77. sqlspec/adapters/psqlpy/driver.py +108 -41
  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 +962 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +91 -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 +582 -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 +331 -62
  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 +55 -47
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +234 -47
  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 +183 -160
  129. sqlspec/driver/_common.py +197 -109
  130. sqlspec/driver/_sync.py +189 -161
  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 +69 -61
  142. sqlspec/extensions/fastapi/__init__.py +21 -0
  143. sqlspec/extensions/fastapi/extension.py +331 -0
  144. sqlspec/extensions/fastapi/providers.py +543 -0
  145. sqlspec/extensions/flask/__init__.py +36 -0
  146. sqlspec/extensions/flask/_state.py +71 -0
  147. sqlspec/extensions/flask/_utils.py +40 -0
  148. sqlspec/extensions/flask/extension.py +389 -0
  149. sqlspec/extensions/litestar/__init__.py +21 -4
  150. sqlspec/extensions/litestar/cli.py +54 -10
  151. sqlspec/extensions/litestar/config.py +56 -266
  152. sqlspec/extensions/litestar/handlers.py +46 -17
  153. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  154. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  155. sqlspec/extensions/litestar/plugin.py +349 -224
  156. sqlspec/extensions/litestar/providers.py +25 -25
  157. sqlspec/extensions/litestar/store.py +265 -0
  158. sqlspec/extensions/starlette/__init__.py +10 -0
  159. sqlspec/extensions/starlette/_state.py +25 -0
  160. sqlspec/extensions/starlette/_utils.py +52 -0
  161. sqlspec/extensions/starlette/extension.py +254 -0
  162. sqlspec/extensions/starlette/middleware.py +154 -0
  163. sqlspec/loader.py +30 -49
  164. sqlspec/migrations/base.py +200 -76
  165. sqlspec/migrations/commands.py +591 -62
  166. sqlspec/migrations/context.py +6 -9
  167. sqlspec/migrations/fix.py +199 -0
  168. sqlspec/migrations/loaders.py +47 -19
  169. sqlspec/migrations/runner.py +241 -75
  170. sqlspec/migrations/tracker.py +237 -21
  171. sqlspec/migrations/utils.py +51 -3
  172. sqlspec/migrations/validation.py +177 -0
  173. sqlspec/protocols.py +106 -36
  174. sqlspec/storage/_utils.py +85 -0
  175. sqlspec/storage/backends/fsspec.py +133 -107
  176. sqlspec/storage/backends/local.py +78 -51
  177. sqlspec/storage/backends/obstore.py +276 -168
  178. sqlspec/storage/registry.py +75 -39
  179. sqlspec/typing.py +30 -84
  180. sqlspec/utils/__init__.py +25 -4
  181. sqlspec/utils/arrow_helpers.py +81 -0
  182. sqlspec/utils/config_resolver.py +6 -6
  183. sqlspec/utils/correlation.py +4 -5
  184. sqlspec/utils/data_transformation.py +3 -2
  185. sqlspec/utils/deprecation.py +9 -8
  186. sqlspec/utils/fixtures.py +4 -4
  187. sqlspec/utils/logging.py +46 -6
  188. sqlspec/utils/module_loader.py +205 -5
  189. sqlspec/utils/portal.py +311 -0
  190. sqlspec/utils/schema.py +288 -0
  191. sqlspec/utils/serializers.py +113 -4
  192. sqlspec/utils/sync_tools.py +36 -22
  193. sqlspec/utils/text.py +1 -2
  194. sqlspec/utils/type_guards.py +136 -20
  195. sqlspec/utils/version.py +433 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
  197. sqlspec-0.28.0.dist-info/RECORD +221 -0
  198. sqlspec/builder/mixins/__init__.py +0 -55
  199. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  200. sqlspec/builder/mixins/_delete_operations.py +0 -50
  201. sqlspec/builder/mixins/_insert_operations.py +0 -282
  202. sqlspec/builder/mixins/_merge_operations.py +0 -698
  203. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  204. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  205. sqlspec/builder/mixins/_select_operations.py +0 -930
  206. sqlspec/builder/mixins/_update_operations.py +0 -199
  207. sqlspec/builder/mixins/_where_clause.py +0 -1298
  208. sqlspec-0.26.0.dist-info/RECORD +0 -157
  209. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  210. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
  211. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
  212. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/core/result.py CHANGED
@@ -10,17 +10,20 @@ Classes:
10
10
  """
11
11
 
12
12
  from abc import ABC, abstractmethod
13
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
13
+ from typing import TYPE_CHECKING, Any, Optional, cast, overload
14
14
 
15
15
  from mypy_extensions import mypyc_attr
16
16
  from typing_extensions import TypeVar
17
17
 
18
18
  from sqlspec.core.compiler import OperationType
19
+ from sqlspec.utils.module_loader import ensure_pandas, ensure_polars, ensure_pyarrow
20
+ from sqlspec.utils.schema import to_schema
19
21
 
20
22
  if TYPE_CHECKING:
21
23
  from collections.abc import Iterator
22
24
 
23
25
  from sqlspec.core.statement import SQL
26
+ from sqlspec.typing import ArrowTable, PandasDataFrame, PolarsDataFrame, SchemaT
24
27
 
25
28
 
26
29
  __all__ = ("ArrowResult", "SQLResult", "StatementResult")
@@ -52,8 +55,8 @@ class StatementResult(ABC):
52
55
  statement: "SQL",
53
56
  data: Any = None,
54
57
  rows_affected: int = 0,
55
- last_inserted_id: Optional[Union[int, str]] = None,
56
- execution_time: Optional[float] = None,
58
+ last_inserted_id: int | str | None = None,
59
+ execution_time: float | None = None,
57
60
  metadata: Optional["dict[str, Any]"] = None,
58
61
  ) -> None:
59
62
  """Initialize statement result.
@@ -129,19 +132,6 @@ class SQLResult(StatementResult):
129
132
  The operation_type attribute indicates the nature of the operation.
130
133
 
131
134
  For script execution, tracks multiple statement results and errors.
132
-
133
- Attributes:
134
- column_names: Names of columns in the result set.
135
- error: Exception that occurred during execution.
136
- errors: List of error messages for script execution.
137
- has_more: Whether there are additional result pages available.
138
- inserted_ids: List of IDs from INSERT operations.
139
- operation_index: Index of operation in a script.
140
- parameters: Parameters used for the query.
141
- statement_results: Results from individual statements in a script.
142
- successful_statements: Count of successful statements in a script.
143
- total_count: Total number of rows in the complete result set.
144
- total_statements: Total number of statements in a script.
145
135
  """
146
136
 
147
137
  __slots__ = (
@@ -164,19 +154,19 @@ class SQLResult(StatementResult):
164
154
  def __init__(
165
155
  self,
166
156
  statement: "SQL",
167
- data: Optional[list[dict[str, Any]]] = None,
157
+ data: list[dict[str, Any]] | None = None,
168
158
  rows_affected: int = 0,
169
- last_inserted_id: Optional[Union[int, str]] = None,
170
- execution_time: Optional[float] = None,
159
+ last_inserted_id: int | str | None = None,
160
+ execution_time: float | None = None,
171
161
  metadata: Optional["dict[str, Any]"] = None,
172
- error: Optional[Exception] = None,
162
+ error: Exception | None = None,
173
163
  operation_type: OperationType = "SELECT",
174
- operation_index: Optional[int] = None,
175
- parameters: Optional[Any] = None,
164
+ operation_index: int | None = None,
165
+ parameters: Any | None = None,
176
166
  column_names: Optional["list[str]"] = None,
177
- total_count: Optional[int] = None,
167
+ total_count: int | None = None,
178
168
  has_more: bool = False,
179
- inserted_ids: Optional["list[Union[int, str]]"] = None,
169
+ inserted_ids: Optional["list[int | str]"] = None,
180
170
  statement_results: Optional["list[SQLResult]"] = None,
181
171
  errors: Optional["list[str]"] = None,
182
172
  total_statements: int = 0,
@@ -280,15 +270,25 @@ class SQLResult(StatementResult):
280
270
 
281
271
  return False
282
272
 
283
- def get_data(self) -> "list[dict[str,Any]]":
273
+ @overload
274
+ def get_data(self, *, schema_type: "type[SchemaT]") -> "list[SchemaT]": ...
275
+
276
+ @overload
277
+ def get_data(self, *, schema_type: None = None) -> "list[dict[str, Any]]": ...
278
+
279
+ def get_data(self, *, schema_type: "type[SchemaT] | None" = None) -> "list[SchemaT] | list[dict[str, Any]]":
284
280
  """Get the data from the result.
285
281
 
286
282
  For regular operations, returns the list of rows.
287
283
  For script operations, returns a summary dictionary containing
288
284
  execution statistics and results.
289
285
 
286
+ Args:
287
+ schema_type: Optional schema type to transform the data into.
288
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
289
+
290
290
  Returns:
291
- List of result rows or script summary.
291
+ List of result rows (optionally transformed to schema_type) or script summary.
292
292
  """
293
293
  op_type_upper = self.operation_type.upper()
294
294
  if op_type_upper == "SCRIPT":
@@ -303,7 +303,10 @@ class SQLResult(StatementResult):
303
303
  "total_rows_affected": self.get_total_rows_affected(),
304
304
  }
305
305
  ]
306
- return self.data or []
306
+ data = cast("list[dict[str, Any]]", self.data or [])
307
+ if schema_type:
308
+ return cast("list[SchemaT]", to_schema(data, schema_type=schema_type))
309
+ return data
307
310
 
308
311
  def add_statement_result(self, result: "SQLResult") -> None:
309
312
  """Add a statement result to the script execution results.
@@ -348,13 +351,28 @@ class SQLResult(StatementResult):
348
351
  """
349
352
  return len(self.column_names) if self.column_names else 0
350
353
 
351
- def get_first(self) -> "Optional[dict[str, Any]]":
354
+ @overload
355
+ def get_first(self, *, schema_type: "type[SchemaT]") -> "SchemaT | None": ...
356
+
357
+ @overload
358
+ def get_first(self, *, schema_type: None = None) -> "dict[str, Any] | None": ...
359
+
360
+ def get_first(self, *, schema_type: "type[SchemaT] | None" = None) -> "SchemaT | dict[str, Any] | None":
352
361
  """Get the first row from the result, if any.
353
362
 
363
+ Args:
364
+ schema_type: Optional schema type to transform the data into.
365
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
366
+
354
367
  Returns:
355
- First row or None if no data.
368
+ First row (optionally transformed to schema_type) or None if no data.
356
369
  """
357
- return self.data[0] if self.data else None
370
+ if not self.data:
371
+ return None
372
+ row = cast("dict[str, Any]", self.data[0])
373
+ if schema_type:
374
+ return to_schema(row, schema_type=schema_type)
375
+ return row
358
376
 
359
377
  def get_count(self) -> int:
360
378
  """Get the number of rows in the current result set (e.g., a page of data).
@@ -434,19 +452,42 @@ class SQLResult(StatementResult):
434
452
  """
435
453
  return iter(self.data or [])
436
454
 
437
- def all(self) -> list[dict[str, Any]]:
455
+ @overload
456
+ def all(self, *, schema_type: "type[SchemaT]") -> "list[SchemaT]": ...
457
+
458
+ @overload
459
+ def all(self, *, schema_type: None = None) -> list[dict[str, Any]]: ...
460
+
461
+ def all(self, *, schema_type: "type[SchemaT] | None" = None) -> "list[SchemaT] | list[dict[str, Any]]":
438
462
  """Return all rows as a list.
439
463
 
464
+ Args:
465
+ schema_type: Optional schema type to transform the data into.
466
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
467
+
440
468
  Returns:
441
- List of all rows in the result
469
+ List of all rows (optionally transformed to schema_type)
442
470
  """
443
- return self.data or []
471
+ data = cast("list[dict[str, Any]]", self.data or [])
472
+ if schema_type:
473
+ return cast("list[SchemaT]", to_schema(data, schema_type=schema_type))
474
+ return data
475
+
476
+ @overload
477
+ def one(self, *, schema_type: "type[SchemaT]") -> "SchemaT": ...
478
+
479
+ @overload
480
+ def one(self, *, schema_type: None = None) -> "dict[str, Any]": ...
444
481
 
445
- def one(self) -> "dict[str, Any]":
482
+ def one(self, *, schema_type: "type[SchemaT] | None" = None) -> "SchemaT | dict[str, Any]":
446
483
  """Return exactly one row.
447
484
 
485
+ Args:
486
+ schema_type: Optional schema type to transform the data into.
487
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
488
+
448
489
  Returns:
449
- The single row
490
+ The single row (optionally transformed to schema_type)
450
491
 
451
492
  Raises:
452
493
  ValueError: If no results or more than one result
@@ -463,13 +504,26 @@ class SQLResult(StatementResult):
463
504
  msg = f"Multiple results found ({data_len}), exactly one row expected"
464
505
  raise ValueError(msg)
465
506
 
466
- return cast("dict[str, Any]", self.data[0])
507
+ row = cast("dict[str, Any]", self.data[0])
508
+ if schema_type:
509
+ return to_schema(row, schema_type=schema_type)
510
+ return row
511
+
512
+ @overload
513
+ def one_or_none(self, *, schema_type: "type[SchemaT]") -> "SchemaT | None": ...
467
514
 
468
- def one_or_none(self) -> "Optional[dict[str, Any]]":
515
+ @overload
516
+ def one_or_none(self, *, schema_type: None = None) -> "dict[str, Any] | None": ...
517
+
518
+ def one_or_none(self, *, schema_type: "type[SchemaT] | None" = None) -> "SchemaT | dict[str, Any] | None":
469
519
  """Return at most one row.
470
520
 
521
+ Args:
522
+ schema_type: Optional schema type to transform the data into.
523
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
524
+
471
525
  Returns:
472
- The single row or None if no results
526
+ The single row (optionally transformed to schema_type) or None if no results
473
527
 
474
528
  Raises:
475
529
  ValueError: If more than one result
@@ -484,7 +538,10 @@ class SQLResult(StatementResult):
484
538
  msg = f"Multiple results found ({data_len}), at most one row expected"
485
539
  raise ValueError(msg)
486
540
 
487
- return cast("dict[str, Any]", self.data[0])
541
+ row = cast("dict[str, Any]", self.data[0])
542
+ if schema_type:
543
+ return to_schema(row, schema_type=schema_type)
544
+ return row
488
545
 
489
546
  def scalar(self) -> Any:
490
547
  """Return the first column of the first row.
@@ -527,8 +584,8 @@ class ArrowResult(StatementResult):
527
584
  statement: "SQL",
528
585
  data: Any,
529
586
  rows_affected: int = 0,
530
- last_inserted_id: Optional[Union[int, str]] = None,
531
- execution_time: Optional[float] = None,
587
+ last_inserted_id: int | str | None = None,
588
+ execution_time: float | None = None,
532
589
  metadata: Optional["dict[str, Any]"] = None,
533
590
  schema: Optional["dict[str, Any]"] = None,
534
591
  ) -> None:
@@ -562,7 +619,7 @@ class ArrowResult(StatementResult):
562
619
  """
563
620
  return self.data is not None
564
621
 
565
- def get_data(self) -> Any:
622
+ def get_data(self) -> "ArrowTable":
566
623
  """Get the Apache Arrow Table from the result.
567
624
 
568
625
  Returns:
@@ -570,10 +627,19 @@ class ArrowResult(StatementResult):
570
627
 
571
628
  Raises:
572
629
  ValueError: If no Arrow table is available.
630
+ TypeError: If data is not an Arrow Table.
573
631
  """
574
632
  if self.data is None:
575
633
  msg = "No Arrow table available for this result"
576
634
  raise ValueError(msg)
635
+
636
+ ensure_pyarrow()
637
+
638
+ import pyarrow as pa
639
+
640
+ if not isinstance(self.data, pa.Table):
641
+ msg = f"Expected an Arrow Table, but got {type(self.data).__name__}"
642
+ raise TypeError(msg)
577
643
  return self.data
578
644
 
579
645
  @property
@@ -624,13 +690,134 @@ class ArrowResult(StatementResult):
624
690
 
625
691
  return cast("int", self.data.num_columns)
626
692
 
693
+ def to_pandas(self) -> "PandasDataFrame":
694
+ """Convert Arrow data to pandas DataFrame.
695
+
696
+ Returns:
697
+ pandas DataFrame containing the result data.
698
+
699
+ Raises:
700
+ ValueError: If no Arrow table is available.
701
+
702
+ Examples:
703
+ >>> result = session.select_to_arrow("SELECT * FROM users")
704
+ >>> df = result.to_pandas()
705
+ >>> print(df.head())
706
+ """
707
+ if self.data is None:
708
+ msg = "No Arrow table available"
709
+ raise ValueError(msg)
710
+
711
+ ensure_pandas()
712
+
713
+ import pandas as pd
714
+
715
+ result = self.data.to_pandas()
716
+ if not isinstance(result, pd.DataFrame):
717
+ msg = f"Expected a pandas DataFrame, but got {type(result).__name__}"
718
+ raise TypeError(msg)
719
+ return result
720
+
721
+ def to_polars(self) -> "PolarsDataFrame":
722
+ """Convert Arrow data to Polars DataFrame.
723
+
724
+ Returns:
725
+ Polars DataFrame containing the result data.
726
+
727
+ Raises:
728
+ ValueError: If no Arrow table is available.
729
+
730
+ Examples:
731
+ >>> result = session.select_to_arrow("SELECT * FROM users")
732
+ >>> df = result.to_polars()
733
+ >>> print(df.head())
734
+ """
735
+ if self.data is None:
736
+ msg = "No Arrow table available"
737
+ raise ValueError(msg)
738
+
739
+ ensure_polars()
740
+
741
+ import polars as pl
742
+
743
+ result = pl.from_arrow(self.data)
744
+ if not isinstance(result, pl.DataFrame):
745
+ msg = f"Expected a Polars DataFrame, but got {type(result).__name__}"
746
+ raise TypeError(msg)
747
+ return result
748
+
749
+ def to_dict(self) -> "list[dict[str, Any]]":
750
+ """Convert Arrow data to list of dictionaries.
751
+
752
+ Returns:
753
+ List of dictionaries, one per row.
754
+
755
+ Raises:
756
+ ValueError: If no Arrow table is available.
757
+
758
+ Examples:
759
+ >>> result = session.select_to_arrow(
760
+ ... "SELECT id, name FROM users"
761
+ ... )
762
+ >>> rows = result.to_dict()
763
+ >>> print(rows[0])
764
+ {'id': 1, 'name': 'Alice'}
765
+ """
766
+ if self.data is None:
767
+ msg = "No Arrow table available"
768
+ raise ValueError(msg)
769
+
770
+ return cast("list[dict[str, Any]]", self.data.to_pylist())
771
+
772
+ def __len__(self) -> int:
773
+ """Return number of rows in the Arrow table.
774
+
775
+ Returns:
776
+ Number of rows.
777
+
778
+ Raises:
779
+ ValueError: If no Arrow table is available.
780
+
781
+ Examples:
782
+ >>> result = session.select_to_arrow("SELECT * FROM users")
783
+ >>> print(len(result))
784
+ 100
785
+ """
786
+ if self.data is None:
787
+ msg = "No Arrow table available"
788
+ raise ValueError(msg)
789
+
790
+ return cast("int", self.data.num_rows)
791
+
792
+ def __iter__(self) -> "Iterator[dict[str, Any]]":
793
+ """Iterate over rows as dictionaries.
794
+
795
+ Yields:
796
+ Dictionary for each row.
797
+
798
+ Raises:
799
+ ValueError: If no Arrow table is available.
800
+
801
+ Examples:
802
+ >>> result = session.select_to_arrow(
803
+ ... "SELECT id, name FROM users"
804
+ ... )
805
+ >>> for row in result:
806
+ ... print(row["name"])
807
+ """
808
+ if self.data is None:
809
+ msg = "No Arrow table available"
810
+ raise ValueError(msg)
811
+
812
+ yield from self.data.to_pylist()
813
+
627
814
 
628
815
  def create_sql_result(
629
816
  statement: "SQL",
630
- data: Optional[list[dict[str, Any]]] = None,
817
+ data: list[dict[str, Any]] | None = None,
631
818
  rows_affected: int = 0,
632
- last_inserted_id: Optional[Union[int, str]] = None,
633
- execution_time: Optional[float] = None,
819
+ last_inserted_id: int | str | None = None,
820
+ execution_time: float | None = None,
634
821
  metadata: Optional["dict[str, Any]"] = None,
635
822
  **kwargs: Any,
636
823
  ) -> SQLResult:
@@ -663,8 +850,8 @@ def create_arrow_result(
663
850
  statement: "SQL",
664
851
  data: Any,
665
852
  rows_affected: int = 0,
666
- last_inserted_id: Optional[Union[int, str]] = None,
667
- execution_time: Optional[float] = None,
853
+ last_inserted_id: int | str | None = None,
854
+ execution_time: float | None = None,
668
855
  metadata: Optional["dict[str, Any]"] = None,
669
856
  schema: Optional["dict[str, Any]"] = None,
670
857
  ) -> ArrowResult:
sqlspec/core/splitter.py CHANGED
@@ -16,13 +16,12 @@ MySQL, SQLite, DuckDB, and BigQuery.
16
16
  import re
17
17
  import threading
18
18
  from abc import ABC, abstractmethod
19
- from collections.abc import Generator
19
+ from collections.abc import Callable, Generator
20
20
  from enum import Enum
21
21
  from re import Pattern
22
- from typing import Any, Callable, Final, Optional, Union, cast
22
+ from typing import Any, Final, TypeAlias, cast
23
23
 
24
24
  from mypy_extensions import mypyc_attr
25
- from typing_extensions import TypeAlias
26
25
 
27
26
  from sqlspec.core.cache import CacheKey, UnifiedCache
28
27
  from sqlspec.utils.logging import get_logger
@@ -98,9 +97,9 @@ class Token:
98
97
  return f"Token({self.type.value}, {self.value!r}, {self.line}:{self.column})"
99
98
 
100
99
 
101
- TokenHandler: TypeAlias = Callable[[str, int, int, int], Optional[Token]]
102
- TokenPattern: TypeAlias = Union[str, TokenHandler]
103
- CompiledTokenPattern: TypeAlias = Union[Pattern[str], TokenHandler]
100
+ TokenHandler: TypeAlias = Callable[[str, int, int, int], Token | None]
101
+ TokenPattern: TypeAlias = str | TokenHandler
102
+ CompiledTokenPattern: TypeAlias = Pattern[str] | TokenHandler
104
103
 
105
104
 
106
105
  @mypyc_attr(allow_interpreted_subclasses=False)
@@ -111,13 +110,13 @@ class DialectConfig(ABC):
111
110
 
112
111
  def __init__(self) -> None:
113
112
  """Initialize dialect configuration."""
114
- self._name: Optional[str] = None
115
- self._block_starters: Optional[set[str]] = None
116
- self._block_enders: Optional[set[str]] = None
117
- self._statement_terminators: Optional[set[str]] = None
118
- self._batch_separators: Optional[set[str]] = None
119
- self._special_terminators: Optional[dict[str, Callable[[list[Token], int], bool]]] = None
120
- self._max_nesting_depth: Optional[int] = None
113
+ self._name: str | None = None
114
+ self._block_starters: set[str] | None = None
115
+ self._block_enders: set[str] | None = None
116
+ self._statement_terminators: set[str] | None = None
117
+ self._batch_separators: set[str] | None = None
118
+ self._special_terminators: dict[str, Callable[[list[Token], int], bool]] | None = None
119
+ self._max_nesting_depth: int | None = None
121
120
 
122
121
  @property
123
122
  @abstractmethod
@@ -436,7 +435,7 @@ class PostgreSQLDialectConfig(DialectConfig):
436
435
  return [(TokenType.STRING_LITERAL, self._handle_dollar_quoted_string)]
437
436
 
438
437
  @staticmethod
439
- def _handle_dollar_quoted_string(text: str, position: int, line: int, column: int) -> Optional[Token]:
438
+ def _handle_dollar_quoted_string(text: str, position: int, line: int, column: int) -> Token | None:
440
439
  """Handle PostgreSQL dollar-quoted string literals.
441
440
 
442
441
  Parses dollar-quoted strings in the format $tag$content$tag$
@@ -613,8 +612,8 @@ class BigQueryDialectConfig(DialectConfig):
613
612
  return self._statement_terminators
614
613
 
615
614
 
616
- _pattern_cache: Optional[UnifiedCache] = None
617
- _result_cache: Optional[UnifiedCache] = None
615
+ _pattern_cache: UnifiedCache | None = None
616
+ _result_cache: UnifiedCache | None = None
618
617
  _cache_lock = threading.Lock()
619
618
 
620
619
 
@@ -878,7 +877,7 @@ class StatementSplitter:
878
877
  return False
879
878
 
880
879
 
881
- def split_sql_script(script: str, dialect: Optional[str] = None, strip_trailing_terminator: bool = False) -> list[str]:
880
+ def split_sql_script(script: str, dialect: str | None = None, strip_trailing_terminator: bool = False) -> list[str]:
882
881
  """Split SQL script into individual statements.
883
882
 
884
883
  Args: