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.
- sqlspec/__init__.py +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {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,
|
|
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: "
|
|
84
|
+
parsed_expression: "exp.Expression | None" = None,
|
|
83
85
|
operation_type: "OperationType" = "UNKNOWN",
|
|
84
|
-
|
|
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: "
|
|
124
|
-
*parameters: "
|
|
127
|
+
statement: "str | exp.Expression | 'SQL'",
|
|
128
|
+
*parameters: "Any | StatementFilter | list[Any | StatementFilter]",
|
|
125
129
|
statement_config: Optional["StatementConfig"] = None,
|
|
126
|
-
is_many:
|
|
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:
|
|
142
|
-
self._hash:
|
|
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: "
|
|
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: "
|
|
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:
|
|
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) -> "
|
|
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) -> "
|
|
353
|
+
def dialect(self) -> "str | None":
|
|
350
354
|
"""SQL dialect."""
|
|
351
355
|
return self._dialect
|
|
352
356
|
|
|
353
357
|
@property
|
|
354
|
-
def _statement(self) -> "
|
|
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) -> "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
630
|
-
parameter_validator: "
|
|
631
|
-
dialect: "
|
|
632
|
-
pre_process_steps: "
|
|
633
|
-
post_process_steps: "
|
|
634
|
-
execution_mode: "
|
|
635
|
-
execution_args: "
|
|
636
|
-
output_transformer: "
|
|
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 =
|
|
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
|
+
)
|
sqlspec/driver/__init__.py
CHANGED
|
@@ -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 =
|
|
20
|
+
DriverAdapterProtocol = SyncDriverAdapterBase | AsyncDriverAdapterBase
|