dataclasses-json icon indicating copy to clipboard operation
dataclasses-json copied to clipboard

Support union type syntax as defined in PEP 604 and implemented in Python 3.10

Open zyv opened this issue 4 years ago • 6 comments

I have originally reported this to marshmallow as marshmallow-code/marshmallow#1962, because I assumed that the warning comes from there, but it seems that it is emitted by dataclasses_json instead. Therefore, I'm raising an issue in this repository.


A new syntax for union types has been introduced in PEP 604 and implemented in Python 3.10:

dict[str, Union[str, bool]] == dict[str, str | bool]

Unfortunately, it is not currently supported in dataclasses_json:

UserWarning: Unknown type str | bool at XXX: list[dict[str, str | bool]] It's advised to pass the correct marshmallow type to YYY.

Because of that, we can't ban Union in our linter.

zyv avatar Apr 04 '22 18:04 zyv

We run the pyupgrade linter which automatically updates the type hints and causes serious issues with this

tolomea avatar May 21 '22 14:05 tolomea

Actually with light of the new day the problem we see is somewhat more aggressive.

..\..\..\.venv\eligible2\lib\site-packages\marshmallow\schema.py:751: in loads
    return self.load(data, many=many, partial=partial, unknown=unknown)
..\..\..\.venv\eligible2\lib\site-packages\marshmallow\schema.py:717: in load
    return self._do_load(
..\..\..\.venv\eligible2\lib\site-packages\marshmallow\schema.py:888: in _do_load
    result = self._invoke_load_processors(
..\..\..\.venv\eligible2\lib\site-packages\marshmallow\schema.py:1086: in _invoke_load_processors
    data = self._invoke_processors(
..\..\..\.venv\eligible2\lib\site-packages\marshmallow\schema.py:1211: in _invoke_processors
    data = [processor(item, many=many, **kwargs) for item in data]
..\..\..\.venv\eligible2\lib\site-packages\marshmallow\schema.py:1211: in <listcomp>
    data = [processor(item, many=many, **kwargs) for item in data]
..\..\..\.venv\eligible2\lib\site-packages\dataclasses_json\mm.py:335: in make_instance
    return _decode_dataclass(cls, kvs, partial)
..\..\..\.venv\eligible2\lib\site-packages\dataclasses_json\core.py:152: in _decode_dataclass
    types = get_type_hints(cls)
..\..\..\AppData\Local\Programs\Python\Python38\lib\typing.py:1232: in get_type_hints
    value = _eval_type(value, base_globals, localns)
..\..\..\AppData\Local\Programs\Python\Python38\lib\typing.py:270: in _eval_type
    return t._evaluate(globalns, localns)
..\..\..\AppData\Local\Programs\Python\Python38\lib\typing.py:518: in _evaluate
    eval(self.__forward_code__, globalns, localns),
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   ???
E   TypeError: 'type' object is not subscriptable

<string>:1: TypeError

tolomea avatar May 22 '22 10:05 tolomea

Actually my issues are related to pyupgrade applying 3.9/3.10 features to 3.8 which can be suppressed with it's --keep-runtime-typing flag.

tolomea avatar May 22 '22 11:05 tolomea

The issue is still there:

# stdlib imports
from dataclasses import dataclass, field
from typing import Union

# 3rd party imports
from marshmallow import Schema
from marshmallow_dataclass import add_schema


@add_schema
@dataclass
class SchemaClass1:
    str_or_int: Union[str, int] = field(default=0)


class_obj1 = SchemaClass1()
class_obj_dump1 = type(class_obj1).Schema().dump(class_obj1)
print(class_obj_dump1)


@add_schema
@dataclass
class SchemaClass2:
    str_or_int: str | int = field(default=0)


class_obj2 = SchemaClass2()
class_obj_dump2 = type(class_obj2).Schema().dump(class_obj2)
print(class_obj_dump2)

Results in:

{'str_or_int': 0}
Traceback (most recent call last):
  File ".../tests/fake.py", line 28, in <module>
    class_obj_dump2 = type(class_obj2).Schema().dump(class_obj2)
  File ".../venv/lib/python3.10/site-packages/marshmallow_dataclass/lazy_class_attribute.py", line 33, in __get__
    setattr(cls, self.name, self.func())
  File ".../venv/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 356, in class_schema
    return _internal_class_schema(clazz, base_schema, clazz_frame)
  File ".../venv/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 402, in _internal_class_schema
    attributes.update(
  File ".../venv/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 405, in <genexpr>
    field_for_schema(
  File ".../venv/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 729, in field_for_schema
    or _internal_class_schema(typ, base_schema, typ_frame)
  File ".../venv/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 367, in _internal_class_schema
    _RECURSION_GUARD.seen_classes[clazz] = clazz.__name__
AttributeError: 'types.UnionType' object has no attribute '__name__'. Did you mean: '__ne__'?

Process finished with exit code 1

korondizeno avatar Jun 21 '22 08:06 korondizeno

@lidatong any update on this? Thank you for looking into it!

zyv avatar Feb 02 '23 10:02 zyv

@zyv we recently fixed this for T | None, but in your case it seems some other code is failing. I'll retest

george-zubrienko avatar Jul 02 '23 17:07 george-zubrienko