3

Long gone are the days of creating marshmallow schemas identical to my models. I found this excellent answer that explained how I could auto generate schemas from my SQA models using a simple decorator, so I implemented it and replaced the deprecated ModelSchema for the newer SQLAlchemyAutoSchema:

def add_schema(cls):
    class Schema(SQLAlchemyAutoSchema):
        class Meta:
            model = cls
    cls.Schema = Schema
return cls

This worked great... until I bumped into a model with a bloody Enum.

The error: Object of type MyEnum is not JSON serializable

I searched online and I found this useful answer.

But I'd like to implement it as part of the decorator so that it is generated automatically as well. In other words, I'd like to automatically overwrite all Enums in my model with EnumField(TheEnum, by_value=True) when generating the schema using the add_schema decorator; that way I won't have to overwrite all the fields manually.

What would be the best way to do this?

Ilja Everilä
  • 50,538
  • 7
  • 126
  • 127
jgozal
  • 1,480
  • 6
  • 22
  • 43

2 Answers2

1

I have found that the support for enum types that was initially suggested only works if OneOf is the only validation class that exists in field_details. I added in some argument parsing (in a rudimentary way by looking for choices after stringifying the results _repr_args() from OneOf) to check the validation classes to hopefully make this implementation more universally usable:

def add_schema(cls):
    class Schema(ma.SQLAlchemyAutoSchema):
        class Meta:
            model = cls

    fields = Schema._declared_fields

    # support for enum types
    for field_name, field_details in fields.items():
        if len(field_details.validate) > 0:
            check = str(field_details.validate[0]._repr_args)
            if check.__contains__("choices") :
                enum_list = field_details.validate[0].choices
                enum_dict = {enum_list[i]: enum_list[i] for i in range(0, len(enum_list))}
                enum_clone = Enum(field_name.capitalize(), enum_dict)
                fields[field_name] = EnumField(enum_clone, by_value=True, validate=validate.OneOf(enum_list))

    cls.Schema = Schema
    return cls

Thank you jgozal for the initial solution, as I really needed this lead for my current project.

neets
  • 11
  • 2
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 25 '21 at 05:42
0

This is my solution:

from marshmallow import validate
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from marshmallow_enum import EnumField
from enum import Enum

def add_schema(cls):
    class Schema(SQLAlchemyAutoSchema):
        class Meta:
            model = cls

    fields = Schema._declared_fields

    # support for enum types
    for field_name, field_details in fields.items():
        if len(field_details.validate) > 0:
            enum_list = field_details.validate[0].choices
            enum_dict = {enum_list[i]: enum_list[i] for i in range(0, len(enum_list))}
            enum_clone = Enum(field_name.capitalize(), enum_dict)
            fields[field_name] = EnumField(enum_clone, by_value=True, validate=validate.OneOf(enum_list))

    cls.Schema = Schema
    return cls

The idea is to iterate over the fields in the Schema and find those that have validation (usually enums). From there we can extract a list of choices which can then be used to build an enum from scratch. Finally we overwrite the schema field with a new EnumField.

By all means, feel free to improve the answer!

jgozal
  • 1,480
  • 6
  • 22
  • 43