Search code examples
pythonpython-3.xatomicpeewee

Peewee atomic updates complex logic


I need to update fields for a whole users table. It is stated in the docs that I shouldn't iterate through all table records. But to calculate a new field value I need to perform calculations using loops, user's relations and other complex logic relied on a concrete model.

I tried to include this calculations in the model's property, but I got:

peewee.InterfaceError: Error binding parameter 0 - probably unsupported type.

Atomic update:

    query = User.update(balance=(User.balance + User.partners_reward))\
    .where(User.deposit >= 3)

Model:

class User(peewee.Model):
    deposit = peewee.DecimalField(default=0)
    balance = peewee.DecimalField(default=0)

    @property
    def partners(self):
        query = (
            User.select(User, Partnership)
                .join(Partnership, JOIN.INNER, on=Partnership.invited)
                .where(Partnership.referral == self.id)
        )
        partners = query.execute()
        return partners


    @property
    def partners_reward(self):
        partners = self.partners
        sum_reward = 0
        partner_reward = 0.02

        for partner in partners:
            reward = partner.deposit * partner_reward
            sum_reward += reward

        return sum_reward

Prop description:

Each user has partners (other users invited by him) and depending on them he gets bonuses. So to accomplish the update I need to collect user partners using relations and then calculate the actual bonus using loops.

Changing property to a method solves this, but I don't know how I can get access to a concrete user instance.

So how can some complex logic be implemented in atomic updates or should I do it using loop as not recommended?


Solution

  • You can use a subquery. If I understand your query example correctly, it's just the sum of each partner deposit * .02?

    subq = (Partnership
            .select(fn.COALESCE(fn.SUM(Partnership.deposit * .02)))
            .where(Partnership.referral == User.id))
    res = (User
           .update(reward=subq)
           .execute())
    

    What the above query does is, for each user, it selects all the partnership rows associated with the user, and sums the partnership deposit * .02 and stores the result in the user's "reward" column (assuming such a column existed). The "COALESCE()" bit is to handle the case where a user has no partner, so as to return 0 instead of null.

    If you wished to increment the user's balance instead:

    subq = (Partnership
            .select(fn.COALESCE(fn.SUM(Partnership.deposit * .02)))
            .where(Partnership.referral == User.id))
    res = (User
           .update(balance=User.balance + subq)
           .execute())