Workflow Handlers: Overview

Use Hooks and Reducers to enhance your Flows.

Sym's SDK allows you to inject your own logic at key steps in Flows. There are two mechanisms for customizing the logic of a Flow, known as Handlers. The two types of Handlers are reducers and hooks.

πŸ“˜

Handlers are defined in the sym.sdk.annotations module.

Handler Types

Reducers take in an Event and return a single value, and are prefixed with get_.

Hooks prefixed with on_ allow you to alter control flow by overriding default implementations of Template steps.

Hooks prefixed with after_ allow you to subscribe to Events so as to enact various side-effects.

To demonstrate how handlers are used, here is some pseudocode showing what the Sym runtime would do upon receiving a new "foo" Event.

def handle_foo_event(evt):
    # Hooks can change control flow or have side effects
    if event_override := hooks.on_foo(evt):
        return event_override
  handlers.after_foo(evt)

    # Reducers are helper functions that will return config values
    color = reducers.get_color(evt)
    foo_default_implementation(evt, color=color)

Reducers

A Reducer injects key logic into Flows by taking an Event as input, and returning a single value.

For example, when using the sym:approval Template, you need a way to let Sym know who to route a specific request to. This is exactly what the get_approvers reducer does! To implement it, you write a Python function which takes in an Event (containing the requesting user and requested Target), and return a set of User-like objects (such as email addresses, Slack handles, Slack channels or Okta groups).

from sym.sdk.annotations import reducer
from sym.sdk.integrations import pagerduty, okta, slack

# Reducers can be mandatory, and provide dynamic config values

@reducer 
def get_approvers(evt):
    """Returns a set of approvers, given a user and a target."""
   
    # The import here uses credentials defined in an Integration in Terraform
    if pagerduty.is_on_call(evt.user, schedule="id_of_eng_on_call"):
        # This is a self-approval in a DM
        return slack.user(evt.user)

    if evt.payload.fields["urgency"] == "High":
        # This is a self-approval in a channel
        return slack.channel("#break-glass", allow_self=True)

    on_call_mgrs = okta.group("OnCallManagers").members()
    # This would create a group DM for on-call managers
    return slack.group(on_call_mgrs)

On-Hooks

Hooks are executed before the default implementation of an Event handler in a Template. They offer an opportunity to bypass, short-circuit, or alter control flow, by emitting Events.

For example, when using the sym:approval Template, you may want to auto-approve any requests that come from interns (what could go wrong!).

from sym.sdk.annotations import hook
from sym.sdk.integrations import okta
from sym.sdk.templates.approval import events

# Hooks are optional, and can change control flow by returning Events

@hook
def on_request(evt):
    """Executed after a request is made, before the default handler."""
    if "Intern" in okta.user(evt.user).profile["job_title"]:
        return events.approve()

After-Hooks

After Hooks are executed after the default implementation of an Event handler in a Template. They offer an opportunity to execute additional side-effects, such as logging or notifications.

For example, when using the sym:approval Template, you may want to log every approval to a private Slack channel.

from sym.sdk.annotations import hook
from sym.sdk.integrations import slack

# After-hooks are optional, and let you execute side-effects after an Event

@hook
def after_approve(evt):
    """Executed after an approved event has been fired."""
    message = f"{evt.user.name} has been approved for {evt.target.name}!"
    slack.send_message("#private-audit-log", message)

Did this page help you?