Closed scottee closed 1 month ago
So, the short answer to the question whether the order of hook registration is significant is yes. Simpler hooks should be registered before more complex hooks (which might need the simpler hooks).
The long answer is that it depends on the hooks themselves, and the default hook factories are written to assume it.
In order to handle (structure/unstructure) a piece of data, the cattrs converter needs to do two things: find the function associated with the type of that data (the hook), and call that function. Even though we aggressively cache hooks (using functools.lru_cache
), finding the hook takes a meaningful amount of time.
So in this particular case, the internal hook factory that produces the structuring hooks for attrs classes is written in a way that it only retrieves the hooks for each attribute once, when a class is first seen. This makes cattrs really fast but can be a little surprising if you don't know what's going on. Note that strategies like include_subclasses
are even more complex, and they call various hook factories themselves at time of application.
(It's also worth mentioning that recursive classes get special treatment in order to work, and there's logic in cattrs that tries to bust various caches when new structure hooks are added to minimize problems, but it isn't perfect.)
In your particular case, I'm going to guess include_subclasses
generated the hooks for subclasses of BaseClass
before the list[str]|str
hook was registered, and the generated hooks baked in the wrong list[str]|str
hook.
Hope that answers your question. I'm going to close this now to try to keep the issue list tidy, but feel free to follow up with questions if you have more.
Description
I have a set of classes that all inherit from one base class. Each sub-class has a unique required attribute to differentiate it, so I call
include_subclasses()
to handle this. My classes also have several union attributes likeUnion[str, pathlib.Path]
defined. (There are more unions, but this is the one I got the exception on.) For each of these unions, I have aregister_structure_hook()
call to manage it. Everything was working fine, until I added theUnion[str, Path]
attr. After that I got an exception like this:The trouble is that I already had a structure hook registered for
Union[str, Path]
, but it wasn't being utilized in my call tomy_converter.structure(json_obj, MyMainClass)
.What I Did
The converter initialization originally looked like this when I was getting the exception:
The work-around to this was to reverse the order of registering union hooks and
include_subclasses
:Is it expected that there are ordering dependencies between these calls?