ClassVar fails when __future__ annotations used in python3.9
thomascobb opened this issue · comments
from __future__ import annotations
from dataclasses import dataclass
from typing import ClassVar
from apischema.json_schema import deserialization_schema
@dataclass
class MyClass:
a: ClassVar[str] = ""
def test_fails():
assert deserialization_schema(MyClass)
Fails with:
./tests/test_classvar.py::test_fails Failed: [undefined]TypeError: typing.ClassVar[str] is not valid as type argument
Traceback (most recent call last):
File "/home/tom/Programming/pvi/tests/test_classvar.py", line 15, in test_fails
assert deserialization_schema(MyClass)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/utils.py", line 423, in wrapper
return wrapped(*args, **kwargs)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/json_schema/schema.py", line 558, in deserialization_schema
return _schema(
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/json_schema/schema.py", line 517, in _schema
refs = _extract_refs([(tp, conversion)], default_conversion, builder, all_refs)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/json_schema/schema.py", line 476, in _extract_refs
builder.RefsExtractor(default_conversion, refs).visit_with_conv(tp, conversion)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/conversions/visitor.py", line 100, in visit_with_conv
return self.visit(tp)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/conversions/visitor.py", line 135, in visit
return self.visit_conversion(tp, conversion, dynamic, next_conversion)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/json_schema/refs.py", line 119, in visit_conversion
for ref_tp in self.resolve_conversion(tp):
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/json_schema/conversions_resolver.py", line 129, in resolve_conversion
return Resolver(self.default_conversion).visit_with_conv(
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/conversions/visitor.py", line 100, in visit_with_conv
return self.visit(tp)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/conversions/visitor.py", line 135, in visit
return self.visit_conversion(tp, conversion, dynamic, next_conversion)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/json_schema/conversions_resolver.py", line 97, in visit_conversion
results = super().visit_conversion(
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/conversions/visitor.py", line 122, in visit_conversion
return super().visit(tp)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/visitor.py", line 185, in visit
return self.dataclass(tp, *dataclass_types_and_fields(tp)) # type: ignore
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/visitor.py", line 64, in dataclass_types_and_fields
types = get_type_hints2(tp)
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/typing.py", line 190, in get_type_hints2
hints.update(_class_annotations(base, globalns, localns))
File "/home/tom/.local/share/virtualenvs/pvi-YMoh_0sz/lib/python3.9/site-packages/apischema/typing.py", line 167, in _class_annotations
hints[name] = _eval_type(value, base_globals, localns)
File "/usr/lib/python3.9/typing.py", line 292, in _eval_type
return t._evaluate(globalns, localns, recursive_guard)
File "/usr/lib/python3.9/typing.py", line 553, in _evaluate
type_ = _type_check(
File "/usr/lib/python3.9/typing.py", line 158, in _type_check
raise TypeError(f"{arg} is not valid as type argument")
TypeError: typing.ClassVar[str] is not valid as type argument
With apischema 0.16.4 on python 3.9
I had a quick dig around and it appears that ForwardRef
added an is_class
argument for python3.9 and fails for ClassVars if this is not passed.
I'm happy to submit a PR that adds yet another version check in apischema/typing.py if that's useful, but was curious as why you don't use get_type_hints()
to get this information?
but was curious as why you don't use get_type_hints() to get this information?
Because typing.get_type_hints
is not sufficient to retrieve type annotation of generic classes, particularly in case of inheritance, so I'd to made my own wrapper around it. You can look at python/typing#776 and python/typing#777, where I've tried to add better generic handling in standard library (without result).
Here is an example:
from typing import Generic, TypeVar, get_type_hints
from apischema.typing import get_type_hints2
T = TypeVar("T")
class A(Generic[T]):
a: T
U = TypeVar("U")
class B(A[U]):
b: list[U]
# How can I work with this result?
assert get_type_hints(B) == {"a": T, "b": list[U]}
# Better
assert get_type_hints2(B) == {"a": U, "b": list[U]}
However, it seems that I have to rewrite my wrapper, because it is using too much internal implementation details. I will try to release a patch for v0.15 and v0.16 this weekend.
Having written the fix, I remember now why I did a rewrite of get_type_hints
. As you can see in python/typing#776, the goal was to propose a new implementation of get_type_hints
, that's why I used a lot of internal features. But it was a mistake to do it, I should have kept this implementation for the typing issue, and make my own wrapper.
Thanks for the quick fix, much appreciated!