Объявляя 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'}]}