thds.atacama 1.0.20250331210237__py3-none-any.whl → 1.1.20250402145016__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 thds.atacama might be problematic. Click here for more details.
- thds/atacama/_attrs.py +4 -2
- thds/atacama/_typevars.py +65 -0
- thds/atacama/fields.py +6 -2
- thds/atacama/leaf.py +3 -0
- thds/atacama/schemas.py +11 -3
- {thds_atacama-1.0.20250331210237.dist-info → thds_atacama-1.1.20250402145016.dist-info}/METADATA +1 -1
- {thds_atacama-1.0.20250331210237.dist-info → thds_atacama-1.1.20250402145016.dist-info}/RECORD +9 -8
- {thds_atacama-1.0.20250331210237.dist-info → thds_atacama-1.1.20250402145016.dist-info}/WHEEL +0 -0
- {thds_atacama-1.0.20250331210237.dist-info → thds_atacama-1.1.20250402145016.dist-info}/top_level.txt +0 -0
thds/atacama/_attrs.py
CHANGED
|
@@ -3,6 +3,8 @@ import typing as ty
|
|
|
3
3
|
import attrs
|
|
4
4
|
import marshmallow
|
|
5
5
|
|
|
6
|
+
from . import _typevars
|
|
7
|
+
|
|
6
8
|
T = ty.TypeVar("T")
|
|
7
9
|
|
|
8
10
|
|
|
@@ -38,8 +40,8 @@ class Attribute(ty.NamedTuple):
|
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def yield_attributes(attrs_class: type) -> ty.Iterator[Attribute]:
|
|
41
|
-
hints =
|
|
42
|
-
for attribute in attrs.fields(
|
|
43
|
+
base_class, hints = _typevars.base_class_and_hints(attrs_class)
|
|
44
|
+
for attribute in attrs.fields(base_class):
|
|
43
45
|
yield Attribute(
|
|
44
46
|
attribute.name,
|
|
45
47
|
hints.get(attribute.name, attribute.type),
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# currently only in use by the _attrs type introspection tool,
|
|
2
|
+
# but probably could be useful for other things, and doesn't directly deal with attrs.
|
|
3
|
+
import typing as ty
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _resolve_typevars(
|
|
7
|
+
type_hint: type, type_params: ty.Tuple[ty.TypeVar, ...], type_args: ty.Tuple[type, ...]
|
|
8
|
+
) -> type:
|
|
9
|
+
"""Recursively resolve TypeVars in a type hint with their actual types."""
|
|
10
|
+
|
|
11
|
+
# Direct TypeVar replacement - this is the 'base case'
|
|
12
|
+
if isinstance(type_hint, ty.TypeVar) and type_hint in type_params:
|
|
13
|
+
idx = type_params.index(type_hint)
|
|
14
|
+
if idx < len(type_args):
|
|
15
|
+
return type_args[idx]
|
|
16
|
+
return type_hint
|
|
17
|
+
|
|
18
|
+
# Handle generic types that might contain TypeVars
|
|
19
|
+
if hasattr(type_hint, "__origin__"):
|
|
20
|
+
origin = type_hint.__origin__
|
|
21
|
+
args = getattr(type_hint, "__args__", ())
|
|
22
|
+
|
|
23
|
+
# Recursively resolve each argument
|
|
24
|
+
resolved_args = tuple(_resolve_typevars(arg, type_params, type_args) for arg in args)
|
|
25
|
+
|
|
26
|
+
# If nothing changed, return the original
|
|
27
|
+
if resolved_args == args:
|
|
28
|
+
return type_hint
|
|
29
|
+
|
|
30
|
+
# Create a new generic with resolved arguments
|
|
31
|
+
try:
|
|
32
|
+
return origin[resolved_args]
|
|
33
|
+
except (TypeError, IndexError):
|
|
34
|
+
# Fall back to original if we can't create a new generic
|
|
35
|
+
return type_hint
|
|
36
|
+
|
|
37
|
+
# Return unmodified for simple types
|
|
38
|
+
return type_hint
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def base_class_and_hints(class_: type) -> ty.Tuple[type, ty.Dict[str, type]]:
|
|
42
|
+
# Get the original class and type arguments if it's a generic
|
|
43
|
+
if not hasattr(class_, "__origin__"):
|
|
44
|
+
return class_, ty.get_type_hints(class_)
|
|
45
|
+
|
|
46
|
+
# Get type hints from the base class
|
|
47
|
+
base_class = class_.__origin__
|
|
48
|
+
base_hints = ty.get_type_hints(base_class)
|
|
49
|
+
if not hasattr(base_class, "__parameters__"):
|
|
50
|
+
return base_class, base_hints
|
|
51
|
+
|
|
52
|
+
# Create a modified hints dictionary with resolved types
|
|
53
|
+
type_params = base_class.__parameters__
|
|
54
|
+
assert hasattr(
|
|
55
|
+
class_, "__args__"
|
|
56
|
+
), f"Generic class {class_} must have type arguments, or we cannot determine types for it."
|
|
57
|
+
type_args = class_.__args__
|
|
58
|
+
hints = {}
|
|
59
|
+
|
|
60
|
+
for name, hint_type in base_hints.items():
|
|
61
|
+
# Check if this hint is a TypeVar or contains TypeVars
|
|
62
|
+
resolved_type = _resolve_typevars(hint_type, type_params, type_args)
|
|
63
|
+
hints[name] = resolved_type
|
|
64
|
+
|
|
65
|
+
return base_class, hints
|
thds/atacama/fields.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# portions copyright Desert contributors - see LICENSE_desert.md
|
|
2
2
|
import enum
|
|
3
|
+
import logging
|
|
3
4
|
import typing as ty
|
|
4
5
|
from functools import partial
|
|
5
6
|
|
|
@@ -19,8 +20,7 @@ class VariadicTuple(marshmallow.fields.List):
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
T = ty.TypeVar("T")
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
# most kwargs for a field belong to the surrounding context,
|
|
26
26
|
# e.g. the name and the aggregation that this field lives in.
|
|
@@ -36,6 +36,7 @@ def generate_field(
|
|
|
36
36
|
schema_generator: ty.Callable[[type], ty.Type[marshmallow.Schema]],
|
|
37
37
|
typ: type,
|
|
38
38
|
field_kwargs: ty.Mapping[str, ty.Any] = dict(), # noqa: [B006]
|
|
39
|
+
debug_name: str = "",
|
|
39
40
|
) -> marshmallow.fields.Field:
|
|
40
41
|
"""Return a Marshmallow Field or Schema.
|
|
41
42
|
|
|
@@ -104,6 +105,9 @@ def generate_field(
|
|
|
104
105
|
nested_schema = schema_generator(typ)
|
|
105
106
|
except RecursionError:
|
|
106
107
|
nested_schema = lambda: schema_generator(typ) # type: ignore # noqa: E731
|
|
108
|
+
except Exception:
|
|
109
|
+
logger.exception(f"Failed to generate schema for {debug_name}={typ}")
|
|
110
|
+
raise
|
|
107
111
|
return marshmallow.fields.Nested(nested_schema, **field_kwargs)
|
|
108
112
|
|
|
109
113
|
return generic_types_dispatch(
|
thds/atacama/leaf.py
CHANGED
|
@@ -59,6 +59,8 @@ def allow_already_parsed(typ: ty.Type, mm_field: FieldT) -> FieldT:
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
NoneType = type(None)
|
|
63
|
+
|
|
62
64
|
NATIVE_TO_MARSHMALLOW: LeafTypeMapping = {
|
|
63
65
|
float: marshmallow.fields.Float,
|
|
64
66
|
int: marshmallow.fields.Integer,
|
|
@@ -72,6 +74,7 @@ NATIVE_TO_MARSHMALLOW: LeafTypeMapping = {
|
|
|
72
74
|
uuid.UUID: marshmallow.fields.UUID,
|
|
73
75
|
ty.Union[int, float]: marshmallow.fields.Number,
|
|
74
76
|
ty.Any: _field_with_default_kwargs(marshmallow.fields.Raw, allow_none=True),
|
|
77
|
+
NoneType: _field_with_default_kwargs(marshmallow.fields.Constant, constant=None, allow_none=True),
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
|
thds/atacama/schemas.py
CHANGED
|
@@ -3,7 +3,7 @@ import typing as ty
|
|
|
3
3
|
import marshmallow # type: ignore
|
|
4
4
|
from typing_extensions import Protocol
|
|
5
5
|
|
|
6
|
-
from ._attrs import generate_attrs_post_load, is_attrs_class, yield_attributes
|
|
6
|
+
from ._attrs import Attribute, generate_attrs_post_load, is_attrs_class, yield_attributes
|
|
7
7
|
from ._cache import GenSchemaCachingDeco
|
|
8
8
|
from ._config import PerGenerationConfigContext, _GenConfig
|
|
9
9
|
from ._meta import SchemaMeta
|
|
@@ -142,7 +142,9 @@ class SchemaGenerator:
|
|
|
142
142
|
),
|
|
143
143
|
)
|
|
144
144
|
|
|
145
|
-
def _named_field_discriminator(
|
|
145
|
+
def _named_field_discriminator(
|
|
146
|
+
self, attribute: Attribute, named_field: NamedField
|
|
147
|
+
) -> marshmallow.fields.Field:
|
|
146
148
|
"""When we are given a field name with a provided value, there are 4 possibilities."""
|
|
147
149
|
# 1. A Field. This should be plugged directly into the Schema
|
|
148
150
|
# without being touched. Recursion ends here.
|
|
@@ -161,6 +163,7 @@ class SchemaGenerator:
|
|
|
161
163
|
lambda _s: ty.cast(ty.Type[marshmallow.Schema], named_field),
|
|
162
164
|
attribute.type,
|
|
163
165
|
_set_default(attribute.default),
|
|
166
|
+
debug_name=attribute.name,
|
|
164
167
|
)
|
|
165
168
|
# 3. A nested Schema Generator, with inner keyword
|
|
166
169
|
# arguments. This would be used in the case where you want the
|
|
@@ -173,7 +176,11 @@ class SchemaGenerator:
|
|
|
173
176
|
# .nested on the current SchemaGenerator.
|
|
174
177
|
if isinstance(named_field, _NestedSchemaGenerator):
|
|
175
178
|
return generate_field(
|
|
176
|
-
self._leaf_types,
|
|
179
|
+
self._leaf_types,
|
|
180
|
+
named_field,
|
|
181
|
+
attribute.type,
|
|
182
|
+
named_field.field_kwargs,
|
|
183
|
+
debug_name=attribute.name,
|
|
177
184
|
)
|
|
178
185
|
# 4. A partial Field with some 'inner' keyword arguments for
|
|
179
186
|
# the Field only. Recursion continues - simply adds keyword
|
|
@@ -188,6 +195,7 @@ class SchemaGenerator:
|
|
|
188
195
|
self,
|
|
189
196
|
attribute.type,
|
|
190
197
|
dict(_set_default(attribute.default), **named_field.field_kwargs),
|
|
198
|
+
debug_name=attribute.name,
|
|
191
199
|
)
|
|
192
200
|
|
|
193
201
|
def _gen_fields(
|
{thds_atacama-1.0.20250331210237.dist-info → thds_atacama-1.1.20250402145016.dist-info}/RECORD
RENAMED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
thds/atacama/__init__.py,sha256=GYDLPjg7E91sPlOKZ8UyhSDCRvhUSbg9hbL-fK2hzZY,265
|
|
2
|
-
thds/atacama/_attrs.py,sha256=
|
|
2
|
+
thds/atacama/_attrs.py,sha256=CwDIplm48Z4vrj9fDY4YdoB7imUOVpGmguKZW4PpAxo,1605
|
|
3
3
|
thds/atacama/_cache.py,sha256=fYZ6iYKvQClCRtuGIQAMdJHyVHJ9Ffyr0dW1eyaMnwA,1477
|
|
4
4
|
thds/atacama/_config.py,sha256=Z2gqxnBJvk9plS-3vgNchfq3Z7wBsuyu0SqAmnKFg2o,1442
|
|
5
5
|
thds/atacama/_generic_dispatch.py,sha256=N5gZGTLMDVCwbf3OAx1EIA-wG3_T6CICiy65D-A_RKI,2256
|
|
6
6
|
thds/atacama/_meta.py,sha256=sOjP0Y0KiNZUAXrxg2JWwolSWlxgU_OUqniUR_2Iv8g,498
|
|
7
|
+
thds/atacama/_typevars.py,sha256=Qp6TDyA-Wfpjv2icAVH4HTOUWsdpGUfPgmuQRtqmEsI,2391
|
|
7
8
|
thds/atacama/custom_fields.py,sha256=leIsn40R5TexbAgTj1X-HUtkopfEMp5Hwb-0PYQGQ2U,945
|
|
8
9
|
thds/atacama/field_transforms.py,sha256=9Y1HO0U7oZZhmpYegTM1ruZX0hZMgdWS6b4gARb_Yo8,528
|
|
9
|
-
thds/atacama/fields.py,sha256=
|
|
10
|
+
thds/atacama/fields.py,sha256=ZxdL1f-Gr0eWxUfIucjphEOP8_7k-BOE92izdk1iuQI,5462
|
|
10
11
|
thds/atacama/generators.py,sha256=O6LvFaFpsfvAD4l_cCa82usF1RQ4aBHhv8bLikJ5rL0,486
|
|
11
|
-
thds/atacama/leaf.py,sha256=
|
|
12
|
+
thds/atacama/leaf.py,sha256=Gp2kt8l5dgUq7K_QgMc6ddSbIvCW3kmNO5-7vCxhwEo,4701
|
|
12
13
|
thds/atacama/nonempty.py,sha256=bL1cqNpUzATosiqGdEPEn6zlYcliySKH0tj__2UnijU,1384
|
|
13
14
|
thds/atacama/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
thds/atacama/schemas.py,sha256=
|
|
15
|
-
thds_atacama-1.
|
|
16
|
-
thds_atacama-1.
|
|
17
|
-
thds_atacama-1.
|
|
18
|
-
thds_atacama-1.
|
|
15
|
+
thds/atacama/schemas.py,sha256=azhePCqWc2IBAqU6xmBR4fDPXrmXX2GhRJU9FliYL6c,9628
|
|
16
|
+
thds_atacama-1.1.20250402145016.dist-info/METADATA,sha256=Dik4f2QJQx382c8n4oFEy5ARDMakNgBnVzsfkBAxjkQ,17789
|
|
17
|
+
thds_atacama-1.1.20250402145016.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
18
|
+
thds_atacama-1.1.20250402145016.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
|
|
19
|
+
thds_atacama-1.1.20250402145016.dist-info/RECORD,,
|
{thds_atacama-1.0.20250331210237.dist-info → thds_atacama-1.1.20250402145016.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|