marshmallow_dataclass icon indicating copy to clipboard operation
marshmallow_dataclass copied to clipboard

Is it possible to use this with the full set of marshmallow's supported fields?

Open hutchisr opened this issue 3 years ago • 1 comments

Apologies is this is a dumb question, but I'm a bit confused about what field types are allowed or how to support them. For example, using just marshmallow I can set up a dataclass and schema like this:

@dataclass
class FourTupleFilter:
    ip_src: Optional[IPv4Interface] = None
    ip_dst: Optional[IPv4Interface] = None
    port_src: Optional[int] = None
    port_dst: Optional[int] = None


class FourTupleFilterSchema(Schema):
    ip_src = fields.IPv4Interface()
    ip_dst: fields.IPv4Interface()
    port_src: fields.Int()
    port_dst: fields.Int()

    @post_load
    def make_filter(self, data, **kwargs):
        return FourTupleFilter(**data)

schema = FourTupleFilterSchema()

schema.loads('{"ip_src": "192.168.1.1"}')
# FourTupleFilter(ip_src=IPv4Interface('192.168.1.1/32'), ip_dst=None, port_src=None, port_dst=None)

as Ipv4Interface is part of https://marshmallow.readthedocs.io/en/stable/marshmallow.fields.html?highlight=fields#module-marshmallow.fields

However, if I try to do the same thing with an automatically generated schema from marshmallow_dataclass trying to deserialize the same input results in ValidationError: {'ip_src': {'_schema': ['Invalid input type.']}}

Is there some config I'm missing or does this package only work with basic data types?

hutchisr avatar Feb 08 '22 22:02 hutchisr

By default, marshmallow_dataclass knows what field types to use for types which are listed in marshmallow.Schema.TYPE_MAPPING. Marshmallow's TYPE_MAPPING does not currently include IPv4Interface — I'm not sure why.

The (or a) way around this is to use a custom Schema base class that augments TYPE_MAPPING to include whatever types or customizations you need. (Here is the section on this in the README.)

This works:

from dataclasses import dataclass
from ipaddress import IPv4Interface
from typing import Optional

import marshmallow
import marshmallow_dataclass

class BaseSchema(marshmallow.Schema):
    TYPE_MAPPING = {IPv4Interface: marshmallow.fields.IPv4Interface}

@dataclass
class FourTupleFilter:
    ip_src: Optional[IPv4Interface] = None
    ip_dst: Optional[IPv4Interface] = None
    port_src: Optional[int] = None
    port_dst: Optional[int] = None

schema_class = marshmallow_dataclass.class_schema(
    FourTupleFilter, base_schema=BaseSchema
)
schema = schema_class()

print(schema.loads('{"ip_src": "192.168.1.1"}'))

dairiki avatar Feb 09 '22 00:02 dairiki

For the sake of completeness in posterity, one may also specify a marshmallow.fields.Field instance to use directly using dataclasses.field by setting marshmallow_field in the fields metadata:

from dataclasses import dataclass
from dataclasses import field
from ipaddress import IPv4Interface
from typing import Optional

import marshmallow
import marshmallow_dataclass

@dataclass
class FourTupleFilter:
    ip_src: Optional[IPv4Interface] = field(
        default=None,
        metadata={"marshmallow_field": marshmallow.fields.IPv4Interface()}
    )
    ip_dst: Optional[IPv4Interface] = field(
        default=None,
        metadata={"marshmallow_field": marshmallow.fields.IPv4Interface()}
    )
    port_src: Optional[int] = None
    port_dst: Optional[int] = None

schema_class = marshmallow_dataclass.class_schema(FourTupleFilter)
schema = schema_class()

print(schema.loads('{"ip_src": "192.168.1.1"}'))

dairiki avatar Jan 13 '23 00:01 dairiki

Closing, since this is a request for help rather than an issue report.

dairiki avatar Jan 13 '23 00:01 dairiki