Возвращать объединенные таблицы в формате JSON с помощью SQLAlchemy и Flask jsonify - PullRequest
0 голосов
/ 25 апреля 2018

Я создаю конечную точку в python, которая будет возвращать мой каталог со всеми элементами в каждой категории. Я хотел бы объединить две таблицы ( Catalog и Items ) в моей базе данных на основе ограничения внешнего ключа и вывести его в формате JSON.

В настоящее время я пробовал

@app.route('/catalog/JSON/')
@login_required
  def getCatalog():
  categories = session.query(Category).join(Item).all()
  return jsonify(Catalog=[r.serializable for r in categories])

Тем не менее, это возвращает только данные об элементе и данные о каталоге с таким именем.

Мои текущие модели

class Category(Base):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
name = Column(String(32), nullable=False)

@property
def serializable(self):
    return {'id': self.id, 'username': self.username}

class Item(Base):
__tablename__ = 'item'
id = Column(Integer, primary_key=True)
name = Column(String(32), nullable=False)
description = Column(String(255))
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User)
category_id = Column(Integer, ForeignKey('category.id'))
category = relationship(Category)

@property
def serializable(self):
    return {
        'id': self.id,
        'name': self.name,
        'description': self.description,
        'category_id': self.category_id,
        'user_id': self.user_id
    }

Я новичок в фляге, поэтому не уверен на 100%, что то, что я пытаюсь сделать, уже решено фреймворком или sqlalchemy.

1 Ответ

0 голосов
/ 25 апреля 2018

Объявляя category = relationship(Category) в Item, экземпляры Item имеют атрибут category, который соответствует правильной строке в базе данных. В фоновом режиме при необходимости будет извлечена строка из базы данных. Вы должны быть осторожны с этим при обработке коллекций элементов, так как это может привести к вызову базы данных один раз для каждого элемента - это называется проблемой n + 1.

Поэтому, чтобы ответить на вопрос «Как включить self.category в сериализуемый элемент?», Вы можете буквально написать:

class Item(Base):
    ...

    @property
    def serializable(self):
        return {
            'id': self.id,
            'name': self.name,
            ...
            'category': self.category.serializable
        }

Но это, вероятно, не очень хорошая идея, так как вы можете случайно вызвать дополнительный вызов базы данных при записи item.serializable.

В любом случае мы действительно хотим перечислить все элементы в категории, поэтому нам нужно использовать отношение внешнего ключа в другом направлении. Это делается путем добавления аргумента backref в отношение:

category = relationship(Category, backref='items')

и теперь Category экземпляры будут иметь атрибут items. Тогда вот как написать getCatalog:

def getCatalog():
    categories = Session().query(Category).options(joinedload(Category.items)).all()
    return dict(Catalog=[dict(c.serializable, items=[i.serializable
                                                     for i in c.items])
                         for c in categories])

Здесь .options(joinedload(Category.items)) выполняет SQL JOIN для предварительного извлечения элементов, чтобы c.items не вызывало дополнительных запросов к базе данных. (Спасибо Илья)

Вот полный код для полной демонстрации:

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, joinedload

engine = create_engine('sqlite://', echo=True)

Session = sessionmaker(bind=engine)

Base = declarative_base()


class Category(Base):
    __tablename__ = 'category'
    id = Column(Integer, primary_key=True)
    name = Column(String(32), nullable=False)

    @property
    def serializable(self):
        return {'id': self.id, 'name': self.name}


class Item(Base):
    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    name = Column(String(32), nullable=False)
    category_id = Column(Integer, ForeignKey('category.id'))
    category = relationship(Category, backref='items')

    @property
    def serializable(self):
        return {'id': self.id, 'name': self.name}


Base.metadata.create_all(engine)

category1 = Category(id=1, name='fruit')
category2 = Category(id=2, name='clothes')
session = Session()
session.add_all([category1, category2,
                 Item(id=1, name='apple', category=category1),
                 Item(id=2, name='orange', category=category1),
                 Item(id=3, name='shirt', category=category2),
                 Item(id=4, name='pants', category=category2)])
session.commit()


def getCatalog():
    categories = Session().query(Category).options(joinedload(Category.items)).all()
    return dict(Catalog=[dict(c.serializable, items=[i.serializable
                                                     for i in c.items])
                         for c in categories])


from pprint import pprint

pprint(getCatalog())

Повторяющийся SQL показывает, что в базу данных отправляется только один SELECT. Фактический результат:

{'Catalog': [{'id': 1,
              'items': [{'id': 1, 'name': 'apple'},
                        {'id': 2, 'name': 'orange'}],
              'name': 'fruit'},
             {'id': 2,
              'items': [{'id': 3, 'name': 'shirt'}, {'id': 4, 'name': 'pants'}],
              'name': 'clothes'}]}
...