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

[BUG] .from_dict fails with from __future__ import annotations and dataclass definition within the same function #548

Open
orilg opened this issue Sep 30, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@orilg
Copy link

orilg commented Sep 30, 2024

Description

I've encountered this issue when writing tests for my project with pytest.
The test is contained in one function and I've added a file to test the behavior of from __future__ import annotations
All my nested dataclass_json field tests failed (when a field of one dataclass_json is another dataclass_json) on .from_dict

for the example below I've got this traceback :

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[1], line 17
     15     class_with_inner = MyClass(InnerClass('asdf', 2))
     16     return MyClass.from_dict(class_with_inner.to_dict())
---> 17 print(do_something())

Cell In[1], line 16, in do_something()
     13     my_inner_inst: InnerClass
     15 class_with_inner = MyClass(InnerClass('asdf', 2))
---> 16 return MyClass.from_dict(class_with_inner.to_dict())

File /data/.venv-temp/lib/python3.11/site-packages/dataclasses_json/api.py:70, in DataClassJsonMixin.from_dict(cls, kvs, infer_missing)
     65 @classmethod
     66 def from_dict(cls: Type[A],
     67               kvs: Json,
     68               *,
     69               infer_missing=False) -> A:
---> 70     return _decode_dataclass(cls, kvs, infer_missing)

File /data/.venv-temp/lib/python3.11/site-packages/dataclasses_json/core.py:178, in _decode_dataclass(cls, kvs, infer_missing)
    175 kvs = _handle_undefined_parameters_safe(cls, kvs, usage="from")
    177 init_kwargs = {}
--> 178 types = get_type_hints(cls)
    179 for field in fields(cls):
    180     # The field should be skipped from being added
    181     # to init_kwargs as it's not intended as a constructor argument.
    182     if not field.init:

File /usr/lib/python3.11/typing.py:2336, in get_type_hints(obj, globalns, localns, include_extras)
   2334         if isinstance(value, str):
   2335             value = ForwardRef(value, is_argument=False, is_class=True)
-> 2336         value = _eval_type(value, base_globals, base_locals)
   2337         hints[name] = value
   2338 return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}

File /usr/lib/python3.11/typing.py:371, in _eval_type(t, globalns, localns, recursive_guard)
    364 """Evaluate all forward references in the given type t.
    365
    366 For use of globalns and localns see the docstring for get_type_hints().
    367 recursive_guard is used to prevent infinite recursion with a recursive
    368 ForwardRef.
    369 """
    370 if isinstance(t, ForwardRef):
--> 371     return t._evaluate(globalns, localns, recursive_guard)
    372 if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)):
    373     if isinstance(t, GenericAlias):

File /usr/lib/python3.11/typing.py:877, in ForwardRef._evaluate(self, globalns, localns, recursive_guard)
    872 if self.__forward_module__ is not None:
    873     globalns = getattr(
    874         sys.modules.get(self.__forward_module__, None), '__dict__', globalns
    875     )
    876 type_ = _type_check(
--> 877     eval(self.__forward_code__, globalns, localns),
    878     "Forward references must evaluate to types.",
    879     is_argument=self.__forward_is_argument__,
    880     allow_special_forms=self.__forward_is_class__,
    881 )
    882 self.__forward_value__ = _eval_type(
    883     type_, globalns, localns, recursive_guard | {self.__forward_arg__}
    884 )
    885 self.__forward_evaluated__ = True

File <string>:1

NameError: name 'InnerClass' is not defined

Workaround : moving the inner dataclass definition out of the function code block.

Code snippet that reproduces the issue

from __future__ import annotations
from dataclasses import dataclass
from dataclasses_json import DataClassJsonMixin
  
def do_something() -> MyClass:
    @dataclass
     class InnerClass(DataClassJsonMixin):
        my_str: str
        my_int: int
  
    @dataclass
    class MyClass(DataClassJsonMixin):
        my_inner_inst: InnerClass

    class_with_inner = MyClass(InnerClass('asdf', 2))
    return MyClass.from_dict(class_with_inner.to_dict())
print(do_something())

Describe the results you expected

MyClass(my_inner_inst=InnerClass(my_str='asdf', my_int=2))

Python version you are using

3.11.4

Environment description

asttokens==2.4.1
dataclasses-json==0.6.7
decorator==5.1.1
executing==2.1.0
ipython==8.27.0
jedi==0.19.1
marshmallow==3.22.0
matplotlib-inline==0.1.7
mypy-extensions==1.0.0
packaging==24.1
parso==0.8.4
pexpect==4.9.0
prompt_toolkit==3.0.48
ptyprocess==0.7.0
pure_eval==0.2.3
Pygments==2.18.0
six==1.16.0
stack-data==0.6.3
traitlets==5.14.3
typing-inspect==0.9.0
typing_extensions==4.12.2
wcwidth==0.2.13
@orilg orilg added the bug Something isn't working label Sep 30, 2024
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

1 participant