Open
Description
- cattrs version: 1.10.0
- Python version: 3.10.2
- Operating System: Ubuntu 20.04.3
Description
This is related to #201, but probably worth making a separate issue for, since it affects one of the (previously working) workarounds mentioned in that issue. Feel free to close this if you feel this is redundant with #201 or #94.
I have a converter that registers a ForwardRef
. In python 3.6.x through 3.10.1, this has worked as intended. As of python 3.10.2, this is now throwing a TypeError
:
TypeError: Invalid first argument to `register()`. ForwardRef('MyClass') is not a class.
Strangely, I don't see anything in the python 3.10.2 changelog that would indicate changed behavior with ForwardRef
or register()
.
What I Did
Minimal example:
from attr import define, field
from cattr import Converter # Note: same behavior with GenConverter
from typing import ForwardRef, List
@define()
class MyClass:
history: List['MyClass'] = field(factory=list)
converter = Converter()
converter.register_structure_hook(
ForwardRef('MyClass'), lambda obj, _: converter.structure(obj, MyClass)
)
Traceback:
TypeError Traceback (most recent call last)
Input In [17], in <module>
----> 1 converter.register_structure_hook(
2 ForwardRef("MyClass"), (lambda obj, _: converter.structure(obj, MyClass))
3 )
File ~/.virtualenvs/rc-310/lib/python3.10/site-packages/cattr/converters.py:269, in Converter.register_structure_hook(self, cl, func)
267 self._structure_func.clear_cache()
268 else:
--> 269 self._structure_func.register_cls_list([(cl, func)])
File ~/.virtualenvs/rc-310/lib/python3.10/site-packages/cattr/dispatch.py:57, in MultiStrategyDispatch.register_cls_list(self, cls_and_handler, direct)
55 self._direct_dispatch[cls] = handler
56 else:
---> 57 self._single_dispatch.register(cls, handler)
58 self.clear_direct()
59 self.dispatch.cache_clear()
File ~/.pyenv/versions/3.10.2/lib/python3.10/functools.py:856, in singledispatch.<locals>.register(cls, func)
854 else:
855 if func is not None:
--> 856 raise TypeError(
857 f"Invalid first argument to `register()`. "
858 f"{cls!r} is not a class."
859 )
860 ann = getattr(cls, '__annotations__', {})
861 if not ann:
TypeError: Invalid first argument to `register()`. ForwardRef('MyClass') is not a class.
Activity
JWCook commentedon Jan 15, 2022
Note that one of the other solutions posted in #201 still works in python 3.10.2: #201 (comment)
So the above example would become:
So this may be a non-issue, but probably still useful for someone else out there googling for the same error.
aha79 commentedon Jan 15, 2022
I think the error is due to this commit (a change in functools.py), which was a fix due to bpo-46032.
It seems that the newly added check
_is_valid_dispatch_type()
returns false and thus we get the TypeError. (I cannot run the example as only Python 10.2 for macos is out).It is a bit hard to follow the logic, but ForwardRef("MyClass") technically is an instance and not a type (which may explain the behavior).
However, I do not understand is why the solution in your last comment works.
Also what the change to singledispatch means for cattrs. I have the feeling that this will bite us again.
JWCook commentedon Jan 15, 2022
Good to know, thanks for tracking that down. It looks like this may be an unintended side effect of that bugfix rather than an intentional change, then.
True, but an instance of
ForwardRef
also looks like a class:Tinche would be able to give a more thorough explanation, but I believe it works because
Converter.register_structure_hook_func()
takes a different path thanregister_structure_hook()
, viaFunctionDispatch.dispatch()
:cattrs/src/cattr/dispatch.py
Lines 95 to 131 in 22b24c2
We're using the function we provide (in this case testing
t.__class__ is typing.ForwardRef
) instead of the checks infunctools.singledispatch.register()
, so_is_valid_dispatch_type()
is never called on aForwardRef
instance.Tinche commentedon Jan 16, 2022
Yeah, as you folks found out,
singledispatch
is very inadequate for a bunch of use cases using more abstract types (i.e. not actual classes). That's why after checking singledispatch cattrs has a list of predicates that it'll check in order, and that's what you're enabling withregister_structure_hook_func
. I'm actually surprised ForwardRefs worked before with singledispatch.So the predicate approach would be preferred, yeah.
register()
. ForwardRef('CachedResponse') is not a class. keymanapp/keyman#6119ermshiperete commentedon Jan 18, 2022
Same bug occurs with Python 3.9.10 (on Debian/Ubuntu).
fix(linux): Add workaround for Python bug
fix(linux): Add workaround for Python bug
fix(linux): Add workaround for Python bug
fix(linux): Add workaround for Python bug
fix(linux): Add workaround for Python bug
ermshiperete commentedon Jan 19, 2022
See also the bug in requests-cache: requests-cache/requests-cache#501
20 remaining items