Plain Marshmallow

Map a dictionary to a custom object, or a list of dictionaries to a list of custom objects and reversely

Here we refer to a dictionary but we can expand the idea to a JSON also because the JSON load of a string/input file(such as a flask request json value) will be dumped in the python side as a dictionary or a list of dictionary.

A mapping without customization: @post_load to load the model and nothing to dump the model

Here the idea is to declare in the model and in the schema representing the model the exact same field attributes and to specify in a method annotated with @post_load how to instantiate the model after the load() method is invoked: in the general case, we need only to invoke the constructor of the model by passing to it the attributes bound in the schema instance.
Model and mapping:

from dataclasses import dataclass
from datetime import date
from enum import Enum
from enum import auto
 
from marshmallow import EXCLUDE
from marshmallow import Schema
from marshmallow import fields
from marshmallow import post_load
 
 
class Gender(Enum):
    MALE = auto()
    FEMALE = auto()
    UNKNOWN = auto()
 
 
@dataclass()
class Person:
    first_name: str
    last_name: str
    birthday: date
    gender: Gender
 
 
class PersonSchema(Schema):
    class Meta:
        unknown = EXCLUDE
 
    first_name = fields.Str()
    last_name = fields.Str()
    birthday = fields.Date()
    gender = fields.Enum(Gender)
 
    # Register a method to invoke after deserializing an object. The method receives the deserialized 
    # data and returns the processed data
    @post_load
    def post_load(self, data, **kwargs):
        return Person(**data)

Processing

def create_person_dump(gender: str) -> dict:
    person_dump: dict = {
            'first_name': 'David',
            'last_name': 'Doe',
            'birthday': '2022-12-31',
            'gender': gender
    }
    return person_dump
 
 
def create_person(gender: Gender) -> Person:
    return Person(first_name='David', last_name='Doe', birthday=date(2022, 12, 31), gender=gender)
 
 
def load_person_from_dicts():
    print(f'load_person_from_dicts()')
    # dict to person
    person_dump: dict = create_person_dump(gender='MALE')
    person: Person = PersonSchema().load(person_dump)
    print(f'person={person}')
    # list of dict to list of person
    person_dump: list[dict] = [create_person_dump(gender='MALE'), create_person_dump(gender='UNKNOWN')]
    persons: list[Person] = PersonSchema().load(person_dump, many=True)
    print(f'persons={persons}')
 
 
def dump_person_to_dicts():
    print(f'dump_person_to_dicts()')
    # person to dict  
    person: Person = create_person(gender=Gender.MALE)
    person_dump: dict = PersonSchema().dump(person)
    print(f'person_dump={person_dump}')
    # list of person to list of dict 
    persons: list[Person] = [create_person(gender=Gender.MALE), create_person(gender=Gender.UNKNOWN)]
    persons_dump: list[dict] = PersonSchema().dump(persons, many=True)
    print(f'persons_dump={persons_dump}')
 
 
load_person_from_dicts()
dump_person_to_dicts()

Output:

load_person_from_dicts()
person=Person(first_name='David', last_name='Doe', birthday=datetime.date(2022, 12, 31), gender=<Gender.MALE: 1>)
persons=[Person(first_name='David', last_name='Doe', birthday=datetime.date(2022, 12, 31), gender=<Gender.MALE: 1>), Person(first_name='David', last_name='Doe', birthday=datetime.date(2022, 12, 31), gender=<Gender.UNKNOWN: 3>)]
dump_person_to_dicts()
person_dump={'first_name': 'David', 'last_name': 'Doe', 'birthday': '2022-12-31', 'gender': 'MALE'}
persons_dump=[{'first_name': 'David', 'last_name': 'Doe', 'birthday': '2022-12-31', 'gender': 'MALE'}, {'first_name': 'David', 'last_name': 'Doe', 'birthday': '2022-12-31', 'gender': 'UNKNOWN'}]

A mapping with load object customization: @pre_load to adjust the input data and @post_load to load the adjusted model

Model and mapping:

class PersonSchema(Schema):
    class Meta:
        unknown = EXCLUDE
 
    first_name = fields.Str()
    last_name = fields.Str()
    birthday = fields.Date()
    gender = fields.Enum(Gender)
 
    @post_load
    def post_load(self, data, **kwargs):
        return Person(**data)
 
    @pre_load
    def pre_load(self, data, **kwargs):
        gender: str = data['gender']['type']
        data['gender'] = gender
        return data

Processing

person_dump: dict = {
        'first_name': 'a',
        'last_name': 'a',
        'birthday': '2022-12-31',
        'gender': {
                'description': 'a man or a boy',
                'type': 'MALE'
        }
}
person: Person = PersonSchema().load(person_dump)
print(f'person={person}')

Output:

person=Person(first_name='a', last_name='a', birthday=datetime.date(2022, 12, 31), gender=<Gender.MALE: 'a man or a boy'>)

A mapping with dump customization: @post_dump to adjust the dumped data

Model and mapping:

from dataclasses import dataclass
from datetime import date
from enum import Enum
 
from marshmallow import EXCLUDE
from marshmallow import Schema
from marshmallow import fields
from marshmallow import post_dump
 
 
class Gender(Enum):
    MALE = 'a man or a boy'
    FEMALE = 'a woman or a girl'
    UNKNOWN = "we don't know"
 
 
@dataclass()
class Person:
    first_name: str
    last_name: str
    birthday: date
    gender: Gender
 
 
class PersonSchema(Schema):
    class Meta:
        unknown = EXCLUDE
 
    first_name = fields.Str()
    last_name = fields.Str()
    birthday = fields.Date()
 
    @post_dump(pass_original=True)
    def post_dump(self, data: dict, original_data: Person, **kwargs):
        data['gender'] = {
                'description': original_data.gender.value,
                'type': original_data.gender.name
        }
        return data

Possessing:

person = Person(first_name='David', last_name='Doe', birthday=date(2022, 12, 31), gender=Gender.MALE)
dump: dict = PersonSchema().dump(person)
print(f'dump={dump}')

Output:

dump={'first_name': 'David', 'last_name': 'Doe', 'birthday': '2022-12-31', 'gender': {'description': 'a man or a boy', 'type': 'MALE'}}
Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *