arvel-audit¶
Introduction¶
arvel-audit provides two logging layers for compliance and history:
- Audit trail — mix
Auditableinto a model and every create/update/delete writes anAuditEntryin the same transaction. - Activity log — record business events with the fluent
activity()API, modeled after Spatie's Laravel ActivityLog.
Installation¶
Register the provider and publish the migrations:
# bootstrap/providers.py
from arvel_audit import AuditServiceProvider
providers = [AuditServiceProvider]
The migrations create audit_entries and activity_entries. The provider binds AuditConfig and wires the Auditable observers on boot.
Auditing Model Changes¶
from arvel.database import Model, id_, string
from arvel_audit import Auditable, AuditLog
class Order(Model, Auditable):
__tablename__ = "orders"
id: int = id_()
status: str = string(20, default="new")
Saves now record automatically:
order = Order(status="new")
await order.save() # writes an AuditEntry, action="created"
order.status = "paid"
await order.save() # writes an AuditEntry, action="updated"
Control which columns are recorded:
class Order(Model, Auditable):
__audit_redact__ = ("card_number",) # stored as "***"
__audit_exclude__ = ("updated_at",) # left out entirely
The actor is read from request context — set user_id (the auth middleware does this for you) and it lands in AuditEntry.actor_id.
Reading the Audit Trail¶
history = await AuditLog(session).for_model(order).get()
recent = await (
AuditLog(session)
.by_actor("alice")
.action("updated")
.since(some_datetime)
.paginate(per_page=15, page=1)
)
AuditLog filters: for_model, by_actor, action, since, until; terminals get, first, count, paginate.
Activity Log¶
For business events that aren't a single model change:
from arvel_audit import activity, ActivityQuery
await (
activity("orders", session=session)
.log("Order exported")
.by(current_user)
.on(order)
.with_properties({"format": "pdf"})
.save()
)
entries = await ActivityQuery(session).for_subject(order).get()
ActivityQuery filters: in_log, for_subject, by_causer; terminals get, first, count.
Configuration¶
| Env var | Default | Effect |
|---|---|---|
AUDIT_ENABLED | true | When false, skips all automatic audit writes |
AUDIT_ENCRYPT_VALUES | false | Encrypts old_values / new_values with the app encrypter |
Warning
AUDIT_ENCRYPT_VALUES is read when arvel_audit's models are imported and fixes the column type then, so set it in the environment before the app boots. Encryption needs APP_KEY (arvel key:generate).
Gotchas¶
ActivityQueryhas nopaginate— onlyAuditLogdoes.__audit_exclude__works but isn't covered by the package's own tests.