How to get marshmallow to give out a List of nested classes with 1 entry pulled out

351 Views Asked by At

I am trying to get flask-marshmallow to give me a list of translated entries from a Table called Type that is associated to a table called Language. I cannot figure out how to nest the responses in a list. I gave an example of my current output JSON along with my goal JSON.

class TypeSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Type
        ordered = True
    language = ma.String(attribute="language.language", dump_only=True)
    translated_languages = ma.List(ma.String(attribute="language"))

class Language(Updateable, db.Model):
    __tablename__ = 'language'

    id = sqla.Column(sqla.Integer, primary_key=True)
    language = sqla.Column(sqla.String(2), nullable=False)

    type_id = sqla.Column(sqla.Integer, sqla.ForeignKey('type.id'), index=True)
    type = sqla_orm.relationship('Type', foreign_keys='Language.type_id', back_populates='translated_languages')

    types_id = sqla.Column(sqla.Integer, sqla.ForeignKey('type.id'), index=True)
    types = sqla_orm.relationship('Type', foreign_keys='Language.types_id', back_populates='language')


class Type(Updateable, db.Model):
    __tablename__ = 'type'

    id = sqla.Column(sqla.Integer, primary_key=True)

    translated_languages = sqla_orm.relationship('Language', back_populates='type', foreign_keys='Language.type_id')
    language = sqla_orm.relationship('Language', back_populates='types', uselist=False, foreign_keys='Language.types_id')

Here is the resulting JSON

    {
      "id": 1,
      "translated_languages": [
        "<api.models.Language object at 0x00000171D3730490>", 
        "<api.models.Language object at 0x00000171D3730400>", 
        "<api.models.Language object at 0x00000171D3730520>"
      ], 
      "language": "en", 
    }

Here is my goal JSON

    {
      "id": 1,
      "translated_languages": [
        "fr", 
        "es", 
        "de"
      ], 
      "language": "en", 
    }
1

There are 1 best solutions below

4
snakecharmerb On BEST ANSWER

You can do this with a combination of marshmallow, marshmallow-sqlalchemy and flask-marshmallow. Nested objects are usually serialised as dictionaries to enable easy round-tripping of the data. Since you want simple strings rather than dicts we can use marshmallow's post_dump decorator to massage the data after it has been generated.

import pprint as pp

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import marshmallow as mm
import marshmallow_sqlalchemy as ms
import flask_marshmallow as ma

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
db = SQLAlchemy(app)

# Model definitions omitted.


class LanguageSchema(ma.sqla.SQLAlchemyAutoSchema):
    class Meta:
        model = Language
    fields = ('language',)


class TypeSchema(ma.sqla.SQLAlchemyAutoSchema):
    class Meta:
        model = Type
        include_relationships = True

    language = ma.fields.fields.String(attribute='language.language', dump_only=True)
    translated_languages = ms.fields.Nested(
        LanguageSchema, many=True, exclude=('id',)
    )

    @mm.post_dump
    def patch_translated_languages(self, data, many, **kwargs):
        """Represent translated languages as simple strings."""
        language_names = [d['language'] for d in data['translated_languages']]
        data['translated_languages'] = language_names
        return data

# Flask 3.x requires app_context.
with app.app_context():
    db.create_all()
    de, en, es, fr = [
        Language(language=lang) for lang in ['de', 'en', 'es', 'fr']
    ]
    type_ = Type(language=en, translated_languages=[fr, es, de])
    db.session.add(type_)
    db.session.commit()

with app.app_context():
    type_ = Type.query.first()
    dump = TypeSchema().dump(type_)
    pp.pprint(dump)

It wasn't clear to me what your imports were, so I've included mine for clarity.