Thanks to visit codestin.com
Credit goes to github.com

Skip to content

get_type_hints crashes on TypedDict with non-builtin types from other modules #11

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

Closed
SKHecht opened this issue Jul 25, 2019 · 8 comments
Assignees
Labels
bug Something isn't working

Comments

@SKHecht
Copy link

SKHecht commented Jul 25, 2019

Here's a min repro of the crash:

$ cat a.py
from __future__ import annotations

from typing import Any
from mypy_extensions import TypedDict


class ATypedDict(TypedDict):
    f: Any

$ cat b.py
from a import ATypedDict
from typing import get_type_hints

print(getattr(ATypedDict, '__module__'))
get_type_hints(ATypedDict)

$ python b.py
importlib._bootstrap
Traceback (most recent call last):
  File "b.py", line 5, in <module>
    get_type_hints(ATypedDict)
  File "/usr/lib/python3.7/typing.py", line 973, in get_type_hints
    value = _eval_type(value, base_globals, localns)
  File "/usr/lib/python3.7/typing.py", line 260, in _eval_type
    return t._evaluate(globalns, localns)
  File "/usr/lib/python3.7/typing.py", line 464, in _evaluate
    eval(self.__forward_code__, globalns, localns),
  File "<string>", line 1, in <module>
NameError: name 'Any' is not defined

Here's my understanding of the issue:

When using from __future__ import annotations, type annotations become ForwardRefs. get_type_hints resolves these using globals and locals. Generally, calling get_type_hints on a class doesn't require globals or locals because get_type_hints retrieves the __dict__ of class's module. However, it seems like, in some cases, the __module__ for TypedDict classes is not set to the module it is defined in, thus when get_type_hints tries to resolve the type annotation it fails.

This issue does not occur with builtin types because they are always available.

@JukkaL JukkaL added the bug Something isn't working label Jul 25, 2019
@JukkaL
Copy link
Collaborator

JukkaL commented Jul 25, 2019

This works with TypedDict imported from typing_extensions, so fixing this may be a matter of updating the implementation to reflect the one in typing_extensions. Can you check if using typing_extensions fixes the issue for you?

cc @ilevkivskyi

@SKHecht
Copy link
Author

SKHecht commented Jul 25, 2019

Yep, that fixes it! Thanks!

@ilevkivskyi
Copy link
Member

This is related to https://bugs.python.org/issue28869. There is a sys._getframe() hack to work around this in both modules, but TBH I don't understand why it works in one case and doesn't work in another case, looking at it now it seems wrong to me both in mypy_extensions and in typing_extensions. I will try to investigate this in more depth later.

@ilevkivskyi ilevkivskyi self-assigned this Aug 7, 2019
@githorse
Copy link

I'm seeing what looks like this error without TypedDict. I've confirmed that's dependent on from __future__ import annotations. Until this fix is merged, is there any other workaround besides the sys._getframe() hack (which is too rich for my blood) that would allow me to still use from __future__ import annotations without breaking get_type_hints?

@gvanrossum
Copy link
Member

@githorse Can you post a detailed example of code that crashes and the full traceback? Also please tell us the exact Python version.

@githorse
Copy link

@gvanrossum Here's a minimal example in Python 3.7.5:

# pizza.py
from __future__ import annotations   # remove me to fix issue
from dataclasses import dataclass
from decimal import Decimal

@dataclass
class Pizza:
    radius: Decimal
>>> from pizza import Pizza
>>> from decimal import Decimal
>>> from typing import get_type_hints
>>> get_type_hints(Pizza)
{'radius': <class 'decimal.Decimal'>}
>>> pizza = Pizza(radius=Decimal(99.0))
>>> pizza
Pizza(radius=Decimal('99'))
>>> get_type_hints(pizza)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 1004, in get_type_hints
    value = _eval_type(value, globalns, localns)
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 263, in _eval_type
    return t._evaluate(globalns, localns)
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 467, in _evaluate
    eval(self.__forward_code__, globalns, localns),
  File "<string>", line 1, in <module>
NameError: name 'Decimal' is not defined
>>> get_type_hints(pizza.__class__)
{'radius': <class 'decimal.Decimal'>}

I realize now that I'm only seeing the error when calling get_type_hints on the instance, not on the class, so it seems I have my workaround :)

@gvanrossum
Copy link
Member

That's not just a workaround. The docstring for get_type_hints() state "The argument may be a module, class, method, or function." The docs on docs.python.org state the same in a slightly different phrasing: "Return a dictionary containing type hints for a function, method, module or class object." So your case is entirely unrelated to the issue that was originally reported here. (So much for Googling error messages. :-)

@JelleZijlstra
Copy link
Member

We're not going to fix this in mypy-extensions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants