The acid.events module defines a set of events that may be fired when some mutation occurs on a collection.
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'])
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.
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.
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)
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
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()
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()
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.")
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()
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)
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()
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)
Request func be invoked as func() following abort of any transaction.
Request func be invoked as func() prior to commit of any transaction.
Request func be invoked as func() following commit of any transaction.