Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClassVar fails when __future__ annotations used in python3.9 #279

Closed
thomascobb opened this issue Dec 17, 2021 · 3 comments · Fixed by #280
Closed

ClassVar fails when __future__ annotations used in python3.9 #279

thomascobb opened this issue Dec 17, 2021 · 3 comments · Fixed by #280

Comments

@thomascobb
Copy link
Contributor

thomascobb commented Dec 17, 2021

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?

@wyfo
Copy link
Owner

wyfo commented Dec 17, 2021

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.

@wyfo wyfo linked a pull request Dec 17, 2021 that will close this issue
@wyfo
Copy link
Owner

wyfo commented Dec 17, 2021

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.

@wyfo wyfo closed this as completed in #280 Dec 18, 2021
@thomascobb
Copy link
Contributor Author

Thanks for the quick fix, much appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants