如何仅加载指定列并获取模型对象?(不使用延迟)
我想要:
- 只加载我需要的字段,从多个表或模型中。
- 得到的是“模型对象”,而不是通过
session.query
传递列名后得到的类似命名元组的对象。这个过程应该能够跨越关系,比如说employee.company.name
,这里的company是员工模型中的一个关系字段。 - 防止在初始选择查询后,意外加载其他字段。我可以使用
defer
或者.load_only('field_name')
,但这样的话,其他人可能会访问我没有指定的模型属性,这样就会导致另一个查询被执行。理想情况下,访问一个在查询中没有指定的字段时,应该抛出一个AttributeError
,即使这个字段在模型中是定义过的。
使用SqlAlchemy提供的机制,这样做可能吗?这样做是否是个好主意?
我写了一个函数来实现我想要的功能,但看起来其他人可能有更好、更标准的解决方案。
class Attributable(object):
pass
def spread_result(row, columns):
"""
:type row: sqlalchemy.util._collections.KeyedTuple
:type columns: tuple
Usage:
>>> result = session.query(Model.field, AnotherModel.other_field).first()
>>> obj = spread_result(result, ('field', 'another_model.other_field'))
>>> obj.field
'field_value'
>>> obj.another_model.other_field
'other_field_value'
>>> obj.another_mapped_field
AttributeError: 'Attributable' object has no attribute 'another_mapped_field'
"""
root = Attributable()
for column, value in zip(columns, row):
obj = root
parts = column.split('.')
for i, attr in enumerate(parts):
if i == len(parts) - 1:
setattr(obj, attr, value)
else:
setattr(obj, attr, Attributable())
obj = getattr(obj, attr)
return root
1 个回答
1
最简单的方法就是创建一个“公共”模型,这个模型映射到同一张表,但只包含你想要加载或访问的列和其他属性。
相关文档提到:
include_properties 或 exclude_properties 参数可以指定只映射部分列。
如果你有一个 Person 模型,用户只需要看到 id 和 name,那么这个“公共”类看起来会是这样的:
class PublicPerson(Base):
__table__ = Person.__table__
__mapper_args__ = {
'include_properties': ['id', 'name']
}
这里有一个简单的可运行示例:
from datetime import datetime
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
engine = create_engine('sqlite://', echo=True)
session = Session(bind=engine)
Base = declarative_base(bind=engine)
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
birthday = Column(DateTime, nullable=False)
class PublicPerson(Base):
__table__ = Person.__table__
__mapper_args__ = {
'include_properties': ['id', 'name']
}
Base.metadata.create_all()
session.add(Person(name='Jan', birthday=datetime(2001, 1, 1)))
# query the full person, prints birthday
print(session.query(Person.birthday).scalar())
# query the "public" person, raises exception on birthday
print(session.query(PublicPerson.birthday).scalar())