sqlspec 0.4.0__py3-none-any.whl → 0.5.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/_serialization.py +1 -1
- sqlspec/_typing.py +31 -1
- sqlspec/adapters/adbc/config.py +7 -7
- sqlspec/adapters/aiosqlite/config.py +2 -2
- sqlspec/adapters/asyncmy/config.py +9 -4
- sqlspec/adapters/asyncpg/config.py +12 -8
- sqlspec/adapters/duckdb/__init__.py +3 -0
- sqlspec/adapters/duckdb/config.py +15 -14
- sqlspec/adapters/oracledb/__init__.py +1 -1
- sqlspec/adapters/oracledb/config/_asyncio.py +6 -3
- sqlspec/adapters/oracledb/config/_common.py +7 -7
- sqlspec/adapters/oracledb/config/_sync.py +7 -4
- sqlspec/adapters/psycopg/config/__init__.py +2 -2
- sqlspec/adapters/psycopg/config/_async.py +6 -3
- sqlspec/adapters/psycopg/config/_common.py +3 -3
- sqlspec/adapters/psycopg/config/_sync.py +6 -3
- sqlspec/adapters/sqlite/config.py +3 -3
- sqlspec/base.py +87 -0
- sqlspec/filters.py +2 -1
- sqlspec/typing.py +119 -31
- sqlspec/utils/__init__.py +0 -0
- sqlspec/utils/deprecation.py +111 -0
- sqlspec/utils/fixtures.py +66 -0
- sqlspec/utils/module_loader.py +94 -0
- sqlspec/utils/text.py +46 -0
- sqlspec-0.5.0.dist-info/METADATA +126 -0
- sqlspec-0.5.0.dist-info/RECORD +44 -0
- sqlspec/config.py +0 -16
- sqlspec-0.4.0.dist-info/METADATA +0 -84
- sqlspec-0.4.0.dist-info/RECORD +0 -39
- {sqlspec-0.4.0.dist-info → sqlspec-0.5.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.4.0.dist-info → sqlspec-0.5.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/typing.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
1
|
+
from __future__ import annotations # noqa: A005
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from dataclasses import Field, fields
|
|
@@ -48,11 +48,21 @@ FilterTypeT = TypeVar("FilterTypeT", bound="StatementFilter")
|
|
|
48
48
|
|
|
49
49
|
:class:`~advanced_alchemy.filters.StatementFilter`
|
|
50
50
|
"""
|
|
51
|
+
ModelDTOT = TypeVar("ModelDTOT", bound="Struct | BaseModel")
|
|
52
|
+
"""Type variable for model DTOs.
|
|
53
|
+
|
|
54
|
+
:class:`msgspec.Struct`|:class:`pydantic.BaseModel`
|
|
55
|
+
"""
|
|
56
|
+
PydanticOrMsgspecT: TypeAlias = Union[Struct, BaseModel]
|
|
57
|
+
"""Type alias for pydantic or msgspec models.
|
|
58
|
+
|
|
59
|
+
:class:`msgspec.Struct` or :class:`pydantic.BaseModel`
|
|
60
|
+
"""
|
|
51
61
|
ModelDictT: TypeAlias = Union[dict[str, Any], ModelT, DataclassProtocol, Struct, BaseModel]
|
|
52
62
|
"""Type alias for model dictionaries.
|
|
53
63
|
|
|
54
64
|
Represents:
|
|
55
|
-
- :type:`dict[str, Any]` |
|
|
65
|
+
- :type:`dict[str, Any]` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel`
|
|
56
66
|
"""
|
|
57
67
|
ModelDictListT: TypeAlias = Sequence[Union[dict[str, Any], ModelT, DataclassProtocol, Struct, BaseModel]]
|
|
58
68
|
"""Type alias for model dictionary lists.
|
|
@@ -72,7 +82,7 @@ def is_dataclass_instance(obj: Any) -> TypeGuard[DataclassProtocol]:
|
|
|
72
82
|
Returns:
|
|
73
83
|
True if the object is a dataclass instance.
|
|
74
84
|
"""
|
|
75
|
-
return hasattr(type(obj), "__dataclass_fields__")
|
|
85
|
+
return hasattr(type(obj), "__dataclass_fields__") # pyright: ignore[reportUnknownArgumentType]
|
|
76
86
|
|
|
77
87
|
|
|
78
88
|
@lru_cache(typed=True)
|
|
@@ -104,7 +114,33 @@ def is_pydantic_model(v: Any) -> TypeGuard[BaseModel]:
|
|
|
104
114
|
return PYDANTIC_INSTALLED and isinstance(v, BaseModel)
|
|
105
115
|
|
|
106
116
|
|
|
107
|
-
def
|
|
117
|
+
def is_pydantic_model_with_field(v: Any, field_name: str) -> TypeGuard[BaseModel]:
|
|
118
|
+
"""Check if a pydantic model has a specific field.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
v: Value to check.
|
|
122
|
+
field_name: Field name to check for.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
bool
|
|
126
|
+
"""
|
|
127
|
+
return is_pydantic_model(v) and field_name in v.model_fields
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def is_pydantic_model_without_field(v: Any, field_name: str) -> TypeGuard[BaseModel]:
|
|
131
|
+
"""Check if a pydantic model does not have a specific field.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
v: Value to check.
|
|
135
|
+
field_name: Field name to check for.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
bool
|
|
139
|
+
"""
|
|
140
|
+
return not is_pydantic_model_with_field(v, field_name)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def is_msgspec_struct(v: Any) -> TypeGuard[Struct]:
|
|
108
144
|
"""Check if a value is a msgspec model.
|
|
109
145
|
|
|
110
146
|
Args:
|
|
@@ -116,6 +152,32 @@ def is_msgspec_model(v: Any) -> TypeGuard[Struct]:
|
|
|
116
152
|
return MSGSPEC_INSTALLED and isinstance(v, Struct)
|
|
117
153
|
|
|
118
154
|
|
|
155
|
+
def is_msgspec_struct_with_field(v: Any, field_name: str) -> TypeGuard[Struct]:
|
|
156
|
+
"""Check if a msgspec model has a specific field.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
v: Value to check.
|
|
160
|
+
field_name: Field name to check for.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
bool
|
|
164
|
+
"""
|
|
165
|
+
return is_msgspec_struct(v) and field_name in v.__struct_fields__
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def is_msgspec_struct_without_field(v: Any, field_name: str) -> TypeGuard[Struct]:
|
|
169
|
+
"""Check if a msgspec model does not have a specific field.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
v: Value to check.
|
|
173
|
+
field_name: Field name to check for.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
bool
|
|
177
|
+
"""
|
|
178
|
+
return not is_msgspec_struct_with_field(v, field_name)
|
|
179
|
+
|
|
180
|
+
|
|
119
181
|
def is_dict(v: Any) -> TypeGuard[dict[str, Any]]:
|
|
120
182
|
"""Check if a value is a dictionary.
|
|
121
183
|
|
|
@@ -154,8 +216,8 @@ def is_dict_without_field(v: Any, field_name: str) -> TypeGuard[dict[str, Any]]:
|
|
|
154
216
|
return is_dict(v) and field_name not in v
|
|
155
217
|
|
|
156
218
|
|
|
157
|
-
def
|
|
158
|
-
"""Check if a value is a
|
|
219
|
+
def is_schema(v: Any) -> TypeGuard[Struct | BaseModel]:
|
|
220
|
+
"""Check if a value is a msgspec Struct or Pydantic model.
|
|
159
221
|
|
|
160
222
|
Args:
|
|
161
223
|
v: Value to check.
|
|
@@ -163,11 +225,23 @@ def is_dataclass(v: Any) -> TypeGuard[DataclassProtocol]:
|
|
|
163
225
|
Returns:
|
|
164
226
|
bool
|
|
165
227
|
"""
|
|
166
|
-
return
|
|
228
|
+
return is_msgspec_struct(v) or is_pydantic_model(v)
|
|
167
229
|
|
|
168
230
|
|
|
169
|
-
def
|
|
170
|
-
"""Check if a
|
|
231
|
+
def is_schema_or_dict(v: Any) -> TypeGuard[Struct | BaseModel | dict[str, Any]]:
|
|
232
|
+
"""Check if a value is a msgspec Struct, Pydantic model, or dict.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
v: Value to check.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
bool
|
|
239
|
+
"""
|
|
240
|
+
return is_schema(v) or is_dict(v)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def is_schema_with_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel]:
|
|
244
|
+
"""Check if a value is a msgspec Struct or Pydantic model with a specific field.
|
|
171
245
|
|
|
172
246
|
Args:
|
|
173
247
|
v: Value to check.
|
|
@@ -176,11 +250,11 @@ def is_dataclass_with_field(v: Any, field_name: str) -> TypeGuard[DataclassProto
|
|
|
176
250
|
Returns:
|
|
177
251
|
bool
|
|
178
252
|
"""
|
|
179
|
-
return
|
|
253
|
+
return is_msgspec_struct_with_field(v, field_name) or is_pydantic_model_with_field(v, field_name)
|
|
180
254
|
|
|
181
255
|
|
|
182
|
-
def
|
|
183
|
-
"""Check if a
|
|
256
|
+
def is_schema_without_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel]:
|
|
257
|
+
"""Check if a value is a msgspec Struct or Pydantic model without a specific field.
|
|
184
258
|
|
|
185
259
|
Args:
|
|
186
260
|
v: Value to check.
|
|
@@ -189,11 +263,11 @@ def is_dataclass_without_field(v: Any, field_name: str) -> TypeGuard[DataclassPr
|
|
|
189
263
|
Returns:
|
|
190
264
|
bool
|
|
191
265
|
"""
|
|
192
|
-
return
|
|
266
|
+
return not is_schema_with_field(v, field_name)
|
|
193
267
|
|
|
194
268
|
|
|
195
|
-
def
|
|
196
|
-
"""Check if a
|
|
269
|
+
def is_schema_or_dict_with_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel | dict[str, Any]]:
|
|
270
|
+
"""Check if a value is a msgspec Struct, Pydantic model, or dict with a specific field.
|
|
197
271
|
|
|
198
272
|
Args:
|
|
199
273
|
v: Value to check.
|
|
@@ -202,11 +276,11 @@ def is_pydantic_model_with_field(v: Any, field_name: str) -> TypeGuard[BaseModel
|
|
|
202
276
|
Returns:
|
|
203
277
|
bool
|
|
204
278
|
"""
|
|
205
|
-
return
|
|
279
|
+
return is_schema_with_field(v, field_name) or is_dict_with_field(v, field_name)
|
|
206
280
|
|
|
207
281
|
|
|
208
|
-
def
|
|
209
|
-
"""Check if a
|
|
282
|
+
def is_schema_or_dict_without_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel | dict[str, Any]]:
|
|
283
|
+
"""Check if a value is a msgspec Struct, Pydantic model, or dict without a specific field.
|
|
210
284
|
|
|
211
285
|
Args:
|
|
212
286
|
v: Value to check.
|
|
@@ -215,11 +289,23 @@ def is_pydantic_model_without_field(v: Any, field_name: str) -> TypeGuard[BaseMo
|
|
|
215
289
|
Returns:
|
|
216
290
|
bool
|
|
217
291
|
"""
|
|
218
|
-
return not
|
|
292
|
+
return not is_schema_or_dict_with_field(v, field_name)
|
|
219
293
|
|
|
220
294
|
|
|
221
|
-
def
|
|
222
|
-
"""Check if a
|
|
295
|
+
def is_dataclass(v: Any) -> TypeGuard[DataclassProtocol]:
|
|
296
|
+
"""Check if a value is a dataclass.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
v: Value to check.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
bool
|
|
303
|
+
"""
|
|
304
|
+
return is_dataclass_instance(v)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def is_dataclass_with_field(v: Any, field_name: str) -> TypeGuard[DataclassProtocol]:
|
|
308
|
+
"""Check if a dataclass has a specific field.
|
|
223
309
|
|
|
224
310
|
Args:
|
|
225
311
|
v: Value to check.
|
|
@@ -228,11 +314,11 @@ def is_msgspec_model_with_field(v: Any, field_name: str) -> TypeGuard[Struct]:
|
|
|
228
314
|
Returns:
|
|
229
315
|
bool
|
|
230
316
|
"""
|
|
231
|
-
return
|
|
317
|
+
return is_dataclass(v) and field_name in v.__dataclass_fields__
|
|
232
318
|
|
|
233
319
|
|
|
234
|
-
def
|
|
235
|
-
"""Check if a
|
|
320
|
+
def is_dataclass_without_field(v: Any, field_name: str) -> TypeGuard[DataclassProtocol]:
|
|
321
|
+
"""Check if a dataclass does not have a specific field.
|
|
236
322
|
|
|
237
323
|
Args:
|
|
238
324
|
v: Value to check.
|
|
@@ -241,7 +327,7 @@ def is_msgspec_model_without_field(v: Any, field_name: str) -> TypeGuard[Struct]
|
|
|
241
327
|
Returns:
|
|
242
328
|
bool
|
|
243
329
|
"""
|
|
244
|
-
return
|
|
330
|
+
return is_dataclass(v) and field_name not in v.__dataclass_fields__
|
|
245
331
|
|
|
246
332
|
|
|
247
333
|
def extract_dataclass_fields(
|
|
@@ -339,7 +425,7 @@ def dataclass_to_dict(
|
|
|
339
425
|
ret[field.name] = dataclass_to_dict(value, exclude_none, exclude_empty)
|
|
340
426
|
else:
|
|
341
427
|
ret[field.name] = getattr(obj, field.name)
|
|
342
|
-
return ret
|
|
428
|
+
return cast("dict[str, Any]", ret)
|
|
343
429
|
|
|
344
430
|
|
|
345
431
|
def schema_dump(
|
|
@@ -355,13 +441,15 @@ def schema_dump(
|
|
|
355
441
|
Returns:
|
|
356
442
|
:type: dict[str, Any]
|
|
357
443
|
"""
|
|
444
|
+
if is_dict(data):
|
|
445
|
+
return data
|
|
358
446
|
if is_dataclass(data):
|
|
359
447
|
return dataclass_to_dict(data, exclude_empty=exclude_unset)
|
|
360
448
|
if is_pydantic_model(data):
|
|
361
449
|
return data.model_dump(exclude_unset=exclude_unset)
|
|
362
|
-
if
|
|
450
|
+
if is_msgspec_struct(data) and exclude_unset:
|
|
363
451
|
return {f: val for f in data.__struct_fields__ if (val := getattr(data, f, None)) != UNSET}
|
|
364
|
-
if
|
|
452
|
+
if is_msgspec_struct(data) and not exclude_unset:
|
|
365
453
|
return {f: getattr(data, f, None) for f in data.__struct_fields__}
|
|
366
454
|
return cast("dict[str,Any]", data)
|
|
367
455
|
|
|
@@ -394,9 +482,9 @@ __all__ = (
|
|
|
394
482
|
"is_dict",
|
|
395
483
|
"is_dict_with_field",
|
|
396
484
|
"is_dict_without_field",
|
|
397
|
-
"
|
|
398
|
-
"
|
|
399
|
-
"
|
|
485
|
+
"is_msgspec_struct",
|
|
486
|
+
"is_msgspec_struct_with_field",
|
|
487
|
+
"is_msgspec_struct_without_field",
|
|
400
488
|
"is_pydantic_model",
|
|
401
489
|
"is_pydantic_model_with_field",
|
|
402
490
|
"is_pydantic_model_without_field",
|
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Callable, Literal
|
|
6
|
+
from warnings import warn
|
|
7
|
+
|
|
8
|
+
from typing_extensions import ParamSpec, TypeVar
|
|
9
|
+
|
|
10
|
+
__all__ = ("deprecated", "warn_deprecation")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
P = ParamSpec("P")
|
|
15
|
+
DeprecatedKind = Literal["function", "method", "classmethod", "attribute", "property", "class", "parameter", "import"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def warn_deprecation(
|
|
19
|
+
version: str,
|
|
20
|
+
deprecated_name: str,
|
|
21
|
+
kind: DeprecatedKind,
|
|
22
|
+
*,
|
|
23
|
+
removal_in: str | None = None,
|
|
24
|
+
alternative: str | None = None,
|
|
25
|
+
info: str | None = None,
|
|
26
|
+
pending: bool = False,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Warn about a call to a (soon to be) deprecated function.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
version: Advanced Alchemy version where the deprecation will occur
|
|
32
|
+
deprecated_name: Name of the deprecated function
|
|
33
|
+
removal_in: Advanced Alchemy version where the deprecated function will be removed
|
|
34
|
+
alternative: Name of a function that should be used instead
|
|
35
|
+
info: Additional information
|
|
36
|
+
pending: Use :class:`warnings.PendingDeprecationWarning` instead of :class:`warnings.DeprecationWarning`
|
|
37
|
+
kind: Type of the deprecated thing
|
|
38
|
+
"""
|
|
39
|
+
parts = []
|
|
40
|
+
|
|
41
|
+
if kind == "import":
|
|
42
|
+
access_type = "Import of"
|
|
43
|
+
elif kind in {"function", "method"}:
|
|
44
|
+
access_type = "Call to"
|
|
45
|
+
else:
|
|
46
|
+
access_type = "Use of"
|
|
47
|
+
|
|
48
|
+
if pending:
|
|
49
|
+
parts.append(f"{access_type} {kind} awaiting deprecation {deprecated_name!r}") # pyright: ignore[reportUnknownMemberType]
|
|
50
|
+
else:
|
|
51
|
+
parts.append(f"{access_type} deprecated {kind} {deprecated_name!r}") # pyright: ignore[reportUnknownMemberType]
|
|
52
|
+
|
|
53
|
+
parts.extend( # pyright: ignore[reportUnknownMemberType]
|
|
54
|
+
(
|
|
55
|
+
f"Deprecated in advanced-alchemy {version}",
|
|
56
|
+
f"This {kind} will be removed in {removal_in or 'the next major version'}",
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
if alternative:
|
|
60
|
+
parts.append(f"Use {alternative!r} instead") # pyright: ignore[reportUnknownMemberType]
|
|
61
|
+
|
|
62
|
+
if info:
|
|
63
|
+
parts.append(info) # pyright: ignore[reportUnknownMemberType]
|
|
64
|
+
|
|
65
|
+
text = ". ".join(parts) # pyright: ignore[reportUnknownArgumentType]
|
|
66
|
+
warning_class = PendingDeprecationWarning if pending else DeprecationWarning
|
|
67
|
+
|
|
68
|
+
warn(text, warning_class, stacklevel=2)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def deprecated(
|
|
72
|
+
version: str,
|
|
73
|
+
*,
|
|
74
|
+
removal_in: str | None = None,
|
|
75
|
+
alternative: str | None = None,
|
|
76
|
+
info: str | None = None,
|
|
77
|
+
pending: bool = False,
|
|
78
|
+
kind: Literal["function", "method", "classmethod", "property"] | None = None,
|
|
79
|
+
) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
|
80
|
+
"""Create a decorator wrapping a function, method or property with a warning call about a (pending) deprecation.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
version: Litestar version where the deprecation will occur
|
|
84
|
+
removal_in: Litestar version where the deprecated function will be removed
|
|
85
|
+
alternative: Name of a function that should be used instead
|
|
86
|
+
info: Additional information
|
|
87
|
+
pending: Use :class:`warnings.PendingDeprecationWarning` instead of :class:`warnings.DeprecationWarning`
|
|
88
|
+
kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure
|
|
89
|
+
out if it's a function or method
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
A decorator wrapping the function call with a warning
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def decorator(func: Callable[P, T]) -> Callable[P, T]:
|
|
96
|
+
@wraps(func)
|
|
97
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
98
|
+
warn_deprecation(
|
|
99
|
+
version=version,
|
|
100
|
+
deprecated_name=func.__name__,
|
|
101
|
+
info=info,
|
|
102
|
+
alternative=alternative,
|
|
103
|
+
pending=pending,
|
|
104
|
+
removal_in=removal_in,
|
|
105
|
+
kind=kind or ("method" if inspect.ismethod(func) else "function"),
|
|
106
|
+
)
|
|
107
|
+
return func(*args, **kwargs)
|
|
108
|
+
|
|
109
|
+
return wrapped
|
|
110
|
+
|
|
111
|
+
return decorator
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from sqlspec._serialization import decode_json
|
|
6
|
+
from sqlspec.exceptions import MissingDependencyError
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from anyio import Path as AsyncPath
|
|
12
|
+
|
|
13
|
+
__all__ = ("open_fixture", "open_fixture_async")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def open_fixture(fixtures_path: Path | AsyncPath, fixture_name: str) -> Any:
|
|
17
|
+
"""Loads JSON file with the specified fixture name
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
fixtures_path: :class:`pathlib.Path` | :class:`anyio.Path` The path to look for fixtures
|
|
21
|
+
fixture_name (str): The fixture name to load.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
:class:`FileNotFoundError`: Fixtures not found.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Any: The parsed JSON data
|
|
28
|
+
"""
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
fixture = Path(fixtures_path / f"{fixture_name}.json")
|
|
32
|
+
if fixture.exists():
|
|
33
|
+
with fixture.open(mode="r", encoding="utf-8") as f:
|
|
34
|
+
f_data = f.read()
|
|
35
|
+
return decode_json(f_data)
|
|
36
|
+
msg = f"Could not find the {fixture_name} fixture"
|
|
37
|
+
raise FileNotFoundError(msg)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def open_fixture_async(fixtures_path: Path | AsyncPath, fixture_name: str) -> Any:
|
|
41
|
+
"""Loads JSON file with the specified fixture name
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
fixtures_path: :class:`pathlib.Path` | :class:`anyio.Path` The path to look for fixtures
|
|
45
|
+
fixture_name (str): The fixture name to load.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
:class:`~advanced_alchemy.exceptions.MissingDependencyError`: The `anyio` library is required to use this function.
|
|
49
|
+
:class:`FileNotFoundError`: Fixtures not found.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Any: The parsed JSON data
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
from anyio import Path as AsyncPath
|
|
56
|
+
except ImportError as exc:
|
|
57
|
+
msg = "The `anyio` library is required to use this function. Please install it with `pip install anyio`."
|
|
58
|
+
raise MissingDependencyError(msg) from exc
|
|
59
|
+
|
|
60
|
+
fixture = AsyncPath(fixtures_path / f"{fixture_name}.json")
|
|
61
|
+
if await fixture.exists():
|
|
62
|
+
async with await fixture.open(mode="r", encoding="utf-8") as f:
|
|
63
|
+
f_data = await f.read()
|
|
64
|
+
return decode_json(f_data)
|
|
65
|
+
msg = f"Could not find the {fixture_name} fixture"
|
|
66
|
+
raise FileNotFoundError(msg)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""General utility functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from importlib import import_module
|
|
7
|
+
from importlib.util import find_spec
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from types import ModuleType
|
|
13
|
+
|
|
14
|
+
__all__ = (
|
|
15
|
+
"import_string",
|
|
16
|
+
"module_to_os_path",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def module_to_os_path(dotted_path: str = "app") -> Path:
|
|
21
|
+
"""Find Module to OS Path.
|
|
22
|
+
|
|
23
|
+
Return a path to the base directory of the project or the module
|
|
24
|
+
specified by `dotted_path`.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
dotted_path: The path to the module. Defaults to "app".
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
TypeError: The module could not be found.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Path: The path to the module.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
if (src := find_spec(dotted_path)) is None: # pragma: no cover
|
|
37
|
+
msg = f"Couldn't find the path for {dotted_path}"
|
|
38
|
+
raise TypeError(msg)
|
|
39
|
+
except ModuleNotFoundError as e:
|
|
40
|
+
msg = f"Couldn't find the path for {dotted_path}"
|
|
41
|
+
raise TypeError(msg) from e
|
|
42
|
+
|
|
43
|
+
path = Path(str(src.origin))
|
|
44
|
+
return path.parent if path.is_file() else path
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def import_string(dotted_path: str) -> Any:
|
|
48
|
+
"""Dotted Path Import.
|
|
49
|
+
|
|
50
|
+
Import a dotted module path and return the attribute/class designated by the
|
|
51
|
+
last name in the path. Raise ImportError if the import failed.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
dotted_path: The path of the module to import.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ImportError: Could not import the module.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
object: The imported object.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def _is_loaded(module: ModuleType | None) -> bool:
|
|
64
|
+
spec = getattr(module, "__spec__", None)
|
|
65
|
+
initializing = getattr(spec, "_initializing", False)
|
|
66
|
+
return bool(module and spec and not initializing)
|
|
67
|
+
|
|
68
|
+
def _cached_import(module_path: str, class_name: str) -> Any:
|
|
69
|
+
"""Import and cache a class from a module.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
module_path: dotted path to module.
|
|
73
|
+
class_name: Class or function name.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
object: The imported class or function
|
|
77
|
+
"""
|
|
78
|
+
# Check whether module is loaded and fully initialized.
|
|
79
|
+
module = sys.modules.get(module_path)
|
|
80
|
+
if not _is_loaded(module):
|
|
81
|
+
module = import_module(module_path)
|
|
82
|
+
return getattr(module, class_name)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
module_path, class_name = dotted_path.rsplit(".", 1)
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
msg = "%s doesn't look like a module path"
|
|
88
|
+
raise ImportError(msg, dotted_path) from e
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
return _cached_import(module_path, class_name)
|
|
92
|
+
except AttributeError as e:
|
|
93
|
+
msg = "Module '%s' does not define a '%s' attribute/class"
|
|
94
|
+
raise ImportError(msg, module_path, class_name) from e
|
sqlspec/utils/text.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""General utility functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import unicodedata
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"check_email",
|
|
10
|
+
"slugify",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def check_email(email: str) -> str:
|
|
15
|
+
"""Validate an email."""
|
|
16
|
+
if "@" not in email:
|
|
17
|
+
msg = "Invalid email!"
|
|
18
|
+
raise ValueError(msg)
|
|
19
|
+
return email.lower()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def slugify(value: str, allow_unicode: bool = False, separator: str | None = None) -> str:
|
|
23
|
+
"""Slugify.
|
|
24
|
+
|
|
25
|
+
Convert to ASCII if ``allow_unicode`` is ``False``. Convert spaces or repeated
|
|
26
|
+
dashes to single dashes. Remove characters that aren't alphanumerics,
|
|
27
|
+
underscores, or hyphens. Convert to lowercase. Also strip leading and
|
|
28
|
+
trailing whitespace, dashes, and underscores.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
value (str): the string to slugify
|
|
32
|
+
allow_unicode (bool, optional): allow unicode characters in slug. Defaults to False.
|
|
33
|
+
separator (str, optional): by default a `-` is used to delimit word boundaries.
|
|
34
|
+
Set this to configure something different.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: a slugified string of the value parameter
|
|
38
|
+
"""
|
|
39
|
+
if allow_unicode:
|
|
40
|
+
value = unicodedata.normalize("NFKC", value)
|
|
41
|
+
else:
|
|
42
|
+
value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
|
|
43
|
+
value = re.sub(r"[^\w\s-]", "", value.lower())
|
|
44
|
+
if separator is not None:
|
|
45
|
+
return re.sub(r"[-\s]+", "-", value).strip("-_").replace("-", separator)
|
|
46
|
+
return re.sub(r"[-\s]+", "-", value).strip("-_")
|