python-attrs / python-attrs/cattrs
`make_unstructure_dict_unstructure_fn` does not honor `use_class_methods`
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 159
- Avg merge
- 12h 21m
- Merged PRs (30d)
- 6
Description
From comments in #558. Custom metamethods and make_unstructure_dict_unstructure_fn cannot be used together:
import cattrs
from cattrs.strategies import use_class_methods
import attrs
# Use a set of metamethods
@attrs.define
class Thing:
a: int
def _unstructure(self):
return {"a" : str(self.a)}
@classmethod
def _structure(cls, val):
return cls(a=int(val["a"]))
conv = cattrs.Converter()
use_class_methods(
conv,
"_structure",
"_unstructure",
)
assert conv.unstructure(Thing(1)) == {
"a" : "1",
}
def tag_attrs_hook_factory(cl):
base_hook = cattrs.gen.make_dict_unstructure_fn(cl, conv)
def hook(instance):
unstruct = base_hook(instance)
unstruct["_type"] = type(instance).__name__
return unstruct
return hook
tagging_conv_a = cattrs.Converter()
tagging_conv_a.register_unstructure_hook_factory(attrs.has, tag_attrs_hook_factory)
use_class_methods(
tagging_conv_a,
"_structure",
"_unstructure",
)
# THIS IS WRONG. Does not have the tag
assert tagging_conv_a.unstructure(Thing(1)) == {
"a" : "1",
}
tagging_conv_b = cattrs.Converter()
use_class_methods(
tagging_conv_b,
"_structure",
"_unstructure",
)
tagging_conv_b.register_unstructure_hook_factory(attrs.has, tag_attrs_hook_factory)
# THIS IS WRONG. Does not convert the sub value correctly via the metamethod
assert tagging_conv_b.unstructure(Thing(1)) == {
"_type" : "Thing",
"a" : 1,
}
What I tried initially was to just inject the converter so it dispatches to the metamethods properly. But this causes infinite recursion, e.g.:
def tag_attrs_hook_factory(cl):
converter = ... # via closure
def hook(instance, converter):
unstruct = converter.unstructure(instance)
unstruct["_type"] = type(instance).__name__
return unstruct
return hook
I think that make_dict_unstructure_fn and friends should honor the metamethods for the passed in converter and break recursion. I'm not sure if this makes sense though... In any case this is a difficult case to handle generally.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the two tagging examples and inspect make_dict_unstructure_fn, make_unstructure_dict_unstructure_fn, and use_class_methods to trace hook dispatch and recursion. Done means the tagging hook preserves its _type field while nested values still use the configured metamethods, without infinite recursion.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100