Посмотрите на marshmallow-sqlalchemy , так как он делает именно то, что вы ищете.
Я настоятельно рекомендую не включать вашу сериализацию непосредственно в вашу модель, так как в конечном итоге у вас будет двасервисы, запрашивающие те же данные, но сериализованные другим способом (включая, например, меньшее или большее количество вложенных отношений для повышения производительности), и вы либо получите (1) множество ошибок, которые пропустит ваш набор тестов, если только вы не 'проверяя буквально каждое поле или (2) больше сериализованных данных, чем вам нужно, и вы столкнетесь с проблемами производительности по мере масштабирования вашего приложения.
С помощью marshmallow-sqlalchemy вам необходимо определитьсхема для каждой модели, которую вы хотите сериализовать.Да, это немного лишний шаблон, но, поверьте мне, в конечном итоге вы будете намного счастливее.
Мы создаем приложения, используя такие параметры как flask-sqlalchemy и marshmallow-sqlalchemy (также настоятельно рекомендуем factory_boy , чтобы вы могли смоделировать свой сервис и написать модульные тесты вместо интеграционных тестов, которые должны касаться базы данных):
# models
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship('Parent', back_populates='children',
foreign_keys=[parent_id])
# schemas. Don't put these in your models. Avoid tight coupling here
from marshmallow_sqlalchemy import ModelSchema
import marshmallow as ma
class ParentSchema(ModelSchema):
children = ma.fields.Nested(
'myapp.schemas.child.Child', exclude=('parent',), many=True)
class Meta(ModelSchema.Meta):
model = Parent
strict = True
dump_only = ('id',)
class ChildSchema(ModelSchema):
parent = ma.fields.Nested(
'myapp.schemas.parent.Parent', exclude=('children',))
class Meta(ModelSchema.Meta):
model = Child
strict = True
dump_only = ('id',)
# services
class ParentService:
'''
This service intended for use exclusively by /api/parent
'''
def __init__(self, params, _session=None):
# your unit tests can pass in _session=MagicMock()
self.session = _session or db.session
self.params = params
def _parents(self) -> typing.List[Parent]:
return self.session.query(Parent).options(
joinedload(Parent.children)
).all()
def get(self):
schema = ParentSchema(only=(
# highly recommend specifying every field explicitly
# rather than implicit
'id',
'children.id',
))
return schema.dump(self._parents()).data
# views
@app.route('/api/parent')
def get_parents():
service = ParentService(params=request.get_json())
return jsonify(data=service.get())
# test factories
class ModelFactory(SQLAlchemyModelFactory):
class Meta:
abstract = True
sqlalchemy_session = db.session
class ParentFactory(ModelFactory):
id = factory.Sequence(lambda n: n + 1)
children = factory.SubFactory('tests.factory.children.ChildFactory')
class ChildFactory(ModelFactory):
id = factory.Sequence(lambda n: n + 1)
parent = factory.SubFactory('tests.factory.parent.ParentFactory')
# tests
from unittest.mock import MagicMock, patch
def test_can_serialize_parents():
parents = ParentFactory.build_batch(4)
session = MagicMock()
service = ParentService(params={}, _session=session)
assert service.session is session
with patch.object(service, '_parents') as _parents:
_parents.return_value = parents
assert service.get()[0]['id'] == parents[0].id
assert service.get()[1]['id'] == parents[1].id
assert service.get()[2]['id'] == parents[2].id
assert service.get()[3]['id'] == parents[3].id