sqlspec 0.26.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 (197) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +62 -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 +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +52 -2
  9. sqlspec/adapters/adbc/driver.py +144 -45
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +25 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +352 -144
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +138 -43
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +292 -84
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
  77. sqlspec/adapters/psqlpy/driver.py +101 -31
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +40 -11
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +77 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +231 -60
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +95 -161
  129. sqlspec/driver/_common.py +133 -80
  130. sqlspec/driver/_sync.py +95 -162
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +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/base.py +200 -76
  153. sqlspec/migrations/commands.py +591 -62
  154. sqlspec/migrations/context.py +6 -9
  155. sqlspec/migrations/fix.py +199 -0
  156. sqlspec/migrations/loaders.py +47 -19
  157. sqlspec/migrations/runner.py +241 -75
  158. sqlspec/migrations/tracker.py +237 -21
  159. sqlspec/migrations/utils.py +51 -3
  160. sqlspec/migrations/validation.py +177 -0
  161. sqlspec/protocols.py +66 -36
  162. sqlspec/storage/_utils.py +98 -0
  163. sqlspec/storage/backends/fsspec.py +134 -106
  164. sqlspec/storage/backends/local.py +78 -51
  165. sqlspec/storage/backends/obstore.py +278 -162
  166. sqlspec/storage/registry.py +75 -39
  167. sqlspec/typing.py +14 -84
  168. sqlspec/utils/config_resolver.py +6 -6
  169. sqlspec/utils/correlation.py +4 -5
  170. sqlspec/utils/data_transformation.py +3 -2
  171. sqlspec/utils/deprecation.py +9 -8
  172. sqlspec/utils/fixtures.py +4 -4
  173. sqlspec/utils/logging.py +46 -6
  174. sqlspec/utils/module_loader.py +2 -2
  175. sqlspec/utils/schema.py +288 -0
  176. sqlspec/utils/serializers.py +3 -3
  177. sqlspec/utils/sync_tools.py +21 -17
  178. sqlspec/utils/text.py +1 -2
  179. sqlspec/utils/type_guards.py +111 -20
  180. sqlspec/utils/version.py +433 -0
  181. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  182. sqlspec-0.27.0.dist-info/RECORD +207 -0
  183. sqlspec/builder/mixins/__init__.py +0 -55
  184. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  185. sqlspec/builder/mixins/_delete_operations.py +0 -50
  186. sqlspec/builder/mixins/_insert_operations.py +0 -282
  187. sqlspec/builder/mixins/_merge_operations.py +0 -698
  188. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  189. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  190. sqlspec/builder/mixins/_select_operations.py +0 -930
  191. sqlspec/builder/mixins/_update_operations.py +0 -199
  192. sqlspec/builder/mixins/_where_clause.py +0 -1298
  193. sqlspec-0.26.0.dist-info/RECORD +0 -157
  194. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  195. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  197. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -8,33 +8,19 @@ scheme-based routing, and named aliases for common configurations.
8
8
  import logging
9
9
  import re
10
10
  from pathlib import Path
11
- from typing import Any, Final, Optional, Union, cast
11
+ from typing import Any, Final, cast
12
12
 
13
13
  from mypy_extensions import mypyc_attr
14
14
 
15
15
  from sqlspec.exceptions import ImproperConfigurationError, MissingDependencyError
16
16
  from sqlspec.protocols import ObjectStoreProtocol
17
17
  from sqlspec.typing import FSSPEC_INSTALLED, OBSTORE_INSTALLED
18
+ from sqlspec.utils.type_guards import is_local_path
18
19
 
19
20
  __all__ = ("StorageRegistry", "storage_registry")
20
21
 
21
22
  logger = logging.getLogger(__name__)
22
23
 
23
-
24
- def _is_local_uri(uri: str) -> bool:
25
- """Check if URI represents a local filesystem path."""
26
- if "://" in uri and not uri.startswith("file://"):
27
- return False
28
- windows_drive_min_length = 3
29
- return (
30
- Path(uri).exists()
31
- or Path(uri).is_absolute()
32
- or uri.startswith(("~", ".", "/"))
33
- or (len(uri) >= windows_drive_min_length and uri[1:3] == ":\\")
34
- or "/" in uri
35
- )
36
-
37
-
38
24
  SCHEME_REGEX: Final = re.compile(r"([a-zA-Z0-9+.-]+)://")
39
25
 
40
26
 
@@ -74,7 +60,7 @@ class StorageRegistry:
74
60
  def __init__(self) -> None:
75
61
  self._alias_configs: dict[str, tuple[type[ObjectStoreProtocol], str, dict[str, Any]]] = {}
76
62
  self._aliases: dict[str, dict[str, Any]] = {}
77
- self._instances: dict[Union[str, tuple[str, tuple[tuple[str, Any], ...]]], ObjectStoreProtocol] = {}
63
+ self._instances: dict[str | tuple[str, tuple[tuple[str, Any], ...]], ObjectStoreProtocol] = {}
78
64
  self._cache: dict[str, tuple[str, type[ObjectStoreProtocol]]] = {}
79
65
 
80
66
  def _make_hashable(self, obj: Any) -> Any:
@@ -88,7 +74,7 @@ class StorageRegistry:
88
74
  return obj
89
75
 
90
76
  def register_alias(
91
- self, alias: str, uri: str, *, backend: Optional[str] = None, base_path: str = "", **kwargs: Any
77
+ self, alias: str, uri: str, *, backend: str | None = None, base_path: str = "", **kwargs: Any
92
78
  ) -> None:
93
79
  """Register a named alias for a storage configuration.
94
80
 
@@ -110,9 +96,7 @@ class StorageRegistry:
110
96
  test_config["uri"] = uri
111
97
  self._aliases[alias] = test_config
112
98
 
113
- def get(
114
- self, uri_or_alias: Union[str, Path], *, backend: Optional[str] = None, **kwargs: Any
115
- ) -> ObjectStoreProtocol:
99
+ def get(self, uri_or_alias: str | Path, *, backend: str | None = None, **kwargs: Any) -> ObjectStoreProtocol:
116
100
  """Get backend instance using URI-first routing with automatic backend selection.
117
101
 
118
102
  Args:
@@ -133,11 +117,15 @@ class StorageRegistry:
133
117
  if isinstance(uri_or_alias, Path):
134
118
  uri_or_alias = f"file://{uri_or_alias.resolve()}"
135
119
 
136
- cache_key = (uri_or_alias, self._make_hashable(kwargs)) if kwargs else uri_or_alias
120
+ # Include backend in cache key to ensure different backends for same URI are cached separately
121
+ cache_params = dict(kwargs)
122
+ if backend:
123
+ cache_params["__backend__"] = backend
124
+ cache_key = (uri_or_alias, self._make_hashable(cache_params)) if cache_params else uri_or_alias
137
125
  if cache_key in self._instances:
138
126
  return self._instances[cache_key]
139
127
  scheme = self._get_scheme(uri_or_alias)
140
- if not scheme and _is_local_uri(uri_or_alias):
128
+ if not scheme and is_local_path(uri_or_alias):
141
129
  scheme = "file"
142
130
  uri_or_alias = f"file://{uri_or_alias}"
143
131
 
@@ -154,57 +142,105 @@ class StorageRegistry:
154
142
  self._instances[cache_key] = instance
155
143
  return instance
156
144
 
157
- def _resolve_from_uri(
158
- self, uri: str, *, backend_override: Optional[str] = None, **kwargs: Any
159
- ) -> ObjectStoreProtocol:
160
- """Resolve backend from URI with optional backend override."""
145
+ def _resolve_from_uri(self, uri: str, *, backend_override: str | None = None, **kwargs: Any) -> ObjectStoreProtocol:
146
+ """Resolve backend from URI with optional backend override.
147
+
148
+ Backend selection priority for local files (file:// or bare paths):
149
+ 1. obstore (if installed) - provides async I/O performance
150
+ 2. fsspec (if installed) - async wrapper fallback
151
+ 3. local (always available) - zero-dependency sync backend
152
+
153
+ For cloud storage, prefer obstore over fsspec when available.
154
+
155
+ Args:
156
+ uri: Storage URI to resolve.
157
+ backend_override: Force specific backend type.
158
+ **kwargs: Additional backend configuration.
159
+
160
+ Returns:
161
+ Configured backend instance.
162
+
163
+ Raises:
164
+ MissingDependencyError: No backend available for URI scheme.
165
+ """
161
166
  if backend_override:
162
167
  return self._create_backend(backend_override, uri, **kwargs)
168
+
163
169
  scheme = self._get_scheme(uri)
164
170
 
165
- # For local files, prefer LocalStore first
171
+ # NEW: Prefer obstore for local files when available
166
172
  if scheme in {None, "file"}:
173
+ # Try obstore first for async performance
174
+ if OBSTORE_INSTALLED:
175
+ try:
176
+ return self._create_backend("obstore", uri, **kwargs)
177
+ except (ValueError, ImportError, NotImplementedError):
178
+ pass
179
+
180
+ # Fallback to fsspec if available
181
+ if FSSPEC_INSTALLED:
182
+ try:
183
+ return self._create_backend("fsspec", uri, **kwargs)
184
+ except (ValueError, ImportError, NotImplementedError):
185
+ pass
186
+
187
+ # Final fallback: local zero-dependency backend
167
188
  return self._create_backend("local", uri, **kwargs)
168
189
 
169
- # Try ObStore first if available and appropriate
190
+ # For cloud schemes, prefer obstore over fsspec
170
191
  if scheme not in FSSPEC_ONLY_SCHEMES and OBSTORE_INSTALLED:
171
192
  try:
172
193
  return self._create_backend("obstore", uri, **kwargs)
173
194
  except (ValueError, ImportError, NotImplementedError):
174
195
  pass
175
196
 
176
- # Try FSSpec if available
197
+ # Try fsspec if available
177
198
  if FSSPEC_INSTALLED:
178
199
  try:
179
200
  return self._create_backend("fsspec", uri, **kwargs)
180
201
  except (ValueError, ImportError, NotImplementedError):
181
202
  pass
182
203
 
183
- # For cloud schemes without backends, provide helpful error
204
+ # No backend available
184
205
  msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec for cloud storage support."
185
206
  raise MissingDependencyError(msg)
186
207
 
187
208
  def _determine_backend_class(self, uri: str) -> type[ObjectStoreProtocol]:
188
- """Determine the backend class for a URI based on availability."""
209
+ """Determine the backend class for a URI based on availability.
210
+
211
+ Args:
212
+ uri: Storage URI to analyze.
213
+
214
+ Returns:
215
+ Backend class type to use.
216
+
217
+ Raises:
218
+ MissingDependencyError: No backend available for URI scheme.
219
+ """
189
220
  scheme = self._get_scheme(uri)
190
221
 
191
- # For local files, always use LocalStore
222
+ # NEW: For local files, prefer obstore > fsspec > local
192
223
  if scheme in {None, "file"}:
224
+ if OBSTORE_INSTALLED:
225
+ return self._get_backend_class("obstore")
226
+ if FSSPEC_INSTALLED:
227
+ return self._get_backend_class("fsspec")
193
228
  return self._get_backend_class("local")
194
229
 
195
230
  # FSSpec-only schemes require FSSpec
196
- if scheme in FSSPEC_ONLY_SCHEMES and FSSPEC_INSTALLED:
231
+ if scheme in FSSPEC_ONLY_SCHEMES:
232
+ if not FSSPEC_INSTALLED:
233
+ msg = f"Scheme '{scheme}' requires fsspec. Install with: pip install fsspec"
234
+ raise MissingDependencyError(msg)
197
235
  return self._get_backend_class("fsspec")
198
236
 
199
- # Prefer ObStore for cloud storage if available
237
+ # For cloud schemes, prefer obstore
200
238
  if OBSTORE_INSTALLED:
201
239
  return self._get_backend_class("obstore")
202
240
 
203
- # Fall back to FSSpec if available
204
241
  if FSSPEC_INSTALLED:
205
242
  return self._get_backend_class("fsspec")
206
243
 
207
- # For cloud schemes without backends, provide helpful error
208
244
  msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec for cloud storage support."
209
245
  raise MissingDependencyError(msg)
210
246
 
@@ -229,7 +265,7 @@ class StorageRegistry:
229
265
  """Create backend instance for URI."""
230
266
  return self._get_backend_class(backend_type)(uri, **kwargs)
231
267
 
232
- def _get_scheme(self, uri: str) -> Optional[str]:
268
+ def _get_scheme(self, uri: str) -> str | None:
233
269
  """Extract the scheme from a URI using regex."""
234
270
  if not uri:
235
271
  return None
@@ -244,7 +280,7 @@ class StorageRegistry:
244
280
  """List all registered aliases."""
245
281
  return list(self._alias_configs.keys())
246
282
 
247
- def clear_cache(self, uri_or_alias: Optional[str] = None) -> None:
283
+ def clear_cache(self, uri_or_alias: str | None = None) -> None:
248
284
  """Clear resolved backend cache."""
249
285
  if uri_or_alias:
250
286
  self._instances.pop(uri_or_alias, None)
sqlspec/typing.py CHANGED
@@ -1,9 +1,9 @@
1
1
  # pyright: ignore[reportAttributeAccessIssue]
2
- from collections.abc import Iterator, Mapping
2
+ from collections.abc import Iterator
3
3
  from functools import lru_cache
4
- from typing import TYPE_CHECKING, Annotated, Any, Protocol, Union
4
+ from typing import Annotated, Any, Protocol, TypeAlias, _TypedDict # pyright: ignore
5
5
 
6
- from typing_extensions import TypeAlias, TypeVar
6
+ from typing_extensions import TypeVar
7
7
 
8
8
  from sqlspec._typing import (
9
9
  AIOSQL_INSTALLED,
@@ -23,7 +23,6 @@ from sqlspec._typing import (
23
23
  UNSET,
24
24
  AiosqlAsyncProtocol,
25
25
  AiosqlParamType,
26
- AiosqlProtocol,
27
26
  AiosqlSQLOperationType,
28
27
  AiosqlSyncProtocol,
29
28
  ArrowRecordBatch,
@@ -41,6 +40,7 @@ from sqlspec._typing import (
41
40
  FailFast,
42
41
  Gauge,
43
42
  Histogram,
43
+ NumpyArray,
44
44
  Span,
45
45
  Status,
46
46
  StatusCode,
@@ -61,9 +61,6 @@ from sqlspec._typing import (
61
61
  trace,
62
62
  )
63
63
 
64
- if TYPE_CHECKING:
65
- from collections.abc import Sequence
66
-
67
64
 
68
65
  class DictLike(Protocol):
69
66
  """A protocol for objects that behave like a dictionary for reading."""
@@ -87,30 +84,22 @@ PoolT = TypeVar("PoolT")
87
84
 
88
85
  :class:`~sqlspec.typing.PoolT`
89
86
  """
90
- PoolT_co = TypeVar("PoolT_co", covariant=True)
91
- """Type variable for covariant pool types.
92
-
93
- :class:`~sqlspec.typing.PoolT_co`
94
- """
95
- ModelT = TypeVar("ModelT", bound="Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub]")
96
- """Type variable for model types.
87
+ SchemaT = TypeVar("SchemaT", default=dict[str, Any])
88
+ """Type variable for schema types (models, TypedDict, dataclasses, etc.).
97
89
 
98
- :class:`DictLike` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel` | :class:`DataclassProtocol` | :class:`AttrsInstance`
90
+ Unbounded TypeVar for use with schema_type parameter in driver methods.
91
+ Supports all schema types including TypedDict which cannot be bounded to a class hierarchy.
99
92
  """
100
- RowT = TypeVar("RowT", bound="dict[str, Any]")
101
-
102
93
 
103
- DictRow: TypeAlias = "dict[str, Any]"
104
- """Type variable for DictRow types."""
105
- TupleRow: TypeAlias = "tuple[Any, ...]"
106
- """Type variable for TupleRow types."""
107
94
 
108
- SupportedSchemaModel: TypeAlias = "Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub]"
95
+ SupportedSchemaModel: TypeAlias = (
96
+ DictLike | StructStub | BaseModelStub | DataclassProtocol | AttrsInstanceStub | _TypedDict
97
+ )
109
98
  """Type alias for pydantic or msgspec models.
110
99
 
111
100
  :class:`msgspec.Struct` | :class:`pydantic.BaseModel` | :class:`DataclassProtocol` | :class:`AttrsInstance`
112
101
  """
113
- StatementParameters: TypeAlias = "Union[Any, dict[str, Any], list[Any], tuple[Any, ...], None]"
102
+ StatementParameters: TypeAlias = "Any | dict[str, Any] | list[Any] | tuple[Any, ...] | None"
114
103
  """Type alias for statement parameters.
115
104
 
116
105
  Represents:
@@ -119,40 +108,6 @@ Represents:
119
108
  - :type:`tuple[Any, ...]`
120
109
  - :type:`None`
121
110
  """
122
- ModelDTOT = TypeVar("ModelDTOT", bound="SupportedSchemaModel")
123
- """Type variable for model DTOs.
124
-
125
- :class:`msgspec.Struct`|:class:`pydantic.BaseModel`
126
- """
127
- PydanticOrMsgspecT = SupportedSchemaModel
128
- """Type alias for pydantic or msgspec models.
129
-
130
- :class:`msgspec.Struct` or :class:`pydantic.BaseModel`
131
- """
132
- ModelDict: TypeAlias = (
133
- "Union[dict[str, Any], Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub], Any]"
134
- )
135
- """Type alias for model dictionaries.
136
-
137
- Represents:
138
- - :type:`dict[str, Any]` | :class:`DataclassProtocol` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel`
139
- """
140
- ModelDictList: TypeAlias = (
141
- "Sequence[Union[dict[str, Any], Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub]]]"
142
- )
143
- """Type alias for model dictionary lists.
144
-
145
- A list or sequence of any of the following:
146
- - :type:`Sequence`[:type:`dict[str, Any]` | :class:`DataclassProtocol` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel`]
147
-
148
- """
149
- BulkModelDict: TypeAlias = "Union[Sequence[Union[dict[str, Any], Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub]]], Any]"
150
- """Type alias for bulk model dictionaries.
151
-
152
- Represents:
153
- - :type:`Sequence`[:type:`dict[str, Any]` | :class:`DataclassProtocol` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel`]
154
- - :class:`DTOData`[:type:`list[ModelT]`]
155
- """
156
111
 
157
112
 
158
113
  @lru_cache(typed=True)
@@ -170,18 +125,6 @@ def get_type_adapter(f: "type[T]") -> Any:
170
125
  return TypeAdapter(f)
171
126
 
172
127
 
173
- def MixinOf(base: type[T]) -> type[T]: # noqa: N802
174
- """Useful function to make mixins with baseclass type hint
175
-
176
- ```
177
- class StorageMixin(MixinOf(DriverProtocol)): ...
178
- ```
179
- """
180
- if TYPE_CHECKING:
181
- return base
182
- return type("<MixinOf>", (base,), {})
183
-
184
-
185
128
  __all__ = (
186
129
  "AIOSQL_INSTALLED",
187
130
  "ATTRS_INSTALLED",
@@ -201,38 +144,26 @@ __all__ = (
201
144
  "UNSET",
202
145
  "AiosqlAsyncProtocol",
203
146
  "AiosqlParamType",
204
- "AiosqlProtocol",
205
147
  "AiosqlSQLOperationType",
206
148
  "AiosqlSyncProtocol",
207
149
  "ArrowRecordBatch",
208
150
  "ArrowTable",
209
151
  "AttrsInstance",
210
152
  "BaseModel",
211
- "BulkModelDict",
212
153
  "ConnectionT",
213
154
  "Counter",
214
155
  "DTOData",
215
156
  "DataclassProtocol",
216
157
  "DictLike",
217
- "DictRow",
218
158
  "Empty",
219
159
  "EmptyEnum",
220
160
  "EmptyType",
221
161
  "FailFast",
222
162
  "Gauge",
223
163
  "Histogram",
224
- "Mapping",
225
- "MixinOf",
226
- "ModelDTOT",
227
- "ModelDict",
228
- "ModelDict",
229
- "ModelDictList",
230
- "ModelDictList",
231
- "ModelT",
164
+ "NumpyArray",
232
165
  "PoolT",
233
- "PoolT_co",
234
- "PydanticOrMsgspecT",
235
- "RowT",
166
+ "SchemaT",
236
167
  "Span",
237
168
  "StatementParameters",
238
169
  "Status",
@@ -240,7 +171,6 @@ __all__ = (
240
171
  "Struct",
241
172
  "SupportedSchemaModel",
242
173
  "Tracer",
243
- "TupleRow",
244
174
  "TypeAdapter",
245
175
  "UnsetType",
246
176
  "aiosql",
@@ -7,7 +7,7 @@ Supports both synchronous and asynchronous callable functions.
7
7
 
8
8
  import inspect
9
9
  from collections.abc import Sequence
10
- from typing import TYPE_CHECKING, Any, Union, cast
10
+ from typing import TYPE_CHECKING, Any, cast
11
11
 
12
12
  from sqlspec.exceptions import ConfigResolverError
13
13
  from sqlspec.utils.module_loader import import_string
@@ -21,7 +21,7 @@ __all__ = ("resolve_config_async", "resolve_config_sync")
21
21
 
22
22
  async def resolve_config_async(
23
23
  config_path: str,
24
- ) -> "Union[list[Union[AsyncDatabaseConfig, SyncDatabaseConfig]], Union[AsyncDatabaseConfig, SyncDatabaseConfig]]":
24
+ ) -> "list[AsyncDatabaseConfig | SyncDatabaseConfig] | AsyncDatabaseConfig | SyncDatabaseConfig":
25
25
  """Resolve config from dotted path, handling callables and direct instances.
26
26
 
27
27
  This is the async-first version that handles both sync and async callables efficiently.
@@ -58,7 +58,7 @@ async def resolve_config_async(
58
58
 
59
59
  def resolve_config_sync(
60
60
  config_path: str,
61
- ) -> "Union[list[Union[AsyncDatabaseConfig, SyncDatabaseConfig]], Union[AsyncDatabaseConfig, SyncDatabaseConfig]]":
61
+ ) -> "list[AsyncDatabaseConfig | SyncDatabaseConfig] | AsyncDatabaseConfig | SyncDatabaseConfig":
62
62
  """Synchronous wrapper for resolve_config.
63
63
 
64
64
  Args:
@@ -90,7 +90,7 @@ def resolve_config_sync(
90
90
 
91
91
  def _validate_config_result(
92
92
  config_result: Any, config_path: str
93
- ) -> "Union[list[Union[AsyncDatabaseConfig, SyncDatabaseConfig]], Union[AsyncDatabaseConfig, SyncDatabaseConfig]]":
93
+ ) -> "list[AsyncDatabaseConfig | SyncDatabaseConfig] | AsyncDatabaseConfig | SyncDatabaseConfig":
94
94
  """Validate that the config result is a valid config or list of configs.
95
95
 
96
96
  Args:
@@ -117,13 +117,13 @@ def _validate_config_result(
117
117
  msg = f"Config '{config_path}' returned invalid config at index {i}. Expected database config instance."
118
118
  raise ConfigResolverError(msg)
119
119
 
120
- return cast("list[Union[AsyncDatabaseConfig, SyncDatabaseConfig]]", list(config_result))
120
+ return cast("list[AsyncDatabaseConfig | SyncDatabaseConfig]", list(config_result))
121
121
 
122
122
  if not _is_valid_config(config_result):
123
123
  msg = f"Config '{config_path}' returned invalid type '{type(config_result).__name__}'. Expected database config instance or list."
124
124
  raise ConfigResolverError(msg)
125
125
 
126
- return cast("Union[AsyncDatabaseConfig, SyncDatabaseConfig]", config_result)
126
+ return cast("AsyncDatabaseConfig | SyncDatabaseConfig", config_result)
127
127
 
128
128
 
129
129
  def _is_valid_config(config: Any) -> bool:
@@ -4,15 +4,14 @@ This module provides utilities for tracking correlation IDs across
4
4
  database operations, enabling distributed tracing and debugging.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  import uuid
8
+ from collections.abc import Generator
10
9
  from contextlib import contextmanager
11
10
  from contextvars import ContextVar
12
11
  from typing import TYPE_CHECKING, Any
13
12
 
14
13
  if TYPE_CHECKING:
15
- from collections.abc import Generator, MutableMapping
14
+ from collections.abc import MutableMapping
16
15
  from logging import LoggerAdapter
17
16
 
18
17
  __all__ = ("CorrelationContext", "correlation_context", "get_correlation_adapter")
@@ -115,7 +114,7 @@ def correlation_context(correlation_id: str | None = None) -> Generator[str, Non
115
114
  yield cid
116
115
 
117
116
 
118
- def get_correlation_adapter(logger: Any) -> LoggerAdapter:
117
+ def get_correlation_adapter(logger: Any) -> "LoggerAdapter":
119
118
  """Get a logger adapter that automatically includes correlation ID.
120
119
 
121
120
  Args:
@@ -129,7 +128,7 @@ def get_correlation_adapter(logger: Any) -> LoggerAdapter:
129
128
  class CorrelationAdapter(LoggerAdapter):
130
129
  """Logger adapter that adds correlation ID to all logs."""
131
130
 
132
- def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, dict[str, Any]]:
131
+ def process(self, msg: str, kwargs: "MutableMapping[str, Any]") -> tuple[str, dict[str, Any]]:
133
132
  """Add correlation ID to the log record.
134
133
 
135
134
  Args:
@@ -5,7 +5,8 @@ field name conversion when mapping database results to schema objects.
5
5
  Used primarily for msgspec field name conversion with rename configurations.
6
6
  """
7
7
 
8
- from typing import Any, Callable, Union
8
+ from collections.abc import Callable
9
+ from typing import Any
9
10
 
10
11
  __all__ = ("transform_dict_keys",)
11
12
 
@@ -30,7 +31,7 @@ def _safe_convert_key(key: Any, converter: Callable[[str], str]) -> Any:
30
31
  return key
31
32
 
32
33
 
33
- def transform_dict_keys(data: Union[dict, list, Any], converter: Callable[[str], str]) -> Union[dict, list, Any]:
34
+ def transform_dict_keys(data: dict | list | Any, converter: Callable[[str], str]) -> dict | list | Any:
34
35
  """Transform dictionary keys using the provided converter function.
35
36
 
36
37
  Recursively transforms all dictionary keys in a data structure using
@@ -5,8 +5,9 @@ Used to communicate API changes and migration paths to users.
5
5
  """
6
6
 
7
7
  import inspect
8
+ from collections.abc import Callable
8
9
  from functools import wraps
9
- from typing import Callable, Literal, Optional
10
+ from typing import Literal
10
11
  from warnings import warn
11
12
 
12
13
  from typing_extensions import ParamSpec, TypeVar
@@ -24,9 +25,9 @@ def warn_deprecation(
24
25
  deprecated_name: str,
25
26
  kind: DeprecatedKind,
26
27
  *,
27
- removal_in: Optional[str] = None,
28
- alternative: Optional[str] = None,
29
- info: Optional[str] = None,
28
+ removal_in: str | None = None,
29
+ alternative: str | None = None,
30
+ info: str | None = None,
30
31
  pending: bool = False,
31
32
  ) -> None:
32
33
  """Warn about a call to a deprecated function.
@@ -72,11 +73,11 @@ def warn_deprecation(
72
73
  def deprecated(
73
74
  version: str,
74
75
  *,
75
- removal_in: Optional[str] = None,
76
- alternative: Optional[str] = None,
77
- info: Optional[str] = None,
76
+ removal_in: str | None = None,
77
+ alternative: str | None = None,
78
+ info: str | None = None,
78
79
  pending: bool = False,
79
- kind: Optional[Literal["function", "method", "classmethod", "property"]] = None,
80
+ kind: Literal["function", "method", "classmethod", "property"] | None = None,
80
81
  ) -> Callable[[Callable[P, T]], Callable[P, T]]:
81
82
  """Create a decorator wrapping a function, method or property with a deprecation warning.
82
83
 
sqlspec/utils/fixtures.py CHANGED
@@ -7,7 +7,7 @@ used in testing and development. Supports both sync and async operations.
7
7
  import gzip
8
8
  import zipfile
9
9
  from pathlib import Path
10
- from typing import TYPE_CHECKING, Any, Union
10
+ from typing import TYPE_CHECKING, Any
11
11
 
12
12
  from sqlspec.storage import storage_registry
13
13
  from sqlspec.utils.serializers import from_json as decode_json
@@ -16,7 +16,7 @@ from sqlspec.utils.sync_tools import async_
16
16
  from sqlspec.utils.type_guards import schema_dump
17
17
 
18
18
  if TYPE_CHECKING:
19
- from sqlspec.typing import ModelDictList, SupportedSchemaModel
19
+ from sqlspec.typing import SupportedSchemaModel
20
20
 
21
21
  __all__ = ("open_fixture", "open_fixture_async", "write_fixture", "write_fixture_async")
22
22
 
@@ -171,7 +171,7 @@ def _serialize_data(data: Any) -> str:
171
171
  def write_fixture(
172
172
  fixtures_path: str,
173
173
  table_name: str,
174
- data: "Union[ModelDictList, list[dict[str, Any]], SupportedSchemaModel]",
174
+ data: "list[SupportedSchemaModel] | list[dict[str, Any]] | SupportedSchemaModel",
175
175
  storage_backend: str = "local",
176
176
  compress: bool = False,
177
177
  **storage_kwargs: Any,
@@ -219,7 +219,7 @@ def write_fixture(
219
219
  async def write_fixture_async(
220
220
  fixtures_path: str,
221
221
  table_name: str,
222
- data: "Union[ModelDictList, list[dict[str, Any]], SupportedSchemaModel]",
222
+ data: "list[SupportedSchemaModel] | list[dict[str, Any]] | SupportedSchemaModel",
223
223
  storage_backend: str = "local",
224
224
  compress: bool = False,
225
225
  **storage_kwargs: Any,
sqlspec/utils/logging.py CHANGED
@@ -8,16 +8,24 @@ SQLSpec provides StructuredFormatter for JSON-formatted logs if desired.
8
8
  import logging
9
9
  from contextvars import ContextVar
10
10
  from logging import LogRecord
11
- from typing import Any, Optional
11
+ from typing import Any
12
12
 
13
13
  from sqlspec._serialization import encode_json
14
14
 
15
- __all__ = ("StructuredFormatter", "correlation_id_var", "get_correlation_id", "get_logger", "set_correlation_id")
15
+ __all__ = (
16
+ "SqlglotCommandFallbackFilter",
17
+ "StructuredFormatter",
18
+ "correlation_id_var",
19
+ "get_correlation_id",
20
+ "get_logger",
21
+ "set_correlation_id",
22
+ "suppress_erroneous_sqlglot_log_messages",
23
+ )
16
24
 
17
- correlation_id_var: "ContextVar[Optional[str]]" = ContextVar("correlation_id", default=None)
25
+ correlation_id_var: "ContextVar[str | None]" = ContextVar("correlation_id", default=None)
18
26
 
19
27
 
20
- def set_correlation_id(correlation_id: "Optional[str]") -> None:
28
+ def set_correlation_id(correlation_id: "str | None") -> None:
21
29
  """Set the correlation ID for the current context.
22
30
 
23
31
  Args:
@@ -26,7 +34,7 @@ def set_correlation_id(correlation_id: "Optional[str]") -> None:
26
34
  correlation_id_var.set(correlation_id)
27
35
 
28
36
 
29
- def get_correlation_id() -> "Optional[str]":
37
+ def get_correlation_id() -> "str | None":
30
38
  """Get the current correlation ID.
31
39
 
32
40
  Returns:
@@ -86,7 +94,27 @@ class CorrelationIDFilter(logging.Filter):
86
94
  return True
87
95
 
88
96
 
89
- def get_logger(name: "Optional[str]" = None) -> logging.Logger:
97
+ class SqlglotCommandFallbackFilter(logging.Filter):
98
+ """Filter to suppress sqlglot's confusing 'Falling back to Command' warning.
99
+
100
+ This filter suppresses the warning message that sqlglot emits when it
101
+ encounters unsupported syntax and falls back to parsing as a Command.
102
+ This is expected behavior in SQLSpec and the warning is confusing to users.
103
+ """
104
+
105
+ def filter(self, record: LogRecord) -> bool:
106
+ """Suppress the 'Falling back to Command' warning message.
107
+
108
+ Args:
109
+ record: The log record to evaluate
110
+
111
+ Returns:
112
+ False if the record contains the fallback warning, True otherwise
113
+ """
114
+ return "Falling back to parsing as a 'Command'" not in record.getMessage()
115
+
116
+
117
+ def get_logger(name: "str | None" = None) -> logging.Logger:
90
118
  """Get a logger instance with standardized configuration.
91
119
 
92
120
  Args:
@@ -121,3 +149,15 @@ def log_with_context(logger: logging.Logger, level: int, message: str, **extra_f
121
149
  record = logger.makeRecord(logger.name, level, "(unknown file)", 0, message, (), None)
122
150
  record.extra_fields = extra_fields
123
151
  logger.handle(record)
152
+
153
+
154
+ def suppress_erroneous_sqlglot_log_messages() -> None:
155
+ """Suppress confusing sqlglot warning messages.
156
+
157
+ Adds a filter to the sqlglot logger to suppress the warning message
158
+ about falling back to parsing as a Command. This is expected behavior
159
+ in SQLSpec and the warning is confusing to users.
160
+ """
161
+ sqlglot_logger = logging.getLogger("sqlglot")
162
+ if not any(isinstance(f, SqlglotCommandFallbackFilter) for f in sqlglot_logger.filters):
163
+ sqlglot_logger.addFilter(SqlglotCommandFallbackFilter())