Skip to content

Commit

Permalink
Introduce SimpleStructureHook
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Nov 7, 2024
1 parent ea30af6 commit b414bf5
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 38 deletions.
5 changes: 3 additions & 2 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
- Some `defaultdicts` are now [supported by default](https://catt.rs/en/latest/defaulthooks.html#defaultdicts), and
{func}`cattrs.cols.is_defaultdict`{func} and `cattrs.cols.defaultdict_structure_factory` are exposed through {mod}`cattrs.cols`.
([#519](https://github.com/python-attrs/cattrs/issues/519) [#588](https://github.com/python-attrs/cattrs/pull/588))
- Replace `cattrs.gen.MappingStructureFn` with `cattrs.SimpleStructureHook[In, T]`.
- Python 3.13 is now supported.
([#543](https://github.com/python-attrs/cattrs/pull/543) [#547](https://github.com/python-attrs/cattrs/issues/547))
- Python 3.8 is no longer supported, as it is end-of-life. Use previous versions on this Python version.
([#591](https://github.com/python-attrs/cattrs/pull/591))
- Change type of Converter.__init__.unstruct_collection_overrides from Callable to Mapping[type, UnstructureHook]
([#594](https://github.com/python-attrs/cattrs/pull/594).
- Change type of `Converter.__init__.unstruct_collection_overrides` from `Callable` to `Mapping[type, UnstructureHook]`
([#594](https://github.com/python-attrs/cattrs/pull/594)).

## 24.1.2 (2024-09-22)

Expand Down
28 changes: 15 additions & 13 deletions src/cattrs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,34 @@
StructureHandlerNotFoundError,
)
from .gen import override
from .types import SimpleStructureHook
from .v import transform_error

__all__ = [
"structure",
"unstructure",
"get_structure_hook",
"get_unstructure_hook",
"register_structure_hook_func",
"register_structure_hook",
"register_unstructure_hook_func",
"register_unstructure_hook",
"structure_attrs_fromdict",
"structure_attrs_fromtuple",
"global_converter",
"BaseConverter",
"Converter",
"AttributeValidationNote",
"BaseConverter",
"BaseValidationError",
"ClassValidationError",
"Converter",
"ForbiddenExtraKeysError",
"GenConverter",
"get_structure_hook",
"get_unstructure_hook",
"global_converter",
"IterableValidationError",
"IterableValidationNote",
"override",
"register_structure_hook_func",
"register_structure_hook",
"register_unstructure_hook_func",
"register_unstructure_hook",
"SimpleStructureHook",
"structure_attrs_fromdict",
"structure_attrs_fromtuple",
"structure",
"StructureHandlerNotFoundError",
"transform_error",
"unstructure",
"UnstructureStrategy",
]

Expand Down
59 changes: 40 additions & 19 deletions src/cattrs/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@
DictStructureFn,
HeteroTupleUnstructureFn,
IterableUnstructureFn,
MappingStructureFn,
MappingUnstructureFn,
make_dict_structure_fn,
make_dict_unstructure_fn,
make_hetero_tuple_unstructure_fn,
)
from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn
from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn
from .types import SimpleStructureHook

__all__ = ["UnstructureStrategy", "BaseConverter", "Converter", "GenConverter"]

Expand Down Expand Up @@ -137,6 +137,7 @@

UnstructureHookT = TypeVar("UnstructureHookT", bound=UnstructureHook)
StructureHookT = TypeVar("StructureHookT", bound=StructureHook)
CounterT = TypeVar("CounterT", bound=Counter)


class UnstructureStrategy(Enum):
Expand Down Expand Up @@ -318,10 +319,12 @@ def unstruct_strat(self) -> UnstructureStrategy:
)

@overload
def register_unstructure_hook(self, cls: UnstructureHookT) -> UnstructureHookT: ...
def register_unstructure_hook(self, cls: UnstructureHookT) -> UnstructureHookT:
...

@overload
def register_unstructure_hook(self, cls: Any, func: UnstructureHook) -> None: ...
def register_unstructure_hook(self, cls: Any, func: UnstructureHook) -> None:
...

def register_unstructure_hook(
self, cls: Any = None, func: UnstructureHook | None = None
Expand Down Expand Up @@ -369,19 +372,22 @@ def register_unstructure_hook_func(
@overload
def register_unstructure_hook_factory(
self, predicate: Predicate
) -> Callable[[AnyUnstructureHookFactoryBase], AnyUnstructureHookFactoryBase]: ...
) -> Callable[[AnyUnstructureHookFactoryBase], AnyUnstructureHookFactoryBase]:
...

@overload
def register_unstructure_hook_factory(
self, predicate: Predicate, factory: UnstructureHookFactory
) -> UnstructureHookFactory: ...
) -> UnstructureHookFactory:
...

@overload
def register_unstructure_hook_factory(
self,
predicate: Predicate,
factory: ExtendedUnstructureHookFactory[BaseConverter],
) -> ExtendedUnstructureHookFactory[BaseConverter]: ...
) -> ExtendedUnstructureHookFactory[BaseConverter]:
...

def register_unstructure_hook_factory(self, predicate, factory=None):
"""
Expand Down Expand Up @@ -450,10 +456,12 @@ def get_unstructure_hook(
)

@overload
def register_structure_hook(self, cl: StructureHookT) -> StructureHookT: ...
def register_structure_hook(self, cl: StructureHookT) -> StructureHookT:
...

@overload
def register_structure_hook(self, cl: Any, func: StructureHook) -> None: ...
def register_structure_hook(self, cl: Any, func: StructureHook) -> None:
...

def register_structure_hook(
self, cl: Any, func: StructureHook | None = None
Expand Down Expand Up @@ -504,17 +512,20 @@ def register_structure_hook_func(
@overload
def register_structure_hook_factory(
self, predicate: Predicate
) -> Callable[[AnyStructureHookFactoryBase], AnyStructureHookFactoryBase]: ...
) -> Callable[[AnyStructureHookFactoryBase], AnyStructureHookFactoryBase]:
...

@overload
def register_structure_hook_factory(
self, predicate: Predicate, factory: StructureHookFactory
) -> StructureHookFactory: ...
) -> StructureHookFactory:
...

@overload
def register_structure_hook_factory(
self, predicate: Predicate, factory: ExtendedStructureHookFactory[BaseConverter]
) -> ExtendedStructureHookFactory[BaseConverter]: ...
) -> ExtendedStructureHookFactory[BaseConverter]:
...

def register_structure_hook_factory(self, predicate, factory=None):
"""
Expand Down Expand Up @@ -1187,17 +1198,20 @@ def __init__(
@overload
def register_unstructure_hook_factory(
self, predicate: Predicate
) -> Callable[[AnyUnstructureHookFactory], AnyUnstructureHookFactory]: ...
) -> Callable[[AnyUnstructureHookFactory], AnyUnstructureHookFactory]:
...

@overload
def register_unstructure_hook_factory(
self, predicate: Predicate, factory: UnstructureHookFactory
) -> UnstructureHookFactory: ...
) -> UnstructureHookFactory:
...

@overload
def register_unstructure_hook_factory(
self, predicate: Predicate, factory: ExtendedUnstructureHookFactory[Converter]
) -> ExtendedUnstructureHookFactory[Converter]: ...
) -> ExtendedUnstructureHookFactory[Converter]:
...

def register_unstructure_hook_factory(self, predicate, factory=None):
# This dummy wrapper is required due to how `@overload` works.
Expand All @@ -1206,17 +1220,20 @@ def register_unstructure_hook_factory(self, predicate, factory=None):
@overload
def register_structure_hook_factory(
self, predicate: Predicate
) -> Callable[[AnyStructureHookFactory], AnyStructureHookFactory]: ...
) -> Callable[[AnyStructureHookFactory], AnyStructureHookFactory]:
...

@overload
def register_structure_hook_factory(
self, predicate: Predicate, factory: StructureHookFactory
) -> StructureHookFactory: ...
) -> StructureHookFactory:
...

@overload
def register_structure_hook_factory(
self, predicate: Predicate, factory: ExtendedStructureHookFactory[Converter]
) -> ExtendedStructureHookFactory[Converter]: ...
) -> ExtendedStructureHookFactory[Converter]:
...

def register_structure_hook_factory(self, predicate, factory=None):
# This dummy wrapper is required due to how `@overload` works.
Expand Down Expand Up @@ -1342,7 +1359,9 @@ def gen_unstructure_mapping(
self._unstructure_func.register_cls_list([(cl, h)], direct=True)
return h

def gen_structure_counter(self, cl: Any) -> MappingStructureFn[T]:
def gen_structure_counter(
self, cl: type[CounterT]
) -> SimpleStructureHook[Mapping[Any, Any], CounterT]:
h = mapping_structure_factory(
cl,
self,
Expand All @@ -1353,7 +1372,9 @@ def gen_structure_counter(self, cl: Any) -> MappingStructureFn[T]:
self._structure_func.register_cls_list([(cl, h)], direct=True)
return h

def gen_structure_mapping(self, cl: Any) -> MappingStructureFn[T]:
def gen_structure_mapping(
self, cl: Any
) -> SimpleStructureHook[Mapping[Any, Any], Any]:
structure_to = get_origin(cl) or cl
if structure_to in (
MutableMapping,
Expand Down
8 changes: 4 additions & 4 deletions src/cattrs/gen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
StructureHandlerNotFoundError,
)
from ..fns import identity
from ..types import SimpleStructureHook
from ._consts import AttributeOverride, already_generating, neutral
from ._generics import generate_mapping
from ._lc import generate_unique_filename
Expand Down Expand Up @@ -892,8 +893,6 @@ def mapping_unstructure_factory(

make_mapping_unstructure_fn: Final = mapping_unstructure_factory

MappingStructureFn = Callable[[Mapping[Any, Any], Any], T]


# This factory is here for backwards compatibility and circular imports.
def mapping_structure_factory(
Expand All @@ -903,7 +902,7 @@ def mapping_structure_factory(
key_type=NOTHING,
val_type=NOTHING,
detailed_validation: bool | Literal["from_converter"] = "from_converter",
) -> MappingStructureFn[T]:
) -> SimpleStructureHook[Mapping[Any, Any], T]:
"""Generate a specialized structure function for a mapping."""
fn_name = "structure_mapping"

Expand Down Expand Up @@ -1010,7 +1009,8 @@ def mapping_structure_factory(
for k, v in internal_arg_parts.items():
globs[k] = v

def_line = f"def {fn_name}(mapping, _{internal_arg_line}):"
globs["cl"] = cl
def_line = f"def {fn_name}(mapping, cl=cl{internal_arg_line}):"
total_lines = [def_line, *lines, " return res"]
script = "\n".join(total_lines)

Expand Down
13 changes: 13 additions & 0 deletions src/cattrs/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import Protocol, TypeVar

In = TypeVar("In")
T = TypeVar("T")

__all__ = ["SimpleStructureHook"]


class SimpleStructureHook(Protocol[In, T]):
"""A structure hook with an optional (ignored) second argument."""

def __call__(self, _: In, /, cl=...) -> T:
...

0 comments on commit b414bf5

Please sign in to comment.