Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLAlchemy update parent when related child changes

I'm trying to model an entity that as one or more one-to-many relationships, such that it's last_modified attribute is updated, when

  • a child is added or removed
  • a child is modified
  • the entity itself is modified

I've put together the following minimal example:

class Config(Base):
    __tablename__ = 'config'

    ID = Column('ID', Integer, primary_key=True)
    name = Column('name', String)
    last_modified = Column('last_modified', DateTime, default=now, onupdate=now)

    params = relationship('ConfigParam', backref='config')

class ConfigParam(Base):

    __tablename__ = 'config_params'

    ID = Column('ID', Integer, primary_key=True)
    ConfigID = Column('ConfigID', Integer, ForeignKey('config.ID'), nullable=False)

    key = Column('key', String)
    value = Column('value', Float)

@event.listens_for(Config.params, 'append')
@event.listens_for(Config.params, 'remove')
def receive_append_or_remove(target, value, initiator):
    target.last_modified = now()

@event.listens_for(ConfigParam.key, 'set')
@event.listens_for(ConfigParam.value, 'set')
def receive_attr_change(target, value, oldvalue, initiator):
    if target.config:
        # don't act if the parent config isn't yet set
        # i.e. during __init__
        target.config.last_modified = now()

This seems to work, but I'm wondering if there's a better way to do this?

Specifically, this becomes very verbose since my actual ConfigParam implementation has more attributes and I'm having multiple one-to-many relations configured on the parent Config class.

like image 413
sebastian Avatar asked Mar 12 '15 13:03

sebastian


1 Answers

Take this with a huge grain of salt, it "seems" to work, could explode:

def rel_listener(t, v, i):
    t.last_modified = now()

def listener(t, v, o, i):
    if t.config:
        t.config.last_modified = now()

from sqlalchemy import inspect

for rel in inspect(Config).relationships:
    event.listen(rel, 'append', rel_listener)
    event.listen(rel, 'remove', rel_listener)

for col in inspect(ConfigParam).column_attrs:
    event.listen(col, 'set', listener)

Problem is that the inspections make no exceptions and columns such as 'ID' and 'ConfigID' will be bound to event listeners.

Another perhaps slightly less tedious form would be to just use a list of attributes to bind events to in a similar fashion:

for attr in ['key', 'value']:
    event.listen(getattr(ConfigParam, attr), 'set', listener)

This gives you control over what is bound to events and what is not.

like image 63
Ilja Everilä Avatar answered Nov 04 '22 16:11

Ilja Everilä