From 2e74e358df5d3fe0597fa496235b08a3ac85848a Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sat, 31 Aug 2024 18:38:49 +0200 Subject: [PATCH 1/3] Raise errors for missing structure handlers more eagerly --- HISTORY.md | 7 +++++++ docs/index.md | 1 + docs/migrations.md | 21 +++++++++++++++++++++ src/cattrs/converters.py | 14 ++++++++++++-- src/cattrs/gen/_shared.py | 10 ++++++++-- tests/test_converter.py | 12 ++++++++++++ 6 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 docs/migrations.md diff --git a/HISTORY.md b/HISTORY.md index 78e261df..9cbcc017 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,13 @@ The third number is for emergencies when we need to start branches for older rel Our backwards-compatibility policy can be found [here](https://github.com/python-attrs/cattrs/blob/main/.github/SECURITY.md). +## 24.2.0 (UNRELEASED) + +- **Potentially breaking**: The converters raise `StructureHandlerNotFoundError` more eagerly (on hook creation, instead of on hook use). + This helps surfacing problems with missing hooks sooner. + See [migrations](https://catt.rs/latest/migrations.html) for steps to restore legacy behavior. +- Add a migrations page, with instructions on migrating changed behavior for each version. + ## 24.1.0 (2024-08-28) - **Potentially breaking**: Unstructuring hooks for `typing.Any` are consistent now: values are unstructured using their runtime type. diff --git a/docs/index.md b/docs/index.md index d8c2505b..e41634c7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -47,6 +47,7 @@ validation preconf unions usage +migrations indepth ``` diff --git a/docs/migrations.md b/docs/migrations.md new file mode 100644 index 00000000..aabe9bde --- /dev/null +++ b/docs/migrations.md @@ -0,0 +1,21 @@ +# Migrations + +_cattrs_ sometimes changes in backwards-incompatible ways. +This page contains guidance for changes and workarounds for restoring legacy behavior. + +## 24.2.0 + +### The default structure hook fallback factory + +The default structure hook fallback factory was changed to more eagerly raise errors for missing hooks. + +The old behavior can be restored by explicitly passing in the old hook fallback factory when instantiating the converter. + + +```python +>>> from cattrs.fns import raise_error + +>>> c = Converter(structure_fallback_factory=lambda _: raise_error) +# Or +>>> c = BaseConverter(structure_fallback_factory=lambda _: raise_error) +``` diff --git a/src/cattrs/converters.py b/src/cattrs/converters.py index 8d283b30..2d7184b3 100644 --- a/src/cattrs/converters.py +++ b/src/cattrs/converters.py @@ -161,7 +161,9 @@ def __init__( prefer_attrib_converters: bool = False, detailed_validation: bool = True, unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, - structure_fallback_factory: HookFactory[StructureHook] = lambda _: raise_error, + structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error( + None, t + ), ) -> None: """ :param detailed_validation: Whether to use a slightly slower mode for detailed @@ -173,6 +175,9 @@ def __init__( .. versionadded:: 23.2.0 *unstructure_fallback_factory* .. versionadded:: 23.2.0 *structure_fallback_factory* + .. versionchanged:: 24.2.0 + The default `structure_fallback_factory` now raises errors for missing handlers + more eagerly, surfacing problems earlier. """ unstruct_strat = UnstructureStrategy(unstruct_strat) self._prefer_attrib_converters = prefer_attrib_converters @@ -1031,7 +1036,9 @@ def __init__( prefer_attrib_converters: bool = False, detailed_validation: bool = True, unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, - structure_fallback_factory: HookFactory[StructureHook] = lambda _: raise_error, + structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error( + None, t + ), ): """ :param detailed_validation: Whether to use a slightly slower mode for detailed @@ -1043,6 +1050,9 @@ def __init__( .. versionadded:: 23.2.0 *unstructure_fallback_factory* .. versionadded:: 23.2.0 *structure_fallback_factory* + .. versionchanged:: 24.2.0 + The default `structure_fallback_factory` now raises errors for missing handlers + more eagerly, surfacing problems earlier. """ super().__init__( dict_factory=dict_factory, diff --git a/src/cattrs/gen/_shared.py b/src/cattrs/gen/_shared.py index 4e631437..904c7744 100644 --- a/src/cattrs/gen/_shared.py +++ b/src/cattrs/gen/_shared.py @@ -6,6 +6,7 @@ from .._compat import is_bare_final from ..dispatch import StructureHook +from ..errors import StructureHandlerNotFoundError from ..fns import raise_error if TYPE_CHECKING: @@ -27,9 +28,14 @@ def find_structure_handler( elif ( a.converter is not None and not prefer_attrs_converters and type is not None ): - handler = c.get_structure_hook(type, cache_result=False) - if handler == raise_error: + try: + handler = c.get_structure_hook(type, cache_result=False) + except StructureHandlerNotFoundError: handler = None + else: + # The legacy way, should still work. + if handler == raise_error: + handler = None elif type is not None: if ( is_bare_final(type) diff --git a/tests/test_converter.py b/tests/test_converter.py index 47e70edc..92c9bbb3 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -721,6 +721,18 @@ class Outer: assert structured == Outer(Inner(2), [Inner(2)], Inner(2)) +def test_default_structure_fallback(converter_cls: Type[BaseConverter]): + """The default structure fallback hook factory eagerly errors.""" + + class Test: + """Unsupported by default.""" + + c = converter_cls() + + with pytest.raises(StructureHandlerNotFoundError): + c.get_structure_hook(Test) + + def test_unstructure_fallbacks(converter_cls: Type[BaseConverter]): """Unstructure fallback factories work.""" From 238ebef28f8d73c79ad8b2869cf752863653dc02 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sat, 31 Aug 2024 18:40:31 +0200 Subject: [PATCH 2/3] Fill in PR number --- HISTORY.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 9cbcc017..dc96d8fb 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -13,8 +13,10 @@ Our backwards-compatibility policy can be found [here](https://github.com/python - **Potentially breaking**: The converters raise `StructureHandlerNotFoundError` more eagerly (on hook creation, instead of on hook use). This helps surfacing problems with missing hooks sooner. - See [migrations](https://catt.rs/latest/migrations.html) for steps to restore legacy behavior. -- Add a migrations page, with instructions on migrating changed behavior for each version. + See [Migrations](https://catt.rs/latest/migrations.html) for steps to restore legacy behavior. + ([#577](https://github.com/python-attrs/cattrs/pull/577)) +- Add a [Migrations] page, with instructions on migrating changed behavior for each version. + ([#577](https://github.com/python-attrs/cattrs/pull/577)) ## 24.1.0 (2024-08-28) From aeae36e528b8ed3201604fa9b803f8c28e4b159e Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sat, 31 Aug 2024 18:47:04 +0200 Subject: [PATCH 3/3] Tweak HISTORY --- HISTORY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index dc96d8fb..4d818364 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,11 +11,11 @@ Our backwards-compatibility policy can be found [here](https://github.com/python ## 24.2.0 (UNRELEASED) -- **Potentially breaking**: The converters raise `StructureHandlerNotFoundError` more eagerly (on hook creation, instead of on hook use). +- **Potentially breaking**: The converters raise {class}`StructureHandlerNotFoundError` more eagerly (on hook creation, instead of on hook use). This helps surfacing problems with missing hooks sooner. - See [Migrations](https://catt.rs/latest/migrations.html) for steps to restore legacy behavior. + See [Migrations](https://catt.rs/latest/migrations.html#the-default-structure-hook-fallback-factory) for steps to restore legacy behavior. ([#577](https://github.com/python-attrs/cattrs/pull/577)) -- Add a [Migrations] page, with instructions on migrating changed behavior for each version. +- Add a [Migrations](https://catt.rs/latest/migrations.html) page, with instructions on migrating changed behavior for each version. ([#577](https://github.com/python-attrs/cattrs/pull/577)) ## 24.1.0 (2024-08-28)