Creating Record Triggers in Valstorm
Record triggers in Valstorm are a powerful feature that allows you to inject custom automations into the standard Create, Update, and Delete API request lifecycle. This enables you to run specific logic at different stages of a data operation.
Trigger Contexts
You can configure your triggers to execute in one of three distinct contexts:
- Before: This trigger runs before the Create, Update, or Delete request is processed by the system. It's ideal for data validation or permission checks.
- After: This trigger runs after the request has been successfully processed, but before the response is sent to the client (synchronously). Use this for actions that need to happen immediately after the main operation.
- Async: This trigger runs after the request is processed, but it executes asynchronously in the background. This is perfect for non-blocking tasks like sending notifications or queuing up longer jobs.
General Code Patterns
All triggers follow a similar structure. Your custom logic is placed within a def execute function.
1. Before Create, Update, or Delete
This trigger is executed before the database operation. It's useful for validating data or enforcing business rules.
Code Pattern
from fastapi import BackgroundTasks, HTTPException
from valstorm.models import User
from valstorm.dependencies import add_log
def execute(new_data: list, current_user: User, old_data: list = None, background_task: BackgroundTasks = None):
try:
# Your validation or modification logic here
return new_data
except HTTPException as e:
raise e
except Exception as e:
add_log(f"Error: {e} occurred in Before trigger", 'error')
# Return new_data to allow the process to continue even if an error occurs
return new_data
Explanation
- Function Signature: The function receives
new_data(a list of records being processed), thecurrent_userobject, and optionallyold_data(for updates/deletes). - Logic: Your custom code goes inside the
tryblock. You can modifynew_datahere before it's saved. - Exception Handling: You can raise an
HTTPExceptionto stop the process and return a specific error to the user. Other exceptions are caught and logged, and returningnew_dataallows the operation to proceed. - Return: You must return the
new_datalist for the operation to continue.
2. After Create, Update, or Delete (Synchronous)
This trigger runs after the database operation is complete but before the API response is sent.
Code Pattern
from fastapi import BackgroundTasks, Response
from valstorm.models import User
from valstorm.dependencies import add_log
def execute(new_data: list, current_user: User, old_data: list = None, background_task: BackgroundTasks = None):
try:
# Your logic to run after the operation
return new_data
except Exception as e:
add_log(f"Error: {e} occurred in After trigger", 'error')
return new_data
Explanation
- Function Signature: The signature is the same as the "Before" trigger.
- Logic: Place your custom logic, like logging or triggering a related synchronous process, in the
tryblock. - Return: The function should return the
new_datalist.
3. Create, Update, or Delete (Background)
This trigger executes in the background after the API request is completed. It's ideal for operations that don't need to block the user's response time.
Code Pattern
from fastapi import BackgroundTasks, Response
from valstorm.models import User
from valstorm.dependencies import add_log
def execute(new_data: list, current_user: User, old_data: list = None, background_task: BackgroundTasks = None):
try:
# Your background logic here
except Exception as e:
add_log(f"Error: {e} occurred in trigger", 'error')
# This return is for the trigger system, not the end-user
return new_data
Explanation
- Function Signature: The signature includes an optional
background_taskparameter. - Logic: Your logic for background tasks (e.g., sending emails, calling external APIs) goes in the
tryblock. - Return: The function should still return the
new_datalist.
Example Implementations
Before Update Example
Here, the trigger prevents a user from modifying a restricted field.
from fastapi import HTTPException
from valstorm.models import User
from valstorm.dependencies import add_log
def execute(new_data: list, current_user: User, old_data: list = None):
try:
# Check if user is allowed to update certain fields
for d in new_data:
if d['restricted_field'] != old_data['restricted_field']:
raise HTTPException(status_code=403, detail="You are not allowed to update this field")
return new_data
except HTTPException as e:
raise e
except Exception as e:
add_log(f"Error: {e} occurred in Before trigger", 'error')
return new_data
After Create Example
This trigger logs a message after a new record is successfully created.
from fastapi import BackgroundTasks, Response
from valstorm.models import User
from valstorm.dependencies import add_log
def execute(new_data: list, current_user: User, old_data: list = None, background_task: BackgroundTasks = None):
try:
# Perform some action after the record is created
for d in new_data:
add_log(f"New record created: {d['id']}", 'info')
return new_data
except Exception as e:
add_log(f"Error: {e} occurred in After trigger", 'error')
return new_data
Delete Example
This trigger logs the ID of a deleted record as a background task.
from fastapi import BackgroundTasks, Response
from valstorm.models import User
from valstorm.dependencies import add_log
def execute(new_data: list, current_user: User, old_data: list = None, background_task: BackgroundTasks = None):
try:
# Perform some background action after the record is deleted
for d in old_data:
add_log(f"Record deleted: {d['id']}", 'info')
except Exception as e:
add_log(f"Error: {e} occurred in trigger", 'error')
return new_data
By customizing the logic within the try blocks, you can create powerful automations for your Valstorm applications.