I am using SQLAlchemy's Single Table Inheritance for Transaction
, StudentTransaction
, and CompanyTransaction
:
class Transaction(Base):
__tablename__ = 'transaction'
id = Column(Integer, primary_key=True)
# Who paid? This depends on whether the Transaction is a
# CompanyTransaction or a StudentTransaction. We use
# SQLAlchemy's Single Table Inheritance to make this work.
discriminator = Column('origin', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
# When?
time = Column(DateTime, default=datetime.utcnow)
# Who administered it?
staff_id = Column(Integer, ForeignKey('staff.id'))
staff = relationship(
'Staff',
primaryjoin='and_(Transaction.staff_id==Staff.id)'
)
# How much?
amount = Column(Integer) # Negative for refunds, includes the decimal part
# Type of transaction
type = Column(Enum(
'cash',
'card',
'transfer'
))
class CompanyTransaction(Transaction):
__mapper_args__ = {'polymorphic_identity': 'company'}
company_id = Column(Integer, ForeignKey('company.id'))
company = relationship(
'Company',
primaryjoin='and_(CompanyTransaction.company_id==Company.id)'
)
class StudentTransaction(Transaction):
__mapper_args__ = {'polymorphic_identity': 'student'}
student_id = Column(Integer, ForeignKey('student.id'))
student = relationship(
'Student',
primaryjoin='and_(StudentTransaction.student_id==Student.id)'
)
Then, I have a Student which defines a one-to-many relationship with StudentTransactions:
class Student(Base):
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
transactions = relationship(
'StudentTransaction',
primaryjoin='and_(Student.id==StudentTransaction.student_id)',
back_populates='student'
)
@hybrid_property
def balance(self):
return sum([transaction.amount for transaction in self.transactions])
The problem is, invoking Student yields: NotImplementedError: <built-in function getitem>
for the return line in Student.balance()
function.
What am I doing wrong?
Thanks.
a hybrid property is a construct that allows a Python descriptor to be produced which behaves in one way at the instance level, and in another way at the class level. At the class level we wish for it to produce a SQL expression. It's not legal to use plain Python functions like sum()
or list comprehensions in order to produce SQL expressions.
In this case, if I were querying from the "student" table and I wished to produce a summation of the "amount" column in the "transaction" table, I'd probably want to use a correlated subquery with a SQL aggregate function. SQL we'd look to see here would resemble:
SELECT * FROM student WHERE (
SELECT SUM(amount) FROM transaction WHERE student_id=student.id) > 500
our hybrid has to take control and produce this expression:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
Base = declarative_base()
class Transaction(Base):
__tablename__ = 'transaction'
id = Column(Integer, primary_key=True)
discriminator = Column('origin', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
amount = Column(Integer)
class StudentTransaction(Transaction):
__mapper_args__ = {'polymorphic_identity': 'student'}
student_id = Column(Integer, ForeignKey('student.id'))
student = relationship(
'Student',
primaryjoin='and_(StudentTransaction.student_id==Student.id)'
)
class Student(Base):
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
transactions = relationship(
'StudentTransaction',
primaryjoin='and_(Student.id==StudentTransaction.student_id)',
back_populates='student'
)
@hybrid_property
def balance(self):
return sum([transaction.amount for transaction in self.transactions])
@balance.expression
def balance(cls):
return select([
func.sum(StudentTransaction.amount)
]).where(StudentTransaction.student_id==cls.id).as_scalar()
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
s.add_all([
Student(transactions=[StudentTransaction(amount=50), StudentTransaction(amount=180)]),
Student(transactions=[StudentTransaction(amount=600), StudentTransaction(amount=180)]),
Student(transactions=[StudentTransaction(amount=25), StudentTransaction(amount=400)]),
])
print s.query(Student).filter(Student.balance > 400).all()
the output at the end:
SELECT student.id AS student_id
FROM student
WHERE (SELECT sum("transaction".amount) AS sum_1
FROM "transaction"
WHERE "transaction".student_id = student.id) > ?
2014-04-19 19:38:10,866 INFO sqlalchemy.engine.base.Engine (400,)
[<__main__.Student object at 0x101f2e4d0>, <__main__.Student object at 0x101f2e6d0>]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With