Reputation: 1387
I want to:
session.query
. This should span relationships, so for instance employee.company.name
, where company is a relationship field in the Employee model.defer
or .load_only('field_name')
but then someone else could access an attribute of the model I didn't specify and that would cause another query to run. Ideally, accessing a field that wasn't specified in the query would raise an AttributeError
, even if that field is defined in the model.Is this possible using mechanisms provided by SqlAlchemy? Would this even be a good idea?
I wrote the following function that does what I want, but it seems someone else must've met these needs in a better and more standard way.
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
Upvotes: 1
Views: 1158
Reputation: 127180
The easiest way would be to create a "public" model that maps to the same table but only contains the columns and other properties that you want loadable/accessible.
The relevant documentation says:
the include_properties or exclude_properties arguments can specify that only a subset of columns should be mapped
If you have a Person model which users should just see id and name from, the "public" class would look like this:
class PublicPerson(Base):
__table__ = Person.__table__
__mapper_args__ = {
'include_properties': ['id', 'name']
}
Here is a simple runnable example:
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())
Upvotes: 1