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), the current_user object, and optionally old_data (for updates/deletes).
  • Logic: Your custom code goes inside the try block. You can modify new_data here before it's saved.
  • Exception Handling: You can raise an HTTPException to stop the process and return a specific error to the user. Other exceptions are caught and logged, and returning new_data allows the operation to proceed.
  • Return: You must return the new_data list 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 try block.
  • Return: The function should return the new_data list.

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_task parameter.
  • Logic: Your logic for background tasks (e.g., sending emails, calling external APIs) goes in the try block.
  • Return: The function should still return the new_data list.

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.