How to use marshmallow to serialize a custom sqlal

2019-02-14 07:45发布

问题:

I just start a simple project called flask_wiki this days and I'm using some flask extensions as the follows:

  • Flask-SQLAlchemy
  • Flask-Restful
  • MarshMallow

Well, I just discovered that the MarshMallow project provides a class called 'ModelSchema', which reads all fields from my SQLAlchemy Model and provide a fully automated (de)serialializer.

In my case, I created a 'GUID' Field which is RDBM agnostic and inserted it on my Sqlalchemy model as follows:

from flask.ext.sqlalchemy import SQLAlchemy
from flask_wiki.backend.custom_fields import GUIDField

class Page(db.Model):
    """
    Implements the Page Model.
    """
    guid = db.Column(GUIDField, primary_key=True, default=uuid.uuid4)
    name = db.Column(db.String, nullable=False)
    raw_content = db.Column(db.Text)
    rendered_content = db.Column(db.Text)

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return self.name

The GUIDField is implemented is this way:

from sqlalchemy.types import TypeDecorator, CHAR
import uuid


class GUIDField(TypeDecorator):
    # Platform independent GUID Implementation that uses little endianess.
    impl = CHAR

    def load_dialect_impl(self, dialect):
        return dialect.type_descriptor(CHAR(32))

    def process_bind_param(self, value, dialect):
        if value is None:
            return value
        else:
            if isinstance(value, uuid.UUID):
                return value.bytes_le

    def process_result_value(self, value, dialect):
        if value is None:
            return value
        else:
            return uuid.UUID(bytes_le=value)

The code for create test objects (through mixer) is working; all the guids are generated and verified correctly.

With this in mind, a just created the following MarshMallow Serializer Field:

from marshmallow import fields
import uuid


class GUIDSerializationField(fields.Field):
    def _serialize(self, value, attr, obj):
        if value is None:
            return value
        else:
            if isinstance(value, uuid.UUID):
                return str(value)
            else:
                return None

Finally, I created the SerializerClass:

from flask_wiki.backend.backend import marsh
from flask_wiki.backend.custom_serialization_fields import GUIDSerializationField
from flask_wiki.backend.models import Page
from marshmallow import fields


class PageSchema(marsh.ModelSchema):
    class Meta:
        model = Page

    guid = GUIDSerializationField()


page_schema = PageSchema()
pages_schema = PageSchema(many=True)

I tried to use this last code with and without inserting the guid field, but in all cases the following error occurs:

Traceback (most recent call last):
  File "/home/arthas/dev/flask-wiki/flask_wiki/wiki.py", line 3, in <module>
    from flask_wiki.backend import backend
  File "/home/arthas/dev/flask-wiki/flask_wiki/backend/__init__.py", line 1, in <module>
    import flask_wiki.backend.routes
  File "/home/arthas/dev/flask-wiki/flask_wiki/backend/routes.py", line 2, in <module>
    from flask_wiki.backend.views import PageView
  File "/home/arthas/dev/flask-wiki/flask_wiki/backend/views.py", line 3, in <module>
    from flask_wiki.backend.serializers import pages_schema, page_schema
  File "/home/arthas/dev/flask-wiki/flask_wiki/backend/serializers.py", line 7, in <module>
    class PageSchema(marsh.ModelSchema):
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow/schema.py", line 116, in __new__
    dict_cls=dict_cls
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow_sqlalchemy/schema.py", line 53, in get_declared_fields
    declared_fields = mcs.get_fields(converter, opts)
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow_sqlalchemy/schema.py", line 77, in get_fields
    return converter.fields_for_model(opts.model, fields=opts.fields, exclude=opts.exclude)
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow_sqlalchemy/convert.py", line 75, in fields_for_model
    field = self.property2field(prop)
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow_sqlalchemy/convert.py", line 93, in property2field
    field_class = self._get_field_class_for_property(prop)
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow_sqlalchemy/convert.py", line 151, in _get_field_class_for_property
    field_cls = self._get_field_class_for_column(column)
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow_sqlalchemy/convert.py", line 121, in _get_field_class_for_column
    return self._get_field_class_for_data_type(column.type)
  File "/home/arthas/env/wiki/lib/python3.5/site-packages/marshmallow_sqlalchemy/convert.py", line 143, in _get_field_class_for_data_type
    'Could not find field column of type {0}.'.format(types[0]))
marshmallow_sqlalchemy.exceptions.ModelConversionError: Could not find field column of type <class 'flask_wiki.backend.custom_fields.GUIDField'>.

So, I finally ask: how to use marshmallow to serialize a custom sqlalchemy field?

回答1:

You need to create your own Converter. Try something like this:

import uuid
from flask_wiki.backend.models import Page
from flask_wiki.backend.backend import marsh
from marshmallow_sqlalchemy.convert import ModelConverter
from flask_wiki.backend.custom_fields import GUIDField
from marshmallow import fields


#Here you are overwriting the list of types of the SQLAlchemy, adding your custom type 
class GUIDConverter(ModelConverter):
    SQLA_TYPE_MAPPING = dict(
        list(ModelConverter.SQLA_TYPE_MAPPING.items()) + 
        [(GUIDField, fields.Str)] 
    )

class GUIDSerializationField(fields.Field):
    def _serialize(self, value, attr, obj):
        if value is None:
            return value
        else:
            if isinstance(value, uuid.UUID):
                return str(value)
            else:
                return None

class PageSchema(marsh.ModelSchema):
    class Meta:
        model = Page        
        model_converter = GUIDConverter #Tell to Marshmallow to use your custom converter for this model

    guid = GUIDSerializationField(attribute="guid")

You can also see this link for help. I hope it helped and sorry for my bad english



回答2:

According to what I've read in the docs, you can specify a model_converter attribute in your ModelSchema Meta class. The ModelConverter class has a SQLA_TYPE_MAPPING attribute you can override in a subclass to add your custom GUID field to the types detected by the automatic schema generator.

That said, I've never used it so I don't know if this will work or not.