首页 > 解决方案 > 使用flask + marshmallow + sqlalachemy 发送枚举值并将它们保存到数据库?

问题描述

我正在使用烧瓶 + 棉花糖 + sqlalchemy。

我必须:

要发送包含此内容的 http 请求:

{
    "first_name": "John"
    "last_name": "Doe"
    "gender": "male"
}

并将值作为整数保存在数据库中,无论0是男性还是1女性。

ID 性别
42 约翰 能源部 0

到目前为止的路:

要求.txt

...
Flask==2.0.1
flask-marshmallow==0.14.0
Flask-SQLAlchemy==2.5.1
marshmallow==3.13.0
marshmallow-enum==1.5.1
marshmallow-sqlalchemy==0.26.1
SQLAlchemy==1.4.22
...

架构.py

from .models import Users, Genders
from .extensions import ma


class SignUpSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Users

    first_name = ma.auto_field(data_key='firstName', required=True)
    last_name = ma.auto_field(data_key='lastName', required=True)
    gender = EnumField(enum=Genders, required=True)

视图.py

from flask import request
from flask.views import MethodView
from .schemas import SignUpSchema
from .models import Users, Genders
from .extensions import db


class SignUp(MethodView):
    methods = ['POST']

    def post(self):
        json_data = request.get_json()
        schema = SignUpSchema()
        data = schema.load(json_data)
        user = Users(**data)
        db.session.add(user)
        db.session.commit()
        return {'id': user.id}

模型.py

import enum
from sqlalchemy import Column,Integer, String, Enum
from .extensions import db


class Genders(enum.Enum):
    MALE = "male"
    FEMALE = "female"


class Users(db.Model):
    id = Column(Integer, primary_key=True)
    first_name = Column(String(100))
    last_name = Column(String(100))
    gender = Column(Enum(Genders))

当前状态:

{
    "firstName": "john",
    "lastName": "doe",
    "gender": "MALE"
}
ID 性别
42 约翰 母鹿 男性

我只是不知道如何更改请求和数据库插入中的枚举值。

标签: pythonflaskenumssqlalchemymarshmallow

解决方案


我设法通过实现自定义棉花糖字段并将 sqlalchemy 模型中的字段更改为Integer而不是Enum字段来解决该问题。这确实有一个缺点,即无法使用此自定义 marshmallow 序列化从数据库中检索到的性别GenderField

from marshmallow.fields import Field


class GenderField(Field):

    def _serialize(self, value, attr, obj, **kwargs):
        return value.value if value is not None else ''

    def _deserialize(self, value, attr, data, **kwargs):
        options = [e.value for e in Genders]
        genders = {val: i for i, val in enumerate(options)}
        try:
            return genders[value]
        except KeyError as err:
            raise ValidationError(f'Value must be one of: {options}')

编辑:

将棉花糖字段与 sqlalchemy 相结合TypeDecorator是我更喜欢的另一个解决方案。这样,我们将字符串拆分为棉花糖模式作为枚举元组中的第二个值,第一个值(int)进入数据库,整个类型作为烧瓶应用程序的枚举处理。

class Gender(Enum):
    MALE = 0, 'male'
    FEMALE = 1, 'female'

class TupleEnum(TypeDecorator):
    impl = Integer

    @property
    def python_type(self):
        return type(self.enum_type)

    def __init__(self, enum_type, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.enum_type = enum_type

    def process_bind_param(self, value: Enum, dialect):
        return value.value[0]

    def process_result_value(self, value: int, dialect):
        options = {e.value[0]: e for e in self.enum_type}
        return options[value]

    def process_literal_param(self, value: int, dialect):
        return self.process_bind_param(value, dialect)

推荐阅读