-
-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: strategy for using class methods
- Loading branch information
Showing
4 changed files
with
147 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
"""High level strategies for converters.""" | ||
from ._class_methods import use_class_methods | ||
from ._subclasses import include_subclasses | ||
from ._unions import configure_tagged_union | ||
|
||
__all__ = ["configure_tagged_union", "include_subclasses"] | ||
__all__ = ["configure_tagged_union", "include_subclasses", "use_class_methods"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
"""Strategy for using class-specific (un)structuring methods.""" | ||
from typing import Optional | ||
|
||
from cattrs import BaseConverter | ||
|
||
|
||
def use_class_methods( | ||
converter: BaseConverter, | ||
structure_method_name: Optional[str] = None, | ||
unstructure_method_name: Optional[str] = None, | ||
) -> None: | ||
""" | ||
Configure the converter such that dedicated methods are used for (un)structuring | ||
the instance of a class if such methods are available. The default (un)structuring | ||
will be applied if such an (un)structuring methods cannot be found. | ||
:param converter: The `Converter` on which this strategy is applied. You can use | ||
:class:`cattrs.BaseConverter` or any other derived class. | ||
:param structure_method_name: Optional string with the name of the class method | ||
which should be used for structuring. If not provided, no class method will be | ||
used for structuring. | ||
:param unstructure_method_name: Optional string with the name of the class method | ||
which should be used for unstructuring. If not provided, no class method will | ||
be used for unstructuring. | ||
.. versionadded:: 23.2.0 | ||
""" | ||
if structure_method_name: | ||
converter.register_structure_hook_func( | ||
lambda t: hasattr(t, structure_method_name), | ||
lambda v, t: getattr(t, structure_method_name)(v), | ||
) | ||
|
||
if unstructure_method_name: | ||
converter.register_unstructure_hook_func( | ||
lambda t: hasattr(t, unstructure_method_name), | ||
lambda v: getattr(v, unstructure_method_name)(), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import itertools | ||
|
||
import pytest | ||
from attrs import define | ||
|
||
from cattrs import BaseConverter | ||
from cattrs.strategies import use_class_methods | ||
|
||
|
||
@define | ||
class Base: | ||
a: int | ||
|
||
|
||
class Structure(Base): | ||
@classmethod | ||
def _structure(cls, data: dict): | ||
return cls(data["b"]) # expecting "b", not "a" | ||
|
||
|
||
class Unstructure(Base): | ||
def _unstructure(self): | ||
return {"c": self.a} # unstructuring as "c", not "a" | ||
|
||
|
||
class Both(Structure, Unstructure): | ||
pass | ||
|
||
|
||
@pytest.fixture | ||
def get_converter(converter: BaseConverter): | ||
def aux(structure: str, unstructure: str) -> BaseConverter: | ||
use_class_methods(converter, structure, unstructure) | ||
return converter | ||
|
||
return aux | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"cls,structure_method,unstructure_method", | ||
itertools.product( | ||
[Structure, Unstructure, Both], | ||
["_structure", "_undefined", None], | ||
["_unstructure", "_undefined", None], | ||
), | ||
) | ||
def test(get_converter, structure_method, unstructure_method, cls) -> None: | ||
converter = get_converter(structure_method, unstructure_method) | ||
|
||
assert converter.structure( | ||
{ | ||
"b" | ||
if structure_method == "_structure" and hasattr(cls, "_structure") | ||
else "a": 42 | ||
}, | ||
cls, | ||
) == cls(42) | ||
|
||
assert converter.unstructure(cls(42)) == { | ||
"c" | ||
if unstructure_method == "_unstructure" and hasattr(cls, "_unstructure") | ||
else "a": 42 | ||
} |