marshmallow_dataclass
marshmallow_dataclass copied to clipboard
marshmallow-oneofschema support
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
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.
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 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?
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.
Just in case, while Schema#load
works with your example, Schema#dump
fails to serialize elements
field properly. Such a shame :(
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 This works for me as well. Thanks for the example!
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:
- snake_case api
- 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
type: str = field(metadata={"validate": Equal("Point")})
this is rather verbose, is typing.Literal
supported?
type: Literal["Point"]
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?