thds.atacama 1.1.20250401153235__py3-none-any.whl → 1.1.20250407135820__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 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 = ty.get_type_hints(attrs_class)
42
- for attribute in attrs.fields(attrs_class):
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
- NoneType = type(None)
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(self, attribute, named_field: NamedField) -> marshmallow.fields.Field:
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, named_field, attribute.type, named_field.field_kwargs
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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: thds.atacama
3
- Version: 1.1.20250401153235
3
+ Version: 1.1.20250407135820
4
4
  Summary: A Marshmallow schema generator for `attrs` classes. Inspired by `desert`.
5
5
  Author-email: Trilliant Health <info@trillianthealth.com>
6
6
  License: MIT
@@ -1,18 +1,19 @@
1
1
  thds/atacama/__init__.py,sha256=GYDLPjg7E91sPlOKZ8UyhSDCRvhUSbg9hbL-fK2hzZY,265
2
- thds/atacama/_attrs.py,sha256=3g3mXdUJRsaKgGnzDWZ8AoqM8pgLL9GMeY1cF7-sN6o,1556
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=zIfFCyUO1YcbKiLsl3OX-xhx3JxnyRBkP6kf-nOezsA,5281
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=C7HDGBS_Evds35qWeBLQAw1ZahERJONh-WrguZzIMvs,4575
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=CqSaj1KJGPuXzHkjdSVrZUhhd3dagmEUtYuBEiJGJ_g,9418
15
- thds_atacama-1.1.20250401153235.dist-info/METADATA,sha256=S6kicNnKaPii9WxV_YHWnJBU6U58OlOTGxVHen90lik,17789
16
- thds_atacama-1.1.20250401153235.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
17
- thds_atacama-1.1.20250401153235.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
18
- thds_atacama-1.1.20250401153235.dist-info/RECORD,,
15
+ thds/atacama/schemas.py,sha256=azhePCqWc2IBAqU6xmBR4fDPXrmXX2GhRJU9FliYL6c,9628
16
+ thds_atacama-1.1.20250407135820.dist-info/METADATA,sha256=U1PmeLc29fhXPvT0dYk6GPdc4iOwtdUM74QKYZFNVyA,17789
17
+ thds_atacama-1.1.20250407135820.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
18
+ thds_atacama-1.1.20250407135820.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
19
+ thds_atacama-1.1.20250407135820.dist-info/RECORD,,