-
-
Notifications
You must be signed in to change notification settings - Fork 128
An initial implementation of unstructuring and structuring enums with complex values #702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,7 +53,6 @@ When unstructuring, these types are passed through unchanged. | |
| ### Enums | ||
|
|
||
| Enums are structured by their values, and unstructured to their values. | ||
| This works even for complex values, like tuples. | ||
|
|
||
| ```{doctest} | ||
|
|
||
|
|
@@ -70,6 +69,30 @@ This works even for complex values, like tuples. | |
| 'siamese' | ||
| ``` | ||
|
|
||
| Enum structuring and unstructuring even works for complex values, like tuples, but if you have anything but simple literal types in those tuples (`str`, `bool`, `int`, `float`) you should consider defining the Enum value's type via the `_value_` attribute's type hint so that cattrs can properly structure it. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's link https://typing.python.org/en/latest/spec/enums.html#member-values in here somewhere. |
||
|
|
||
| ```{doctest} | ||
|
|
||
| >>> @unique | ||
| ... class VideoStandard(Enum): | ||
| ... NTSC = "ntsc" | ||
| ... PAL = "pal" | ||
|
|
||
| >>> @unique | ||
| ... class Resolution(Enum): | ||
| ... _value_: tuple[VideoStandard, int] | ||
| ... NTSC_0 = (VideoStandard.NTSC, 0) | ||
| ... PAL_0 = (VideoStandard.PAL, 0) | ||
| ... NTSC_1 = (VideoStandard.NTSC, 1) | ||
| ... PAL_1 = (VideoStandard.PAL, 1) | ||
|
|
||
| >>> cattrs.structure(("ntsc", 1), Resolution) | ||
| <Resolution.NTSC_1: (<VideoStandard.NTSC: 'ntsc'>, 1)> | ||
|
|
||
| >>> cattrs.unstructure(Resolution.PAL_0) | ||
| ['pal', 0] | ||
| ``` | ||
|
|
||
| Again, in case of errors, the expected exceptions are raised. | ||
|
|
||
| ### `pathlib.Path` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| from inspect import Signature | ||
| from inspect import signature as inspect_signature | ||
| from pathlib import Path | ||
| from typing import Any, Optional, Tuple, TypeVar, overload | ||
| from typing import Any, Optional, Tuple, TypeVar, get_type_hints, overload | ||
|
|
||
| from attrs import Attribute, resolve_types | ||
| from attrs import has as attrs_has | ||
|
|
@@ -308,7 +308,7 @@ def __init__( | |
| (bytes, self._structure_call), | ||
| (int, self._structure_call), | ||
| (float, self._structure_call), | ||
| (Enum, self._structure_call), | ||
| (Enum, self._structure_enum), | ||
| (Path, self._structure_call), | ||
| ] | ||
| ) | ||
|
|
@@ -631,8 +631,8 @@ def unstructure_attrs_astuple(self, obj: Any) -> tuple[Any, ...]: | |
| return tuple(res) | ||
|
|
||
| def _unstructure_enum(self, obj: Enum) -> Any: | ||
| """Convert an enum to its value.""" | ||
| return obj.value | ||
| """Convert an enum to its unstructured value.""" | ||
| return self._unstructure_func.dispatch(obj.value.__class__)(obj.value) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I'm a little concerned about performance and backwards comp. Can we do a deep unstructure only if the enum has a |
||
|
|
||
| def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]: | ||
| """Convert a sequence to primitive equivalents.""" | ||
|
|
@@ -713,6 +713,16 @@ def _structure_simple_literal(val, type): | |
| raise Exception(f"{val} not in literal {type}") | ||
| return val | ||
|
|
||
| def _structure_enum(self, val: Any, cl: type[Enum]) -> Enum: | ||
| """Structure ``val`` if possible and return the enum it corresponds to. | ||
|
|
||
| Uses type hints for the "_value_" attribute if they exist to structure | ||
| the enum values before returning the result.""" | ||
| hints = get_type_hints(cl) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I want to avoid doing too much extra work in the majority of cases. |
||
| if "_value_" in hints: | ||
| val = self.structure(val, hints["_value_"]) | ||
| return cl(val) | ||
|
|
||
| @staticmethod | ||
| def _structure_enum_literal(val, type): | ||
| vals = {(x.value if isinstance(x, Enum) else x): x for x in type.__args__} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rephrase this a little bit, maybe something like:
Take account of the optionalvaluetype hint when handling enums, if present.