cattrs
cattrs copied to clipboard
Using make_dict_structure_fn seems to break my other structure hooks
- cattrs version: 1.10.0
- Python version: 3.9.7
- Operating System: MacOS 11.4
Description
I'm registering two structure hooks which should help me transform my object into a dict and vice versa. One is renaming my field (from id to _id), while the other is transforming my pendulum DateTime into an isostring. For some reason the DateTime unstructure hook is getting lost if I define it after the Class structure hook. If I start with it, everything works.
What I Did
import cattr
import pendulum
from attr import define, field
from cattr.gen import make_dict_unstructure_fn, override, make_dict_structure_fn
from pendulum import DateTime
@define
class Car:
id: int
manufactured: DateTime
volvo = Car(id=5, manufactured=DateTime.now())
converter = cattr.Converter()
converter.register_unstructure_hook(Car, make_dict_unstructure_fn(Car, converter, id=override(rename="_id")))
converter.register_unstructure_hook(DateTime, lambda dt: dt.to_iso8601_string())
converter.register_structure_hook(Car, make_dict_structure_fn(Car, converter, id=override(rename="_id")))
converter.register_structure_hook(DateTime, lambda dt, _: pendulum.parse(dt))
print(volvo)
unstructured = converter.unstructure(volvo)
print(unstructured)
structured_again = converter.structure(unstructured, Car)
print(structured_again)
The above results in following error (note the "manufactured" field also did not get unstructured the way it should). It complains I should register a structure hook, which I actually did...:
Car(id=5, manufactured=DateTime(2022, 4, 3, 16, 22, 40, 51844, tzinfo=Timezone('Europe/Brussels')))
{'_id': 5, 'manufactured': DateTime(2022, 4, 3, 16, 22, 40, 51844, tzinfo=Timezone('Europe/Brussels'))}
Traceback (most recent call last):
...
cattr.errors.StructureHandlerNotFoundError: Unsupported type: <class 'pendulum.datetime.DateTime'>. Register a structure hook for it.
Whereas if I switch the order of the (un)structure hooks like below, everything just works:
converter.register_unstructure_hook(DateTime, lambda dt: dt.to_iso8601_string())
converter.register_unstructure_hook(Car, make_dict_unstructure_fn(Car, converter, id=override(rename="_id")))
converter.register_structure_hook(DateTime, lambda dt, _: pendulum.parse(dt))
converter.register_structure_hook(Car, make_dict_structure_fn(Car, converter, id=override(rename="_id")))
Results in ("manufactured" got unstructured correctly in an iso8601 timestamp and id got renamed into _id during unstructure and vice versa):
Car(id=5, manufactured=DateTime(2022, 4, 3, 16, 25, 12, 840444, tzinfo=Timezone('Europe/Brussels')))
{'_id': 5, 'manufactured': '2022-04-03T16:25:12.840444+02:00'}
Car(id=5, manufactured=DateTime(2022, 4, 3, 16, 25, 12, 840444, tzinfo=Timezone('+02:00')))
Yeah, that's expected. make_dict_unstructure_fn resolves the hooks for all attributes from the given converter at the time it's created - there's no dispatch at the time it's called. So you need to register dependent hooks earlier, or call make_dict_unstructure_fn again. It's a speed thing, and I've found the condition isn't too difficult to satisfy.
You might also consider using a GenConverter instead - it's faster and has more features.