1

Situation

Users make purchases, which are stored as transactions in 3 different tables (depending on the type). I need to calculate total amount of transactions/purchases of female and male users, so I need to look into all 3 tables.

For this I created a @property in the User table:

@property
def count_credits_purchases(self):
    trans = object_session(self).query(Transaction_1).filter(Transaction_1.type == "credits").with_parent(self).count()
    trans_vk = object_session(self).query(Transaction_2).filter(Transaction_2.type == "credits").with_parent(self).count()
    trans_stripe = object_session(self).query(Transaction_3).filter(Transaction_3.type == "credits").with_parent(self).count()
    value = trans + trans_vk + trans_stripe
    return int(value)

I am trying to calculate the total amount of purchases by using sqlalchemy func.sum():

total_purchases_males_credits = db_session.query(func.sum(Users.count_credits_purchases))
.filter(Users.date_added >= start_date, Users.date_added <= end_date, Users.gender == "1")
.scalar()

Problem

AttributeError: 'property' object has no attribute 'translate'

The translate method is a string method, what is happening here? I definitely return an integer in count_credits_purchases.

I made a test and checking the value per user is always correct:

all_users = db_session.query(Users).limit(200)
for user in all_users:
    print (user.count_credits_purchases) # gives correct result

I could make a variable and calculate it in the loop, but it is super unefficient and would need probably 1 hour if there are 50k users. I need to understand how to work with the @property attribute

2
  • Shouldn't you be using hybrid_property? Commented Aug 10, 2018 at 9:12
  • @vishes_shell I am currently experimenting with column_property, next will be hybrid_property Commented Aug 10, 2018 at 9:16

2 Answers 2

2

As the docs Using Descriptors and Hybrids says you should be using hybrid_property to be able to use them in your queries.

Take a look at example in docs:

class EmailAddress(Base):
    __tablename__ = 'email_address'

    id = Column(Integer, primary_key=True)

    # name the attribute with an underscore,
    # different from the column name
    _email = Column("email", String)

    # then create an ".email" attribute
    # to get/set "._email"
    @property
    def email(self):
        return self._email

While our EmailAddress object will shuttle the value through the email descriptor and into the _email mapped attribute, the class level EmailAddress.email attribute does not have the usual expression semantics usable with Query. To provide these, we instead use the hybrid extension

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the reply. Already two people telling me to try @hybrid_property. I was currently trying to use column_property but I switch to hybrid now. It doesn't work right of the bat: sqlalchemy.orm.exc.UnmappedInstanceError: Class 'sqlalchemy.ext.declarative.api.DeclarativeMeta' is not mapped; was a class (stats_models.Users) supplied where an instance was required? But I try to figure it out, thanks
If you have the time, check my answer please. Is the normal method a viable solution here? I have problems to make hybrid_method work.
1

The best solution here is probably using @hybrid_property but I had problems to make it work.

I came up with a completely different solution, using a classical method. This was super fast and so far I dont see any downsides:

# Normal method to calculate | Best case would probably be @hybrid_method
def count_credits_purchases(self, start_date, end_date, gender):
    trans = db_session.query(Transaction_1).filter(Transaction_1.type == "credits", Transaction_1.user_id == Users.id).filter(Users.date_added >= start_date, Users.date_added <= end_date, Users.gender == gender).count()
    trans_vk = db_session.query(Transaction_2).filter(Transaction_2.type == "credits", Transaction_2.user_id == Users.id).filter(Users.date_added >= start_date, Users.date_added <= end_date, Users.gender == gender).count()
    trans_stripe = db_session.query(Transaction_3).filter(Transaction_3.type == "credits", Transaction_3.user_id == Users.id).filter(Users.date_added >= start_date, Users.date_added <= end_date, Users.gender == gender).count()
    value = trans + trans_vk + trans_stripe
    return value

Call in python:

total_purchases_males_credits = Users().count_credits_purchases(start_date, end_date, "1")

I would still like to know how good ths approach is compared to hybrid_property?

EDIT:

Its also possible to use @hybrid_method:

@hybrid_method
def count_credits_purchases(self, start_date, end_date, gender):
    trans = db_session.query(Transaction_1).filter(Transaction_1.type == "credits", Transaction_1.user_id == Users.id).filter(Users.date_added >= start_date, Users.date_added <= end_date, Users.gender == gender).count()
    trans_vk = db_session.query(Transaction_2).filter(Transaction_2.type == "credits", Transaction_2.user_id == Users.id).filter(Users.date_added >= start_date, Users.date_added <= end_date, Users.gender == gender).count()
    trans_stripe = db_session.query(Transaction_3).filter(Transaction_3.type == "credits", Transaction_3.user_id == Users.id).filter(Users.date_added >= start_date, Users.date_added <= end_date, Users.gender == gender).count()
    value = trans + trans_vk + trans_stripe
    return value

And use it:

total_purchases_males_credits = db_session.query(func.sum(Users.count_credits_purchases(start_date, end_date, "1"))).scalar()

1 Comment

The problem is pretty much specific (so i cannot dive in it completely), and as well it works for you, and is fast enough, then it is a problem solver. Good work!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.