konradhalas / dacite

Simple creation of data classes from dictionaries.
MIT License
1.72k stars 107 forks source link

TypeError: 'type' object is not subscriptable with __future__.annotations #102

Closed eddawley closed 4 years ago

eddawley commented 4 years ago

Dacite throws the following error under Python 3.7 with __future__.annotations features:

root@09bea08c2d96:/usr/src# ipython
Python 3.7.4 (default, Oct 17 2019, 05:59:21) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.15.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from __future__ import annotations                                                                                                                                       

In [2]: import dacite, dataclasses                                                                                                                                               

In [3]: @dataclasses.dataclass 
   ...: class Foo: 
   ...:     a: list[str] 
   ...:                                                                                                                                                                          

In [4]: dacite.from_dict(data_class=Foo, data={ 
   ...: "a": ["b"], 
   ...: })                                                                                                                                                                       
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-9e1270a160ab> in <module>
      1 dacite.from_dict(data_class=Foo, data={
----> 2 "a": ["b"],
      3 })

/usr/local/lib/python3.7/site-packages/dacite/core.py in from_dict(data_class, data, config)
     40     config = config or Config()
     41     try:
---> 42         data_class_hints = get_type_hints(data_class, globalns=config.forward_references)
     43     except NameError as error:
     44         raise ForwardReferenceError(str(error))

/usr/local/lib/python3.7/typing.py in get_type_hints(obj, globalns, localns)
    974                 if isinstance(value, str):
    975                     value = ForwardRef(value, is_argument=False)
--> 976                 value = _eval_type(value, base_globals, localns)
    977                 hints[name] = value
    978         return hints

/usr/local/lib/python3.7/typing.py in _eval_type(t, globalns, localns)
    261     """
    262     if isinstance(t, ForwardRef):
--> 263         return t._evaluate(globalns, localns)
    264     if isinstance(t, _GenericAlias):
    265         ev_args = tuple(_eval_type(a, globalns, localns) for a in t.__args__)

/usr/local/lib/python3.7/typing.py in _evaluate(self, globalns, localns)
    465                 localns = globalns
    466             self.__forward_value__ = _type_check(
--> 467                 eval(self.__forward_code__, globalns, localns),
    468                 "Forward references must evaluate to types.",
    469                 is_argument=self.__forward_is_argument__)

<string> in <module>

TypeError: 'type' object is not subscriptable
konradhalas commented 4 years ago

Hi @eddawley - thank you for using dacite :)

PEP 563 (from __future__ import annotations) fixes forward references problem, so if you want to use some eg. class before it's defined you don't have to use "" to use it as a type hint. Short example:

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class A:
    b: B  # <- you don't have to use ""

@dataclass
class B:
    i: int

In your example I don't see any forward reference, but it looks that instead of typing.List you are using list - this is the reason why you get object is not subscriptable error.

eddawley-noyo commented 4 years ago

That's the wrong PEP for this error. PEP 585 describes the other feature that future annotations provides. The bug has nothing to do with forward references.

konradhalas commented 4 years ago

Sorry I was confused because you are using Python 3.7.

PEP 585 works in dacite, but only with Python 3.9. dacite plays with type hints during the runtime so with annotations like list[int] and Python <3.9 it's impossible to run functions like e.g. typing.get_type_hints (which dacite is using).

The following example....

from __future__ import annotations
from typing import get_type_hints

def foo(x: list[int]): pass

print(get_type_hints(foo))

... will pass with 3.9 but it will raise object is not subscriptable with <3.9.

I don't know if I can do something here.

eddawley commented 4 years ago

Understood. Mostly I wanted to document the bug for any future users who hit it.

Kludex commented 2 years ago

Does something similar as the following solve the issue here? https://github.com/pydantic/pydantic/blob/f2c3a49d662378998dd1962fe0ca0ec160a7ad53/pydantic/typing.py#L369-L398