marshmallow-sqlalchemy icon indicating copy to clipboard operation
marshmallow-sqlalchemy copied to clipboard

Support for Dictionary Collections (collection=attribute_keyed_dict)

Open jsormaz opened this issue 1 year ago • 1 comments

In sqlalchemy I can map a collection as a dictionary rather than a list: https://docs.sqlalchemy.org/en/20/orm/collection_api.html#dictionary-collections

Is this supported at all by marshmallow-sqlalchemy?

jsormaz avatar Apr 16 '24 19:04 jsormaz

You can deserialize to nested dictionaries for relationships by passing columns to auto_field

from pprint import pprint

import sqlalchemy as sa
from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker

from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"
    id = sa.Column(sa.Integer, primary_key=True)
    full_name = sa.Column(sa.String(255))

    blog_posts = relationship(lambda: BlogPost, back_populates="author")


class BlogPost(Base):
    __tablename__ = "blog_post"
    id = sa.Column(sa.Integer, primary_key=True)
    title = sa.Column(sa.String(255), nullable=False)

    author_id = sa.Column(sa.Integer, sa.ForeignKey(User.id), nullable=False)
    author = relationship(User)


class AuthorSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = User
        include_relationships = True

    blog_posts = auto_field(columns=("id", "title"))


engine = sa.create_engine("sqlite:///:memory:")
Session = sessionmaker(engine)

Base.metadata.create_all(engine)

with Session() as session:
    user = User(full_name="Freddie Mercury")
    post = BlogPost(title="Bohemian Rhapsody Revisited", author=user)

    session.add_all([user, post])
    session.commit()

    assert len(user.blog_posts) == 1
    session.refresh(user)

    dumped_user = AuthorSchema().dump(user)
    pprint(dumped_user, indent=2)
    # { 'blog_posts': [{'id': 1, 'title': 'Bohemian Rhapsody Revisited'}],
    #   'full_name': 'Freddie Mercury',
    #   'id': 1}

However, if User.blog_posts model is changed to use, attribute_keyed_dict, the serialized input is broken

class User(Base):
    __tablename__ = "user"
    id = sa.Column(sa.Integer, primary_key=True)
    full_name = sa.Column(sa.String(255))

    blog_posts = relationship(
        lambda: BlogPost,
        back_populates="author",
        collection_class=attribute_keyed_dict("id"),
    )
    dumped_user = AuthorSchema().dump(user)
    pprint(dumped_user, indent=2)
    # { 'blog_posts': [{'id': 1, 'title': 'Bohemian Rhapsody Revisited'}],
    #   'full_name': 'Freddie Mercury',
    #   'id': 1}

i'll leave this issue open as a feature request to support attribute_keyed_dict

sloria avatar Jan 12 '25 04:01 sloria