简体   繁体   English

Flask-Restx 未将枚举字段类型转换为 JSON

[英]Flask-Restx not converting enum field type to JSON

I need help with Enum field type as it is not accepted by Swagger and I am getting error message **TypeError: Object or Type eGameLevel is not JSON serializable** .我需要 Enum 字段类型的帮助,因为它不被 Swagger 接受,并且我收到错误消息**TypeError: Object or Type eGameLevel is not JSON serializable** Below is the complete set of code for table.以下是表格的完整代码集。 Complete set of code with DB table and sqlalchemy settings is provided.提供了包含 DB 表和 sqlalchemy 设置的完整代码集。 I already tried it with Marshmallow-Enum Flask package and it didn't worked.我已经用 Marshmallow-Enum Flask package 进行了尝试,但没有成功。 Looking for kind help with some explanation about the solution so I can learn it well.寻求有关解决方案的一些解释的帮助,以便我可以很好地学习它。 :-) :-)

I am using MySQL with the Flask.我正在使用 MySQL 和 Flask。 In Postgres its pretty easy to manage all the choice fields.在 Postgres 中,管理所有选择字段非常容易。 All I need is a working example or a link to repository where MySQL choice fields are showing up in swagger drop down.我需要的只是一个工作示例或存储库链接,其中 MySQL 选择字段显示在 swagger 下拉列表中。

My Model:我的 Model:

import enum
from app import db
from typing import List


class eGameLevel(enum.Enum):
    BEGINNER    =   'Beginner'
    ADVANCED    =   'Advanced'


class Game(Base):
    __tablename__ = 'game_stage'

    id                              = db.Column(db.Integer(), primary_key=True)
    game_level= db.Column(db.Enum(eGameLevel), 
             default=eGameLevel.BEGINNER, nullable=False)
    user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    user = db.relationship('User', backref='game__level_submissions', lazy=True)

def __init__(self, game_level, user_id):
    self.game_level = game_level
    self.user_id = user_id

def __repr__(self):
    return 'Game(game_level%s, ' \
           'user_id%s'%(self.game_level,
                        self.user_id)
def json(self):
    return {'game_level':self.game_level,
            'user_id':self.user_id}

@classmethod
def by_game_id(cls, _id):
    return cls.query.filter_by(id=_id)

@classmethod
def find_by_game_level(cls, game_level):
    return cls.query.filter_by(game_level=game_level)

@classmethod
def by_user_id(cls, _user_id):
    return cls.query.filter_by(user_id=_user_id)

@classmethod
def find_all(cls) -> List["Game"]:
    return cls.query.all()

def save_to_db(self) -> None:
    db.session.add(self)
    db.session.commit()

def delete_from_db(self) -> None:
    db.session.delete(self)
    db.session.commit()

My Schema我的架构

from app import ma
from app.models import Gode

class GameSchema(ma.SQLAlchemyAutoSchema):
    game = ma.Nested('GameSchema', many=True)
    class Meta:
        model =  Game
        load_instance = True
        include_fk= True

My Resources:我的资源:

from flask_restx import Resource, fields, Namespace
from app.models import Game
from app import db
from app.schemas import GameSchema

GAME_REQUEST_NOT_FOUND = "Game request not found."
GAME_REQUEST_ALREADY_EXSISTS = "Game request '{}' Already exists."

game_ns = Namespace('Game', description='Available Game Requests')
games_ns = Namespace('Game Requests', description='All Games Requests')


game_schema = GameSchema()
games_list_schema = GameSchema(many=True)


gamerequest = game_ns.model('Game', {
    'game_level': fields.String('Game Level: Must be one of: BEGINNER, ADVANCED.'),
    'user_id': fields.Integer,

})


class GameRequestsListAPI(Resource):
    @games_ns.doc('Get all Game requests.')
    def get(self):
        return games_list_schema.dump(Game.find_all()), 200
    @games_ns.expect(gamerequest)
    @games_ns.doc("Create a Game request.")
    def post(self):
        game_json = request.get_json()
        game_data = game_schema.load(game_json)
        game_data.save_to_db()

        return game_schema.dump(game_data), 201

Instead of trying to manage Enum fields for MySQL schema I suggest to use another table with backref to your eGameLevel.我建议不要尝试管理 MySQL 模式的枚举字段,而是使用另一个带有 backref 的表到您的 eGameLevel。 You can get rid of this whole fuss and also in future if you needed to add another option in your choice field you won't have to hardcode it.您可以摆脱这整个大惊小怪,并且将来如果您需要在您的选择字段中添加另一个选项,您将不必对其进行硬编码。

Simply create a main table as Game and sub table as eGameLevel (with only one string field).只需将主表创建为 Game,将子表创建为 eGameLevel(只有一个字符串字段)。 You will be able to access choices from your Game table.您将能够从您的游戏桌访问选项。

Whenever I get stuck I go to basics as mentioned in here .每当我被卡住时,我 go 到 这里提到的基础知识。

Instead of using Enum:而不是使用枚举:

class eGameLevel(enum.Enum):
    BEGINNER    =   'Beginner'
    ADVANCED    =   'Advanced'

You can make use of dictionary:您可以使用字典:

eGameLevel = {"BEGINNER": 1, "ADVANCED": 2}

Then you can replace enum type for sql data model to String type as:然后您可以将 sql 数据 model 的枚举类型替换为字符串类型:

game_level= db.Column(db.String(), 
             default=eGameLevel["BEGINNER"], nullable=False)

And make appropriate checks using the defined dictionary throughout application.并在整个应用程序中使用定义的字典进行适当的检查。 This will also solve issues with alembic as well for making db migrations.这也将解决 alembic 以及进行数据库迁移的问题。

I couldn't run your example, it seems that some files are missing and the structure of your project is not clear to me.我无法运行您的示例,似乎缺少某些文件,并且我不清楚您的项目结构。 Could you share a repository with all the source code and a requirements file?您可以与所有源代码和需求文件共享一个存储库吗? I made a small example just to test the serialization of an Enum我做了一个小例子只是为了测试一个枚举的序列化

from enum import Enum

import sqlalchemy as sa
from flask import Flask
from flask_restx import Api, Namespace, Resource
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker


class eGameLevel(str, Enum):
    BEGINNER = "Beginner"
    ADVANCED = "Advanced"


engine = sa.create_engine("sqlite:///:memory:")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()


class Game(Base):
    __tablename__ = "game"
    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
    level = sa.Column(sa.Enum(eGameLevel), default=eGameLevel.BEGINNER, nullable=False)

    def __repr__(self):
        return f"Game(id={self.id}, level={self.level})"

    def json(self):
        data = {"id": self.id, "level": self.level}
        return data


Base.metadata.create_all(engine)

g1 = Game(level=eGameLevel.BEGINNER)
g2 = Game(level=eGameLevel.ADVANCED)

session.add_all([g1, g2])
session.commit()
query_content = session.query(Game).all()

games_ns = Namespace("Game Requests", description="All Games Requests")


app = Flask(__name__)
api = Api(app)


@api.route("/game")
class GameRequestsListAPI(Resource):
    @games_ns.doc("Get all Game requests.")
    def get(self):
        data = [x.json() for x in query_content]
        return data, 200


app.run(debug=True)

This example works and I think the serialization is possible due to the str in the Enum declaration: class eGameLevel(str, Enum) .此示例有效,我认为由于 Enum 声明中的str ,序列化是可能的: class eGameLevel(str, Enum)

If you share the whole source code and I can give it a try to fix it.如果您共享整个源代码,我可以尝试修复它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM