Как обрабатывать вложенные отношения в зефире-sqlalchemy

У меня есть проект, в котором я пытаюсь загрузить данные из JSON в базу данных Sqlalchemy с помощью пакета marshmallow-sqlalchemy. Модель содержит отношение "один ко многим" с дочерней моделью.

Используя классический пример автора со многими книгами:

class Book(Base):
    __tablename__ = "book"
    id = Column(Integer, primary_key=True)
    title = Column(String(50))

    author_id = Column(Integer, ForeignKey("author.id"), nullable=False)
    author = relationship("Author", backref=backref("books"))


class Author(Base):
    __tablename__ = "author"
    id = Column(Integer, primary_key=True)
    name = Column(String(250))

    books = relationship("Author", back_populates="author")


class BookSchema(ModelSchema):
    class Meta:
        model = Book
        sqla_session = Session


class AuthorSchema(ModelSchema):
    class Meta:
        model = Author
        sqla_session = Session

    books = fields.Nested(BookSchema, many=True)

И вход JSON

{
    "name": "Author A",
    "books": [
        {"title": "Book 1"},
        {"title": "Book 2"}
    ]
 }

Когда я пытаюсь загрузить этот JSON, используя следующее:

json_repr = {...}
author_schema = AuthorSchema()
obj_repr = author_schema.load(json_repr)

Возникает исключение, когда он пытается десериализовать документ, в котором говорится, что Book - непригодный для использования тип.

Я считаю, что происходит то, что десериализатор пытается создать объекты, используя что-то вроде

obj = model(**data)

который не будет работать с отношением один-ко-многим, так как экземпляры списка должны быть append()'к собственности Author.books.

Я не смог найти ни одного примера этой работы где-либо в Интернете. Каждый пример, который я видел, кажется, вкладывает один экземпляр, а не список. Есть ли рекомендуемый способ сделать это с помощью marshmallow-sqlalchemy, или я должен вернуться к использованию пакета прямого зефира и ручному добавлению объектов отношений с помощью методов @post_load.

1 ответ

У меня были подобные проблемы с этим старым постом. Удалось исправить этот пост под несколько иным фреймворком Flask + SQLAlchemy + Marshmallow-SQLAlchemy (версия 2). Отправленный код на случай, если полезно.

Большинство изменений в models.py

  1. Смена линии books = relationship("Book", back_populates="author")
  2. Используемый back_populates вместо backref как я получаю ошибку sqlalchemy.exc.ArgumentError: Error creating backref 'books' on relationship 'Book.author': property of that name exists on mapper 'mapped class Author->author

models.py

class Book(db.Model):
    __tablename__ = "book"
    id = Column(db.Integer, primary_key=True)
    title = Column(db.String(50))

    author_id = Column(db.Integer, db.ForeignKey("author.id"), nullable=False)
    author = relationship("Author", back_populates="books")


class Author(db.Model):
    __tablename__ = "author"
    id = Column(db.Integer, primary_key=True)
    name = Column(db.String(250))

    books = relationship("Book", back_populates="author")

schemas.py - в основном то же самое

class BookSchema(ModelSchema):
    class Meta:
        model = Book
        sqla_session = db.session


class AuthorSchema(ModelSchema):
    class Meta:
        model = Author
        sqla_session = db.session

    books = fields.Nested(BookSchema, many=True)

views.py

@api.route('/author/', methods=['POST'])
def new_author():
    schema = AuthorSchema()
    author = schema.load(request.get_json())
    db.session.add(author.data) # version 2 marshmallow
    db.session.commit()
    return jsonify({"success": True})

Похоже, что один из способов справиться с этим - добавить инициализатор в модель sqlalchemy, которая явно добавляется в коллекцию.

class Author(Model):
    __tablename__ = "author"

    def __init__(self, books=None, *args, **kwargs):
        super(Author, self).__init__(*args, **kwargs)
        books = books or []
        for book in books:
            self.books.append(book)

Тем не менее, любопытно, если есть лучшее решение там.

Другие вопросы по тегам