sqlspec 0.3.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 +58 -2
- sqlspec/adapters/adbc/config.py +8 -10
- sqlspec/adapters/aiosqlite/config.py +4 -21
- sqlspec/adapters/asyncmy/config.py +12 -25
- sqlspec/adapters/asyncpg/config.py +15 -21
- sqlspec/adapters/duckdb/__init__.py +3 -0
- sqlspec/adapters/duckdb/config.py +130 -29
- sqlspec/adapters/oracledb/__init__.py +1 -1
- sqlspec/adapters/oracledb/config/_asyncio.py +14 -14
- sqlspec/adapters/oracledb/config/_common.py +9 -9
- sqlspec/adapters/oracledb/config/_sync.py +9 -13
- sqlspec/adapters/psycopg/config/__init__.py +2 -2
- sqlspec/adapters/psycopg/config/_async.py +8 -14
- sqlspec/adapters/psycopg/config/_common.py +6 -5
- sqlspec/adapters/psycopg/config/_sync.py +8 -14
- sqlspec/adapters/sqlite/config.py +6 -23
- sqlspec/base.py +87 -0
- sqlspec/filters.py +8 -5
- sqlspec/typing.py +248 -32
- 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/utils/dataclass.py +0 -138
- sqlspec/utils/empty.py +0 -18
- sqlspec-0.3.0.dist-info/METADATA +0 -84
- sqlspec-0.3.0.dist-info/RECORD +0 -42
- {sqlspec-0.3.0.dist-info → sqlspec-0.5.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.3.0.dist-info → sqlspec-0.5.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/typing.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
1
|
+
from __future__ import annotations # noqa: A005
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
+
from dataclasses import Field, fields
|
|
4
5
|
from functools import lru_cache
|
|
5
6
|
from typing import (
|
|
6
7
|
TYPE_CHECKING,
|
|
@@ -18,15 +19,22 @@ from sqlspec._typing import (
|
|
|
18
19
|
PYDANTIC_INSTALLED,
|
|
19
20
|
UNSET,
|
|
20
21
|
BaseModel,
|
|
22
|
+
DataclassProtocol,
|
|
23
|
+
Empty,
|
|
24
|
+
EmptyType,
|
|
21
25
|
FailFast,
|
|
22
26
|
Struct,
|
|
23
27
|
TypeAdapter,
|
|
28
|
+
UnsetType,
|
|
24
29
|
convert,
|
|
25
30
|
)
|
|
26
|
-
from sqlspec.utils.dataclass import DataclassProtocol, is_dataclass_instance, simple_asdict
|
|
27
31
|
|
|
28
32
|
if TYPE_CHECKING:
|
|
29
|
-
from .
|
|
33
|
+
from collections.abc import Iterable
|
|
34
|
+
from collections.abc import Set as AbstractSet
|
|
35
|
+
|
|
36
|
+
from sqlspec.filters import StatementFilter
|
|
37
|
+
|
|
30
38
|
|
|
31
39
|
PYDANTIC_USE_FAILFAST = False # leave permanently disabled for now
|
|
32
40
|
|
|
@@ -40,11 +48,21 @@ FilterTypeT = TypeVar("FilterTypeT", bound="StatementFilter")
|
|
|
40
48
|
|
|
41
49
|
:class:`~advanced_alchemy.filters.StatementFilter`
|
|
42
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
|
+
"""
|
|
43
61
|
ModelDictT: TypeAlias = Union[dict[str, Any], ModelT, DataclassProtocol, Struct, BaseModel]
|
|
44
62
|
"""Type alias for model dictionaries.
|
|
45
63
|
|
|
46
64
|
Represents:
|
|
47
|
-
- :type:`dict[str, Any]` |
|
|
65
|
+
- :type:`dict[str, Any]` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel`
|
|
48
66
|
"""
|
|
49
67
|
ModelDictListT: TypeAlias = Sequence[Union[dict[str, Any], ModelT, DataclassProtocol, Struct, BaseModel]]
|
|
50
68
|
"""Type alias for model dictionary lists.
|
|
@@ -55,6 +73,18 @@ A list or sequence of any of the following:
|
|
|
55
73
|
"""
|
|
56
74
|
|
|
57
75
|
|
|
76
|
+
def is_dataclass_instance(obj: Any) -> TypeGuard[DataclassProtocol]:
|
|
77
|
+
"""Check if an object is a dataclass instance.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
obj: An object to check.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if the object is a dataclass instance.
|
|
84
|
+
"""
|
|
85
|
+
return hasattr(type(obj), "__dataclass_fields__") # pyright: ignore[reportUnknownArgumentType]
|
|
86
|
+
|
|
87
|
+
|
|
58
88
|
@lru_cache(typed=True)
|
|
59
89
|
def get_type_adapter(f: type[T]) -> TypeAdapter[T]:
|
|
60
90
|
"""Caches and returns a pydantic type adapter.
|
|
@@ -84,7 +114,33 @@ def is_pydantic_model(v: Any) -> TypeGuard[BaseModel]:
|
|
|
84
114
|
return PYDANTIC_INSTALLED and isinstance(v, BaseModel)
|
|
85
115
|
|
|
86
116
|
|
|
87
|
-
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]:
|
|
88
144
|
"""Check if a value is a msgspec model.
|
|
89
145
|
|
|
90
146
|
Args:
|
|
@@ -96,6 +152,32 @@ def is_msgspec_model(v: Any) -> TypeGuard[Struct]:
|
|
|
96
152
|
return MSGSPEC_INSTALLED and isinstance(v, Struct)
|
|
97
153
|
|
|
98
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
|
+
|
|
99
181
|
def is_dict(v: Any) -> TypeGuard[dict[str, Any]]:
|
|
100
182
|
"""Check if a value is a dictionary.
|
|
101
183
|
|
|
@@ -134,8 +216,8 @@ def is_dict_without_field(v: Any, field_name: str) -> TypeGuard[dict[str, Any]]:
|
|
|
134
216
|
return is_dict(v) and field_name not in v
|
|
135
217
|
|
|
136
218
|
|
|
137
|
-
def
|
|
138
|
-
"""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.
|
|
139
221
|
|
|
140
222
|
Args:
|
|
141
223
|
v: Value to check.
|
|
@@ -143,11 +225,23 @@ def is_dataclass(v: Any) -> TypeGuard[DataclassProtocol]:
|
|
|
143
225
|
Returns:
|
|
144
226
|
bool
|
|
145
227
|
"""
|
|
146
|
-
return
|
|
228
|
+
return is_msgspec_struct(v) or is_pydantic_model(v)
|
|
147
229
|
|
|
148
230
|
|
|
149
|
-
def
|
|
150
|
-
"""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.
|
|
151
245
|
|
|
152
246
|
Args:
|
|
153
247
|
v: Value to check.
|
|
@@ -156,11 +250,11 @@ def is_dataclass_with_field(v: Any, field_name: str) -> TypeGuard[DataclassProto
|
|
|
156
250
|
Returns:
|
|
157
251
|
bool
|
|
158
252
|
"""
|
|
159
|
-
return
|
|
253
|
+
return is_msgspec_struct_with_field(v, field_name) or is_pydantic_model_with_field(v, field_name)
|
|
160
254
|
|
|
161
255
|
|
|
162
|
-
def
|
|
163
|
-
"""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.
|
|
164
258
|
|
|
165
259
|
Args:
|
|
166
260
|
v: Value to check.
|
|
@@ -169,11 +263,11 @@ def is_dataclass_without_field(v: Any, field_name: str) -> TypeGuard[DataclassPr
|
|
|
169
263
|
Returns:
|
|
170
264
|
bool
|
|
171
265
|
"""
|
|
172
|
-
return
|
|
266
|
+
return not is_schema_with_field(v, field_name)
|
|
173
267
|
|
|
174
268
|
|
|
175
|
-
def
|
|
176
|
-
"""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.
|
|
177
271
|
|
|
178
272
|
Args:
|
|
179
273
|
v: Value to check.
|
|
@@ -182,11 +276,11 @@ def is_pydantic_model_with_field(v: Any, field_name: str) -> TypeGuard[BaseModel
|
|
|
182
276
|
Returns:
|
|
183
277
|
bool
|
|
184
278
|
"""
|
|
185
|
-
return
|
|
279
|
+
return is_schema_with_field(v, field_name) or is_dict_with_field(v, field_name)
|
|
186
280
|
|
|
187
281
|
|
|
188
|
-
def
|
|
189
|
-
"""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.
|
|
190
284
|
|
|
191
285
|
Args:
|
|
192
286
|
v: Value to check.
|
|
@@ -195,11 +289,23 @@ def is_pydantic_model_without_field(v: Any, field_name: str) -> TypeGuard[BaseMo
|
|
|
195
289
|
Returns:
|
|
196
290
|
bool
|
|
197
291
|
"""
|
|
198
|
-
return not
|
|
292
|
+
return not is_schema_or_dict_with_field(v, field_name)
|
|
199
293
|
|
|
200
294
|
|
|
201
|
-
def
|
|
202
|
-
"""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.
|
|
203
309
|
|
|
204
310
|
Args:
|
|
205
311
|
v: Value to check.
|
|
@@ -208,11 +314,11 @@ def is_msgspec_model_with_field(v: Any, field_name: str) -> TypeGuard[Struct]:
|
|
|
208
314
|
Returns:
|
|
209
315
|
bool
|
|
210
316
|
"""
|
|
211
|
-
return
|
|
317
|
+
return is_dataclass(v) and field_name in v.__dataclass_fields__
|
|
212
318
|
|
|
213
319
|
|
|
214
|
-
def
|
|
215
|
-
"""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.
|
|
216
322
|
|
|
217
323
|
Args:
|
|
218
324
|
v: Value to check.
|
|
@@ -221,7 +327,105 @@ def is_msgspec_model_without_field(v: Any, field_name: str) -> TypeGuard[Struct]
|
|
|
221
327
|
Returns:
|
|
222
328
|
bool
|
|
223
329
|
"""
|
|
224
|
-
return
|
|
330
|
+
return is_dataclass(v) and field_name not in v.__dataclass_fields__
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def extract_dataclass_fields(
|
|
334
|
+
dt: DataclassProtocol,
|
|
335
|
+
exclude_none: bool = False,
|
|
336
|
+
exclude_empty: bool = False,
|
|
337
|
+
include: AbstractSet[str] | None = None,
|
|
338
|
+
exclude: AbstractSet[str] | None = None,
|
|
339
|
+
) -> tuple[Field[Any], ...]:
|
|
340
|
+
"""Extract dataclass fields.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
dt: A dataclass instance.
|
|
344
|
+
exclude_none: Whether to exclude None values.
|
|
345
|
+
exclude_empty: Whether to exclude Empty values.
|
|
346
|
+
include: An iterable of fields to include.
|
|
347
|
+
exclude: An iterable of fields to exclude.
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
A tuple of dataclass fields.
|
|
352
|
+
"""
|
|
353
|
+
include = include or set()
|
|
354
|
+
exclude = exclude or set()
|
|
355
|
+
|
|
356
|
+
if common := (include & exclude):
|
|
357
|
+
msg = f"Fields {common} are both included and excluded."
|
|
358
|
+
raise ValueError(msg)
|
|
359
|
+
|
|
360
|
+
dataclass_fields: Iterable[Field[Any]] = fields(dt)
|
|
361
|
+
if exclude_none:
|
|
362
|
+
dataclass_fields = (field for field in dataclass_fields if getattr(dt, field.name) is not None)
|
|
363
|
+
if exclude_empty:
|
|
364
|
+
dataclass_fields = (field for field in dataclass_fields if getattr(dt, field.name) is not Empty)
|
|
365
|
+
if include:
|
|
366
|
+
dataclass_fields = (field for field in dataclass_fields if field.name in include)
|
|
367
|
+
if exclude:
|
|
368
|
+
dataclass_fields = (field for field in dataclass_fields if field.name not in exclude)
|
|
369
|
+
|
|
370
|
+
return tuple(dataclass_fields)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def extract_dataclass_items(
|
|
374
|
+
dt: DataclassProtocol,
|
|
375
|
+
exclude_none: bool = False,
|
|
376
|
+
exclude_empty: bool = False,
|
|
377
|
+
include: AbstractSet[str] | None = None,
|
|
378
|
+
exclude: AbstractSet[str] | None = None,
|
|
379
|
+
) -> tuple[tuple[str, Any], ...]:
|
|
380
|
+
"""Extract dataclass name, value pairs.
|
|
381
|
+
|
|
382
|
+
Unlike the 'asdict' method exports by the stdlib, this function does not pickle values.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
dt: A dataclass instance.
|
|
386
|
+
exclude_none: Whether to exclude None values.
|
|
387
|
+
exclude_empty: Whether to exclude Empty values.
|
|
388
|
+
include: An iterable of fields to include.
|
|
389
|
+
exclude: An iterable of fields to exclude.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
A tuple of key/value pairs.
|
|
393
|
+
"""
|
|
394
|
+
dataclass_fields = extract_dataclass_fields(dt, exclude_none, exclude_empty, include, exclude)
|
|
395
|
+
return tuple((field.name, getattr(dt, field.name)) for field in dataclass_fields)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def dataclass_to_dict(
|
|
399
|
+
obj: DataclassProtocol,
|
|
400
|
+
exclude_none: bool = False,
|
|
401
|
+
exclude_empty: bool = False,
|
|
402
|
+
convert_nested: bool = True,
|
|
403
|
+
exclude: set[str] | None = None,
|
|
404
|
+
) -> dict[str, Any]:
|
|
405
|
+
"""Convert a dataclass to a dictionary.
|
|
406
|
+
|
|
407
|
+
This method has important differences to the standard library version:
|
|
408
|
+
- it does not deepcopy values
|
|
409
|
+
- it does not recurse into collections
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
obj: A dataclass instance.
|
|
413
|
+
exclude_none: Whether to exclude None values.
|
|
414
|
+
exclude_empty: Whether to exclude Empty values.
|
|
415
|
+
convert_nested: Whether to recursively convert nested dataclasses.
|
|
416
|
+
exclude: An iterable of fields to exclude.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
A dictionary of key/value pairs.
|
|
420
|
+
"""
|
|
421
|
+
ret = {}
|
|
422
|
+
for field in extract_dataclass_fields(obj, exclude_none, exclude_empty, exclude=exclude):
|
|
423
|
+
value = getattr(obj, field.name)
|
|
424
|
+
if is_dataclass_instance(value) and convert_nested:
|
|
425
|
+
ret[field.name] = dataclass_to_dict(value, exclude_none, exclude_empty)
|
|
426
|
+
else:
|
|
427
|
+
ret[field.name] = getattr(obj, field.name)
|
|
428
|
+
return cast("dict[str, Any]", ret)
|
|
225
429
|
|
|
226
430
|
|
|
227
431
|
def schema_dump(
|
|
@@ -237,13 +441,15 @@ def schema_dump(
|
|
|
237
441
|
Returns:
|
|
238
442
|
:type: dict[str, Any]
|
|
239
443
|
"""
|
|
444
|
+
if is_dict(data):
|
|
445
|
+
return data
|
|
240
446
|
if is_dataclass(data):
|
|
241
|
-
return
|
|
447
|
+
return dataclass_to_dict(data, exclude_empty=exclude_unset)
|
|
242
448
|
if is_pydantic_model(data):
|
|
243
449
|
return data.model_dump(exclude_unset=exclude_unset)
|
|
244
|
-
if
|
|
450
|
+
if is_msgspec_struct(data) and exclude_unset:
|
|
245
451
|
return {f: val for f in data.__struct_fields__ if (val := getattr(data, f, None)) != UNSET}
|
|
246
|
-
if
|
|
452
|
+
if is_msgspec_struct(data) and not exclude_unset:
|
|
247
453
|
return {f: getattr(data, f, None) for f in data.__struct_fields__}
|
|
248
454
|
return cast("dict[str,Any]", data)
|
|
249
455
|
|
|
@@ -254,6 +460,9 @@ __all__ = (
|
|
|
254
460
|
"PYDANTIC_USE_FAILFAST",
|
|
255
461
|
"UNSET",
|
|
256
462
|
"BaseModel",
|
|
463
|
+
"DataclassProtocol",
|
|
464
|
+
"Empty",
|
|
465
|
+
"EmptyType",
|
|
257
466
|
"FailFast",
|
|
258
467
|
"FilterTypeT",
|
|
259
468
|
"ModelDictListT",
|
|
@@ -262,13 +471,20 @@ __all__ = (
|
|
|
262
471
|
"TypeAdapter",
|
|
263
472
|
"UnsetType",
|
|
264
473
|
"convert",
|
|
474
|
+
"dataclass_to_dict",
|
|
475
|
+
"extract_dataclass_fields",
|
|
476
|
+
"extract_dataclass_items",
|
|
265
477
|
"get_type_adapter",
|
|
478
|
+
"is_dataclass",
|
|
479
|
+
"is_dataclass_instance",
|
|
480
|
+
"is_dataclass_with_field",
|
|
481
|
+
"is_dataclass_without_field",
|
|
266
482
|
"is_dict",
|
|
267
483
|
"is_dict_with_field",
|
|
268
484
|
"is_dict_without_field",
|
|
269
|
-
"
|
|
270
|
-
"
|
|
271
|
-
"
|
|
485
|
+
"is_msgspec_struct",
|
|
486
|
+
"is_msgspec_struct_with_field",
|
|
487
|
+
"is_msgspec_struct_without_field",
|
|
272
488
|
"is_pydantic_model",
|
|
273
489
|
"is_pydantic_model_with_field",
|
|
274
490
|
"is_pydantic_model_without_field",
|
|
@@ -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
|