lovasoa / marshmallow_dataclass

Automatic generation of marshmallow schemas from dataclasses.
https://lovasoa.github.io/marshmallow_dataclass/html/marshmallow_dataclass.html
MIT License
458 stars 78 forks source link

Generic type as schema field type? Is it possible? #203

Closed belo4ya closed 2 years ago

belo4ya commented 2 years ago

I want to implement a Generic marshmallow dataclass, which I can then embed into other class definitions. Is there any way I can do this?

I want to get a class like DateValue with this behavior

import typing as t

from marshmallow import Schema, fields
from marshmallow_dataclass import NewType, dataclass

class SchemaMixin:
    Schema: t.ClassVar[t.Type[Schema]] = Schema

DateTime = NewType('DateTime', str, field=fields.DateTime, format='iso')
T = t.TypeVar('T', str, int, float)

@dataclass
class DateValue(t.Generic[T], SchemaMixin):
    date: DateTime
    value: T  # here is an error: we need a Type object created using NewType

Example of client code that wants to use DateValue:

@dataclass
class Container(SchemaMixin):
    timeseries_str: list[DateValue[str]]
    timeseries_float: list[DateValue[float]]

print(Container.Schema().load({
    'timeseries_str': [
        {'date': '2022-01-01T00:00:00', 'value': 'str1'},
        {'date': '2022-01-02T00:00:00', 'value': 'str2'},
    ],
    'timeseries_float': [
        {'date': '2022-01-01T00:00:00', 'value': 2},
        {'date': '2022-01-02T00:00:00', 'value': 3.14},
    ],
}))

I get the following error. And this is expected, because the type T was not created using the NewType function.

…\src\.venv\lib\site-packages\marshmallow_dataclass\__init__.py:373: UserWarning: ****** WARNING ****** marshmallow_dataclass was called on the class ~T, which is not a dataclass. It is going to try and convert the class into a dataclass, which may have undesirable side effects. To avoid this message, make sure all your classes and all the classes of their fields are either explicitly supported by marshmallow_dataclass, or define the schema explicitly using field(metadata=dict(marshmallow_field=...)). For more information, see https://github.com/lovasoa/marshmallow_dataclass/issues/51 ****** WARNING ******
  warnings.warn(
Traceback (most recent call last):
  File "…\.pyenv\pyenv-win\versions\3.9.13\lib\dataclasses.py", line 1033, in fields
    fields = getattr(class_or_instance, _FIELDS)
AttributeError: 'TypeVar' object has no attribute '__dataclass_fields__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "…\src\.venv\lib\site-packages\marshmallow_dataclass\__init__.py", line 370, in _internal_class_schema
    fields: Tuple[dataclasses.Field, ...] = dataclasses.fields(clazz)
  File "…\.pyenv\pyenv-win\versions\3.9.13\lib\dataclasses.py", line 1035, in fields
    raise TypeError('must be called with a dataclass type or instance')
TypeError: must be called with a dataclass type or instance

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
…
  File "…\.pyenv\pyenv-win\versions\3.9.13\lib\dataclasses.py", line 835, in _process_class
    for b in cls.__mro__[-1:0:-1]:
AttributeError: 'TypeVar' object has no attribute '__mro__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "…\src\_utils\__init__.py", line 33, in <module>
    print(Container.Schema().load({
…
  File "…\src\.venv\lib\site-packages\marshmallow_dataclass\__init__.py", line 387, in _internal_class_schema
    raise TypeError(
TypeError: T is not a dataclass and cannot be turned into one.
belo4ya commented 2 years ago

Found a great solution in support generic dataclasses #172. I hope that the functionality will get to master as soon as possible.