sqlspec 0.24.0__py3-none-any.whl → 0.25.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/_sql.py +21 -23
- sqlspec/_typing.py +2 -0
- sqlspec/adapters/adbc/driver.py +2 -2
- sqlspec/adapters/oracledb/driver.py +5 -0
- sqlspec/adapters/psycopg/config.py +2 -4
- sqlspec/base.py +3 -4
- sqlspec/builder/_base.py +55 -13
- sqlspec/builder/_column.py +9 -0
- sqlspec/builder/_ddl.py +7 -7
- sqlspec/builder/_insert.py +10 -6
- sqlspec/builder/_parsing_utils.py +23 -4
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +31 -22
- sqlspec/builder/mixins/_delete_operations.py +12 -7
- sqlspec/builder/mixins/_insert_operations.py +50 -36
- sqlspec/builder/mixins/_join_operations.py +1 -0
- sqlspec/builder/mixins/_merge_operations.py +54 -28
- sqlspec/builder/mixins/_order_limit_operations.py +1 -0
- sqlspec/builder/mixins/_pivot_operations.py +1 -0
- sqlspec/builder/mixins/_select_operations.py +42 -14
- sqlspec/builder/mixins/_update_operations.py +30 -18
- sqlspec/builder/mixins/_where_clause.py +48 -60
- sqlspec/core/__init__.py +3 -2
- sqlspec/core/cache.py +297 -351
- sqlspec/core/compiler.py +5 -3
- sqlspec/core/filters.py +246 -213
- sqlspec/core/hashing.py +9 -11
- sqlspec/core/parameters.py +20 -7
- sqlspec/core/statement.py +67 -12
- sqlspec/driver/_async.py +2 -2
- sqlspec/driver/_common.py +31 -14
- sqlspec/driver/_sync.py +2 -2
- sqlspec/driver/mixins/_result_tools.py +60 -7
- sqlspec/loader.py +8 -9
- sqlspec/storage/backends/fsspec.py +1 -0
- sqlspec/typing.py +2 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/METADATA +1 -1
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/RECORD +42 -42
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/core/hashing.py
CHANGED
|
@@ -185,26 +185,24 @@ def hash_sql_statement(statement: "SQL") -> str:
|
|
|
185
185
|
"""
|
|
186
186
|
from sqlspec.utils.type_guards import is_expression
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
else:
|
|
191
|
-
expr_hash = hash(statement._raw_sql)
|
|
188
|
+
stmt_expr = statement.statement_expression
|
|
189
|
+
expr_hash = hash_expression(stmt_expr) if is_expression(stmt_expr) else hash(statement.raw_sql)
|
|
192
190
|
|
|
193
191
|
param_hash = hash_parameters(
|
|
194
|
-
positional_parameters=statement.
|
|
195
|
-
named_parameters=statement.
|
|
196
|
-
original_parameters=statement.
|
|
192
|
+
positional_parameters=statement.positional_parameters,
|
|
193
|
+
named_parameters=statement.named_parameters,
|
|
194
|
+
original_parameters=statement.original_parameters,
|
|
197
195
|
)
|
|
198
196
|
|
|
199
|
-
filter_hash = hash_filters(statement.
|
|
197
|
+
filter_hash = hash_filters(statement.filters)
|
|
200
198
|
|
|
201
199
|
state_components = [
|
|
202
200
|
expr_hash,
|
|
203
201
|
param_hash,
|
|
204
202
|
filter_hash,
|
|
205
|
-
hash(statement.
|
|
206
|
-
hash(statement.
|
|
207
|
-
hash(statement.
|
|
203
|
+
hash(statement.dialect),
|
|
204
|
+
hash(statement.is_many),
|
|
205
|
+
hash(statement.is_script),
|
|
208
206
|
]
|
|
209
207
|
|
|
210
208
|
return f"sql:{hash(tuple(state_components))}"
|
sqlspec/core/parameters.py
CHANGED
|
@@ -755,17 +755,30 @@ class ParameterConverter:
|
|
|
755
755
|
parameter_styles = {p.style for p in param_info}
|
|
756
756
|
has_mixed_styles = len(parameter_styles) > 1
|
|
757
757
|
|
|
758
|
+
# Build unique parameter mapping to avoid duplicates when same parameter appears multiple times
|
|
759
|
+
unique_params: dict[str, Any] = {}
|
|
760
|
+
param_order: list[str] = []
|
|
761
|
+
|
|
758
762
|
if has_mixed_styles:
|
|
759
763
|
param_keys = list(parameters.keys())
|
|
760
764
|
for param in param_info:
|
|
761
|
-
|
|
762
|
-
if
|
|
763
|
-
|
|
765
|
+
param_key = param.placeholder_text
|
|
766
|
+
if param_key not in unique_params:
|
|
767
|
+
value, found = self._extract_param_value_mixed_styles(param, parameters, param_keys)
|
|
768
|
+
if found:
|
|
769
|
+
unique_params[param_key] = value
|
|
770
|
+
param_order.append(param_key)
|
|
764
771
|
else:
|
|
765
772
|
for param in param_info:
|
|
766
|
-
|
|
767
|
-
if
|
|
768
|
-
|
|
773
|
+
param_key = param.placeholder_text
|
|
774
|
+
if param_key not in unique_params:
|
|
775
|
+
value, found = self._extract_param_value_single_style(param, parameters)
|
|
776
|
+
if found:
|
|
777
|
+
unique_params[param_key] = value
|
|
778
|
+
param_order.append(param_key)
|
|
779
|
+
|
|
780
|
+
# Build parameter values list from unique parameters in order
|
|
781
|
+
param_values = [unique_params[param_key] for param_key in param_order]
|
|
769
782
|
|
|
770
783
|
if preserve_parameter_format and original_parameters is not None:
|
|
771
784
|
return self._preserve_original_format(param_values, original_parameters)
|
|
@@ -1064,7 +1077,7 @@ class ParameterProcessor:
|
|
|
1064
1077
|
|
|
1065
1078
|
return processed_sql, processed_parameters
|
|
1066
1079
|
|
|
1067
|
-
def
|
|
1080
|
+
def get_sqlglot_compatible_sql(
|
|
1068
1081
|
self, sql: str, parameters: Any, config: ParameterStyleConfig, dialect: Optional[str] = None
|
|
1069
1082
|
) -> "tuple[str, Any]":
|
|
1070
1083
|
"""Get SQL normalized for parsing only (Phase 1 only).
|
sqlspec/core/statement.py
CHANGED
|
@@ -18,6 +18,7 @@ from sqlspec.utils.type_guards import is_statement_filter, supports_where
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
19
|
from sqlglot.dialects.dialect import DialectType
|
|
20
20
|
|
|
21
|
+
from sqlspec.core.cache import FiltersView
|
|
21
22
|
from sqlspec.core.filters import StatementFilter
|
|
22
23
|
|
|
23
24
|
|
|
@@ -161,14 +162,14 @@ class SQL:
|
|
|
161
162
|
self._process_parameters(*parameters, **kwargs)
|
|
162
163
|
|
|
163
164
|
def _create_auto_config(
|
|
164
|
-
self,
|
|
165
|
+
self, _statement: "Union[str, exp.Expression, 'SQL']", _parameters: tuple, _kwargs: dict[str, Any]
|
|
165
166
|
) -> "StatementConfig":
|
|
166
167
|
"""Create default StatementConfig when none provided.
|
|
167
168
|
|
|
168
169
|
Args:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
_statement: The SQL statement (unused)
|
|
171
|
+
_parameters: Statement parameters (unused)
|
|
172
|
+
_kwargs: Additional keyword arguments (unused)
|
|
172
173
|
|
|
173
174
|
Returns:
|
|
174
175
|
Default StatementConfig instance
|
|
@@ -196,14 +197,14 @@ class SQL:
|
|
|
196
197
|
Args:
|
|
197
198
|
sql_obj: Existing SQL object to copy from
|
|
198
199
|
"""
|
|
199
|
-
self._raw_sql = sql_obj.
|
|
200
|
-
self._filters = sql_obj.
|
|
201
|
-
self._named_parameters = sql_obj.
|
|
202
|
-
self._positional_parameters = sql_obj.
|
|
203
|
-
self._is_many = sql_obj.
|
|
204
|
-
self._is_script = sql_obj.
|
|
205
|
-
if sql_obj.
|
|
206
|
-
self._processed_state = sql_obj.
|
|
200
|
+
self._raw_sql = sql_obj.raw_sql
|
|
201
|
+
self._filters = sql_obj.filters.copy()
|
|
202
|
+
self._named_parameters = sql_obj.named_parameters.copy()
|
|
203
|
+
self._positional_parameters = sql_obj.positional_parameters.copy()
|
|
204
|
+
self._is_many = sql_obj.is_many
|
|
205
|
+
self._is_script = sql_obj.is_script
|
|
206
|
+
if sql_obj.is_processed:
|
|
207
|
+
self._processed_state = sql_obj.get_processed_state()
|
|
207
208
|
|
|
208
209
|
def _should_auto_detect_many(self, parameters: tuple) -> bool:
|
|
209
210
|
"""Detect execute_many mode from parameter structure.
|
|
@@ -270,6 +271,15 @@ class SQL:
|
|
|
270
271
|
"""Get the raw SQL string."""
|
|
271
272
|
return self._raw_sql
|
|
272
273
|
|
|
274
|
+
@property
|
|
275
|
+
def raw_sql(self) -> str:
|
|
276
|
+
"""Get raw SQL string (public API).
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
The raw SQL string
|
|
280
|
+
"""
|
|
281
|
+
return self._raw_sql
|
|
282
|
+
|
|
273
283
|
@property
|
|
274
284
|
def parameters(self) -> Any:
|
|
275
285
|
"""Get the original parameters."""
|
|
@@ -277,6 +287,21 @@ class SQL:
|
|
|
277
287
|
return self._named_parameters
|
|
278
288
|
return self._positional_parameters or []
|
|
279
289
|
|
|
290
|
+
@property
|
|
291
|
+
def positional_parameters(self) -> "list[Any]":
|
|
292
|
+
"""Get positional parameters (public API)."""
|
|
293
|
+
return self._positional_parameters or []
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def named_parameters(self) -> "dict[str, Any]":
|
|
297
|
+
"""Get named parameters (public API)."""
|
|
298
|
+
return self._named_parameters
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def original_parameters(self) -> Any:
|
|
302
|
+
"""Get original parameters (public API)."""
|
|
303
|
+
return self._original_parameters
|
|
304
|
+
|
|
280
305
|
@property
|
|
281
306
|
def operation_type(self) -> "OperationType":
|
|
282
307
|
"""SQL operation type."""
|
|
@@ -301,6 +326,25 @@ class SQL:
|
|
|
301
326
|
"""Applied filters."""
|
|
302
327
|
return self._filters.copy()
|
|
303
328
|
|
|
329
|
+
def get_filters_view(self) -> "FiltersView":
|
|
330
|
+
"""Get zero-copy filters view (public API).
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Read-only view of filters without copying
|
|
334
|
+
"""
|
|
335
|
+
from sqlspec.core.cache import FiltersView
|
|
336
|
+
|
|
337
|
+
return FiltersView(self._filters)
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def is_processed(self) -> bool:
|
|
341
|
+
"""Check if SQL has been processed (public API)."""
|
|
342
|
+
return self._processed_state is not Empty
|
|
343
|
+
|
|
344
|
+
def get_processed_state(self) -> Any:
|
|
345
|
+
"""Get processed state (public API)."""
|
|
346
|
+
return self._processed_state
|
|
347
|
+
|
|
304
348
|
@property
|
|
305
349
|
def dialect(self) -> "Optional[str]":
|
|
306
350
|
"""SQL dialect."""
|
|
@@ -311,6 +355,17 @@ class SQL:
|
|
|
311
355
|
"""Internal SQLGlot expression."""
|
|
312
356
|
return self.expression
|
|
313
357
|
|
|
358
|
+
@property
|
|
359
|
+
def statement_expression(self) -> "Optional[exp.Expression]":
|
|
360
|
+
"""Get parsed statement expression (public API).
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Parsed SQLGlot expression or None if not parsed
|
|
364
|
+
"""
|
|
365
|
+
if self._processed_state is not Empty:
|
|
366
|
+
return self._processed_state.parsed_expression
|
|
367
|
+
return None
|
|
368
|
+
|
|
314
369
|
@property
|
|
315
370
|
def is_many(self) -> bool:
|
|
316
371
|
"""Check if this is execute_many."""
|
sqlspec/driver/_async.py
CHANGED
|
@@ -186,10 +186,10 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
|
|
|
186
186
|
config = statement_config or self.statement_config
|
|
187
187
|
|
|
188
188
|
if isinstance(statement, SQL):
|
|
189
|
-
sql_statement = SQL(statement.
|
|
189
|
+
sql_statement = SQL(statement.raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
190
190
|
else:
|
|
191
191
|
base_statement = self.prepare_statement(statement, filters, statement_config=config, kwargs=kwargs)
|
|
192
|
-
sql_statement = SQL(base_statement.
|
|
192
|
+
sql_statement = SQL(base_statement.raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
193
193
|
|
|
194
194
|
return await self.dispatch_statement_execution(statement=sql_statement, connection=self.connection)
|
|
195
195
|
|
sqlspec/driver/_common.py
CHANGED
|
@@ -7,7 +7,7 @@ from sqlglot import exp
|
|
|
7
7
|
|
|
8
8
|
from sqlspec.builder import QueryBuilder
|
|
9
9
|
from sqlspec.core import SQL, ParameterStyle, SQLResult, Statement, StatementConfig, TypedParameter
|
|
10
|
-
from sqlspec.core.cache import
|
|
10
|
+
from sqlspec.core.cache import CachedStatement, get_cache, get_cache_config
|
|
11
11
|
from sqlspec.core.splitter import split_sql_script
|
|
12
12
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
13
13
|
from sqlspec.utils.logging import get_logger
|
|
@@ -206,16 +206,16 @@ class CommonDriverAttributesMixin:
|
|
|
206
206
|
sql_statement = statement.to_statement(statement_config)
|
|
207
207
|
if parameters or kwargs:
|
|
208
208
|
merged_parameters = (
|
|
209
|
-
(*sql_statement.
|
|
209
|
+
(*sql_statement.positional_parameters, *parameters)
|
|
210
210
|
if parameters
|
|
211
|
-
else sql_statement.
|
|
211
|
+
else sql_statement.positional_parameters
|
|
212
212
|
)
|
|
213
213
|
return SQL(sql_statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
|
|
214
214
|
return sql_statement
|
|
215
215
|
if isinstance(statement, SQL):
|
|
216
216
|
if parameters or kwargs:
|
|
217
217
|
merged_parameters = (
|
|
218
|
-
(*statement.
|
|
218
|
+
(*statement.positional_parameters, *parameters) if parameters else statement.positional_parameters
|
|
219
219
|
)
|
|
220
220
|
return SQL(statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
|
|
221
221
|
needs_rebuild = False
|
|
@@ -232,14 +232,14 @@ class CommonDriverAttributesMixin:
|
|
|
232
232
|
needs_rebuild = True
|
|
233
233
|
|
|
234
234
|
if needs_rebuild:
|
|
235
|
-
sql_text = statement.
|
|
235
|
+
sql_text = statement.raw_sql or statement.sql
|
|
236
236
|
|
|
237
237
|
if statement.is_many and statement.parameters:
|
|
238
238
|
new_sql = SQL(sql_text, statement.parameters, statement_config=statement_config, is_many=True)
|
|
239
|
-
elif statement.
|
|
240
|
-
new_sql = SQL(sql_text, statement_config=statement_config, **statement.
|
|
239
|
+
elif statement.named_parameters:
|
|
240
|
+
new_sql = SQL(sql_text, statement_config=statement_config, **statement.named_parameters)
|
|
241
241
|
else:
|
|
242
|
-
new_sql = SQL(sql_text, *statement.
|
|
242
|
+
new_sql = SQL(sql_text, *statement.positional_parameters, statement_config=statement_config)
|
|
243
243
|
|
|
244
244
|
return new_sql
|
|
245
245
|
return statement
|
|
@@ -413,9 +413,10 @@ class CommonDriverAttributesMixin:
|
|
|
413
413
|
cache_key = None
|
|
414
414
|
if cache_config.compiled_cache_enabled and statement_config.enable_caching:
|
|
415
415
|
cache_key = self._generate_compilation_cache_key(statement, statement_config, flatten_single_parameters)
|
|
416
|
-
|
|
417
|
-
if
|
|
418
|
-
|
|
416
|
+
cache = get_cache()
|
|
417
|
+
cached_result = cache.get("statement", cache_key, str(statement.dialect) if statement.dialect else None)
|
|
418
|
+
if cached_result is not None and isinstance(cached_result, CachedStatement):
|
|
419
|
+
return cached_result.compiled_sql, cached_result.parameters
|
|
419
420
|
|
|
420
421
|
prepared_statement = self.prepare_statement(statement, statement_config=statement_config)
|
|
421
422
|
compiled_sql, execution_parameters = prepared_statement.compile()
|
|
@@ -430,7 +431,23 @@ class CommonDriverAttributesMixin:
|
|
|
430
431
|
)
|
|
431
432
|
|
|
432
433
|
if cache_key is not None:
|
|
433
|
-
|
|
434
|
+
cache = get_cache()
|
|
435
|
+
cached_statement = CachedStatement(
|
|
436
|
+
compiled_sql=compiled_sql,
|
|
437
|
+
parameters=tuple(prepared_parameters)
|
|
438
|
+
if isinstance(prepared_parameters, list)
|
|
439
|
+
else (
|
|
440
|
+
prepared_parameters
|
|
441
|
+
if prepared_parameters is None
|
|
442
|
+
else (
|
|
443
|
+
tuple(prepared_parameters)
|
|
444
|
+
if not isinstance(prepared_parameters, tuple)
|
|
445
|
+
else prepared_parameters
|
|
446
|
+
)
|
|
447
|
+
),
|
|
448
|
+
expression=statement.expression,
|
|
449
|
+
)
|
|
450
|
+
cache.put("statement", cache_key, cached_statement, str(statement.dialect) if statement.dialect else None)
|
|
434
451
|
|
|
435
452
|
return compiled_sql, prepared_parameters
|
|
436
453
|
|
|
@@ -562,8 +579,8 @@ class CommonDriverAttributesMixin:
|
|
|
562
579
|
count_expr.set("limit", None)
|
|
563
580
|
count_expr.set("offset", None)
|
|
564
581
|
|
|
565
|
-
return SQL(count_expr, *original_sql.
|
|
582
|
+
return SQL(count_expr, *original_sql.positional_parameters, statement_config=original_sql.statement_config)
|
|
566
583
|
|
|
567
584
|
subquery = cast("exp.Select", expr).subquery(alias="total_query")
|
|
568
585
|
count_expr = exp.select(exp.Count(this=exp.Star())).from_(subquery)
|
|
569
|
-
return SQL(count_expr, *original_sql.
|
|
586
|
+
return SQL(count_expr, *original_sql.positional_parameters, statement_config=original_sql.statement_config)
|
sqlspec/driver/_sync.py
CHANGED
|
@@ -186,10 +186,10 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
|
|
|
186
186
|
config = statement_config or self.statement_config
|
|
187
187
|
|
|
188
188
|
if isinstance(statement, SQL):
|
|
189
|
-
sql_statement = SQL(statement.
|
|
189
|
+
sql_statement = SQL(statement.raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
190
190
|
else:
|
|
191
191
|
base_statement = self.prepare_statement(statement, filters, statement_config=config, kwargs=kwargs)
|
|
192
|
-
sql_statement = SQL(base_statement.
|
|
192
|
+
sql_statement = SQL(base_statement.raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
193
193
|
|
|
194
194
|
return self.dispatch_statement_execution(statement=sql_statement, connection=self.connection)
|
|
195
195
|
|
|
@@ -15,6 +15,7 @@ from mypy_extensions import trait
|
|
|
15
15
|
from sqlspec.exceptions import SQLSpecError
|
|
16
16
|
from sqlspec.typing import (
|
|
17
17
|
CATTRS_INSTALLED,
|
|
18
|
+
NUMPY_INSTALLED,
|
|
18
19
|
ModelDTOT,
|
|
19
20
|
ModelT,
|
|
20
21
|
attrs_asdict,
|
|
@@ -41,12 +42,36 @@ logger = logging.getLogger(__name__)
|
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
_DATETIME_TYPES: Final[set[type]] = {datetime.datetime, datetime.date, datetime.time}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_list_type_target(target_type: Any) -> bool:
|
|
48
|
+
"""Check if target type is a list type (e.g., list[float])."""
|
|
49
|
+
try:
|
|
50
|
+
return hasattr(target_type, "__origin__") and target_type.__origin__ is list
|
|
51
|
+
except (AttributeError, TypeError):
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _convert_numpy_to_list(target_type: Any, value: Any) -> Any:
|
|
56
|
+
"""Convert numpy array to list if target is a list type."""
|
|
57
|
+
if not NUMPY_INSTALLED:
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
import numpy as np
|
|
61
|
+
|
|
62
|
+
if isinstance(value, np.ndarray) and _is_list_type_target(target_type):
|
|
63
|
+
return value.tolist()
|
|
64
|
+
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
|
|
44
68
|
_DEFAULT_TYPE_DECODERS: Final[list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]]] = [
|
|
45
69
|
(lambda x: x is UUID, lambda t, v: t(v.hex)),
|
|
46
70
|
(lambda x: x is datetime.datetime, lambda t, v: t(v.isoformat())),
|
|
47
71
|
(lambda x: x is datetime.date, lambda t, v: t(v.isoformat())),
|
|
48
72
|
(lambda x: x is datetime.time, lambda t, v: t(v.isoformat())),
|
|
49
73
|
(lambda x: x is Enum, lambda t, v: t(v.value)),
|
|
74
|
+
(_is_list_type_target, _convert_numpy_to_list),
|
|
50
75
|
]
|
|
51
76
|
|
|
52
77
|
|
|
@@ -63,6 +88,13 @@ def _default_msgspec_deserializer(
|
|
|
63
88
|
Returns:
|
|
64
89
|
Converted value or original value if conversion not applicable
|
|
65
90
|
"""
|
|
91
|
+
# Handle numpy arrays first for list types
|
|
92
|
+
if NUMPY_INSTALLED:
|
|
93
|
+
import numpy as np
|
|
94
|
+
|
|
95
|
+
if isinstance(value, np.ndarray) and _is_list_type_target(target_type):
|
|
96
|
+
return value.tolist()
|
|
97
|
+
|
|
66
98
|
if type_decoders:
|
|
67
99
|
for predicate, decoder in type_decoders:
|
|
68
100
|
if predicate(target_type):
|
|
@@ -71,17 +103,19 @@ def _default_msgspec_deserializer(
|
|
|
71
103
|
if target_type is UUID and isinstance(value, UUID):
|
|
72
104
|
return value.hex
|
|
73
105
|
|
|
74
|
-
if target_type in _DATETIME_TYPES:
|
|
75
|
-
|
|
76
|
-
return value.isoformat()
|
|
77
|
-
except AttributeError:
|
|
78
|
-
pass
|
|
106
|
+
if target_type in _DATETIME_TYPES and hasattr(value, "isoformat"):
|
|
107
|
+
return value.isoformat() # pyright: ignore
|
|
79
108
|
|
|
80
109
|
if isinstance(target_type, type) and issubclass(target_type, Enum) and isinstance(value, Enum):
|
|
81
110
|
return value.value
|
|
82
111
|
|
|
83
|
-
if
|
|
84
|
-
|
|
112
|
+
# Check if value is already the correct type (but avoid parameterized generics)
|
|
113
|
+
try:
|
|
114
|
+
if isinstance(target_type, type) and isinstance(value, target_type):
|
|
115
|
+
return value
|
|
116
|
+
except TypeError:
|
|
117
|
+
# Handle parameterized generics like list[int] which can't be used with isinstance
|
|
118
|
+
pass
|
|
85
119
|
|
|
86
120
|
if isinstance(target_type, type):
|
|
87
121
|
try:
|
|
@@ -190,6 +224,25 @@ class ToSchemaMixin:
|
|
|
190
224
|
logger.debug("Field name transformation failed for msgspec schema: %s", e)
|
|
191
225
|
transformed_data = data
|
|
192
226
|
|
|
227
|
+
# Pre-process numpy arrays to lists before msgspec conversion
|
|
228
|
+
if NUMPY_INSTALLED:
|
|
229
|
+
try:
|
|
230
|
+
import numpy as np
|
|
231
|
+
|
|
232
|
+
def _convert_numpy_arrays_in_data(obj: Any) -> Any:
|
|
233
|
+
"""Recursively convert numpy arrays to lists in data structures."""
|
|
234
|
+
if isinstance(obj, np.ndarray):
|
|
235
|
+
return obj.tolist()
|
|
236
|
+
if isinstance(obj, dict):
|
|
237
|
+
return {k: _convert_numpy_arrays_in_data(v) for k, v in obj.items()}
|
|
238
|
+
if isinstance(obj, (list, tuple)):
|
|
239
|
+
return type(obj)(_convert_numpy_arrays_in_data(item) for item in obj)
|
|
240
|
+
return obj
|
|
241
|
+
|
|
242
|
+
transformed_data = _convert_numpy_arrays_in_data(transformed_data)
|
|
243
|
+
except ImportError:
|
|
244
|
+
pass
|
|
245
|
+
|
|
193
246
|
if not isinstance(transformed_data, Sequence):
|
|
194
247
|
return convert(obj=transformed_data, type=schema_type, from_attributes=True, dec_hook=deserializer)
|
|
195
248
|
return convert(obj=transformed_data, type=list[schema_type], from_attributes=True, dec_hook=deserializer) # type: ignore[valid-type]
|
sqlspec/loader.py
CHANGED
|
@@ -12,7 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Final, Optional, Union
|
|
13
13
|
from urllib.parse import unquote, urlparse
|
|
14
14
|
|
|
15
|
-
from sqlspec.core.cache import
|
|
15
|
+
from sqlspec.core.cache import get_cache, get_cache_config
|
|
16
16
|
from sqlspec.core.statement import SQL
|
|
17
17
|
from sqlspec.exceptions import SQLFileNotFoundError, SQLFileParseError, StorageOperationFailedError
|
|
18
18
|
from sqlspec.storage.registry import storage_registry as default_storage_registry
|
|
@@ -438,9 +438,8 @@ class SQLFileLoader:
|
|
|
438
438
|
return
|
|
439
439
|
|
|
440
440
|
cache_key_str = self._generate_file_cache_key(file_path)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
cached_file = unified_cache.get(cache_key)
|
|
441
|
+
cache = get_cache()
|
|
442
|
+
cached_file = cache.get("file", cache_key_str)
|
|
444
443
|
|
|
445
444
|
if (
|
|
446
445
|
cached_file is not None
|
|
@@ -475,7 +474,7 @@ class SQLFileLoader:
|
|
|
475
474
|
file_statements[stored_name] = self._queries[query_name]
|
|
476
475
|
|
|
477
476
|
cached_file_data = CachedSQLFile(sql_file=sql_file, parsed_statements=file_statements)
|
|
478
|
-
|
|
477
|
+
cache.put("file", cache_key_str, cached_file_data)
|
|
479
478
|
|
|
480
479
|
def _load_file_without_cache(self, file_path: Union[str, Path], namespace: Optional[str]) -> None:
|
|
481
480
|
"""Load a single SQL file without using cache.
|
|
@@ -592,15 +591,15 @@ class SQLFileLoader:
|
|
|
592
591
|
|
|
593
592
|
cache_config = get_cache_config()
|
|
594
593
|
if cache_config.compiled_cache_enabled:
|
|
595
|
-
|
|
596
|
-
|
|
594
|
+
cache = get_cache()
|
|
595
|
+
cache.clear()
|
|
597
596
|
|
|
598
597
|
def clear_file_cache(self) -> None:
|
|
599
598
|
"""Clear the file cache only, keeping loaded queries."""
|
|
600
599
|
cache_config = get_cache_config()
|
|
601
600
|
if cache_config.compiled_cache_enabled:
|
|
602
|
-
|
|
603
|
-
|
|
601
|
+
cache = get_cache()
|
|
602
|
+
cache.clear()
|
|
604
603
|
|
|
605
604
|
def get_query_text(self, name: str) -> str:
|
|
606
605
|
"""Get raw SQL text for a query.
|
sqlspec/typing.py
CHANGED
|
@@ -12,6 +12,7 @@ from sqlspec._typing import (
|
|
|
12
12
|
FSSPEC_INSTALLED,
|
|
13
13
|
LITESTAR_INSTALLED,
|
|
14
14
|
MSGSPEC_INSTALLED,
|
|
15
|
+
NUMPY_INSTALLED,
|
|
15
16
|
OBSTORE_INSTALLED,
|
|
16
17
|
OPENTELEMETRY_INSTALLED,
|
|
17
18
|
PGVECTOR_INSTALLED,
|
|
@@ -187,6 +188,7 @@ __all__ = (
|
|
|
187
188
|
"FSSPEC_INSTALLED",
|
|
188
189
|
"LITESTAR_INSTALLED",
|
|
189
190
|
"MSGSPEC_INSTALLED",
|
|
191
|
+
"NUMPY_INSTALLED",
|
|
190
192
|
"OBSTORE_INSTALLED",
|
|
191
193
|
"OPENTELEMETRY_INSTALLED",
|
|
192
194
|
"PGVECTOR_INSTALLED",
|