ForwardReference NameError when loading a recursive dict in dataclass

Viewed 153

I'm using marshmallow-dataclass to load a json which represents a sequence of rules where each rule is represented by a LogicalGroup and applies a logical operator on its child expressions, knowing that an expression can itself be a LogicalGroup.

The input dict follows this structure:

import marshmallow_dataclass
from dataclasses import field
from api_handler import BaseSchema
from typing import Sequence, Union, Literal, Type, List, ForwardRef, TypeVar, Generic

filter_input = { "rules" :
  [{
    "groupOperator" : "and",
    "expressions" : [
      { "field": "xxxxx", "operator": "eq", "value": 'level1' },
      { "field": "xxxxx", "operator": "eq", "value": 'm'},
      { "field": "xxxxx", "operator": "eq", "value": "test"},
      {
        "groupOperator" : "or",
        "expressions" : [
          { "field": "xxxx", "operator": "eq", "value": 'level2' },
          { "field": "xxxx", "operator": "eq", "value": 'm' },
          { "field": "xxxx", "operator": "eq", "value": "test" }
        ]
      }
    ]
  }]
}

The dataclasses i'm using for this purpose are the following :

@marshmallow_dataclass.dataclass(base_schema=BaseSchema)
class Expression:
    field    : str
    operator : str
    value    : str 

@marshmallow_dataclass.dataclass(base_schema=BaseSchema)
class LogicalGroup:
    group_operator   : str
    expressions      : List[Union['LogicalGroup', Expression]] = field(default_factory=list)
  
@marshmallow_dataclass.dataclass(base_schema=BaseSchema)
class Filter:
    rules: List[LogicalGroup] = field(default_factory=list)

The problem is when i try to load the dict using the Filter dataclass i get the following error

filt                = Filter.Schema().load(filter_input)
Traceback (most recent call last):
  File "/home/adam/billing/billing/filter/filter.py", line 96, in <module>
    filt                = Filter.Schema().load(filter_input)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow_dataclass/__init__.py", line 628, in load
    all_loaded = super().load(data, many=many, **kwargs)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 725, in load
    return self._do_load(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 859, in _do_load
    result = self._deserialize(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 667, in _deserialize
    value = self._call_and_store(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 496, in _call_and_store
    value = getter_func(data)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 664, in <lambda>
    getter = lambda val: field_obj.deserialize(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 354, in deserialize
    output = self._deserialize(value, attr, data, **kwargs)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 726, in _deserialize
    result.append(self.inner.deserialize(each, **kwargs))
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 354, in deserialize
    output = self._deserialize(value, attr, data, **kwargs)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 609, in _deserialize
    return self._load(value, data, partial=partial)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 592, in _load
    valid_data = self.schema.load(value, unknown=self.unknown, partial=partial)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow_dataclass/__init__.py", line 628, in load
    all_loaded = super().load(data, many=many, **kwargs)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 725, in load
    return self._do_load(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 859, in _do_load
    result = self._deserialize(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 667, in _deserialize
    value = self._call_and_store(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 496, in _call_and_store
    value = getter_func(data)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/schema.py", line 664, in <lambda>
    getter = lambda val: field_obj.deserialize(
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 354, in deserialize
    output = self._deserialize(value, attr, data, **kwargs)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 726, in _deserialize
    result.append(self.inner.deserialize(each, **kwargs))
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow/fields.py", line 354, in deserialize
    output = self._deserialize(value, attr, data, **kwargs)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/marshmallow_dataclass/union_field.py", line 56, in _deserialize
    typeguard.check_type(attr or "anonymous", result, typ)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/typeguard/__init__.py", line 655, in check_type
    expected_type = resolve_forwardref(expected_type, memo)
  File "/home/adam/thanos-envv/lib/python3.9/site-packages/typeguard/__init__.py", line 198, in resolve_forwardref
    return evaluate_forwardref(maybe_ref, memo.globals, memo.locals, frozenset())
  File "/usr/lib/python3.9/typing.py", line 533, in _evaluate
    eval(self.__forward_code__, globalns, localns),
  File "<string>", line 1, in <module>
NameError: name 'LogicalGroup' is not defined

I'm guessing the problem comes from declaring LogicalGroup as a ForwardRef inside type hint Union, because when i use only Union['LogicalGroup'] and modify my dict to be a nested dict of LogicalGroups without the Expressions it works fine.

Does someone have any idea on the source of the bug ? Or maybe a proposition to adress this problem in another way ?

Thanks in advance !

0 Answers
Related