s-knibbs / dataclasses-jsonschema

JSON schema generation from dataclasses
MIT License
166 stars 38 forks source link

The declaration of `ForwardRef` in `get_class_type_hints` doesn't include `is_class`, which breaks evaluation on Python >=3.9.8 #176

Open BarakW opened 2 years ago

BarakW commented 2 years ago

As of python 3.9.8, _type_check will reject a ClassVar type hint if it's not in a class, but the declaration for ForwardRef here:

def get_class_type_hints(klass: Type, localns=None) -> Dict[str, Any]:
    """Return type hints for a class. Adapted from `typing.get_type_hints`, adds support for PEP 585 & PEP 604"""
    hints = {}
    for base in reversed(klass.__mro__):
        base_globals = sys.modules[base.__module__].__dict__
        base_globals['_Union'] = Union
        if sys.version_info < (3, 9):
            base_globals['_List'] = List
            base_globals['_Set'] = Set
            base_globals['_Type'] = Type
            base_globals['_Tuple'] = Tuple
            base_globals['_Dict'] = Dict
        ann = base.__dict__.get('__annotations__', {})
        for name, value in ann.items():
            if value is None:
                value = type(None)
            if isinstance(value, str):
                t = ast.parse(value, '<unknown>', 'eval')
                union_transformer = RewriteUnionTypes()
                t = union_transformer.visit(t)
                builtin_generics_transformer = RewriteBuiltinGenerics()
                if sys.version_info < (3, 9):
                    t = builtin_generics_transformer.visit(t)
                if builtin_generics_transformer.rewritten or union_transformer.rewritten:
                    # Note: ForwardRef raises a TypeError when given anything that isn't a string, so we need
                    # to compile & eval the ast here
                    code = compile(t, '<unknown>', 'eval')
                    hints[name] = eval(code, base_globals, localns)
                    continue
                else:
                    value = ForwardRef(value, is_argument=False)
            value = _eval_type(value, base_globals, localns)
            hints[name] = value
    return hints

Doesn't include a check for if value is a class, which will always cause a failure when the annotation is some variation of the ClassVar generic.

I'm only working on a project using this library and don't know it super closely, would it be enough to always pass in is_class=True?