deschler / django-modeltranslation

Translates Django models using a registration approach.
BSD 3-Clause "New" or "Revised" License
1.34k stars 257 forks source link

fix(types): remove forward references #744

Closed mschoettle closed 10 hours ago

mschoettle commented 3 days ago

We use mkdocstrings (https://github.com/mkdocstrings/mkdocstrings) which calls typing.get_type_hints. With the latest version of modeltranslation this causes the following error when run on modeltranslation.translation.TranslationOptions:

In [1]: from typing import get_type_hints

In [2]: from modeltranslation import translator

In [3]: get_type_hints(translator.TranslationOptions)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 get_type_hints(translator.TranslationOptions)

File /opt/homebrew/Cellar/python@3.11/3.11.9/Frameworks/Python.framework/Versions/3.11/lib/python3.11/typing.py:2379, in get_type_hints(obj, globalns, localns, include_extras)
   2377             value = ForwardRef(value, is_argument=False, is_class=True)
   2378             print(value)
-> 2379         value = _eval_type(value, base_globals, base_locals)
   2380         hints[name] = value
   2381 return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}

File /opt/homebrew/Cellar/python@3.11/3.11.9/Frameworks/Python.framework/Versions/3.11/lib/python3.11/typing.py:395, in _eval_type(t, globalns, localns, recursive_guard)
    388 """Evaluate all forward references in the given type t.
    389 
    390 For use of globalns and localns see the docstring for get_type_hints().
    391 recursive_guard is used to prevent infinite recursion with a recursive
    392 ForwardRef.
    393 """
    394 if isinstance(t, ForwardRef):
--> 395     return t._evaluate(globalns, localns, recursive_guard)
    396 if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)):
    397     if isinstance(t, GenericAlias):

File /opt/homebrew/Cellar/python@3.11/3.11.9/Frameworks/Python.framework/Versions/3.11/lib/python3.11/typing.py:905, in ForwardRef._evaluate(self, globalns, localns, recursive_guard)
    900 if self.__forward_module__ is not None:
    901     globalns = getattr(
    902         sys.modules.get(self.__forward_module__, None), '__dict__', globalns
    903     )
    904 type_ = _type_check(
--> 905     eval(self.__forward_code__, globalns, localns),
    906     "Forward references must evaluate to types.",
    907     is_argument=self.__forward_is_argument__,
    908     allow_special_forms=self.__forward_is_class__,
    909 )
    910 self.__forward_value__ = _eval_type(
    911     type_, globalns, localns, recursive_guard | {self.__forward_arg__}
    912 )
    913 self.__forward_evaluated__ = True

File <string>:1

TypeError: string indices must be integers, not 'type'

The problematic value turned out to be:

ClassVar[_ListOrTuple[str] | dict[str, _ListOrTuple[str]]]
ForwardRef('ClassVar[_ListOrTuple[str] | dict[str, _ListOrTuple[str]]]')

which I traced back to the definition of _ListOrTuple.

mschoettle commented 3 days ago

Hm, there still seems to be a bug in mypy with the union operator (in theory it should be fixed: https://github.com/python/mypy/issues/12211).

So I switched to using Union for now.

last-partizan commented 10 hours ago

Merged, thanks!