sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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 (185) hide show
  1. sqlspec/__init__.py +71 -8
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +930 -136
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +116 -285
  10. sqlspec/adapters/adbc/driver.py +462 -340
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +202 -150
  14. sqlspec/adapters/aiosqlite/driver.py +226 -247
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -199
  18. sqlspec/adapters/asyncmy/driver.py +257 -215
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +81 -214
  22. sqlspec/adapters/asyncpg/driver.py +284 -359
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -299
  26. sqlspec/adapters/bigquery/driver.py +474 -634
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +414 -397
  30. sqlspec/adapters/duckdb/driver.py +342 -393
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -458
  34. sqlspec/adapters/oracledb/driver.py +505 -531
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -307
  38. sqlspec/adapters/psqlpy/driver.py +504 -213
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -472
  42. sqlspec/adapters/psycopg/driver.py +704 -825
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +208 -142
  46. sqlspec/adapters/sqlite/driver.py +263 -278
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
  50. sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
  51. sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
  53. sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
  54. sqlspec/builder/_insert.py +421 -0
  55. sqlspec/builder/_merge.py +71 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
  57. sqlspec/builder/_select.py +170 -0
  58. sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
  59. sqlspec/builder/mixins/__init__.py +55 -0
  60. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  61. sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
  62. sqlspec/builder/mixins/_insert_operations.py +244 -0
  63. sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
  64. sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
  65. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  66. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  67. sqlspec/builder/mixins/_select_operations.py +604 -0
  68. sqlspec/builder/mixins/_update_operations.py +202 -0
  69. sqlspec/builder/mixins/_where_clause.py +644 -0
  70. sqlspec/cli.py +247 -0
  71. sqlspec/config.py +183 -138
  72. sqlspec/core/__init__.py +63 -0
  73. sqlspec/core/cache.py +871 -0
  74. sqlspec/core/compiler.py +417 -0
  75. sqlspec/core/filters.py +830 -0
  76. sqlspec/core/hashing.py +310 -0
  77. sqlspec/core/parameters.py +1237 -0
  78. sqlspec/core/result.py +677 -0
  79. sqlspec/{statement → core}/splitter.py +321 -191
  80. sqlspec/core/statement.py +676 -0
  81. sqlspec/driver/__init__.py +7 -10
  82. sqlspec/driver/_async.py +422 -163
  83. sqlspec/driver/_common.py +545 -287
  84. sqlspec/driver/_sync.py +426 -160
  85. sqlspec/driver/mixins/__init__.py +2 -13
  86. sqlspec/driver/mixins/_result_tools.py +193 -0
  87. sqlspec/driver/mixins/_sql_translator.py +65 -14
  88. sqlspec/exceptions.py +5 -252
  89. sqlspec/extensions/aiosql/adapter.py +93 -96
  90. sqlspec/extensions/litestar/__init__.py +2 -1
  91. sqlspec/extensions/litestar/cli.py +48 -0
  92. sqlspec/extensions/litestar/config.py +0 -1
  93. sqlspec/extensions/litestar/handlers.py +15 -26
  94. sqlspec/extensions/litestar/plugin.py +21 -16
  95. sqlspec/extensions/litestar/providers.py +17 -52
  96. sqlspec/loader.py +423 -104
  97. sqlspec/migrations/__init__.py +35 -0
  98. sqlspec/migrations/base.py +414 -0
  99. sqlspec/migrations/commands.py +443 -0
  100. sqlspec/migrations/loaders.py +402 -0
  101. sqlspec/migrations/runner.py +213 -0
  102. sqlspec/migrations/tracker.py +140 -0
  103. sqlspec/migrations/utils.py +129 -0
  104. sqlspec/protocols.py +51 -186
  105. sqlspec/storage/__init__.py +1 -1
  106. sqlspec/storage/backends/base.py +37 -40
  107. sqlspec/storage/backends/fsspec.py +136 -112
  108. sqlspec/storage/backends/obstore.py +138 -160
  109. sqlspec/storage/capabilities.py +5 -4
  110. sqlspec/storage/registry.py +57 -106
  111. sqlspec/typing.py +136 -115
  112. sqlspec/utils/__init__.py +2 -2
  113. sqlspec/utils/correlation.py +0 -3
  114. sqlspec/utils/deprecation.py +6 -6
  115. sqlspec/utils/fixtures.py +6 -6
  116. sqlspec/utils/logging.py +0 -2
  117. sqlspec/utils/module_loader.py +7 -12
  118. sqlspec/utils/singleton.py +0 -1
  119. sqlspec/utils/sync_tools.py +17 -38
  120. sqlspec/utils/text.py +12 -51
  121. sqlspec/utils/type_guards.py +482 -235
  122. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
  123. sqlspec-0.16.2.dist-info/RECORD +134 -0
  124. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  125. sqlspec/driver/connection.py +0 -207
  126. sqlspec/driver/mixins/_csv_writer.py +0 -91
  127. sqlspec/driver/mixins/_pipeline.py +0 -512
  128. sqlspec/driver/mixins/_result_utils.py +0 -140
  129. sqlspec/driver/mixins/_storage.py +0 -926
  130. sqlspec/driver/mixins/_type_coercion.py +0 -130
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/service/__init__.py +0 -4
  133. sqlspec/service/_util.py +0 -147
  134. sqlspec/service/base.py +0 -1131
  135. sqlspec/service/pagination.py +0 -26
  136. sqlspec/statement/__init__.py +0 -21
  137. sqlspec/statement/builder/insert.py +0 -288
  138. sqlspec/statement/builder/merge.py +0 -95
  139. sqlspec/statement/builder/mixins/__init__.py +0 -65
  140. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  141. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  142. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  143. sqlspec/statement/builder/mixins/_from.py +0 -63
  144. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  145. sqlspec/statement/builder/mixins/_having.py +0 -35
  146. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  147. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  148. sqlspec/statement/builder/mixins/_insert_values.py +0 -67
  149. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  150. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  151. sqlspec/statement/builder/mixins/_pivot.py +0 -79
  152. sqlspec/statement/builder/mixins/_returning.py +0 -37
  153. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  154. sqlspec/statement/builder/mixins/_set_ops.py +0 -122
  155. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  156. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  157. sqlspec/statement/builder/mixins/_update_set.py +0 -94
  158. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  159. sqlspec/statement/builder/mixins/_where.py +0 -401
  160. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  161. sqlspec/statement/builder/select.py +0 -221
  162. sqlspec/statement/filters.py +0 -596
  163. sqlspec/statement/parameter_manager.py +0 -220
  164. sqlspec/statement/parameters.py +0 -867
  165. sqlspec/statement/pipelines/__init__.py +0 -210
  166. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  167. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  168. sqlspec/statement/pipelines/context.py +0 -115
  169. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  170. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  171. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  172. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  173. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  174. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  175. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  176. sqlspec/statement/pipelines/validators/_performance.py +0 -718
  177. sqlspec/statement/pipelines/validators/_security.py +0 -967
  178. sqlspec/statement/result.py +0 -435
  179. sqlspec/statement/sql.py +0 -1704
  180. sqlspec/statement/sql_compiler.py +0 -140
  181. sqlspec/utils/cached_property.py +0 -25
  182. sqlspec-0.13.1.dist-info/RECORD +0 -150
  183. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  184. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  185. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
@@ -1,130 +0,0 @@
1
- """Type coercion mixin for database drivers.
2
-
3
- This module provides a mixin that all database drivers use to handle
4
- TypedParameter objects and perform appropriate type conversions.
5
- """
6
-
7
- from decimal import Decimal
8
- from typing import TYPE_CHECKING, Any, Optional, Union
9
-
10
- from sqlspec.utils.type_guards import has_parameter_value
11
-
12
- if TYPE_CHECKING:
13
- from sqlspec.typing import SQLParameterType
14
-
15
- __all__ = ("TypeCoercionMixin",)
16
-
17
-
18
- class TypeCoercionMixin:
19
- """Mixin providing type coercion for database drivers.
20
-
21
- This mixin is used by all database drivers to handle TypedParameter objects
22
- and convert values to database-specific types.
23
- """
24
-
25
- __slots__ = ()
26
-
27
- def _process_parameters(self, parameters: "SQLParameterType") -> "SQLParameterType":
28
- """Process parameters, extracting values from TypedParameter objects.
29
-
30
- This method is called by drivers before executing SQL to handle
31
- TypedParameter objects and perform necessary type conversions.
32
-
33
- Args:
34
- parameters: Raw parameters that may contain TypedParameter objects
35
-
36
- Returns:
37
- Processed parameters with TypedParameter values extracted and converted
38
- """
39
- if parameters is None:
40
- return None
41
-
42
- if isinstance(parameters, dict):
43
- return self._process_dict_parameters(parameters)
44
- if isinstance(parameters, (list, tuple)):
45
- return self._process_sequence_parameters(parameters)
46
- # Single scalar parameter
47
- return self._coerce_parameter_type(parameters)
48
-
49
- def _process_dict_parameters(self, params: dict[str, Any]) -> dict[str, Any]:
50
- """Process dictionary parameters."""
51
- result = {}
52
- for key, value in params.items():
53
- result[key] = self._coerce_parameter_type(value)
54
- return result
55
-
56
- def _process_sequence_parameters(self, params: Union[list, tuple]) -> Union[list, tuple]:
57
- """Process list/tuple parameters."""
58
- result = [self._coerce_parameter_type(p) for p in params]
59
- return tuple(result) if isinstance(params, tuple) else result
60
-
61
- def _coerce_parameter_type(self, param: Any) -> Any:
62
- """Coerce a single parameter to the appropriate database type.
63
-
64
- This method checks if the parameter is a TypedParameter and extracts
65
- its value, then applies driver-specific type conversions.
66
-
67
- Args:
68
- param: Parameter value or TypedParameter object
69
-
70
- Returns:
71
- Coerced parameter value suitable for the database
72
- """
73
- if has_parameter_value(param):
74
- value = param.value
75
- type_hint = param.type_hint
76
-
77
- return self._apply_type_coercion(value, type_hint)
78
- # Regular parameter - apply default coercion
79
- return self._apply_type_coercion(param, None)
80
-
81
- def _apply_type_coercion(self, value: Any, type_hint: Optional[str]) -> Any:
82
- """Apply driver-specific type coercion.
83
-
84
- This method should be overridden by each driver to implement
85
- database-specific type conversions.
86
-
87
- Args:
88
- value: The value to coerce
89
- type_hint: Optional type hint from TypedParameter
90
-
91
- Returns:
92
- Coerced value
93
- """
94
- # Default implementation - override in specific drivers
95
- # This base implementation handles common cases
96
-
97
- if value is None:
98
- return None
99
-
100
- # Use type hint if available
101
- if type_hint:
102
- if type_hint == "boolean":
103
- return self._coerce_boolean(value)
104
- if type_hint == "decimal":
105
- return self._coerce_decimal(value)
106
- if type_hint == "json":
107
- return self._coerce_json(value)
108
- if type_hint.startswith("array"):
109
- return self._coerce_array(value)
110
-
111
- # Default: return value as-is
112
- return value
113
-
114
- def _coerce_boolean(self, value: Any) -> Any:
115
- """Coerce boolean values. Override in drivers without native boolean support."""
116
- return value
117
-
118
- def _coerce_decimal(self, value: Any) -> Any:
119
- """Coerce decimal values. Override for specific decimal handling."""
120
- if isinstance(value, str):
121
- return Decimal(value)
122
- return value
123
-
124
- def _coerce_json(self, value: Any) -> Any:
125
- """Coerce JSON values. Override for databases needing JSON strings."""
126
- return value
127
-
128
- def _coerce_array(self, value: Any) -> Any:
129
- """Coerce array values. Override for databases without native array support."""
130
- return value
@@ -1,138 +0,0 @@
1
- """Consolidated parameter processing utilities for database drivers.
2
-
3
- This module provides centralized parameter handling logic to avoid duplication
4
- across sync and async driver implementations.
5
- """
6
-
7
- from typing import TYPE_CHECKING, Any, Optional, Union
8
-
9
- from sqlspec.statement.filters import StatementFilter
10
- from sqlspec.utils.type_guards import is_sync_transaction_capable
11
-
12
- if TYPE_CHECKING:
13
- from sqlspec.typing import StatementParameters
14
-
15
- __all__ = (
16
- "convert_parameters_to_positional",
17
- "normalize_parameter_sequence",
18
- "process_execute_many_parameters",
19
- "separate_filters_and_parameters",
20
- "should_use_transaction",
21
- )
22
-
23
-
24
- def separate_filters_and_parameters(
25
- parameters: "tuple[Union[StatementParameters, StatementFilter], ...]",
26
- ) -> "tuple[list[StatementFilter], list[Any]]":
27
- """Separate filters from parameters in a mixed parameter tuple.
28
-
29
- Args:
30
- parameters: Mixed tuple of parameters and filters
31
-
32
- Returns:
33
- Tuple of (filters, parameters) lists
34
- """
35
-
36
- filters: list[StatementFilter] = []
37
- param_values: list[Any] = []
38
-
39
- for param in parameters:
40
- if isinstance(param, StatementFilter):
41
- filters.append(param)
42
- else:
43
- param_values.append(param)
44
-
45
- return filters, param_values
46
-
47
-
48
- def process_execute_many_parameters(
49
- parameters: "tuple[Union[StatementParameters, StatementFilter], ...]",
50
- ) -> "tuple[list[StatementFilter], Optional[list[Any]]]":
51
- """Process parameters for execute_many operations.
52
-
53
- Args:
54
- parameters: Mixed tuple of parameters and filters
55
-
56
- Returns:
57
- Tuple of (filters, parameter_sequence)
58
- """
59
- filters, param_values = separate_filters_and_parameters(parameters)
60
-
61
- # Use first parameter as the sequence for execute_many
62
- param_sequence = param_values[0] if param_values else None
63
-
64
- # Normalize the parameter sequence
65
- param_sequence = normalize_parameter_sequence(param_sequence)
66
-
67
- return filters, param_sequence
68
-
69
-
70
- def normalize_parameter_sequence(params: Any) -> Optional[list[Any]]:
71
- """Normalize a parameter sequence to a list format.
72
-
73
- Args:
74
- params: Parameter sequence in various formats
75
-
76
- Returns:
77
- Normalized list of parameters or None
78
- """
79
- if params is None:
80
- return None
81
-
82
- if isinstance(params, list):
83
- return params
84
-
85
- if isinstance(params, tuple):
86
- return list(params)
87
-
88
- # Check if it's iterable (but not string or dict)
89
- # Use duck typing to check for iterable protocol
90
- try:
91
- iter(params)
92
- if not isinstance(params, (str, dict)):
93
- return list(params)
94
- except TypeError:
95
- pass
96
-
97
- # Single parameter, wrap in list
98
- return [params]
99
-
100
-
101
- def convert_parameters_to_positional(params: "dict[str, Any]", parameter_info: "list[Any]") -> list[Any]:
102
- """Convert named parameters to positional based on SQL order.
103
-
104
- Args:
105
- params: Dictionary of named parameters
106
- parameter_info: List of parameter info from SQL parsing
107
-
108
- Returns:
109
- List of positional parameters
110
- """
111
- if not params:
112
- return []
113
-
114
- # Handle param_0, param_1, etc. pattern
115
- if all(key.startswith("param_") for key in params):
116
- return [params[f"param_{i}"] for i in range(len(params))]
117
-
118
- # Convert based on parameter info order
119
- # Check for name attribute using getattr with default
120
- result = []
121
- for info in parameter_info:
122
- param_name = getattr(info, "name", None)
123
- if param_name is not None:
124
- result.append(params.get(param_name, None))
125
- return result
126
-
127
-
128
- def should_use_transaction(connection: Any, auto_commit: bool = True) -> bool:
129
- """Determine if a transaction should be used.
130
-
131
- Args:
132
- connection: Database connection object
133
- auto_commit: Whether auto-commit is enabled
134
-
135
- Returns:
136
- True if transaction capabilities are available and should be used
137
- """
138
- return False if auto_commit else is_sync_transaction_capable(connection)
@@ -1,4 +0,0 @@
1
- from sqlspec.service.base import SQLSpecAsyncService, SQLSpecSyncService
2
- from sqlspec.service.pagination import OffsetPagination
3
-
4
- __all__ = ("OffsetPagination", "SQLSpecAsyncService", "SQLSpecSyncService")
sqlspec/service/_util.py DELETED
@@ -1,147 +0,0 @@
1
- from collections.abc import Sequence
2
- from functools import partial
3
- from typing import Any, Optional, TypeVar, Union, cast, overload
4
-
5
- from sqlspec.driver.mixins._result_utils import _DEFAULT_TYPE_DECODERS, _default_msgspec_deserializer
6
- from sqlspec.exceptions import SQLSpecError
7
- from sqlspec.service.pagination import OffsetPagination
8
- from sqlspec.statement.filters import FilterTypeT, LimitOffsetFilter, StatementFilter
9
- from sqlspec.typing import BaseModel, DataclassProtocol, ModelDTOT, ModelT, Struct, convert, get_type_adapter
10
- from sqlspec.utils.type_guards import is_dataclass, is_msgspec_struct, is_pydantic_model
11
-
12
- __all__ = ("ResultConverter", "find_filter")
13
-
14
-
15
- T = TypeVar("T")
16
-
17
-
18
- def find_filter(
19
- filter_type: "type[FilterTypeT]", filters: "Optional[Sequence[StatementFilter]]" = None
20
- ) -> "Optional[FilterTypeT]":
21
- """Get the filter specified by filter type from the filters.
22
-
23
- Args:
24
- filter_type: The type of filter to find.
25
- filters: filter types to apply to the query
26
-
27
- Returns:
28
- The match filter instance or None
29
- """
30
- if filters is None:
31
- return None
32
- return next(
33
- (cast("Optional[FilterTypeT]", filter_) for filter_ in filters if isinstance(filter_, filter_type)), None
34
- )
35
-
36
-
37
- # TODO: add overloads for each type of pagination in the future
38
- class ResultConverter:
39
- """Simple mixin to help convert to dictionary or list of dictionaries to specified schema type.
40
-
41
- Single objects are transformed to the supplied schema type, and lists of objects are transformed into a list of the supplied schema type.
42
-
43
- Args:
44
- data: A database model instance or row mapping.
45
- Type: :class:`~sqlspec.typing.ModelDictT`
46
-
47
- Returns:
48
- The converted schema object.
49
- """
50
-
51
- @overload
52
- def to_schema(
53
- self,
54
- data: "ModelT",
55
- total: "int | None" = None,
56
- filters: "Sequence[StatementFilter] | None" = None,
57
- *,
58
- schema_type: None = None,
59
- ) -> "ModelT": ...
60
- @overload
61
- def to_schema(
62
- self,
63
- data: "dict[str, Any] | Struct | BaseModel | DataclassProtocol",
64
- total: "int | None" = None,
65
- filters: "Sequence[StatementFilter] | None" = None,
66
- *,
67
- schema_type: "type[ModelDTOT]",
68
- ) -> "ModelDTOT": ...
69
- @overload
70
- def to_schema(
71
- self,
72
- data: "Sequence[ModelT]",
73
- total: "int | None" = None,
74
- filters: "Sequence[StatementFilter] | None" = None,
75
- *,
76
- schema_type: None = None,
77
- ) -> "OffsetPagination[ModelT]": ...
78
- @overload
79
- def to_schema(
80
- self,
81
- data: "Sequence[dict[str, Any] | Struct | BaseModel | DataclassProtocol]",
82
- total: "int | None" = None,
83
- filters: "Sequence[StatementFilter] | None" = None,
84
- *,
85
- schema_type: "type[ModelDTOT]",
86
- ) -> "OffsetPagination[ModelDTOT]": ...
87
- def to_schema(
88
- self,
89
- data: "ModelT | Sequence[ModelT] | dict[str, Any] | Struct | BaseModel | DataclassProtocol | Sequence[dict[str, Any] | Struct | BaseModel | DataclassProtocol]",
90
- total: "int | None" = None,
91
- filters: "Sequence[StatementFilter] | None" = None,
92
- *,
93
- schema_type: "type[ModelDTOT] | None" = None,
94
- ) -> "Union[ModelT, ModelDTOT , OffsetPagination[ModelT] , OffsetPagination[ModelDTOT]]":
95
- if not isinstance(data, Sequence):
96
- if schema_type is None:
97
- return cast("ModelT", data)
98
- if is_dataclass(schema_type):
99
- return cast("ModelDTOT", schema_type(**data)) # type: ignore[operator]
100
- if is_msgspec_struct(schema_type):
101
- return cast(
102
- "ModelDTOT",
103
- convert(
104
- obj=data,
105
- type=schema_type,
106
- from_attributes=True,
107
- dec_hook=partial(_default_msgspec_deserializer, type_decoders=_DEFAULT_TYPE_DECODERS),
108
- ),
109
- )
110
- if is_pydantic_model(schema_type): # pyright: ignore
111
- return cast(
112
- "ModelDTOT",
113
- get_type_adapter(schema_type).validate_python(data, from_attributes=True), # pyright: ignore
114
- )
115
- assert isinstance(data, Sequence)
116
- limit_offset = find_filter(LimitOffsetFilter, filters=filters)
117
- if schema_type is None:
118
- return OffsetPagination[ModelT](
119
- items=cast("list[ModelT]", data),
120
- limit=limit_offset.limit if limit_offset else len(data),
121
- offset=limit_offset.offset if limit_offset else 0,
122
- total=total if total is not None else len(data),
123
- )
124
- converted_items: Sequence[ModelDTOT]
125
- if is_dataclass(schema_type):
126
- converted_items = [schema_type(**item) for item in data] # type: ignore[operator]
127
- elif is_msgspec_struct(schema_type):
128
- converted_items = convert(
129
- obj=data,
130
- type=list[schema_type], # type: ignore[valid-type]
131
- from_attributes=True,
132
- dec_hook=partial(_default_msgspec_deserializer, type_decoders=_DEFAULT_TYPE_DECODERS),
133
- )
134
- elif is_pydantic_model(schema_type): # pyright: ignore
135
- converted_items = get_type_adapter(list[schema_type]).validate_python(data, from_attributes=True) # type: ignore[valid-type] # pyright: ignore[reportUnknownArgumentType]
136
- else:
137
- # This will also catch the case where a single item had an unrecognized schema_type
138
- # if it somehow bypassed the initial single-item checks.
139
- msg = "`schema_type` should be a valid Dataclass, Pydantic model or Msgspec struct"
140
- raise SQLSpecError(msg)
141
-
142
- return OffsetPagination[ModelDTOT](
143
- items=cast("list[ModelDTOT]", converted_items),
144
- limit=limit_offset.limit if limit_offset else len(data),
145
- offset=limit_offset.offset if limit_offset else 0,
146
- total=total if total is not None else len(data),
147
- )