sqlspec 0.25.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 (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -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 +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -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 +153 -0
  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 +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  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 +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  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 +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  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 +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  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 +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  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 +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  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 +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  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 +102 -0
  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 +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  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 +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -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/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/core/statement.py CHANGED
@@ -1,21 +1,22 @@
1
1
  """SQL statement and configuration management."""
2
2
 
3
- from typing import TYPE_CHECKING, Any, Callable, Final, Optional, Union
3
+ from typing import TYPE_CHECKING, Any, Final, Optional, TypeAlias
4
4
 
5
5
  import sqlglot
6
6
  from mypy_extensions import mypyc_attr
7
7
  from sqlglot import exp
8
8
  from sqlglot.errors import ParseError
9
- from typing_extensions import TypeAlias
10
9
 
10
+ import sqlspec.exceptions
11
11
  from sqlspec.core.compiler import OperationType, SQLProcessor
12
12
  from sqlspec.core.parameters import ParameterConverter, ParameterStyle, ParameterStyleConfig, ParameterValidator
13
- from sqlspec.exceptions import SQLSpecError
14
13
  from sqlspec.typing import Empty, EmptyEnum
15
14
  from sqlspec.utils.logging import get_logger
16
15
  from sqlspec.utils.type_guards import is_statement_filter, supports_where
17
16
 
18
17
  if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
19
20
  from sqlglot.dialects.dialect import DialectType
20
21
 
21
22
  from sqlspec.core.cache import FiltersView
@@ -59,6 +60,7 @@ PROCESSED_STATE_SLOTS: Final = (
59
60
  "execution_parameters",
60
61
  "parsed_expression",
61
62
  "operation_type",
63
+ "parameter_casts",
62
64
  "validation_errors",
63
65
  "is_many",
64
66
  )
@@ -79,15 +81,17 @@ class ProcessedState:
79
81
  self,
80
82
  compiled_sql: str,
81
83
  execution_parameters: Any,
82
- parsed_expression: "Optional[exp.Expression]" = None,
84
+ parsed_expression: "exp.Expression | None" = None,
83
85
  operation_type: "OperationType" = "UNKNOWN",
84
- validation_errors: "Optional[list[str]]" = None,
86
+ parameter_casts: "dict[int, str] | None" = None,
87
+ validation_errors: "list[str] | None" = None,
85
88
  is_many: bool = False,
86
89
  ) -> None:
87
90
  self.compiled_sql = compiled_sql
88
91
  self.execution_parameters = execution_parameters
89
92
  self.parsed_expression = parsed_expression
90
93
  self.operation_type = operation_type
94
+ self.parameter_casts = parameter_casts or {}
91
95
  self.validation_errors = validation_errors or []
92
96
  self.is_many = is_many
93
97
 
@@ -120,10 +124,10 @@ class SQL:
120
124
 
121
125
  def __init__(
122
126
  self,
123
- statement: "Union[str, exp.Expression, 'SQL']",
124
- *parameters: "Union[Any, StatementFilter, list[Union[Any, StatementFilter]]]",
127
+ statement: "str | exp.Expression | 'SQL'",
128
+ *parameters: "Any | StatementFilter | list[Any | StatementFilter]",
125
129
  statement_config: Optional["StatementConfig"] = None,
126
- is_many: Optional[bool] = None,
130
+ is_many: bool | None = None,
127
131
  **kwargs: Any,
128
132
  ) -> None:
129
133
  """Initialize SQL statement.
@@ -138,8 +142,8 @@ class SQL:
138
142
  config = statement_config or self._create_auto_config(statement, parameters, kwargs)
139
143
  self._statement_config = config
140
144
  self._dialect = self._normalize_dialect(config.dialect)
141
- self._processed_state: Union[EmptyEnum, ProcessedState] = Empty
142
- self._hash: Optional[int] = None
145
+ self._processed_state: EmptyEnum | ProcessedState = Empty
146
+ self._hash: int | None = None
143
147
  self._filters: list[StatementFilter] = []
144
148
  self._named_parameters: dict[str, Any] = {}
145
149
  self._positional_parameters: list[Any] = []
@@ -162,7 +166,7 @@ class SQL:
162
166
  self._process_parameters(*parameters, **kwargs)
163
167
 
164
168
  def _create_auto_config(
165
- self, _statement: "Union[str, exp.Expression, 'SQL']", _parameters: tuple, _kwargs: dict[str, Any]
169
+ self, _statement: "str | exp.Expression | 'SQL'", _parameters: tuple, _kwargs: dict[str, Any]
166
170
  ) -> "StatementConfig":
167
171
  """Create default StatementConfig when none provided.
168
172
 
@@ -176,7 +180,7 @@ class SQL:
176
180
  """
177
181
  return get_default_config()
178
182
 
179
- def _normalize_dialect(self, dialect: "Optional[DialectType]") -> "Optional[str]":
183
+ def _normalize_dialect(self, dialect: "DialectType | None") -> "str | None":
180
184
  """Convert dialect to string representation.
181
185
 
182
186
  Args:
@@ -221,7 +225,7 @@ class SQL:
221
225
  return len(param_list) > 1
222
226
  return False
223
227
 
224
- def _process_parameters(self, *parameters: Any, dialect: Optional[str] = None, **kwargs: Any) -> None:
228
+ def _process_parameters(self, *parameters: Any, dialect: str | None = None, **kwargs: Any) -> None:
225
229
  """Process and organize parameters and filters.
226
230
 
227
231
  Args:
@@ -315,7 +319,7 @@ class SQL:
315
319
  return self._statement_config
316
320
 
317
321
  @property
318
- def expression(self) -> "Optional[exp.Expression]":
322
+ def expression(self) -> "exp.Expression | None":
319
323
  """SQLGlot expression."""
320
324
  if self._processed_state is not Empty:
321
325
  return self._processed_state.parsed_expression
@@ -346,17 +350,17 @@ class SQL:
346
350
  return self._processed_state
347
351
 
348
352
  @property
349
- def dialect(self) -> "Optional[str]":
353
+ def dialect(self) -> "str | None":
350
354
  """SQL dialect."""
351
355
  return self._dialect
352
356
 
353
357
  @property
354
- def _statement(self) -> "Optional[exp.Expression]":
358
+ def _statement(self) -> "exp.Expression | None":
355
359
  """Internal SQLGlot expression."""
356
360
  return self.expression
357
361
 
358
362
  @property
359
- def statement_expression(self) -> "Optional[exp.Expression]":
363
+ def statement_expression(self) -> "exp.Expression | None":
360
364
  """Get parsed statement expression (public API).
361
365
 
362
366
  Returns:
@@ -447,10 +451,11 @@ class SQL:
447
451
  execution_parameters=compiled_result.execution_parameters,
448
452
  parsed_expression=compiled_result.expression,
449
453
  operation_type=compiled_result.operation_type,
454
+ parameter_casts=compiled_result.parameter_casts,
450
455
  validation_errors=[],
451
456
  is_many=self._is_many,
452
457
  )
453
- except SQLSpecError:
458
+ except sqlspec.exceptions.SQLSpecError:
454
459
  raise
455
460
  except Exception as e:
456
461
  logger.warning("Processing failed, using fallback: %s", e)
@@ -458,6 +463,7 @@ class SQL:
458
463
  compiled_sql=self._raw_sql,
459
464
  execution_parameters=self._named_parameters or self._positional_parameters,
460
465
  operation_type="UNKNOWN",
466
+ parameter_casts={},
461
467
  is_many=self._is_many,
462
468
  )
463
469
 
@@ -480,7 +486,7 @@ class SQL:
480
486
  return new_sql
481
487
 
482
488
  def copy(
483
- self, statement: "Optional[Union[str, exp.Expression]]" = None, parameters: Optional[Any] = None, **kwargs: Any
489
+ self, statement: "str | exp.Expression | None" = None, parameters: Any | None = None, **kwargs: Any
484
490
  ) -> "SQL":
485
491
  """Create copy with modifications.
486
492
 
@@ -525,7 +531,7 @@ class SQL:
525
531
  new_sql._filters = self._filters.copy()
526
532
  return new_sql
527
533
 
528
- def where(self, condition: "Union[str, exp.Expression]") -> "SQL":
534
+ def where(self, condition: "str | exp.Expression") -> "SQL":
529
535
  """Add WHERE condition to the SQL statement.
530
536
 
531
537
  Args:
@@ -618,7 +624,7 @@ class StatementConfig:
618
624
 
619
625
  def __init__(
620
626
  self,
621
- parameter_config: "Optional[ParameterStyleConfig]" = None,
627
+ parameter_config: "ParameterStyleConfig | None" = None,
622
628
  enable_parsing: bool = True,
623
629
  enable_validation: bool = True,
624
630
  enable_transformations: bool = True,
@@ -626,14 +632,14 @@ class StatementConfig:
626
632
  enable_expression_simplification: bool = False,
627
633
  enable_parameter_type_wrapping: bool = True,
628
634
  enable_caching: bool = True,
629
- parameter_converter: "Optional[ParameterConverter]" = None,
630
- parameter_validator: "Optional[ParameterValidator]" = None,
631
- dialect: "Optional[DialectType]" = None,
632
- pre_process_steps: "Optional[list[Any]]" = None,
633
- post_process_steps: "Optional[list[Any]]" = None,
634
- execution_mode: "Optional[str]" = None,
635
- execution_args: "Optional[dict[str, Any]]" = None,
636
- output_transformer: "Optional[Callable[[str, Any], tuple[str, Any]]]" = None,
635
+ parameter_converter: "ParameterConverter | None" = None,
636
+ parameter_validator: "ParameterValidator | None" = None,
637
+ dialect: "DialectType | None" = None,
638
+ pre_process_steps: "list[Any] | None" = None,
639
+ post_process_steps: "list[Any] | None" = None,
640
+ execution_mode: "str | None" = None,
641
+ execution_args: "dict[str, Any] | None" = None,
642
+ output_transformer: "Callable[[str, Any], tuple[str, Any]] | None" = None,
637
643
  ) -> None:
638
644
  """Initialize StatementConfig.
639
645
 
@@ -800,4 +806,4 @@ def get_default_parameter_config() -> ParameterStyleConfig:
800
806
  )
801
807
 
802
808
 
803
- Statement: TypeAlias = Union[str, exp.Expression, SQL]
809
+ Statement: TypeAlias = str | exp.Expression | SQL
@@ -0,0 +1,235 @@
1
+ """Centralized type conversion and detection for SQLSpec.
2
+
3
+ Provides unified type detection and conversion utilities for all database
4
+ adapters, with MyPyC-compatible optimizations.
5
+ """
6
+
7
+ import re
8
+ from collections.abc import Callable
9
+ from datetime import date, datetime, time, timezone
10
+ from decimal import Decimal
11
+ from typing import Any, Final
12
+ from uuid import UUID
13
+
14
+ from sqlspec._serialization import decode_json
15
+
16
+ # MyPyC-compatible pre-compiled patterns
17
+ SPECIAL_TYPE_REGEX: Final[re.Pattern[str]] = re.compile(
18
+ r"^(?:"
19
+ r"(?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|"
20
+ r"(?P<iso_datetime>\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)|"
21
+ r"(?P<iso_date>\d{4}-\d{2}-\d{2})|"
22
+ r"(?P<iso_time>\d{2}:\d{2}:\d{2}(?:\.\d+)?)|"
23
+ r"(?P<json>[\[{].*[\]}])|"
24
+ r"(?P<ipv4>(?:\d{1,3}\.){3}\d{1,3})|"
25
+ r"(?P<ipv6>(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4})|"
26
+ r"(?P<mac>(?:[0-9a-f]{2}:){5}[0-9a-f]{2})"
27
+ r")$",
28
+ re.IGNORECASE | re.DOTALL,
29
+ )
30
+
31
+
32
+ class BaseTypeConverter:
33
+ """Universal type detection and conversion for all adapters.
34
+
35
+ Provides centralized type detection and conversion functionality
36
+ that can be used across all database adapters to ensure consistent
37
+ behavior. Users can extend this class for custom type conversion needs.
38
+ """
39
+
40
+ __slots__ = ()
41
+
42
+ def detect_type(self, value: str) -> str | None:
43
+ """Detect special types from string values.
44
+
45
+ Args:
46
+ value: String value to analyze.
47
+
48
+ Returns:
49
+ Type name if detected, None otherwise.
50
+ """
51
+ if not isinstance(value, str): # pyright: ignore
52
+ return None
53
+ if not value:
54
+ return None
55
+
56
+ match = SPECIAL_TYPE_REGEX.match(value)
57
+ if not match:
58
+ return None
59
+
60
+ return next((k for k, v in match.groupdict().items() if v), None)
61
+
62
+ def convert_value(self, value: str, detected_type: str) -> Any:
63
+ """Convert string value to appropriate Python type.
64
+
65
+ Args:
66
+ value: String value to convert.
67
+ detected_type: Detected type name.
68
+
69
+ Returns:
70
+ Converted value in appropriate Python type.
71
+ """
72
+ converter = _TYPE_CONVERTERS.get(detected_type)
73
+ if converter:
74
+ return converter(value)
75
+ return value
76
+
77
+ def convert_if_detected(self, value: Any) -> Any:
78
+ """Convert value only if special type detected, else return original.
79
+
80
+ This method provides performance optimization by avoiding expensive
81
+ regex operations on plain strings that don't contain special characters.
82
+
83
+ Args:
84
+ value: Value to potentially convert.
85
+
86
+ Returns:
87
+ Converted value if special type detected, original value otherwise.
88
+ """
89
+ if not isinstance(value, str):
90
+ return value
91
+
92
+ # Quick pre-check for performance - avoid regex on plain strings
93
+ if not any(c in value for c in ["{", "[", "-", ":", "T"]):
94
+ return value # Skip regex entirely for "hello world" etc.
95
+
96
+ detected_type = self.detect_type(value)
97
+ if detected_type:
98
+ try:
99
+ return self.convert_value(value, detected_type)
100
+ except Exception:
101
+ # If conversion fails, return original value
102
+ return value
103
+ return value
104
+
105
+
106
+ def convert_uuid(value: str) -> UUID:
107
+ """Convert UUID string to UUID object.
108
+
109
+ Args:
110
+ value: UUID string.
111
+
112
+ Returns:
113
+ UUID object.
114
+ """
115
+ return UUID(value)
116
+
117
+
118
+ def convert_iso_datetime(value: str) -> datetime:
119
+ """Convert ISO 8601 datetime string to datetime object.
120
+
121
+ Args:
122
+ value: ISO datetime string.
123
+
124
+ Returns:
125
+ datetime object.
126
+ """
127
+ # Handle various ISO formats with timezone
128
+ if value.endswith("Z"):
129
+ value = value[:-1] + "+00:00"
130
+
131
+ # Replace space with T for standard ISO format
132
+ if " " in value and "T" not in value:
133
+ value = value.replace(" ", "T")
134
+
135
+ return datetime.fromisoformat(value)
136
+
137
+
138
+ def convert_iso_date(value: str) -> date:
139
+ """Convert ISO date string to date object.
140
+
141
+ Args:
142
+ value: ISO date string.
143
+
144
+ Returns:
145
+ date object.
146
+ """
147
+ return date.fromisoformat(value)
148
+
149
+
150
+ def convert_iso_time(value: str) -> time:
151
+ """Convert ISO time string to time object.
152
+
153
+ Args:
154
+ value: ISO time string.
155
+
156
+ Returns:
157
+ time object.
158
+ """
159
+ return time.fromisoformat(value)
160
+
161
+
162
+ def convert_json(value: str) -> Any:
163
+ """Convert JSON string to Python object.
164
+
165
+ Args:
166
+ value: JSON string.
167
+
168
+ Returns:
169
+ Decoded Python object.
170
+ """
171
+ return decode_json(value)
172
+
173
+
174
+ def convert_decimal(value: str) -> Decimal:
175
+ """Convert string to Decimal for precise arithmetic.
176
+
177
+ Args:
178
+ value: Decimal string.
179
+
180
+ Returns:
181
+ Decimal object.
182
+ """
183
+ return Decimal(value)
184
+
185
+
186
+ # Converter registry
187
+ _TYPE_CONVERTERS: Final[dict[str, Callable[[str], Any]]] = {
188
+ "uuid": convert_uuid,
189
+ "iso_datetime": convert_iso_datetime,
190
+ "iso_date": convert_iso_date,
191
+ "iso_time": convert_iso_time,
192
+ "json": convert_json,
193
+ }
194
+
195
+
196
+ def format_datetime_rfc3339(dt: datetime) -> str:
197
+ """Format datetime as RFC 3339 compliant string.
198
+
199
+ Args:
200
+ dt: datetime object.
201
+
202
+ Returns:
203
+ RFC 3339 formatted datetime string.
204
+ """
205
+ if dt.tzinfo is None:
206
+ dt = dt.replace(tzinfo=timezone.utc)
207
+ return dt.isoformat()
208
+
209
+
210
+ def parse_datetime_rfc3339(dt_str: str) -> datetime:
211
+ """Parse RFC 3339 datetime string.
212
+
213
+ Args:
214
+ dt_str: RFC 3339 datetime string.
215
+
216
+ Returns:
217
+ datetime object.
218
+ """
219
+ # Handle Z suffix
220
+ if dt_str.endswith("Z"):
221
+ dt_str = dt_str[:-1] + "+00:00"
222
+ return datetime.fromisoformat(dt_str)
223
+
224
+
225
+ __all__ = (
226
+ "BaseTypeConverter",
227
+ "convert_decimal",
228
+ "convert_iso_date",
229
+ "convert_iso_datetime",
230
+ "convert_iso_time",
231
+ "convert_json",
232
+ "convert_uuid",
233
+ "format_datetime_rfc3339",
234
+ "parse_datetime_rfc3339",
235
+ )
@@ -1,19 +1,20 @@
1
1
  """Driver protocols and base classes for database adapters."""
2
2
 
3
- from typing import Union
4
-
5
3
  from sqlspec.driver import mixins
6
- from sqlspec.driver._async import AsyncDriverAdapterBase
7
- from sqlspec.driver._common import CommonDriverAttributesMixin, ExecutionResult
8
- from sqlspec.driver._sync import SyncDriverAdapterBase
4
+ from sqlspec.driver._async import AsyncDataDictionaryBase, AsyncDriverAdapterBase
5
+ from sqlspec.driver._common import CommonDriverAttributesMixin, ExecutionResult, VersionInfo
6
+ from sqlspec.driver._sync import SyncDataDictionaryBase, SyncDriverAdapterBase
9
7
 
10
8
  __all__ = (
9
+ "AsyncDataDictionaryBase",
11
10
  "AsyncDriverAdapterBase",
12
11
  "CommonDriverAttributesMixin",
13
12
  "DriverAdapterProtocol",
14
13
  "ExecutionResult",
14
+ "SyncDataDictionaryBase",
15
15
  "SyncDriverAdapterBase",
16
+ "VersionInfo",
16
17
  "mixins",
17
18
  )
18
19
 
19
- DriverAdapterProtocol = Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]
20
+ DriverAdapterProtocol = SyncDriverAdapterBase | AsyncDriverAdapterBase