Events

The acid.events module defines a set of events that may be fired when some mutation occurs on a collection.

An event function may be used as a decorator applied to a method of an acid.meta Model, in which case the function will be associated with the Model’s Collection when it is created:

class Account(acid.meta.Model):
    @acid.events.on_create
    def generate_password(self):
        """Assign a password to the account during creation."""
        self.password = passlib.generate(chars=8)

Events registered against against a class will also fire for any modifications to instances of any subclasses. Note that notification order is most-specific to least-specific.

Alternatively they may be registered for any free-standing function against any model or Collection:

def generate_password(acct):
    self.password = passlib.generate(chars=8)

# Works against a Model class:
acid.events.on_create(generate_password, target=Account)

# Or a Collection instance:
store.add_collection('accounts')
acid.events.on_create(generate_password, target=store['accounts'])

Model vs. engine events

This interface attempts to unify two very different kinds of events: those occurring logically within the application’s data model, and those occurring as a result of interaction with the storage engine. When applied to acid.meta.Model, most event types rely only on in-memory state tracked by the model class, whereas those applied to acid.Collection relate to the state of the storage engine as reported at the time of the mutation.

The reason for having both types is that usually Model events are good enough for application use, but storage engine events are needed to robustly implement features such as indexing. Instead of burying the storage engine events within Acid’s implementation, they are exposed to user code.

Observing Events

Subscribing to certain events will cause a less efficient strategy to be adopted when interacting with the storage engine. Engines like LmdbEngine support efficient read-modify-write operations, whereas others such as PlyvelEngine have reads that are more expensive than writes. An observing event is one that causes the storage engine to read and return any previous record value during a write operation. Even for engines that support fast read-modify-write operations, emitting the event further requires decoding of the old record value. Therefore where possible, prefer subscribing to a non-observing event if it fits your use case.

Observing Event Types (— means unsupported)
Event Used with Model Used with Collection
constraint No No
on_create No
on_update No No
on_delete No
on_commit No No
after_create No Yes
after_update No No
after_delete No Yes
after_replace Yes Yes
after_abort No No
after_commit No No

While it is possible to support on_create and on_delete engine events, these would necessitate extra roundtrips for any engine that relies on the network, unlike the after_* variants which can be supported by a single “mutate and return previous” message. Additionally for networked storage systems that lack transactions, it is more likely the single message can be supported as an atomic operation.

Debugging

Since it is possible to subscribe to events on a base clase, it is possible to subscribe to events on acid.meta.Model itself, thus capturing all database operations. This may be useful for diagnostics:

def log_create(model):
    print 'Model created!', model

def log_delete(model):
    print 'Model deleted!', model

def install_debug_helpers():
    if config.DEBUG:
        acid.events.after_create(log_create, acid.meta.Model)
        acid.events.after_delete(log_delete, acid.meta.Model)

Constraints

acid.events.constraint()

Mark a function as implementing a collection constraint. The function should return True if the constraint is satisfied, or raise an exception or return any falsey value otherwise. Constraints are implemented as on_update() handlers that convert falsey return values into ConstraintErrors.

@acid.events.constraint
def is_age_valid(self):
    return 0 < self.age < 150

Event Types

acid.events.on_create()

Request func be invoked as func(model) when a new model which has no key assigned is about to be saved for the first time. This event can only be applied to acid.meta.Model, it has no meaning when applied to a collection.

@acid.events.on_create
def set_created(self):
    '''Update the model's creation time.'''
    self.created = datetime.datetime.now()
acid.events.on_update()

Request func be invoked as func(model) when model is about to be saved for any reason. Alternatively when applied to a collection, request func(key, rec) be invoked.

@acid.events.on_update
def set_modified(self):
    '''Update the model's modified time.'''
    self.modified = datetime.datetime.utcnow()
acid.events.on_delete()

Request func be invoked as func(model) when a model that has previously been assigned a key is about to be deleted. This event can only be applied to acid.meta.Model, it has no meaning when applied to a collection.

@acid.events.on_delete
def ensure_can_delete(self):
    '''Prevent deletion if account is active.'''
    if self.state == 'active':
        raise Exception("can't delete while account is active.")
acid.events.after_create()

Request func be invoked as func(model) when a model that had no previous key has been saved. Alternatively when applied to a collection, request func(key, rec) be invoked.

@acid.events.after_create
def send_welcome_message(self):
    '''Send the user a welcome message.'''
    msg = Message(user_id=self.id, text='Welcome to our service!')
    msg.save()
acid.events.after_update()

Request func be invoked as func(model) after any change to model. Alternatively when applied to a collection, request func(key, rec) be invoked.

@acid.events.after_update
def notify_update(self):
    '''Push an update event to message queue subscribers.'''
    my_message_queue.send(topic='account-updated', id=self.id)
acid.events.after_delete()

Request func be invoked as func(model) after any model that previously had an assigned key is deleted. Alternatively when applied to a collection, request func(key, rec) be invoked.

@acid.events.after_delete
def delete_messages(self):
    '''Delete all the account's messages.'''
    for msg in Message.user_index.find(prefix=self.id):
        msg.delete()
acid.events.after_replace()

Request func be invoked as func(self, old) when model is about to replace an older version of itself. Alternatively when applied to a collection, request func(key, old, new) be invoked.

@acid.events.after_replace
def after_replace(self, old):
    print "Record %s replaced: old ctime %s, new time %s" %\
          (self.key, old.ctime, self.ctime)

Transaction Events

These may be applied to a Model, Collection, or Store. In the case of Models or collections, the subscription is proxied through to the associated store.

acid.events.after_abort()

Request func be invoked as func() following abort of any transaction.

acid.events.on_commit()

Request func be invoked as func() prior to commit of any transaction.

acid.events.after_commit()

Request func be invoked as func() following commit of any transaction.