sqlspec 0.12.2__py3-none-any.whl → 0.13.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 -180
- sqlspec/adapters/adbc/config.py +10 -12
- sqlspec/adapters/adbc/driver.py +120 -118
- sqlspec/adapters/aiosqlite/config.py +3 -3
- sqlspec/adapters/aiosqlite/driver.py +100 -130
- sqlspec/adapters/asyncmy/config.py +3 -4
- sqlspec/adapters/asyncmy/driver.py +123 -135
- sqlspec/adapters/asyncpg/config.py +3 -7
- sqlspec/adapters/asyncpg/driver.py +98 -140
- sqlspec/adapters/bigquery/config.py +4 -5
- sqlspec/adapters/bigquery/driver.py +125 -167
- sqlspec/adapters/duckdb/config.py +3 -6
- sqlspec/adapters/duckdb/driver.py +114 -111
- sqlspec/adapters/oracledb/config.py +6 -5
- sqlspec/adapters/oracledb/driver.py +242 -259
- sqlspec/adapters/psqlpy/config.py +3 -7
- sqlspec/adapters/psqlpy/driver.py +118 -93
- sqlspec/adapters/psycopg/config.py +18 -31
- sqlspec/adapters/psycopg/driver.py +283 -236
- sqlspec/adapters/sqlite/config.py +3 -3
- sqlspec/adapters/sqlite/driver.py +103 -97
- sqlspec/config.py +0 -4
- sqlspec/driver/_async.py +89 -98
- sqlspec/driver/_common.py +52 -17
- sqlspec/driver/_sync.py +81 -105
- sqlspec/driver/connection.py +207 -0
- sqlspec/driver/mixins/_csv_writer.py +91 -0
- sqlspec/driver/mixins/_pipeline.py +38 -49
- sqlspec/driver/mixins/_result_utils.py +27 -9
- sqlspec/driver/mixins/_storage.py +67 -181
- sqlspec/driver/mixins/_type_coercion.py +3 -4
- sqlspec/driver/parameters.py +138 -0
- sqlspec/exceptions.py +10 -2
- sqlspec/extensions/aiosql/adapter.py +0 -10
- sqlspec/extensions/litestar/handlers.py +0 -1
- sqlspec/extensions/litestar/plugin.py +0 -3
- sqlspec/extensions/litestar/providers.py +0 -14
- sqlspec/loader.py +25 -90
- sqlspec/protocols.py +542 -0
- sqlspec/service/__init__.py +3 -2
- sqlspec/service/_util.py +147 -0
- sqlspec/service/base.py +1116 -9
- sqlspec/statement/builder/__init__.py +42 -32
- sqlspec/statement/builder/_ddl_utils.py +0 -10
- sqlspec/statement/builder/_parsing_utils.py +10 -4
- sqlspec/statement/builder/base.py +67 -22
- sqlspec/statement/builder/column.py +283 -0
- sqlspec/statement/builder/ddl.py +91 -67
- sqlspec/statement/builder/delete.py +23 -7
- sqlspec/statement/builder/insert.py +29 -15
- sqlspec/statement/builder/merge.py +4 -4
- sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
- sqlspec/statement/builder/mixins/_delete_from.py +1 -1
- sqlspec/statement/builder/mixins/_from.py +10 -8
- sqlspec/statement/builder/mixins/_group_by.py +0 -1
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
- sqlspec/statement/builder/mixins/_insert_values.py +0 -2
- sqlspec/statement/builder/mixins/_join.py +20 -13
- sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
- sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
- sqlspec/statement/builder/mixins/_order_by.py +2 -2
- sqlspec/statement/builder/mixins/_pivot.py +4 -7
- sqlspec/statement/builder/mixins/_select_columns.py +6 -5
- sqlspec/statement/builder/mixins/_unpivot.py +6 -9
- sqlspec/statement/builder/mixins/_update_from.py +2 -1
- sqlspec/statement/builder/mixins/_update_set.py +11 -8
- sqlspec/statement/builder/mixins/_where.py +61 -34
- sqlspec/statement/builder/select.py +32 -17
- sqlspec/statement/builder/update.py +25 -11
- sqlspec/statement/filters.py +39 -14
- sqlspec/statement/parameter_manager.py +220 -0
- sqlspec/statement/parameters.py +210 -79
- sqlspec/statement/pipelines/__init__.py +166 -23
- sqlspec/statement/pipelines/analyzers/_analyzer.py +21 -20
- sqlspec/statement/pipelines/context.py +35 -39
- sqlspec/statement/pipelines/transformers/__init__.py +2 -3
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +628 -58
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
- sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
- sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
- sqlspec/statement/pipelines/validators/_performance.py +38 -23
- sqlspec/statement/pipelines/validators/_security.py +39 -62
- sqlspec/statement/result.py +37 -129
- sqlspec/statement/splitter.py +0 -12
- sqlspec/statement/sql.py +863 -391
- sqlspec/statement/sql_compiler.py +140 -0
- sqlspec/storage/__init__.py +10 -2
- sqlspec/storage/backends/fsspec.py +53 -8
- sqlspec/storage/backends/obstore.py +15 -19
- sqlspec/storage/capabilities.py +101 -0
- sqlspec/storage/registry.py +56 -83
- sqlspec/typing.py +6 -434
- sqlspec/utils/cached_property.py +25 -0
- sqlspec/utils/correlation.py +0 -2
- sqlspec/utils/logging.py +0 -6
- sqlspec/utils/sync_tools.py +0 -4
- sqlspec/utils/text.py +0 -5
- sqlspec/utils/type_guards.py +892 -0
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/METADATA +1 -1
- sqlspec-0.13.0.dist-info/RECORD +150 -0
- sqlspec/statement/builder/protocols.py +0 -20
- sqlspec/statement/pipelines/base.py +0 -315
- sqlspec/statement/pipelines/result_types.py +0 -41
- sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
- sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
- sqlspec/statement/pipelines/validators/base.py +0 -67
- sqlspec/storage/protocol.py +0 -173
- sqlspec-0.12.2.dist-info/RECORD +0 -145
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/storage/registry.py
CHANGED
|
@@ -8,13 +8,13 @@ This module provides a flexible, lazy-loading storage registry that supports:
|
|
|
8
8
|
- Automatic instrumentation integration
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
# TODO: TRY300 - Review try-except patterns for else block opportunities
|
|
12
11
|
import logging
|
|
13
12
|
from pathlib import Path
|
|
14
13
|
from typing import Any, Optional, TypeVar, Union, cast
|
|
15
14
|
|
|
16
15
|
from sqlspec.exceptions import ImproperConfigurationError, MissingDependencyError
|
|
17
|
-
from sqlspec.
|
|
16
|
+
from sqlspec.protocols import ObjectStoreProtocol
|
|
17
|
+
from sqlspec.storage.capabilities import StorageCapabilities
|
|
18
18
|
from sqlspec.typing import FSSPEC_INSTALLED, OBSTORE_INSTALLED
|
|
19
19
|
|
|
20
20
|
__all__ = ("StorageRegistry", "storage_registry")
|
|
@@ -58,9 +58,7 @@ class StorageRegistry:
|
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
60
|
def __init__(self) -> None:
|
|
61
|
-
# Named aliases (secondary feature) - internal storage
|
|
62
61
|
self._alias_configs: dict[str, tuple[type[ObjectStoreProtocol], str, dict[str, Any]]] = {}
|
|
63
|
-
# Expose configs for testing compatibility
|
|
64
62
|
self._aliases: dict[str, dict[str, Any]] = {}
|
|
65
63
|
self._instances: dict[Union[str, tuple[str, tuple[tuple[str, Any], ...]]], ObjectStoreProtocol] = {}
|
|
66
64
|
|
|
@@ -85,21 +83,17 @@ class StorageRegistry:
|
|
|
85
83
|
**kwargs: Backend-specific configuration options
|
|
86
84
|
"""
|
|
87
85
|
if backend is None:
|
|
88
|
-
# Auto-detect from URI using new intelligent selection
|
|
89
86
|
backend = self._determine_backend_class(uri)
|
|
90
87
|
|
|
91
88
|
config = config or {}
|
|
92
89
|
config.update(kwargs)
|
|
93
90
|
|
|
94
|
-
# Store the actual config that will be passed to backend
|
|
95
91
|
backend_config = dict(config)
|
|
96
92
|
if base_path:
|
|
97
93
|
backend_config["base_path"] = base_path
|
|
98
94
|
|
|
99
|
-
# Store backend class, URI, and config separately
|
|
100
95
|
self._alias_configs[alias] = (backend, uri, backend_config)
|
|
101
96
|
|
|
102
|
-
# Store config with URI for test compatibility
|
|
103
97
|
test_config = dict(backend_config)
|
|
104
98
|
test_config["uri"] = uri
|
|
105
99
|
self._aliases[alias] = test_config
|
|
@@ -117,72 +111,35 @@ class StorageRegistry:
|
|
|
117
111
|
Raises:
|
|
118
112
|
ImproperConfigurationError: If alias not found or invalid input
|
|
119
113
|
"""
|
|
120
|
-
# Handle None case - raise AttributeError for test compatibility
|
|
121
|
-
if uri_or_alias is None:
|
|
122
|
-
msg = "uri_or_alias cannot be None"
|
|
123
|
-
raise AttributeError(msg)
|
|
124
|
-
|
|
125
|
-
# Handle empty string
|
|
126
114
|
if not uri_or_alias:
|
|
127
|
-
msg = "
|
|
115
|
+
msg = "URI or alias cannot be empty."
|
|
128
116
|
raise ImproperConfigurationError(msg)
|
|
129
117
|
|
|
130
|
-
# Handle Path objects - convert to file:// URI
|
|
131
118
|
if isinstance(uri_or_alias, Path):
|
|
132
119
|
uri_or_alias = f"file://{uri_or_alias.resolve()}"
|
|
133
120
|
|
|
134
|
-
|
|
135
|
-
cache_key: Union[str, tuple[str, tuple[tuple[str, Any], ...]]] = (
|
|
136
|
-
(uri_or_alias, tuple(sorted(kwargs.items()))) if kwargs else uri_or_alias
|
|
137
|
-
)
|
|
121
|
+
cache_key = (uri_or_alias, tuple(sorted(kwargs.items()))) if kwargs else uri_or_alias
|
|
138
122
|
if cache_key in self._instances:
|
|
139
123
|
return self._instances[cache_key]
|
|
140
124
|
|
|
141
|
-
# PRIMARY: Try URI-first routing
|
|
142
125
|
if "://" in uri_or_alias:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
self._instances[cache_key] = backend
|
|
146
|
-
return backend
|
|
147
|
-
|
|
148
|
-
# SECONDARY: Check if it's a registered alias
|
|
149
|
-
if uri_or_alias in self._alias_configs:
|
|
126
|
+
instance = self._resolve_from_uri(uri_or_alias, **kwargs)
|
|
127
|
+
elif uri_or_alias in self._alias_configs:
|
|
150
128
|
backend_cls, stored_uri, config = self._alias_configs[uri_or_alias]
|
|
151
|
-
|
|
152
|
-
merged_config = dict(config)
|
|
153
|
-
merged_config.update(kwargs)
|
|
154
|
-
# URI is passed as first positional arg
|
|
129
|
+
merged_config = {**config, **kwargs}
|
|
155
130
|
instance = backend_cls(stored_uri, **merged_config)
|
|
156
|
-
|
|
157
|
-
|
|
131
|
+
else:
|
|
132
|
+
msg = f"Unknown storage alias or invalid URI: '{uri_or_alias}'"
|
|
133
|
+
raise ImproperConfigurationError(msg)
|
|
158
134
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
raise ImproperConfigurationError(msg)
|
|
135
|
+
self._instances[cache_key] = instance
|
|
136
|
+
return instance
|
|
162
137
|
|
|
163
138
|
def _resolve_from_uri(self, uri: str, **kwargs: Any) -> ObjectStoreProtocol:
|
|
164
|
-
"""Resolve backend from URI.
|
|
165
|
-
|
|
166
|
-
Tries ObStore first for supported schemes, then falls back to FSSpec.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
uri: URI to resolve backend for
|
|
170
|
-
**kwargs: Additional backend-specific configuration
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
Backend instance
|
|
174
|
-
|
|
175
|
-
Raises:
|
|
176
|
-
MissingDependencyError: If no suitable backend can be created
|
|
177
|
-
"""
|
|
178
|
-
# Schemes that ObStore doesn't support
|
|
179
|
-
|
|
180
|
-
# Extract scheme
|
|
139
|
+
"""Resolve backend from URI, trying ObStore first, then FSSpec."""
|
|
181
140
|
scheme = self._get_scheme(uri)
|
|
182
|
-
|
|
183
141
|
last_exc: Optional[Exception] = None
|
|
184
142
|
|
|
185
|
-
# If scheme is FSSpec-only, skip ObStore
|
|
186
143
|
if scheme not in FSSPEC_ONLY_SCHEMES and OBSTORE_INSTALLED:
|
|
187
144
|
try:
|
|
188
145
|
return self._create_backend("obstore", uri, **kwargs)
|
|
@@ -197,13 +154,14 @@ class StorageRegistry:
|
|
|
197
154
|
logger.debug("FSSpec backend failed for %s: %s", uri, e)
|
|
198
155
|
last_exc = e
|
|
199
156
|
|
|
200
|
-
msg = f"No storage backend available for URI '{uri}'. Install 'obstore' or 'fsspec' and
|
|
157
|
+
msg = f"No storage backend available for URI '{uri}'. Install 'obstore' or 'fsspec' and required dependencies."
|
|
201
158
|
raise MissingDependencyError(msg) from last_exc
|
|
202
159
|
|
|
203
160
|
def _determine_backend_class(self, uri: str) -> type[ObjectStoreProtocol]:
|
|
204
|
-
"""Determine the best backend class for a URI based on availability.
|
|
161
|
+
"""Determine the best backend class for a URI based on availability and capabilities.
|
|
205
162
|
|
|
206
|
-
Prefers ObStore
|
|
163
|
+
Prefers ObStore for its superior performance and native capabilities,
|
|
164
|
+
falls back to FSSpec for extended protocol support.
|
|
207
165
|
|
|
208
166
|
Args:
|
|
209
167
|
uri: URI to determine backend for.
|
|
@@ -211,12 +169,20 @@ class StorageRegistry:
|
|
|
211
169
|
Returns:
|
|
212
170
|
Backend class (not instance)
|
|
213
171
|
"""
|
|
172
|
+
scheme = self._get_scheme(uri)
|
|
173
|
+
|
|
174
|
+
# Check if scheme requires FSSpec (not supported by ObStore)
|
|
175
|
+
if scheme in FSSPEC_ONLY_SCHEMES and FSSPEC_INSTALLED:
|
|
176
|
+
return self._get_backend_class("fsspec")
|
|
177
|
+
|
|
178
|
+
# Prefer ObStore for its superior performance
|
|
214
179
|
if OBSTORE_INSTALLED:
|
|
215
180
|
return self._get_backend_class("obstore")
|
|
181
|
+
# Could check capabilities here if needed
|
|
182
|
+
|
|
216
183
|
if FSSPEC_INSTALLED:
|
|
217
184
|
return self._get_backend_class("fsspec")
|
|
218
185
|
|
|
219
|
-
scheme = uri.split("://", maxsplit=1)[0].lower()
|
|
220
186
|
msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec."
|
|
221
187
|
raise MissingDependencyError(msg)
|
|
222
188
|
|
|
@@ -244,35 +210,14 @@ class StorageRegistry:
|
|
|
244
210
|
raise ValueError(msg)
|
|
245
211
|
|
|
246
212
|
def _create_backend(self, backend_type: str, uri: str, **kwargs: Any) -> ObjectStoreProtocol:
|
|
247
|
-
"""Create backend instance for URI.
|
|
248
|
-
|
|
249
|
-
Args:
|
|
250
|
-
backend_type: Backend type ('obstore' or 'fsspec')
|
|
251
|
-
uri: URI to create backend for
|
|
252
|
-
**kwargs: Additional backend-specific configuration
|
|
253
|
-
|
|
254
|
-
Returns:
|
|
255
|
-
Backend instance
|
|
256
|
-
"""
|
|
213
|
+
"""Create backend instance for URI."""
|
|
257
214
|
backend_cls = self._get_backend_class(backend_type)
|
|
258
|
-
# Both backends accept URI as first positional parameter
|
|
259
215
|
return backend_cls(uri, **kwargs)
|
|
260
216
|
|
|
261
217
|
def _get_scheme(self, uri: str) -> str:
|
|
262
|
-
"""Extract scheme from URI.
|
|
263
|
-
|
|
264
|
-
Args:
|
|
265
|
-
uri: URI to extract scheme from
|
|
266
|
-
|
|
267
|
-
Returns:
|
|
268
|
-
Scheme (e.g., 's3', 'gs', 'file')
|
|
269
|
-
"""
|
|
270
|
-
# Handle file paths without explicit file:// scheme
|
|
218
|
+
"""Extract scheme from URI."""
|
|
271
219
|
if not uri or "://" not in uri:
|
|
272
|
-
# Local path (absolute or relative)
|
|
273
220
|
return "file"
|
|
274
|
-
|
|
275
|
-
# Extract scheme from URI
|
|
276
221
|
return uri.split("://", maxsplit=1)[0].lower()
|
|
277
222
|
|
|
278
223
|
# Utility methods
|
|
@@ -310,6 +255,34 @@ class StorageRegistry:
|
|
|
310
255
|
self._alias_configs.clear()
|
|
311
256
|
self._aliases.clear()
|
|
312
257
|
|
|
258
|
+
def get_backend_capabilities(self, uri_or_alias: Union[str, Path]) -> "StorageCapabilities":
|
|
259
|
+
"""Get capabilities for a backend without creating an instance.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
uri_or_alias: URI or alias to check capabilities for
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
StorageCapabilities object describing backend capabilities
|
|
266
|
+
"""
|
|
267
|
+
if isinstance(uri_or_alias, Path):
|
|
268
|
+
uri_or_alias = f"file://{uri_or_alias.resolve()}"
|
|
269
|
+
|
|
270
|
+
if "://" in uri_or_alias:
|
|
271
|
+
backend_cls = self._determine_backend_class(uri_or_alias)
|
|
272
|
+
elif uri_or_alias in self._alias_configs:
|
|
273
|
+
backend_cls, _, _ = self._alias_configs[uri_or_alias]
|
|
274
|
+
else:
|
|
275
|
+
msg = f"Unknown storage alias or invalid URI: '{uri_or_alias}'"
|
|
276
|
+
raise ImproperConfigurationError(msg)
|
|
277
|
+
|
|
278
|
+
# Get capabilities from the backend class
|
|
279
|
+
if hasattr(backend_cls, "capabilities"):
|
|
280
|
+
return backend_cls.capabilities
|
|
281
|
+
|
|
282
|
+
# Default capabilities if not defined
|
|
283
|
+
|
|
284
|
+
return StorageCapabilities()
|
|
285
|
+
|
|
313
286
|
|
|
314
287
|
# Global registry instance
|
|
315
288
|
storage_registry = StorageRegistry()
|