Skip to main content

Event Hooks

Event hooks in Anchorpoint allow you to respond to specific events that happen within the application. Every time a certain event occurs, a Python callback function is triggered automatically. This enables you to create reactive workflows and automate tasks based on user interactions and system changes.

Usage

To implement event hooks, simply define callback functions with specific names within your action's Python script. You don't need to register these functions explicitly - Anchorpoint will automatically detect and call them when the corresponding events occur.

import anchorpoint

def on_timeout(ctx: anchorpoint.Context):
# This code will be automatically executed once every minute
print("timeout callback")

def on_folder_opened(ctx: anchorpoint.Context):
# This code will be executed when user navigates to a folder
print(f"folder opened: {ctx.path}")

if __name__ == "__main__":
# This code will be executed when the user triggers the action manually
print("Action invoked!")

Important: When mixing event hooks with regular action code, make sure to guard your normal action code using if __name__ == "__main__": to prevent it from executing when the script is loaded for hooks.

Application Events

  • on_timeout(ctx) Called once every minute automatically. Useful for periodic checks, background tasks, or monitoring file system state.

    Arguments

    • ctx (class: Context): Current context with browser state
  • on_folder_opened(ctx) Called whenever the user navigates to a folder in Anchorpoint. Useful for folder-specific initialization or validation.

    Arguments

    • ctx (class: Context): Current context with the opened folder path
  • on_project_directory_changed(ctx) Called when any change is detected within the active project directory. Triggered by file modifications, additions, deletions, etc. Only works for the currently active project in Anchorpoint.

    Arguments

    • ctx (class: Context): Current context of the active project
  • on_application_started(ctx) Called once for each workspace when Anchorpoint starts up. Useful for initialization routines or startup checks.

    Arguments

    • ctx (class: Context): Context for the starting workspace
  • on_application_closing(ctx) Called just before Anchorpoint closes. Useful for cleanup tasks or saving state.

    Arguments

    • ctx (class: Context): Current context before shutdown

Task Events

  • on_task_created(task_id, source, ctx) Called when a new task is created.

    Arguments

    • task_id (str): ID of the newly created task
    • source (class: ChangeSource): Source of the change (You, Action, Other)
    • ctx (class: Context): Current context
  • on_task_changed(task_id, source, ctx) Called when an existing task is modified.

    Arguments

    • task_id (str): ID of the modified task
    • source (class: ChangeSource): Source of the change (You, Action, Other)
    • ctx (class: Context): Current context
  • on_task_removed(task_id, source, ctx) Called when a task is deleted.

    Arguments

    • task_id (str): ID of the deleted task
    • source (class: ChangeSource): Source of the change (You, Action, Other)
    • ctx (class: Context): Current context

Attribute Events

  • on_attributes_changed(parent_path, attributes, ctx) Called when attributes are modified on files, folders, or tasks. Provides detailed information about what changed.

    Arguments

    • parent_path (str): Path of the parent object containing the changed attributes
    • attributes (List[AttributeChange]): List of attribute changes
    • ctx (class: Context): Current context

AttributeChange Class

  • AttributeChange Represents a value change of an attribute. Provides information about what changed, including the old and new values, and the source of the change.

Properties

  • path (str): Path of the object where the attribute changed (None for tasks).
  • task_id (str): ID of the task where the attribute changed (None for files/folders).
  • object_type (class: ObjectType): Type of object (file, folder, or task).
  • name (str): Name of the changed attribute.
  • type (class: AttributeType): Type of the attribute (text, rating, tag, etc.).
  • value (Any): New value of the attribute.
  • old_value (Any): Previous value of the attribute.
  • source (class: ChangeSource): Source of the change.
  • source_value (str): Client name for "Other" source changes.

ChangeSource Enumeration

  • ChangeSource Enumeration that indicates who made a change to an attribute or task. Used to identify the source of modifications for proper handling and filtering.

Values

  • ChangeSource.You (enum): The change was made by yourself
  • ChangeSource.Action (enum): The change was made by an action on your machine
  • ChangeSource.Other (enum): The change was made by someone else

Notification Events

  • on_custom_notification(message, meta_data, project_id, project_path, ctx) Called when a custom notification is triggered. This hook is invoked when schedule_custom_notification is executed, allowing you to respond to custom notifications with metadata.

    Arguments

    • message (str): The notification message sent to the user
    • meta_data (dict or None): Dictionary of metadata included with the notification (if provided)
    • project_id (str or None): Project identifier where the notification occurred (if specified)
    • project_path (str or None): Absolute path to the project (if specified)
    • ctx (class: Context): Current context
  • on_custom_notification_navigation(message, meta_data, project_id, project_path, ctx) Called when a user clicks on a custom notification. This hook allows you to override the default navigation behavior and implement custom actions when a notification is clicked. If this hook is registered, the normal navigation to the project folder will not occur.

    Arguments

    • message (str): The notification message
    • meta_data (dict or None): Dictionary of metadata included with the notification (if provided)
    • project_id (str or None): Project identifier where the notification occurred (if specified)
    • project_path (str or None): Absolute path to the project (if specified)
    • ctx (class: Context): Current context

Action Events

  • on_is_action_enabled(path, type, ctx) Special hook called to determine if an action should be shown to the user. Must be enabled in YAML configuration: register: folder: enable: script_with_hook.py

    Arguments

    • path (str): Path to check (may differ from ctx.path)
    • type (class: Type): Type of the action being checked
    • ctx (class: Context): Current browser context

    Returns: (bool) True if action should be enabled, False otherwise

  • on_event_received(id, payload, ctx) Listens to other actions and triggers once the other action has been triggered. This hook follows the fire and forget principle. Useful for e.g. a post-merge hook.

    Arguments

    • id (str): The id of the event that has been triggered
    • payload (dict): A dictionary with event details, typically {"type": "success"}, {"type": "error"}, {"type": "conflict"}, or {"type": "cancel"}
    • ctx (class: Context): Current context

Built-in Git Events

The following events are sent automatically by Anchorpoint's built-in Git operations. You can listen to them using on_event_received.

Event IDDescriptionPayload Types
gitpullFired after a git pull or sync operationsuccess, error, cancel, conflict
gitcloneFired after cloning or joining a git repositorysuccess, error
gitswitchbranchFired after switching to a different branchsuccess, error, conflict
gitmergebranchFired after merging a branchsuccess, error, conflict

Sending Custom Events

You can also send your own events from actions using ap.send_event and listen to them in other actions with on_event_received.

import anchorpoint as ap

ctx = ap.get_context()
ap.send_event(ctx, "my_custom_event", {"type": "success"})

Version Control Hooks

  • on_before_commit(channel_id, message, changes, all_files_selected, progress, ctx) Called right before a git commit or sync is executed. This hook lets you modify the commit message, validate the commit, or cancel it entirely.

    Arguments

    • channel_id (str): The timeline channel ID (e.g. "Git")
    • message (str): The commit message entered by the user
    • changes (list[VCPendingChange]): List of pending changes that will be committed
    • all_files_selected (bool): True if the user selected all files for the commit
    • progress (class: Progress): Progress indicator for long-running validations
    • ctx (class: Context): Current context

    Returns: (str or None) Return the commit message (original or modified) to proceed with the commit. Return None to cancel the commit.

VCPendingChange Class

Inherits from VCChange. Represents a file that is about to be committed.

Properties

  • is_dirty_module (bool): True if the change is in a dirty submodule
  • is_unsynced_module (bool): True if the change is in an unsynced submodule

VCChange Class

Represents a version-controlled file change.

Properties

  • path (str): Absolute path to the changed file
  • status (class: VCFileStatus): The type of change (new, modified, deleted, etc.)
  • is_module (bool): True if the change is a submodule

VCFileStatus Enumeration

Indicates what kind of change was made to a file.

Values

  • VCFileStatus.Unknown
  • VCFileStatus.New
  • VCFileStatus.Deleted
  • VCFileStatus.Modified
  • VCFileStatus.Renamed
  • VCFileStatus.Conflicted

Examples

Periodic Monitoring

import anchorpoint
import os
from datetime import datetime

def on_timeout(ctx: anchorpoint.Context):
"""Monitor project for large files every minute"""
if not ctx.project_id:
return

api = anchorpoint.get_api()
large_files = []

# Check for files larger than 100MB in current project
for root, dirs, files in os.walk(ctx.path):
for file in files:
file_path = os.path.join(root, file)
if os.path.getsize(file_path) > 100 * 1024 * 1024: # 100MB
large_files.append(file_path)

# Tag large files
for file_path in large_files:
api.attributes.set_attribute_value(file_path, "Size Warning", "Large File")
api.attributes.set_attribute_value(file_path, "Last Checked", datetime.now())

if __name__ == "__main__":
print("Large file monitor action invoked manually")

Checks for large files every minute and sets a text Attribute on the large file

Folder Navigation Tracking

import anchorpoint
import apsync
from datetime import datetime

def on_folder_opened(ctx: anchorpoint.Context):
"""Track folder access and set last visited timestamp"""
api = anchorpoint.get_api()

# Set last visited attribute on folder
api.attributes.set_attribute_value(ctx.path, "Last Visited", datetime.now())
api.attributes.set_attribute_value(ctx.path, "Visited By", ctx.username)

# Check if this is a project folder
project = api.get_project()
if project and ctx.path == project.path:
print(f"Project {project.name} accessed by {ctx.username}")

if __name__ == "__main__":
print("Folder tracking action invoked")

Triggers once the user navigates to a folder in the Anchorpoint browser and sets an Attribute on the current folder that is being opened.

Attribute Change Monitoring

import anchorpoint

def on_attributes_changed(parent_path: str, attributes: list[anchorpoint.AttributeChange], ctx: anchorpoint.Context):
"""Monitor and react to attribute changes"""
api = anchorpoint.get_api()

for attribute in attributes:
print(f"Attribute '{attribute.name}' changed from '{attribute.old_value}' to '{attribute.value}'")

print(f"File/folder '{attribute.path}' attribute changed")

# Auto-create backup when marked as "Final"
if attribute.name == "Version" and attribute.value == "Final":
backup_path = attribute.path + ".backup"
# Implementation would copy file to backup location
api.attributes.set_attribute_value(backup_path, "Backup Of", attribute.path)

if __name__ == "__main__":
print("Attribute monitoring loaded")

Triggers if an Attribute changes and iterates through the changed Attributes.

Conditional Action Enabling

import anchorpoint
import os

def on_is_action_enabled(path: str, type: anchorpoint.Type, ctx: anchorpoint.Context) -> bool:
"""Enable action only for specific conditions"""

# Enable only in "shots" folders
if "shots" in path.lower():
return True

# Enable for video files
video_extensions = ['.mp4', '.mov', '.avi', '.mkv']
if any(path.lower().endswith(ext) for ext in video_extensions):
return True

# Enable if file has specific attribute
api = anchorpoint.get_api()
try:
status = api.attributes.get_attribute_value(path, "Render Status")
return status == "Ready"
except:
return False

if __name__ == "__main__":
print("Conditional action executed")

This hook triggers when Anchorpoint starts up or loads a project and can be used to e.g. enable a button. The Python script has to be referenced in a YAML file like in this example.

Post merge actions

import anchorpoint
import os

def on_event_received(id, payload, ctx: anchorpoint.Context):

    # payload looks like this: {'type': 'success'}
    if isinstance(payload, dict):
        payload = payload.get('type')

    # trigger on pull
    if id == "gitpull" and payload == "success":
        print("Git pull finished")

    # trigger on merge
    if id == "gitmergebranch" and payload == "success":
        print("Git branch merge finished")

    # trigger on switch branch
    if id == "gitswitchbranch" and payload == "success":
        print("Git branch switch finished")

This hook listens to the Git events "git pull", "git merge branch" and "git switch branch" and triggers a print if the payload is "success".

Before Commit Validation

import anchorpoint

def on_before_commit(channel_id, message, changes, all_files_selected, progress, ctx: anchorpoint.Context):
"""Validate commit message and files before committing"""

# Require a commit message with a minimum length
if len(message.strip()) < 10:
anchorpoint.UI().show_info("Commit Rejected", "Commit message must be at least 10 characters.")
return None

# Check for forbidden file types
for change in changes:
if change.path.endswith(".tmp"):
anchorpoint.UI().show_info("Commit Rejected", f"Cannot commit temporary file: {change.path}")
return None

# Optionally modify the commit message
return f"[{ctx.username}] {message}"

This hook validates that the commit message has a minimum length, prevents committing .tmp files, and prepends the username to the commit message. Return None to cancel the commit, or the (modified) message string to proceed.

Before Commit - Cancel

def on_before_commit(channel_id, message, changes, all_files_selected, progress, ctx):
return None

Return None to cancel the commit entirely.

Before Commit - Modify Message

def on_before_commit(channel_id, message, changes, all_files_selected, progress, ctx):
return "[HOOK] " + message

Return a modified string to change the commit message before it is applied.

Custom Notification Handler

import anchorpoint
import apsync

def on_custom_notification(message: str, meta_data: dict, project_id: str,
project_path: str, ctx: anchorpoint.Context):
"""Handle custom notifications with metadata"""

print(f"Custom notification received: {message}")
print(f"Project: {project_id} at {project_path}")

# Check if metadata was provided
if meta_data:
print(f"Metadata: {meta_data}")

# Example: Handle workflow-based notifications
workflow = meta_data.get("workflow")

if workflow == "asset_approval":
status = meta_data.get("status")
asset_path = meta_data.get("asset_path")
priority = meta_data.get("priority")

print(f"Asset approval workflow triggered")
print(f"Status: {status}, Priority: {priority}")
print(f"Asset: {asset_path}")

# Auto-respond to high-priority notifications
if priority == "high":
api = anchorpoint.get_api()
project = api.get_project()

if project and asset_path:
# Set attribute or create task
api.attributes.set_attribute_value(
asset_path,
"Review Status",
"In Review"
)
print(f"Set review status for {asset_path}")

# Example: Handle file update notifications
elif workflow == "file_updated":
file_path = meta_data.get("file_path")
version = meta_data.get("version")

print(f"File updated: {file_path} (version {version})")

# You could trigger a reload, open the file, or sync
if file_path:
print(f"Consider reloading: {file_path}")

# Log the notification
if project_path:
log_file = f"{project_path}/.anchorpoint/custom_notifications.log"
with open(log_file, "a") as f:
f.write(f"[{message}] Metadata: {meta_data}\n")

if __name__ == "__main__":
print("Custom notification handler loaded")

This hook is triggered when schedule_custom_notification is called and receives the message and metadata, allowing you to create automated workflows and respond to custom events.

Custom Notification Navigation

import anchorpoint
import os

def on_custom_notification_navigation(message: str, meta_data: dict, project_id: str,
project_path: str, ctx: anchorpoint.Context):
"""Handle navigation when a custom notification is clicked"""

ui = anchorpoint.UI()

# Show a toast with notification details
ui.show_info("Notification Clicked", f"Message: {message}")

# Handle different notification types based on metadata
if meta_data:
notification_type = meta_data.get("type")

if notification_type == "file_ready":
# Navigate to a specific file
file_path = meta_data.get("file_path")
if file_path and os.path.exists(file_path):
# Open the file location in Anchorpoint
anchorpoint.open_folder(os.path.dirname(file_path))
ui.show_success("Opening File", f"Navigated to {file_path}")

elif notification_type == "task_assigned":
# Open task view
task_id = meta_data.get("task_id")
if task_id:
ui.show_info("Task Assigned", f"Opening task {task_id}")
# You could open a specific task view or dialog here

elif notification_type == "review_request":
# Show custom dialog for review
dialog = anchorpoint.Dialog()
dialog.title = "Review Request"
dialog.add_text(f"Review requested: {message}")

asset_path = meta_data.get("asset_path")
if asset_path:
dialog.add_text(f"Asset: {asset_path}")

dialog.add_button("Open Asset", callback=lambda d: anchorpoint.open_folder(asset_path))
dialog.add_button("Close")
dialog.show()

# Default: Show information
else:
ui.show_info("Custom Notification", message)

if __name__ == "__main__":
print("Custom notification navigation handler loaded")

This hook is triggered when a user clicks on a custom notification. It prevents the default navigation (opening the project folder) and instead implements custom behavior based on the notification's metadata, such as opening specific files, showing dialogs, or navigating to custom views.

Timeline Context Menu Hooks

Timeline context menu hooks allow you to add custom actions to the right-click menu of timeline entries. Register your action using the YAML register keys timeline_commit, timeline_stash, or timeline_changed_files.

Commit and Stash Actions

  • on_timeline_entry_context_action(channel_id, action_id, entry_id, ctx) Called when a user clicks your custom action in the right-click menu of a commit or stash entry.

    Arguments

    • channel_id (str): The timeline channel identifier (e.g. "Git")
    • action_id (str): Your action's unique identifier from the YAML file
    • entry_id (str): The commit hash or stash id
    • ctx (class: Context): Current context

Both timeline_commit and timeline_stash YAML registrations use the same hook function. If you need to distinguish between commits and stashes, you can check the entry_id format — commit hashes are long hex strings, stash ids are short numbers.

version: 1.0
action:
name: "Show Commit in GitLab"
id: "my::show_in_gitlab"
type: python
icon:
path: :/icons/link.svg
script: "show_in_gitlab.py"
register:
timeline_commit:
enable: true
import anchorpoint as ap
import webbrowser

def on_timeline_entry_context_action(channel_id, action_id, entry_id, ctx):
gitlab_url = f"https://gitlab.com/myorg/myrepo/-/commit/{entry_id}"
webbrowser.open(gitlab_url)

Changed Files Actions

  • on_timeline_changed_files_context_action(channel_id, action_id, changes, ctx) Called when a user clicks your custom action in the right-click menu of the "Changed Files" entry.

    Arguments

    • channel_id (str): The timeline channel identifier (e.g. "Git")
    • action_id (str): Your action's unique identifier from the YAML file
    • changes (list[VCPendingChange]): The list of currently pending changes
    • ctx (class: Context): Current context
version: 1.0
action:
name: "List Changed Files"
id: "my::list_changes"
type: python
icon:
path: :/icons/list.svg
script: "list_changes.py"
register:
timeline_changed_files:
enable: true
import anchorpoint as ap

def on_timeline_changed_files_context_action(channel_id, action_id, changes, ctx):
for change in changes:
print(f"Changed: {change.path}")
ap.UI().show_success(f"{len(changes)} files changed")