Chapter 8 · Integrations / Automation Engineer

Config-driven dispatch

When you find yourself writing a growing chain of if action == 'backup': ... elif action == 'notify': ..., that's a smell. Every new action means editing the chain, the branches drift out of order, and one big function knows about everything.

The fix is a registry: a plain dict that maps a name to the function that handles it. Instead of branching, you look up the handler and call it.

  • registry[name] = handler_fn — register a handler under a name.
  • registry[action](payload) — dispatch: find the handler, call it with the data.

This is the plugin pattern. Adding a new action is just registering one more handler — you never touch the dispatch logic. It's the backbone of task runners, event systems, command routers, and webhook processors.

Two things make a registry production-grade: reject duplicate names so you don't silently clobber an existing handler, and fail loudly on an unknown action (a clear KeyError) rather than silently doing nothing.

Syntax

registry = {}                       # the registry is just a dict

registry[name] = handler_fn         # register

handler = registry[action]          # look up
result = handler(payload)           # dispatch

# vs. the anti-pattern this replaces:
# if action == 'a': ...
# elif action == 'b': ...

Worked examples

Register handlers, dispatch by name

def handle_backup(payload):
    return {'action': 'backup', 'target': payload.get('target', 'unknown')}

def handle_notify(payload):
    return {'action': 'notify', 'channel': payload.get('channel', 'slack')}

registry = {}
registry['backup'] = handle_backup
registry['notify'] = handle_notify

print(registry['backup']({'target': '/home'}))
print(registry['notify']({'channel': 'email'}))
# Output:
# {'action': 'backup', 'target': '/home'}
# {'action': 'notify', 'channel': 'email'}

A safe dispatch function (guards both ends)

def register(reg, name, fn):
    if name in reg:
        raise ValueError(f'Handler already registered: {name}')
    reg[name] = fn

def dispatch(reg, action, payload):
    if action not in reg:
        raise KeyError(f'Unknown action: {action}')
    return reg[action](payload)

reg = {}
register(reg, 'cleanup', lambda p: {'deleted': p.get('count', 0)})
print(dispatch(reg, 'cleanup', {'count': 7}))
try:
    dispatch(reg, 'launch_rocket', {})
except KeyError as e:
    print('rejected:', e)
# Output:
# {'deleted': 7}
# rejected: 'Unknown action: launch_rocket'

Adding an action needs no change to dispatch

reg = {}
reg['greet'] = lambda p: f"hi {p['name']}"
reg['shout'] = lambda p: p['text'].upper()

events = [('greet', {'name': 'Sam'}), ('shout', {'text': 'deploy'})]
for action, payload in events:
    print(reg[action](payload))
# Output:
# hi Sam
# DEPLOY

Common mistakes & gotchas

registry[action] vs registry[action]()

registry[action] gives you back the function *object* — it doesn't run it. You have to call it: registry[action](payload). Forgetting the call returns the function itself (e.g. <function handle_backup at 0x...>) instead of the result.

Silently overwriting a handler

A bare registry[name] = fn happily replaces an existing handler with the same name — a typo'd duplicate quietly shadows the real one and you debug it for an hour. Guard registration with an if name in registry: raise ValueError(...).

An unknown key crashes with a bare KeyError

registry[action] on a missing key raises KeyError: 'foo' — technically correct but unhelpful. Check if action not in registry first and raise a message that says what went wrong (f'Unknown action: {action}'), or use registry.get(action) and handle None.

Why it matters

Config-driven dispatch is how you build systems that grow without rotting. New event types, commands, and job kinds get added by registering a handler — the core stays small and untouched. Every serious framework, from web routers to message consumers, is a registry at heart.