How to trigger an event after value change in sqlalchemy

Viewed 681

I am defining a mapped class using sqlalchemy. It contains three user defined values (a,b,c) and one dependend value (abc), which is calculated using a, b and c. I want to attach a listener to a, b and c, which updates abc, when their values are changed. Using event.listen(variable.a, 'set', updateFunction) calls the function, which should update abc. Unfortunately the event is fired before the new value is stored in the variable. Therefore abc will be calculated using the old values.

Example:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, Numeric, Sequence
from sqlalchemy import event

base = declarative_base()

class demo(base):
    __tablename__ = 'demo'

    id = Column(Integer, Sequence('some_id_seq'), primary_key=True)
    a = Column(Integer)
    b = Column(Integer)
    c = Column(Integer)
    abc = Column(Numeric) #calculated from a, b, and c

    def __init__(self, A, B, C):
        self.a = A
        self.b = B
        self.c = C

        self.calcABC() #initial calculation of abc

        event.listen(demo.a, 'set', self.calcABCEvent, named=True)
        event.listen(demo.b, 'set', self.calcABCEvent, named=True)
        event.listen(demo.c, 'set', self.calcABCEvent, named=True)

    #super complicated formular
    def calcABC(self):
        self.abc = ( self.a + self.b ) * self.c

    #This event ist called, before the new value is set
    #Thus the calculation is not correct
    def calcABCEvent(self, target, value, oldvalue, initiator):
        print("\nevent called")
        print(f"self.a \t\t{self.a}")
        print(f"target.a \t{target.a}")
        print(f"oldvalue \t{oldvalue}")
        print(f"value \t\t{value}") #The new value only appears in value

        self.calcABC()

    def __str__(self):
        return f"({self.a} + {self.b}) * {self.c} = {self.abc}"

print("init")
obj = demo(1,2,3)
print(obj) #abc should be (1+2)*3 = 9

print("\nchange a to 4")
obj.a = 4

print("\nchanged object")
print(obj) #abc should be (4+2)*3 = 18

Output:

init
(1 + 2) * 3 = 9

change a to 4

event called
self.a      1
target.a    1
oldvalue    1
value       4

changed object
(4 + 2) * 3 = 9

How can i update abc using only one function? In my application, abc is calculated using seven variables, and i want to avoid writing seven update functions. Is there something like event.listen(variable.a, 'after_set', updateFunction)?

1 Answers

Found a similiar quastion after learning about properties: Python getter and setter via @property within SqlAlchemy

My solution looks like this:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, Numeric, Sequence
from sqlalchemy.ext.hybrid import  hybrid_property

base = declarative_base()

class demo(base):
    __tablename__ = 'demo'

    id = Column(Integer, Sequence('some_id_seq'), primary_key=True)
    _a = Column("a", Integer)
    _b = Column("b", Integer)
    _c = Column("c", Integer)
    abc = Column(Numeric) #calculated from a, b, and c

    def __init__(self, A, B, C):
        self._a = A
        self._b = B
        self._c = C
        self.calcABC() #inital calculation of abc

    #super complicated formular
    def calcABC(self):
        self.abc = ( self._a + self._b ) * self._c

    def __str__(self):
        return f"({self._a} + {self._b}) * {self._c} = {self.abc}"

    @hybrid_property
    def a(self):
        return self._a

    @a.setter
    def a(self, a):
        self._a = a
        self.calcABC()


    @hybrid_property
    def b(self):
        return self._b

    @b.setter
    def b(self, b):
        self._b = b
        self.calcABC()


    @hybrid_property
    def c(self):
        return self._c

    @c.setter
    def c(self, c):
        self._c = c
        self.calcABC()

print("init")
obj = demo(1,2,3)
print(obj) #abc should be (1+2)*3 = 9

print("\nchange a to 4")
obj.a = 4
print(obj) #abc should be (4+2)*3 = 18

print("\nchange b to 5")
obj.b = 5
print(obj) #abc should be (4+5)*3 = 27

print("\nchange c to 6")
obj.c = 5
print(obj) #abc should be (4+5)*6 = 54

producing the following output

init
(1 + 2) * 3 = 9

change a to 4
(4 + 2) * 3 = 18

change b to 5
(4 + 5) * 3 = 27

change c to 6
(4 + 5) * 6 = 54

Process finished with exit code 0
Related