marshmallow-sqlalchemy
marshmallow-sqlalchemy copied to clipboard
validation issue with Enum column
I use the serializer.load() function to validate my data and instantiate the SQLAlchemy object. If I try to set the Enum field as an Integer and load, then it throws:
venv/lib/python2.7/site-packages/marshmallow_sqlalchemy/schema.py:186: in load
ret = super(ModelSchema, self).load(data, *args, **kwargs)
venv/lib/python2.7/site-packages/marshmallow/schema.py:568: in load
result, errors = self._do_load(data, many, partial=partial, postprocess=True)
venv/lib/python2.7/site-packages/marshmallow/schema.py:648: in _do_load
index_errors=self.opts.index_errors,
venv/lib/python2.7/site-packages/marshmallow/marshalling.py:295: in deserialize
index=(index if index_errors else None)
venv/lib/python2.7/site-packages/marshmallow/marshalling.py:68: in call_and_store
value = getter_func(data)
venv/lib/python2.7/site-packages/marshmallow/marshalling.py:288: in <lambda>
data
venv/lib/python2.7/site-packages/marshmallow/fields.py:266: in deserialize
self._validate(output)
venv/lib/python2.7/site-packages/marshmallow/fields.py:196: in _validate
r = validator(value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Length(min=None, max=9, equal=None, error=None)>, value = 123
def __call__(self, value):
> length = len(value)
E TypeError: object of type 'int' has no len()
I assume this should instead just return the normal error instead of throwing an exception.
Here is my model class. I use the Node.create() method to to the validation/instantiation:
from marshmallow_sqlalchemy import ModelSchema
# SQLAlchemy session provider
from app import db
class Node(db.Model):
id = db.Column(db.Integer, primary_key=True)
state = db.Column(db.Enum('allocated', 'free', 'error'), default='free')
@staticmethod
def create(data):
return NodeSerializer.load(data, session=db.session)
class NodeSchema(ModelSchema):
class Meta:
model = Node
NodeSerializer = NodeSchema()
Example failing code using above model class
data = {'state': 123}
node, err = Node.create(data) # Raises TypeError
Did you find a solution for this?
I ran into this issue as well. If you want to serialize by name, I used:
import enum
from flask import Flask
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from marshmallow_sqlalchemy import ModelConverter as _ModelConverter
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
app = Flask(__name__)
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Enum(ma.Field):
default_error_messages = {
'invalid_type': 'Invalid type for value.',
'one_of': 'Must be one of: {choices}.'}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.enum = self.metadata.get('enum', None)
if not self.enum:
raise ValueError('Enum type not set.')
if not issubclass(self.enum, enum.Enum):
raise TypeError('Invalid type for enum.')
def _deserialize(self, value, *args, **kwargs):
return self._validated(value)
def _serialize(self, value, *args, **kwargs):
if not isinstance(value, self.enum):
raise self.make_error('invalid_type')
return value.name
def _validate(self, value, *args, **kwargs):
if not isinstance(value, self.num):
super()._validate(value, *args, **kwargs)
else:
super()._validate(value.name, *args, **kwargs)
def _validated(self, value):
if value is None:
return None
if isinstance(value, self.enum):
return value
try:
return getattr(self.enum, value)
except AttributeError:
choices = ', '.join(value.name for value in self.enum)
raise self.make_error('one_of', choices=choices)
class ModelConverter(_ModelConverter):
SQLA_TYPE_MAPPING = dict(
list(_ModelConverter.SQLA_TYPE_MAPPING.items()) +
[(sa.Enum, Enum), (mysql.ENUM, Enum)])
class PetType(enum.Enum):
bird = 1
cat = 2
dog = 3
class Pet(db.model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.Enum(PetType), nullable=False)
class PetSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model_converter = ModelConverter
type = ma.auto_field(enum=PetType)
The largest issue was with enum.Enum
not being serializable by default, SQLAlchemy serializing by name, and Marshmallow serializing by value. I wanted my enums to serialize by name and ended up with the above. If you want to serialize by value, you'll need to make some modifications.