marshmallow_dataclass icon indicating copy to clipboard operation
marshmallow_dataclass copied to clipboard

marshmallow-oneofschema support

Open ffarella opened this issue 4 years ago • 10 comments

Hi! Looks like a great lib! Do you have any plans to support marshmallow-oneofschema? see issue: https://github.com/marshmallow-code/marshmallow-oneofschema/issues/100

ffarella avatar Jan 11 '20 00:01 ffarella

We do already support marshmallow-union, when you install the library with pip install marshmallow-dataclass[union].

It has the advantage of being completely transparent, you just specify the type of your data as Union[TypeA, TypeB], and do not have to add a specific type field to your data. If you need a type field anyway (for backward compatibility or to avoid ambiguities), you can simply add it together with a validation function that compares its value to the type name.

lovasoa avatar Jan 11 '20 20:01 lovasoa

Here is a quick example of how to handle ambiguous classes:

from marshmallow_dataclass import dataclass
from dataclasses import field
from typing import List, Union
from marshmallow.validate import Equal

@dataclass
class Vector:
    type: str = field(metadata={"validate": Equal("Vector")})
    x: float
    y: float

@dataclass
class Point:
    type: str = field(metadata={"validate": Equal("Point")})
    x: float
    y: float


@dataclass
class Geometries:
    elements: List[Union[Point, Vector]]

pts = Geometries.Schema().load({"elements": [
    {"type":"Point", "x": 1, "y": 1},
    {"type":"Vector", "x": 1, "y": 1},
]})

print(pts)

But I found out that in practice, this is only rarely needed.

lovasoa avatar Jan 11 '20 20:01 lovasoa

@lovasoa thanks for the insightful example. Is there an approach that does not require each dataclass to "know" that it will be used in an ambiguous/polymorphic setting?

ekhahniii avatar Apr 25 '20 22:04 ekhahniii

You could for instance wrap such a class into another one that would add the disambiguation field. You can flatten the parent class and the child with marshmallow if necessary.

lovasoa avatar Apr 25 '20 22:04 lovasoa

Just in case, while Schema#load works with your example, Schema#dump fails to serialize elements field properly. Such a shame :(

pshirshov avatar Jun 20 '20 19:06 pshirshov

This also works for marshmallow-dataclass 0.6.6 (I only tried the load method)

import marshmallow.fields as f

from dataclasses import field, dataclass
from typing import List, Union

import marshmallow_dataclass
from marshmallow_oneofschema import OneOfSchema


@dataclass
class Vector:
    x: float
    y: float


@dataclass
class Point:
    x: float
    y: float
    z: float


class BaseSchema(OneOfSchema):
    type_schemas = {"Vector": marshmallow_dataclass.class_schema(Vector),
                    "Point": marshmallow_dataclass.class_schema(Point)}


@dataclass
class Geometries:
    elements: List[Union[Point, Vector]] = field(metadata={'marshmallow_field': f.List(f.Nested(BaseSchema))})


pts = marshmallow_dataclass.class_schema(Geometries)().load({"elements": [
    {"type": "Point", "x": 1, "y": 1, "z": 22},
    {"type": "Vector", "x": 1, "y": 1},
]})

print(pts)

activeperception avatar Nov 10 '20 10:11 activeperception

@activeperception This works for me as well. Thanks for the example!

angelarosario avatar Mar 18 '22 16:03 angelarosario

Yeah, this approach with explicitly defined field works fine, but it completely ignores base_schema passed to class_schema() and there is no workaround here. We use same dataclass in two different contexts:

  1. snake_case api
  2. camelCase api

Camelization is done with appropriate base schema, like: class_schema(MyDataclass, base_schema=CamelCaseSchema). Neat, hah? But if we ever use OneOfSchema with explicit marshmallow_field, we are in trouble. Fine, fine, I guess we should fall back to mm-dataclass' union field and pray there will be no ambiguous data in practice.

P.S. anyways marshmallow-dataclass reduces boilerplate a lot, to say the least. Thank you for this great tool

hmnid avatar Sep 15 '22 09:09 hmnid

    type: str = field(metadata={"validate": Equal("Point")})

this is rather verbose, is typing.Literal supported?

    type: Literal["Point"]

pydantic does

hydrargyrum avatar Mar 29 '23 08:03 hydrargyrum

this is rather verbose, is typing.Literal supported?

Yes, based on a look at the source code, there is support for Literal types. (It appears that this was added in #110.) Have you tried it?

dairiki avatar Mar 29 '23 15:03 dairiki