Skip to content

Commit e2d8f3e

Browse files
committed
Raise errors for missing structure handlers more eagerly (#577)
* Raise errors for missing structure handlers more eagerly
1 parent 9d406c8 commit e2d8f3e

File tree

6 files changed

+64
-4
lines changed

6 files changed

+64
-4
lines changed

HISTORY.md

+10
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,21 @@ The third number is for emergencies when we need to start branches for older rel
99

1010
Our backwards-compatibility policy can be found [here](https://github.com/python-attrs/cattrs/blob/main/.github/SECURITY.md).
1111

12+
## 24.2.0 (UNRELEASED)
13+
14+
- **Potentially breaking**: The converters raise {class}`StructureHandlerNotFoundError` more eagerly (on hook creation, instead of on hook use).
15+
This helps surfacing problems with missing hooks sooner.
16+
See [Migrations](https://catt.rs/latest/migrations.html#the-default-structure-hook-fallback-factory) for steps to restore legacy behavior.
17+
([#577](https://github.com/python-attrs/cattrs/pull/577))
18+
- Add a [Migrations](https://catt.rs/latest/migrations.html) page, with instructions on migrating changed behavior for each version.
19+
([#577](https://github.com/python-attrs/cattrs/pull/577))
20+
1221
## 24.1.1 (2024-09-11)
1322

1423
- Fix {meth}`BaseConverter.register_structure_hook_factory` and {meth}`BaseConverter.register_unstructure_hook_factory` type hints.
1524
([#578](https://github.com/python-attrs/cattrs/issues/578) [#579](https://github.com/python-attrs/cattrs/pull/579))
1625

26+
1727
## 24.1.0 (2024-08-28)
1828

1929
- **Potentially breaking**: Unstructuring hooks for `typing.Any` are consistent now: values are unstructured using their runtime type.

docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ validation
4747
preconf
4848
unions
4949
usage
50+
migrations
5051
indepth
5152
```
5253

docs/migrations.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Migrations
2+
3+
_cattrs_ sometimes changes in backwards-incompatible ways.
4+
This page contains guidance for changes and workarounds for restoring legacy behavior.
5+
6+
## 24.2.0
7+
8+
### The default structure hook fallback factory
9+
10+
The default structure hook fallback factory was changed to more eagerly raise errors for missing hooks.
11+
12+
The old behavior can be restored by explicitly passing in the old hook fallback factory when instantiating the converter.
13+
14+
15+
```python
16+
>>> from cattrs.fns import raise_error
17+
18+
>>> c = Converter(structure_fallback_factory=lambda _: raise_error)
19+
# Or
20+
>>> c = BaseConverter(structure_fallback_factory=lambda _: raise_error)
21+
```

src/cattrs/converters.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ def __init__(
179179
prefer_attrib_converters: bool = False,
180180
detailed_validation: bool = True,
181181
unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity,
182-
structure_fallback_factory: HookFactory[StructureHook] = lambda _: raise_error,
182+
structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
183+
None, t
184+
),
183185
) -> None:
184186
"""
185187
:param detailed_validation: Whether to use a slightly slower mode for detailed
@@ -191,6 +193,9 @@ def __init__(
191193
192194
.. versionadded:: 23.2.0 *unstructure_fallback_factory*
193195
.. versionadded:: 23.2.0 *structure_fallback_factory*
196+
.. versionchanged:: 24.2.0
197+
The default `structure_fallback_factory` now raises errors for missing handlers
198+
more eagerly, surfacing problems earlier.
194199
"""
195200
unstruct_strat = UnstructureStrategy(unstruct_strat)
196201
self._prefer_attrib_converters = prefer_attrib_converters
@@ -1041,7 +1046,9 @@ def __init__(
10411046
prefer_attrib_converters: bool = False,
10421047
detailed_validation: bool = True,
10431048
unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity,
1044-
structure_fallback_factory: HookFactory[StructureHook] = lambda _: raise_error,
1049+
structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
1050+
None, t
1051+
),
10451052
):
10461053
"""
10471054
:param detailed_validation: Whether to use a slightly slower mode for detailed
@@ -1053,6 +1060,9 @@ def __init__(
10531060
10541061
.. versionadded:: 23.2.0 *unstructure_fallback_factory*
10551062
.. versionadded:: 23.2.0 *structure_fallback_factory*
1063+
.. versionchanged:: 24.2.0
1064+
The default `structure_fallback_factory` now raises errors for missing handlers
1065+
more eagerly, surfacing problems earlier.
10561066
"""
10571067
super().__init__(
10581068
dict_factory=dict_factory,

src/cattrs/gen/_shared.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .._compat import is_bare_final
88
from ..dispatch import StructureHook
9+
from ..errors import StructureHandlerNotFoundError
910
from ..fns import raise_error
1011

1112
if TYPE_CHECKING:
@@ -27,9 +28,14 @@ def find_structure_handler(
2728
elif (
2829
a.converter is not None and not prefer_attrs_converters and type is not None
2930
):
30-
handler = c.get_structure_hook(type, cache_result=False)
31-
if handler == raise_error:
31+
try:
32+
handler = c.get_structure_hook(type, cache_result=False)
33+
except StructureHandlerNotFoundError:
3234
handler = None
35+
else:
36+
# The legacy way, should still work.
37+
if handler == raise_error:
38+
handler = None
3339
elif type is not None:
3440
if (
3541
is_bare_final(type)

tests/test_converter.py

+12
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,18 @@ class Outer:
721721
assert structured == Outer(Inner(2), [Inner(2)], Inner(2))
722722

723723

724+
def test_default_structure_fallback(converter_cls: Type[BaseConverter]):
725+
"""The default structure fallback hook factory eagerly errors."""
726+
727+
class Test:
728+
"""Unsupported by default."""
729+
730+
c = converter_cls()
731+
732+
with pytest.raises(StructureHandlerNotFoundError):
733+
c.get_structure_hook(Test)
734+
735+
724736
def test_unstructure_fallbacks(converter_cls: Type[BaseConverter]):
725737
"""Unstructure fallback factories work."""
726738

0 commit comments

Comments
 (0)