'How to define the same field for load_only and dump_only params at the Marshmallow scheme?

I am trying to build a marshmallow scheme to both load and dump data. And I get everything OK except one field.

Problem description

(If you understand the problem, you don't have to read this).

For load data its type is Decimal. And I used it like this before. Now I want to use this schema for dumping and for that my flask API responses with: TypeError: Object of type Decimal is not JSON serializable. OK, I understand. I changed the type to Float. Then my legacy code started to get an exception while trying to save that field to database (it takes Decimal only). I don't want to change the legacy code so I looked for any solution at the marshmallow docs and found load_only and dump_only params. It seems like those are what I wanted, but here is my problem - I want to set them to the same field. So I just wondered if I can define both fields and tried this:

class PaymentSchema(Schema):
    money = fields.Decimal(load_only=True)
    money = fields.Float(dump_only=True)

I have been expected for a miracle, of course. Actually I was thinking that it will skip first definition (correctly, re-define it). What I got is an absence of the field at all.

Workaround solution

So I tried another solution. I created another schema for dump and inherit it from the former schema:

class PaymentSchema(Schema):
    money = fields.Decimal(load_only=True)

class PaymentDumpSchema(PaymentSchema):
    money = fields.Float(dump_only=True)

It works. But I wonder if there's some another, native, "marshmallow-way" solution for this. I have been looking through the docs but I can't find anything.



Solution 1:[1]

You can use the marshmallow decorator @pre_load in this decorator you can do whatever you want and return with your type

from marshmallow import pre_load

import like this and in this you will get your payload and change the type as per your requirement.

Solution 2:[2]

UPD: I found a good solution finally.

NEW SOLUTION

The trick is to define your field in load_fields and dump_fields inside __init__ method.

from marshmallow.fields import Integer, String, Raw
from marshmallow import Schema


class ItemDumpLoadSchema(Schema):
    item = Raw()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not (self.only and 'item' not in self.only) and \
                not (self.exclude and 'item' in self.exclude):
            self.load_fields['item'] = Integer(missing=0)
            self.dump_fields['item'] = String()

Usage:

>>> ItemDumpLoadSchema().load({})
{'item': 0}
>>> ItemDumpLoadSchema().dump({'item': 0})
{'item': '0'}

Don't forget to define field in a schema with some field (Raw in my example) - otherwise it may raise an exception in some cases (such as usage of only and exclude) keywords.

OLD SOLUTION

A little perverted one. It based on @prashant-suthar answer. I named load field with suffix _load and implemented @pre_load, @post_load and error handling.

class ArticleSchema(Schema):
    id = fields.String()
    title = fields.String()
    text = fields.String()
    
    
class FlowSchema(Schema):
    article = fields.Nested(ArticleSchema, dump_only=True)
    article_load = fields.Int(load_only=True)

    @pre_load
    def pre_load(self, data, *args, **kwargs):
        if data.get('article'):
            data['article_load'] = data.pop('article')
        return data

    @post_load
    def post_load(self, data, *args, **kwargs):
        if data.get('article_load'):
            data['article'] = data.pop('article_load')
        return data

    def handle_error(self, exc, data, **kwargs):
        if 'article_load' in exc.messages:
            exc.messages['article'] = exc.messages.pop('article_load')
        raise exc

Why the old solution is not a good solution?

It doesn't allow to inheritate schemas with different handle_error methods defined. And you have to name pre_load and post_load methods with different names.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Prashant Suthar
Solution 2