marshmallow-code / marshmallow

A lightweight library for converting complex objects to and from simple Python datatypes.
https://marshmallow.readthedocs.io/
MIT License
7.05k stars 629 forks source link

dumping a dictionary with a schema containing an optional field 'items' #1622

Open sanderland opened 4 years ago

sanderland commented 4 years ago

I'll start with a simple example

from dataclasses import dataclass
from marshmallow import Schema, fields

class ListItem(Schema):
    foo = fields.Str(required=True)

class MySchema(Schema):
    bar = fields.Str(required=True)
    items = fields.List(fields.Nested(ListItem()), required=True)

@dataclass
class Thing:
    bar: str = None

thing = Thing(bar="barbarbar")

d = {"bar": "barbarbar"}

print(MySchema().dump(obj=thing)) # this works
print(MySchema().dump(obj=d))  # this gives an error "TypeError: 'builtin_function_or_method' object is not iterable"

The issue appears because on utils.py's function _get_value_for_key it first tries to getattr, then obj[key] (without default), and finally getattr again, resulting in the items method for dict, rather than a default. This is extremely unexpected behaviour.

A workaround is to override get_attribute to try .get first on dictionaries.

class MyBaseSchema(Schema):
    def get_attribute(self, obj: Any, attr: str, default: Any):
        """Defines how to pull values from an object to serialize."""
        if isinstance(obj, Dict):
            return obj.get(attr, default)
        return getattr(obj, attr, default)

I am not sure what the prefered fix in marshmallow would be given the rather dizzying levels of indirection and cases in the get attribute chain.

mdantonio commented 3 years ago

Hello, I hit exactly the same issue with marshmallow 3.12.1 but with values instead of items (and I didn't try, but I think that the problem is there with any other builtin method, like keys)

Edit: It seems to me that the problem only occurs only if the values/items key is missing in the dictionary... Only in that case the serialization is falling back to the extraction of the builtin method

jdclarke5 commented 1 month ago

For anyone who makes it here, the fix is to put the following:

items_ = fields.List(fields.Nested(ListItem), data_key='items')
values_ = fields.List(fields.Nested(ListItem), data_key='values')

The error message could be more descriptive to alert the user to these "special" keys.