How to handle one-to-many relationship during HATEOAS serialization in Marshmallow?

128 views Asked by At

Trying to implement a rather simple REST API in Python (SQLAlchemy + Marshmallow) using HATEOAS resource linking, I am stuck when attempting to create "smart hyperlinks" for one-to-many relationships.

Let's consider the classic book example: One book has exactly one publisher, but 1 to n authors. To keep it simple, in my use case it would be sufficient if every author could just write one book. My class structure could then look like this:

models/book.py:

class Book(db.Model):

    __tablename__ = 'book'
    book_id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255))
    publisher_id = db.Column('publisher_id', db.Integer, db.ForeignKey('publisher.publisher_id'))
    authors = db.relationship('Author', back_populates='book_id')
 
    # some more methods ...

    @staticmethod
    def get_by_id(book_id):
        return Book.query.get(book_id)

models/author.py:

class Author(db.Model):

    __tablename__ = 'author'

    author_id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255))
    book_id = db.Column('book_id', db.Integer, db.ForeignKey('book.book_id'))
    booking = db.relationship('Book', back_populates='authors')

    # some more methods ..

    @staticmethod
    def get_by_id(author_id):
        return Authors.query.get(author_id)

models/book_schema.py:

class BookSchema(ma.SQLAlchemyAutoSchema):

    class Meta:
        model = Book

    book_id=fields.Integer()
    title = fields.String()

    # Smart hyperlinking
    _links = ma.Hyperlinks(
        {
            "self": {"href": ma.AbsoluteURLFor("book", book_id="<booking_id>")},
            "publisher": {"href": ma.AbsoluteURLFor("publishers", publisher_id="<publisher_id>")},
            "authors": {"href": ma.AbsoluteURLFor("authors", author_id="<authors>")} #!!!
        }
    )

app/__init__.py:


def create_app(config_name):

    #...

    api.add_resource(Book, '/books/<int:book_id>', endpoint='books')

    #...

The line in book_schema.py denoted with #!!! is the one causing trouble. I do understand, that authors is a collection and therefore there is no single author_id. But I haven't found any way to add this collection to the hyperlinks.

What I would like to get when querying a book at, let's say, /books/123 looks like this:

{
    "book_id": 123,
    "title": "How to think of a good book title"
    "_links": {
        "publisher": {
            "href": "https://localhost:5000/publishers/4711"
        },
        "authors": [
            { "href": "https://localhost:5000/authors/42" },
            { "href": "https://localhost:5000/authors/333" },
            { "href": "https://localhost:5000/authors/1337" }
        ]
    }
}

Concerning everything else but the one-to-many relation, it works very well. The publisher relation is also populated as expected. I can also verify, that the authors of a book are loading correctly from the database (by debugging in get_by_id of the Book class).

Additionally, I have found this GitHub issue, which, if I got it right, addresses my problem as well.

So, finally, my question is: Is there any way I can achieve the collection serialization? If not by standard means of Marshmallow (I didn't find anything there), then perhaps by any sane kind of post-processing the response before it is sent to the client?

Thanks a lot in advance!

1

There are 1 answers

1
Shivangi Singh On
class Book(db.Model):
    __tablename__ = 'book'
    book_id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255))
    publisher_id = db.Column('publisher_id', db.Integer, db.ForeignKey('publisher.publisher_id'))
    authors = db.relationship('Author', back_populates='book_id')
 
    # some more methods ...

    @staticmethod
    def get_by_id(book_id):
        return Book.query.get(book_id)