diff --git a/HISTORY.md b/HISTORY.md index dcb87542..b999f978 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -30,6 +30,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python ([#748](https://github.com/python-attrs/cattrs/pull/748)) - The [union passthrough strategy](https://catt.rs/en/stable/strategies.html#union-passthrough) now supports PEP 695 type aliases as union members. ([#753](https://github.com/python-attrs/cattrs/pull/753)) +- {meth}`BaseConverter.register_structure_hook_factory` and {meth}`BaseConverter.register_unstructure_hook_factory` now properly return the factory when used as decorators. + ([#724](https://github.com/python-attrs/cattrs/pull/724)) ## 26.1.0 (2026-02-18) diff --git a/src/cattrs/converters.py b/src/cattrs/converters.py index fd645cb4..0e6bed07 100644 --- a/src/cattrs/converters.py +++ b/src/cattrs/converters.py @@ -443,6 +443,7 @@ def decorator(factory): self._unstructure_func.register_func_list( [(predicate, factory, True)] ) + return factory return decorator @@ -581,6 +582,7 @@ def decorator(factory): self._structure_func.register_func_list( [(predicate, factory, True)] ) + return factory return decorator self._structure_func.register_func_list( diff --git a/tests/test_typing_structure.md b/tests/test_typing_structure.md index e0b75b73..50204856 100644 --- a/tests/test_typing_structure.md +++ b/tests/test_typing_structure.md @@ -102,3 +102,33 @@ converter = Converter() value: int = converter.structure("1", int) bad: str = converter.structure("1", int) # mypy-error: [assignment] ``` + +## Hook factory decorators preserve factory types + +```python +from collections.abc import Callable +from typing import Any + +from cattrs import Converter + + +converter = Converter() + + +def accepts_int(cl: Any) -> bool: + return cl is int + + +@converter.register_unstructure_hook_factory(accepts_int) +def unstructure_factory(cl: type[int]) -> Callable[[int], str]: + return str + + +@converter.register_structure_hook_factory(accepts_int) +def structure_factory(cl: type[int]) -> Callable[[str, type[int]], int]: + return lambda value, _: int(value) + + +reveal_type(unstructure_factory) # revealed: def (cl: type[int]) -> def (int) -> str +reveal_type(structure_factory) # revealed: def (cl: type[int]) -> def (str, type[int]) -> int +```