sqlspec 0.21.0__py3-none-any.whl → 0.22.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.

@@ -14,7 +14,6 @@ from mypy_extensions import mypyc_attr
14
14
 
15
15
  from sqlspec.exceptions import ImproperConfigurationError, MissingDependencyError
16
16
  from sqlspec.protocols import ObjectStoreProtocol
17
- from sqlspec.storage.capabilities import StorageCapabilities
18
17
  from sqlspec.typing import FSSPEC_INSTALLED, OBSTORE_INSTALLED
19
18
 
20
19
  __all__ = ("StorageRegistry", "storage_registry")
@@ -22,34 +21,52 @@ __all__ = ("StorageRegistry", "storage_registry")
22
21
  logger = logging.getLogger(__name__)
23
22
 
24
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
+
25
38
  SCHEME_REGEX: Final = re.compile(r"([a-zA-Z0-9+.-]+)://")
26
- FILE_PROTOCOL: Final[str] = "file"
27
- S3_PROTOCOL: Final[str] = "s3"
28
- GCS_PROTOCOL: Final[str] = "gs"
29
- AZURE_PROTOCOL: Final[str] = "az"
39
+
40
+
30
41
  FSSPEC_ONLY_SCHEMES: Final[frozenset[str]] = frozenset({"http", "https", "ftp", "sftp", "ssh"})
31
42
 
32
43
 
33
44
  @mypyc_attr(allow_interpreted_subclasses=True)
34
45
  class StorageRegistry:
35
- """Storage registry with URI-first access and automatic backend selection.
46
+ """Global storage registry for named backend configurations.
36
47
 
37
- Provides URI-first access pattern with automatic backend selection.
38
- Named aliases support complex configurations.
48
+ Allows registering named storage backends that can be accessed from anywhere
49
+ in your application. Backends are automatically selected based on URI scheme
50
+ unless explicitly overridden.
39
51
 
40
52
  Examples:
41
- backend = registry.get("s3://my-bucket/file.parquet")
42
- backend = registry.get("file:///tmp/data.csv")
43
- backend = registry.get("gs://bucket/data.json")
44
-
45
- registry.register_alias(
46
- "production-s3",
47
- uri="s3://prod-bucket/data",
48
- base_path="sqlspec",
49
- aws_access_key_id="...",
50
- aws_secret_access_key="..."
51
- )
52
- backend = registry.get("production-s3")
53
+ # Direct URI access to storage containers
54
+ backend = registry.get("s3://my-bucket")
55
+ backend = registry.get("file:///tmp/data")
56
+ backend = registry.get("gs://my-gcs-bucket")
57
+
58
+ # Named store pattern for environment-specific backends
59
+ # Development
60
+ registry.register_alias("my_app_store", "file:///tmp/dev_data")
61
+
62
+ # Production
63
+ registry.register_alias("my_app_store", "s3://prod-bucket/data")
64
+
65
+ # Access from anywhere in your app
66
+ store = registry.get("my_app_store") # Works in both environments
67
+
68
+ # Force specific backend when multiple options available
69
+ backend = registry.get("s3://bucket", backend="fsspec") # Force fsspec over obstore
53
70
  """
54
71
 
55
72
  __slots__ = ("_alias_configs", "_aliases", "_cache", "_instances")
@@ -60,44 +77,47 @@ class StorageRegistry:
60
77
  self._instances: dict[Union[str, tuple[str, tuple[tuple[str, Any], ...]]], ObjectStoreProtocol] = {}
61
78
  self._cache: dict[str, tuple[str, type[ObjectStoreProtocol]]] = {}
62
79
 
80
+ def _make_hashable(self, obj: Any) -> Any:
81
+ """Convert nested dict/list structures to hashable tuples."""
82
+ if isinstance(obj, dict):
83
+ return tuple(sorted((k, self._make_hashable(v)) for k, v in obj.items()))
84
+ if isinstance(obj, list):
85
+ return tuple(self._make_hashable(item) for item in obj)
86
+ if isinstance(obj, set):
87
+ return tuple(sorted(self._make_hashable(item) for item in obj))
88
+ return obj
89
+
63
90
  def register_alias(
64
- self,
65
- alias: str,
66
- uri: str,
67
- *,
68
- backend: Optional[type[ObjectStoreProtocol]] = None,
69
- base_path: str = "",
70
- config: Optional[dict[str, Any]] = None,
71
- **kwargs: Any,
91
+ self, alias: str, uri: str, *, backend: Optional[str] = None, base_path: str = "", **kwargs: Any
72
92
  ) -> None:
73
93
  """Register a named alias for a storage configuration.
74
94
 
75
95
  Args:
76
- alias: Unique alias name for the configuration
77
- uri: Storage URI (e.g., "s3://bucket", "file:///path")
78
- backend: Backend class to use (auto-detected from URI if not provided)
96
+ alias: Unique alias name (e.g., "my_app_store", "user_uploads")
97
+ uri: Storage URI (e.g., "s3://bucket", "file:///path", "gs://bucket")
98
+ backend: Force specific backend ("local", "fsspec", "obstore") instead of auto-detection
79
99
  base_path: Base path to prepend to all operations
80
- config: Additional configuration dict
81
100
  **kwargs: Backend-specific configuration options
82
101
  """
83
- if backend is None:
84
- backend = self._determine_backend_class(uri)
102
+ backend_cls = self._get_backend_class(backend) if backend else self._determine_backend_class(uri)
85
103
 
86
- config = config or {}
87
- config.update(kwargs)
88
- backend_config = dict(config)
104
+ backend_config = dict(kwargs)
89
105
  if base_path:
90
106
  backend_config["base_path"] = base_path
91
- self._alias_configs[alias] = (backend, uri, backend_config)
107
+ self._alias_configs[alias] = (backend_cls, uri, backend_config)
108
+
92
109
  test_config = dict(backend_config)
93
110
  test_config["uri"] = uri
94
111
  self._aliases[alias] = test_config
95
112
 
96
- def get(self, uri_or_alias: Union[str, Path], **kwargs: Any) -> ObjectStoreProtocol:
113
+ def get(
114
+ self, uri_or_alias: Union[str, Path], *, backend: Optional[str] = None, **kwargs: Any
115
+ ) -> ObjectStoreProtocol:
97
116
  """Get backend instance using URI-first routing with automatic backend selection.
98
117
 
99
118
  Args:
100
- uri_or_alias: URI to resolve directly OR named alias
119
+ uri_or_alias: URI to resolve directly OR named alias (e.g., "my_app_store")
120
+ backend: Force specific backend ("local", "fsspec", "obstore") instead of auto-selection
101
121
  **kwargs: Additional backend-specific configuration options
102
122
 
103
123
  Returns:
@@ -113,24 +133,20 @@ class StorageRegistry:
113
133
  if isinstance(uri_or_alias, Path):
114
134
  uri_or_alias = f"file://{uri_or_alias.resolve()}"
115
135
 
116
- cache_key = (uri_or_alias, tuple(sorted(kwargs.items()))) if kwargs else uri_or_alias
136
+ cache_key = (uri_or_alias, self._make_hashable(kwargs)) if kwargs else uri_or_alias
117
137
  if cache_key in self._instances:
118
138
  return self._instances[cache_key]
119
139
  scheme = self._get_scheme(uri_or_alias)
120
- if not scheme and (
121
- Path(uri_or_alias).exists()
122
- or Path(uri_or_alias).is_absolute()
123
- or uri_or_alias.startswith(("~", "."))
124
- or ":\\" in uri_or_alias
125
- or "/" in uri_or_alias
126
- ):
140
+ if not scheme and _is_local_uri(uri_or_alias):
127
141
  scheme = "file"
128
142
  uri_or_alias = f"file://{uri_or_alias}"
129
143
 
130
144
  if scheme:
131
- instance = self._resolve_from_uri(uri_or_alias, **kwargs)
145
+ instance = self._resolve_from_uri(uri_or_alias, backend_override=backend, **kwargs)
132
146
  elif uri_or_alias in self._alias_configs:
133
147
  backend_cls, stored_uri, config = self._alias_configs[uri_or_alias]
148
+ if backend:
149
+ backend_cls = self._get_backend_class(backend)
134
150
  instance = backend_cls(stored_uri, **{**config, **kwargs})
135
151
  else:
136
152
  msg = f"Unknown storage alias or invalid URI: '{uri_or_alias}'"
@@ -138,36 +154,66 @@ class StorageRegistry:
138
154
  self._instances[cache_key] = instance
139
155
  return instance
140
156
 
141
- def _resolve_from_uri(self, uri: str, **kwargs: Any) -> ObjectStoreProtocol:
142
- """Resolve backend from URI, trying ObStore first, then FSSpec."""
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."""
161
+ if backend_override:
162
+ return self._create_backend(backend_override, uri, **kwargs)
143
163
  scheme = self._get_scheme(uri)
164
+
165
+ # For local files, prefer LocalStore first
166
+ if scheme in {None, "file"}:
167
+ return self._create_backend("local", uri, **kwargs)
168
+
169
+ # Try ObStore first if available and appropriate
144
170
  if scheme not in FSSPEC_ONLY_SCHEMES and OBSTORE_INSTALLED:
145
171
  try:
146
172
  return self._create_backend("obstore", uri, **kwargs)
147
173
  except (ValueError, ImportError, NotImplementedError):
148
174
  pass
175
+
176
+ # Try FSSpec if available
149
177
  if FSSPEC_INSTALLED:
150
178
  try:
151
179
  return self._create_backend("fsspec", uri, **kwargs)
152
180
  except (ValueError, ImportError, NotImplementedError):
153
181
  pass
154
- msg = "obstore"
155
- raise MissingDependencyError(msg, "fsspec")
182
+
183
+ # For cloud schemes without backends, provide helpful error
184
+ msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec for cloud storage support."
185
+ raise MissingDependencyError(msg)
156
186
 
157
187
  def _determine_backend_class(self, uri: str) -> type[ObjectStoreProtocol]:
158
188
  """Determine the backend class for a URI based on availability."""
159
189
  scheme = self._get_scheme(uri)
190
+
191
+ # For local files, always use LocalStore
192
+ if scheme in {None, "file"}:
193
+ return self._get_backend_class("local")
194
+
195
+ # FSSpec-only schemes require FSSpec
160
196
  if scheme in FSSPEC_ONLY_SCHEMES and FSSPEC_INSTALLED:
161
197
  return self._get_backend_class("fsspec")
198
+
199
+ # Prefer ObStore for cloud storage if available
162
200
  if OBSTORE_INSTALLED:
163
201
  return self._get_backend_class("obstore")
202
+
203
+ # Fall back to FSSpec if available
164
204
  if FSSPEC_INSTALLED:
165
205
  return self._get_backend_class("fsspec")
166
- msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec."
206
+
207
+ # For cloud schemes without backends, provide helpful error
208
+ msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec for cloud storage support."
167
209
  raise MissingDependencyError(msg)
168
210
 
169
211
  def _get_backend_class(self, backend_type: str) -> type[ObjectStoreProtocol]:
170
212
  """Get backend class by type name."""
213
+ if backend_type == "local":
214
+ from sqlspec.storage.backends.local import LocalStore
215
+
216
+ return cast("type[ObjectStoreProtocol]", LocalStore)
171
217
  if backend_type == "obstore":
172
218
  from sqlspec.storage.backends.obstore import ObStoreBackend
173
219
 
@@ -176,7 +222,7 @@ class StorageRegistry:
176
222
  from sqlspec.storage.backends.fsspec import FSSpecBackend
177
223
 
178
224
  return cast("type[ObjectStoreProtocol]", FSSpecBackend)
179
- msg = f"Unknown backend type: {backend_type}. Supported types: 'obstore', 'fsspec'"
225
+ msg = f"Unknown backend type: {backend_type}. Supported types: 'local', 'obstore', 'fsspec'"
180
226
  raise ValueError(msg)
181
227
 
182
228
  def _create_backend(self, backend_type: str, uri: str, **kwargs: Any) -> ObjectStoreProtocol:
@@ -220,20 +266,5 @@ class StorageRegistry:
220
266
  self._alias_configs.clear()
221
267
  self._aliases.clear()
222
268
 
223
- def get_backend_capabilities(self, uri_or_alias: Union[str, Path]) -> "StorageCapabilities":
224
- """Get capabilities for a backend without creating an instance."""
225
- if isinstance(uri_or_alias, Path):
226
- uri_or_alias = f"file://{uri_or_alias.resolve()}"
227
- if "://" in uri_or_alias:
228
- backend_cls = self._determine_backend_class(uri_or_alias)
229
- elif uri_or_alias in self._alias_configs:
230
- backend_cls, _, _ = self._alias_configs[uri_or_alias]
231
- else:
232
- msg = f"Unknown storage alias or invalid URI: '{uri_or_alias}'"
233
- raise ImproperConfigurationError(msg)
234
- if hasattr(backend_cls, "capabilities"):
235
- return backend_cls.capabilities
236
- return StorageCapabilities()
237
-
238
269
 
239
270
  storage_registry = StorageRegistry()
@@ -0,0 +1,120 @@
1
+ """Data transformation utilities for SQLSpec.
2
+
3
+ Provides functions for transforming data structures, particularly for
4
+ field name conversion when mapping database results to schema objects.
5
+ Used primarily for msgspec field name conversion with rename configurations.
6
+ """
7
+
8
+ from typing import Any, Callable, Union
9
+
10
+ __all__ = ("transform_dict_keys",)
11
+
12
+
13
+ def _safe_convert_key(key: Any, converter: Callable[[str], str]) -> Any:
14
+ """Safely convert a key using the converter function.
15
+
16
+ Args:
17
+ key: Key to convert (may not be a string).
18
+ converter: Function to convert string keys.
19
+
20
+ Returns:
21
+ Converted key if conversion succeeds, original key otherwise.
22
+ """
23
+ if not isinstance(key, str):
24
+ return key
25
+
26
+ try:
27
+ return converter(key)
28
+ except (TypeError, ValueError, AttributeError):
29
+ # If conversion fails, return the original key
30
+ return key
31
+
32
+
33
+ def transform_dict_keys(data: Union[dict, list, Any], converter: Callable[[str], str]) -> Union[dict, list, Any]:
34
+ """Transform dictionary keys using the provided converter function.
35
+
36
+ Recursively transforms all dictionary keys in a data structure using
37
+ the provided converter function. Handles nested dictionaries, lists
38
+ of dictionaries, and preserves non-dict values unchanged.
39
+
40
+ Args:
41
+ data: The data structure to transform. Can be a dict, list, or any other type.
42
+ converter: Function to convert string keys (e.g., camelize, kebabize).
43
+
44
+ Returns:
45
+ The transformed data structure with converted keys. Non-dict values
46
+ are returned unchanged.
47
+
48
+ Examples:
49
+ Transform snake_case keys to camelCase:
50
+
51
+ >>> from sqlspec.utils.text import camelize
52
+ >>> data = {"user_id": 123, "created_at": "2024-01-01"}
53
+ >>> transform_dict_keys(data, camelize)
54
+ {"userId": 123, "createdAt": "2024-01-01"}
55
+
56
+ Transform nested structures:
57
+
58
+ >>> nested = {
59
+ ... "user_data": {"first_name": "John", "last_name": "Doe"},
60
+ ... "order_items": [
61
+ ... {"item_id": 1, "item_name": "Product A"},
62
+ ... {"item_id": 2, "item_name": "Product B"},
63
+ ... ],
64
+ ... }
65
+ >>> transform_dict_keys(nested, camelize)
66
+ {
67
+ "userData": {
68
+ "firstName": "John",
69
+ "lastName": "Doe"
70
+ },
71
+ "orderItems": [
72
+ {"itemId": 1, "itemName": "Product A"},
73
+ {"itemId": 2, "itemName": "Product B"}
74
+ ]
75
+ }
76
+ """
77
+ if isinstance(data, dict):
78
+ return _transform_dict(data, converter)
79
+ if isinstance(data, list):
80
+ return _transform_list(data, converter)
81
+ return data
82
+
83
+
84
+ def _transform_dict(data: dict, converter: Callable[[str], str]) -> dict:
85
+ """Transform a dictionary's keys recursively.
86
+
87
+ Args:
88
+ data: Dictionary to transform.
89
+ converter: Function to convert string keys.
90
+
91
+ Returns:
92
+ Dictionary with transformed keys and recursively transformed values.
93
+ """
94
+ transformed = {}
95
+
96
+ for key, value in data.items():
97
+ # Convert the key using the provided converter function
98
+ # Use safe conversion that handles edge cases without try-except
99
+ converted_key = _safe_convert_key(key, converter)
100
+
101
+ # Recursively transform the value
102
+ transformed_value = transform_dict_keys(value, converter)
103
+
104
+ transformed[converted_key] = transformed_value
105
+
106
+ return transformed
107
+
108
+
109
+ def _transform_list(data: list, converter: Callable[[str], str]) -> list:
110
+ """Transform a list's elements recursively.
111
+
112
+ Args:
113
+ data: List to transform.
114
+ converter: Function to convert string keys in nested structures.
115
+
116
+ Returns:
117
+ List with recursively transformed elements.
118
+ """
119
+ # Use list comprehension for better performance and avoid try-except in loop
120
+ return [transform_dict_keys(item, converter) for item in data]
@@ -29,6 +29,13 @@ ParamSpecT = ParamSpec("ParamSpecT")
29
29
  T = TypeVar("T")
30
30
 
31
31
 
32
+ class NoValue:
33
+ """Sentinel class for missing values."""
34
+
35
+
36
+ NO_VALUE = NoValue()
37
+
38
+
32
39
  class CapacityLimiter:
33
40
  """Limits the number of concurrent operations using a semaphore."""
34
41
 
@@ -240,11 +247,7 @@ def with_ensure_async_(
240
247
  return obj
241
248
 
242
249
 
243
- class NoValue:
244
- """Sentinel class for missing values."""
245
-
246
-
247
- async def get_next(iterable: Any, default: Any = NoValue, *args: Any) -> Any: # pragma: no cover
250
+ async def get_next(iterable: Any, default: Any = NO_VALUE, *args: Any) -> Any: # pragma: no cover
248
251
  """Return the next item from an async iterator.
249
252
 
250
253
  Args:
sqlspec/utils/text.py CHANGED
@@ -19,25 +19,7 @@ _SNAKE_CASE_HYPHEN_SPACE = re.compile(r"[.\s@-]+", re.UNICODE)
19
19
  _SNAKE_CASE_REMOVE_NON_WORD = re.compile(r"[^\w]+", re.UNICODE)
20
20
  _SNAKE_CASE_MULTIPLE_UNDERSCORES = re.compile(r"__+", re.UNICODE)
21
21
 
22
- __all__ = ("camelize", "check_email", "slugify", "snake_case")
23
-
24
-
25
- def check_email(email: str) -> str:
26
- """Validate an email address.
27
-
28
- Args:
29
- email: The email to validate.
30
-
31
- Raises:
32
- ValueError: If the email is invalid.
33
-
34
- Returns:
35
- The validated email.
36
- """
37
- if "@" not in email:
38
- msg = "Invalid email!"
39
- raise ValueError(msg)
40
- return email.lower()
22
+ __all__ = ("camelize", "kebabize", "pascalize", "slugify", "snake_case")
41
23
 
42
24
 
43
25
  def slugify(value: str, allow_unicode: bool = False, separator: Optional[str] = None) -> str:
@@ -80,6 +62,32 @@ def camelize(string: str) -> str:
80
62
  return "".join(word if index == 0 else word.capitalize() for index, word in enumerate(string.split("_")))
81
63
 
82
64
 
65
+ @lru_cache(maxsize=100)
66
+ def kebabize(string: str) -> str:
67
+ """Convert a string to kebab-case.
68
+
69
+ Args:
70
+ string: The string to convert.
71
+
72
+ Returns:
73
+ The kebab-case version of the string.
74
+ """
75
+ return "-".join(word.lower() for word in string.split("_") if word)
76
+
77
+
78
+ @lru_cache(maxsize=100)
79
+ def pascalize(string: str) -> str:
80
+ """Convert a string to PascalCase.
81
+
82
+ Args:
83
+ string: The string to convert.
84
+
85
+ Returns:
86
+ The PascalCase version of the string.
87
+ """
88
+ return "".join(word.capitalize() for word in string.split("_") if word)
89
+
90
+
83
91
  @lru_cache(maxsize=100)
84
92
  def snake_case(string: str) -> str:
85
93
  """Convert a string to snake_case.
@@ -6,6 +6,7 @@ understand type narrowing, replacing defensive hasattr() and duck typing pattern
6
6
 
7
7
  from collections.abc import Sequence
8
8
  from collections.abc import Set as AbstractSet
9
+ from functools import lru_cache
9
10
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
10
11
 
11
12
  from sqlspec.typing import (
@@ -59,6 +60,7 @@ __all__ = (
59
60
  "extract_dataclass_items",
60
61
  "get_initial_expression",
61
62
  "get_literal_parent",
63
+ "get_msgspec_rename_config",
62
64
  "get_node_expressions",
63
65
  "get_node_this",
64
66
  "get_param_style_and_name",
@@ -429,6 +431,78 @@ def is_msgspec_struct_without_field(obj: Any, field_name: str) -> "TypeGuard[Str
429
431
  return False
430
432
 
431
433
 
434
+ @lru_cache(maxsize=500)
435
+ def _detect_rename_pattern(field_name: str, encode_name: str) -> "Optional[str]":
436
+ """Detect the rename pattern by comparing field name transformations.
437
+
438
+ Args:
439
+ field_name: Original field name (e.g., "user_id")
440
+ encode_name: Encoded field name (e.g., "userId")
441
+
442
+ Returns:
443
+ The detected rename pattern ("camel", "kebab", "pascal") or None
444
+ """
445
+ from sqlspec.utils.text import camelize, kebabize, pascalize
446
+
447
+ # Test camelCase conversion
448
+ if encode_name == camelize(field_name) and encode_name != field_name:
449
+ return "camel"
450
+
451
+ if encode_name == kebabize(field_name) and encode_name != field_name:
452
+ return "kebab"
453
+
454
+ if encode_name == pascalize(field_name) and encode_name != field_name:
455
+ return "pascal"
456
+ return None
457
+
458
+
459
+ def get_msgspec_rename_config(schema_type: type) -> "Optional[str]":
460
+ """Extract msgspec rename configuration from a struct type.
461
+
462
+ Analyzes field name transformations to detect the rename pattern used by msgspec.
463
+ Since msgspec doesn't store the original rename parameter directly, we infer it
464
+ by comparing field names with their encode_name values.
465
+
466
+ Args:
467
+ schema_type: The msgspec struct type to inspect.
468
+
469
+ Returns:
470
+ The rename configuration value ("camel", "kebab", "pascal", etc.) if detected,
471
+ None if no rename configuration exists or if not a msgspec struct.
472
+
473
+ Examples:
474
+ >>> class User(msgspec.Struct, rename="camel"):
475
+ ... user_id: int
476
+ >>> get_msgspec_rename_config(User)
477
+ "camel"
478
+
479
+ >>> class Product(msgspec.Struct):
480
+ ... product_id: int
481
+ >>> get_msgspec_rename_config(Product)
482
+ None
483
+ """
484
+ if not MSGSPEC_INSTALLED:
485
+ return None
486
+
487
+ if not is_msgspec_struct(schema_type):
488
+ return None
489
+
490
+ from msgspec import structs
491
+
492
+ fields = structs.fields(schema_type) # type: ignore[arg-type]
493
+ if not fields:
494
+ return None
495
+
496
+ # Check if any field name differs from its encode_name
497
+ for field in fields:
498
+ if field.name != field.encode_name:
499
+ # Detect the rename pattern by comparing transformations
500
+ return _detect_rename_pattern(field.name, field.encode_name)
501
+
502
+ # If all field names match their encode_name, no rename is applied
503
+ return None
504
+
505
+
432
506
  def is_attrs_instance(obj: Any) -> "TypeGuard[AttrsInstanceStub]":
433
507
  """Check if a value is an attrs class instance.
434
508
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlspec
3
- Version: 0.21.0
3
+ Version: 0.22.0
4
4
  Summary: SQL Experiments in Python
5
5
  Project-URL: Discord, https://discord.gg/litestar
6
6
  Project-URL: Issue, https://github.com/litestar-org/sqlspec/issues/
@@ -4,12 +4,12 @@ sqlspec/__metadata__.py,sha256=IUw6MCTy1oeUJ1jAVYbuJLkOWbiAWorZ5W-E-SAD9N4,395
4
4
  sqlspec/_serialization.py,sha256=6U5-smk2h2yl0i6am2prtOLJTdu4NJQdcLlSfSUMaUQ,2590
5
5
  sqlspec/_sql.py,sha256=j9WljOgCme4jTfL6NegEWOhK-Rr3JEmhtbneh8ZN1bQ,45221
6
6
  sqlspec/_typing.py,sha256=jv-7QHGLrJLfnP86bR-Xcmj3PDoddNZEKDz_vYRBiAU,22684
7
- sqlspec/base.py,sha256=p3qX3nq1qPuLz6AEVizbZ4xpWhMoDXNc7zkPK9ecJac,25467
7
+ sqlspec/base.py,sha256=koDh1AecwCAkntSqSda6J_cpMOLonXiV6hh3GCCXf_s,25459
8
8
  sqlspec/cli.py,sha256=Fe5Wbnrb_fkE9qm4gbBEXx3d0Q7VR-S-1t76ouAx2mg,20120
9
9
  sqlspec/config.py,sha256=PQKKLXst_uMvqvTSQib6qMZfJd-g3Kqqlp7XLn9kA8A,21640
10
10
  sqlspec/exceptions.py,sha256=zBnzQOfYAgqX04GoaC9Io6ardzinldkEuZ3YtR5vr9U,6071
11
- sqlspec/loader.py,sha256=R_lcI8Jg3Jh9jUDFvQyKbBzx20vKH50BKYt8gvTKq7c,23400
12
- sqlspec/protocols.py,sha256=Of6uJyxvawExCEyR3u7jbxOckUcwG0HHOEXmfHyev40,13106
11
+ sqlspec/loader.py,sha256=4Gl4LcdVwEB6-4F-KiCnlI3nzyG3LVOFvRMAQNC64qE,23748
12
+ sqlspec/protocols.py,sha256=jSO2OeZvywqkaIvLRFDkQajJvDlErCICipToUH3Mvoo,12996
13
13
  sqlspec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  sqlspec/typing.py,sha256=yj8D8O-pkfUVZDfVHEgQaB95-5alwgQbp_sqNJOVhvQ,6301
15
15
  sqlspec/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -93,7 +93,7 @@ sqlspec/driver/_async.py,sha256=aS5AwY5IYqnVT8ldDLDwz2AMDN94CI9hfsOz-1k-Nus,1899
93
93
  sqlspec/driver/_common.py,sha256=Fi5NCy5_OVlRKDzUpGsLJn3zDmrsVSsXggMRndIMM1E,23879
94
94
  sqlspec/driver/_sync.py,sha256=wCBV9QfAH8BPjrrVCQc2eM90ai5-FYbKDd81L5sZMS0,18767
95
95
  sqlspec/driver/mixins/__init__.py,sha256=gN4pQyJXxNy0xi91dcMJGA7DQ7TbjGjQI24SSpZc6Go,248
96
- sqlspec/driver/mixins/_result_tools.py,sha256=fDr1sca6jmeZ8EwTamPhlAh_XnK9moIpWD8Q3ZcEhRM,7103
96
+ sqlspec/driver/mixins/_result_tools.py,sha256=0LquMpoLBJrqkxaAPU4Wvn2JElyqdwRAnRlwGTX-D7w,8603
97
97
  sqlspec/driver/mixins/_sql_translator.py,sha256=TACtUUJdx8tJwuq_7g3AR_k0bKokvuJrMEwINyWwdQM,3711
98
98
  sqlspec/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
99
  sqlspec/extensions/aiosql/__init__.py,sha256=-9cefc9pYPf9vCgALoB-y1DtmcgRjKe2azfl6RIarAA,414
@@ -112,27 +112,28 @@ sqlspec/migrations/loaders.py,sha256=wildbpkyHrE--HXspChPOajSHSBUrfG0e6xQ2buze_4
112
112
  sqlspec/migrations/runner.py,sha256=y6fyZi02n8MseKR8XFWXUEOOYQNG_w_DikHVxH9p20M,10730
113
113
  sqlspec/migrations/tracker.py,sha256=hfrZGz8M70SfFniw4aXVtHNg4p8EPFm67vthjfUMUys,6843
114
114
  sqlspec/migrations/utils.py,sha256=Ft5mS1GFiRPLhfUTfPU4ZnEgOkXDnmEHjeeWwfZDcv4,3737
115
- sqlspec/storage/__init__.py,sha256=xWSsq5QXrY7wCsjQYPldfdlm8UEJ-kojU-tWsoldSy0,645
116
- sqlspec/storage/capabilities.py,sha256=vyousxls9jISsykgoybpNHlGWN6Hq_pKcsZ5DmKGWvU,3045
117
- sqlspec/storage/registry.py,sha256=4duy0uOupl0X5VNhwUeQMMWPsraKWyIdjDrKFsFcVG0,9424
118
- sqlspec/storage/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
+ sqlspec/storage/__init__.py,sha256=IX7xLuymGdQKOBQL1sNEA-bzZRJGjncUbQbs6ZToDDE,395
116
+ sqlspec/storage/registry.py,sha256=yJZrLDu1vr86C6En3MlzCPOjUNYkvA5Ow-AKrLEDvpI,10754
117
+ sqlspec/storage/backends/__init__.py,sha256=3oSqwEQ_trU2QoxtUTX-5IFeOvo7WGcESY6gIfcmSaE,24
119
118
  sqlspec/storage/backends/base.py,sha256=KS2JRZILoH_R_xsfKtYkqQ5a1r5OOBDSE5KbibTmhGY,5730
120
- sqlspec/storage/backends/fsspec.py,sha256=8AX8ULwlApMd6WtHDVJBomdvk5UjPtfMf78TJ86gTC8,15994
121
- sqlspec/storage/backends/obstore.py,sha256=v9moaiSVKbpl9MOrb9AAhhSXfQb1ifammrT8WeV1xcw,19735
119
+ sqlspec/storage/backends/fsspec.py,sha256=5X3QhoaAddzMqkeTHs8M5RINj3mrHcnaIVWb8VhC4lQ,13582
120
+ sqlspec/storage/backends/local.py,sha256=vsFnttNesNYjsY_l-KoH6rld51L3E6mxeODtMJawFPQ,12793
121
+ sqlspec/storage/backends/obstore.py,sha256=4RUZAMCF16h1IQ4dEib-0j8TLnhPf0Jhy2lmRJSe7Kw,20512
122
122
  sqlspec/utils/__init__.py,sha256=cNFX26-bLyZTyTfujUitfDkUy1CeG_d-EIr8kZ0z4W8,474
123
123
  sqlspec/utils/correlation.py,sha256=2jvkAY3nkU3UxNU_9pbBR6cz3A1Q1cGG9IaWSSOIb1Q,4195
124
+ sqlspec/utils/data_transformation.py,sha256=U37zyxR4f5PxsxKdC7QzcMyJxfqpsXUxgH_ch5l3PbY,3951
124
125
  sqlspec/utils/deprecation.py,sha256=iy7xzws6Kx0oQpX94smyZzTY6ijdODrdSEFFEXZfp5o,3980
125
126
  sqlspec/utils/fixtures.py,sha256=qnPAdkV91dyKOqslm_TH5UZ8mx4koQMwriE5YPn3PgI,9425
126
127
  sqlspec/utils/logging.py,sha256=zAM7rHJ-KsmAj1yjvU9QFoiwf4Q2hKTere2J62FlllI,3664
127
128
  sqlspec/utils/module_loader.py,sha256=rO4ht-fUSJ3Us7L_7fb_G9bdMCoUSABGUA0pc3ouh9Y,2995
128
129
  sqlspec/utils/serializers.py,sha256=GXsTkJbWAhRS7xDMk6WBouZwPeG4sI_brLdMBlIetNg,318
129
130
  sqlspec/utils/singleton.py,sha256=-j-s6LS0pP_wTEUYIyK2wSdoeIE_tn7O7B-j7_aODRQ,1252
130
- sqlspec/utils/sync_tools.py,sha256=ksfxsvFb1hLrDlxzwdW44OvYgRB0Fr5JDqxswfHwoOs,8744
131
- sqlspec/utils/text.py,sha256=W97aX77A3NzG795AHjhdX6zqOBDmvLaXLCno2JIugCo,3081
132
- sqlspec/utils/type_guards.py,sha256=9C4SRebO4JiQrMzcJZFUA0KjSU48G26RmX6lbijyjBg,30476
133
- sqlspec-0.21.0.dist-info/METADATA,sha256=_mWB33isyFUYPN30oshULbI5KPKA4ztOiY4OoSsnIR8,23548
134
- sqlspec-0.21.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
135
- sqlspec-0.21.0.dist-info/entry_points.txt,sha256=G-ZqY1Nuuw3Iys7nXw23f6ILenk_Lt47VdK2mhJCWHg,53
136
- sqlspec-0.21.0.dist-info/licenses/LICENSE,sha256=MdujfZ6l5HuLz4mElxlu049itenOR3gnhN1_Nd3nVcM,1078
137
- sqlspec-0.21.0.dist-info/licenses/NOTICE,sha256=Lyir8ozXWov7CyYS4huVaOCNrtgL17P-bNV-5daLntQ,1634
138
- sqlspec-0.21.0.dist-info/RECORD,,
131
+ sqlspec/utils/sync_tools.py,sha256=ONdhmx1Dq0_c6ReRaTlXzz6dVmAwz6CybCvsTUAVu1g,8768
132
+ sqlspec/utils/text.py,sha256=ZqaXCVuUbdj_110pdTYjmAxfV3ZtR7J6EixuNazQLFY,3333
133
+ sqlspec/utils/type_guards.py,sha256=ktXwBQLLqOvk1W2wJcmk3bUprrsegs8nAZ879qDe0AU,32880
134
+ sqlspec-0.22.0.dist-info/METADATA,sha256=DZJrWaO7T5jLPe2GlL6M_MmKveqCb-oU1Awx3CDm6J0,23548
135
+ sqlspec-0.22.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
136
+ sqlspec-0.22.0.dist-info/entry_points.txt,sha256=G-ZqY1Nuuw3Iys7nXw23f6ILenk_Lt47VdK2mhJCWHg,53
137
+ sqlspec-0.22.0.dist-info/licenses/LICENSE,sha256=MdujfZ6l5HuLz4mElxlu049itenOR3gnhN1_Nd3nVcM,1078
138
+ sqlspec-0.22.0.dist-info/licenses/NOTICE,sha256=Lyir8ozXWov7CyYS4huVaOCNrtgL17P-bNV-5daLntQ,1634
139
+ sqlspec-0.22.0.dist-info/RECORD,,