Event Handlers¶
SpecStar provides lifecycle hooks around resource operations.
Use event handlers when you want to add audit logging, cross-resource updates, validation, notifications, or custom business rules around create, read, update, delete, and related actions.
The basic idea¶
An event handler receives an event context object describing:
- the current phase
- the requested resource action
- the user and timestamp
- the resource name and resource ID when available
- the current payload or result data
A simple handler looks like this:
from specstar.events import do
from specstar.events import EventContextProto
from specstar.types import ResourceAction
def audit_log(context: EventContextProto) -> None:
print(context.phase, context.action, context.resource_name)
event_handlers = do(audit_log).before(ResourceAction.create)
Registering handlers¶
You can configure handlers globally:
A do(...) builder is a Sequence of handlers, so you can pass it directly,
or drop one (or several) into a list — both work and are flattened for you:
spec.configure(event_handlers=[do(audit_log).before(ResourceAction.create),
do(notify).after(ResourceAction.delete)])
Or pass them for a specific model when registering it.
Available phases¶
before¶
Runs before the operation is executed.
Use it for:
- early validation
- request auditing
- adjusting input data
- enforcing extra safety checks
If a before handler raises an exception, the operation stops.
after¶
Runs after the operation stage is reached.
Use it when you want post-processing that should happen around the operation lifecycle.
on_success¶
Runs only when the operation completes successfully.
Use it for:
- emitting notifications
- writing audit records
- chaining follow-up work
- updating dependent systems
on_failure¶
Runs only when the operation raises an error.
Use it for:
- failure logging
- alerting
- compensation logic
- debugging support
Chaining multiple handlers¶
You can define several handlers in one fluent expression:
from specstar.events import do
from specstar.types import ResourceAction
def before_create(context):
print("before create")
def after_create(context):
print("after create")
def success_create(context):
print("success create")
event_handlers = (
do(before_create)
.before(ResourceAction.create)
.do(after_create)
.after(ResourceAction.create)
.do(success_create)
.on_success(ResourceAction.create)
)
You can also attach one function to multiple actions by using the grouped action flags such as ResourceAction.write.
Common patterns¶
Audit every write¶
from specstar.events import do
from specstar.types import ResourceAction
def audit(context):
print(f"{context.user} -> {context.action} -> {context.resource_name}")
event_handlers = do(audit).on_success(ResourceAction.write)
Block invalid input early¶
from specstar.events import EventContextProto
from specstar.types import ResourceAction
def validate_title(context: EventContextProto) -> None:
if context.action == ResourceAction.create and not context.data.title.strip():
raise ValueError("title must not be empty")
Log failures¶
from specstar.events import do
from specstar.types import ResourceAction
def log_failure(context):
print("operation failed", context.action, context.resource_name)
event_handlers = do(log_failure).on_failure(ResourceAction.full)
What actions are available¶
Event handlers can listen to actions such as:
creategetupdatemodifypatchdeleterestoreswitchdumpload- grouped flags like
read,write,lifecycle, andfull
Good practices¶
- keep handlers small and focused
- raise clear exceptions in
beforewhen rejecting an operation - prefer
on_successfor side effects that should not run on failure - use grouped actions to reduce repetition when the same rule applies broadly
- avoid hiding core business logic in too many scattered handlers