Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use marshmallow to serialize a custom sqlalchemy field?

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?

like image 923
arthas_dk Avatar asked Oct 16 '15 00:10

arthas_dk


People also ask

What is marshmallow serialization?

Introduction. Marshmallow, stylized as “marshmallow”, is an object-relational mapping library which is used to convert objects to and from Python data types. It is often used alongside SQLAlchemy, an ORM that maps database schemas to Python objects.

What is marshmallow SQLAlchemy?

Marshmallow is a Python library that converts complex data types to and from Python data types. It is a powerful tool for both validating and converting data.


2 Answers

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

like image 60
Jair Perrut Avatar answered Oct 09 '22 19:10

Jair Perrut


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.

like image 27
dukebody Avatar answered Oct 09 '22 18:10

dukebody