Plugin System

API stability: provisional

The plugin API is functional but may evolve based on community feedback.

Added in version 3.2.

errortools includes an ultra-lightweight plugin registry. It is useful when you want to:

  • Dynamically extend library behaviour at runtime.

  • Keep optional features as pluggable modules.

  • Swap implementations for different environments (dev vs. prod).

Registering a plugin

Programmatic registration

For cases where a decorator is inconvenient (e.g. third-party adapters), use Registry.register:

from errortools import Registry

def yaml_serialize(obj):
    ...  # implementation

Registry.register("yaml_serialize", yaml_serialize)

Calling a plugin

Direct lookup

get() returns the raw callable. This is the preferred pattern when you will invoke the plugin many times and want to avoid repeated dictionary look-ups:

from errortools import get

serialize = get("json_serialize")

for user in users:
    payload = serialize(user)
    queue.put(payload)

One-shot execution

run() combines lookup and invocation. It is convenient for ad-hoc calls:

from errortools import run

output = run("json_serialize", some_user)

Default value for missing plugins

get() accepts an optional default. When provided, missing plugins return the default instead of raising ValueError:

from errortools import get

formatter = get("rich_formatter", default=plain_formatter)

Inspecting and removing plugins

from errortools import list_all, remove

print(list_all())
# ['json_serialize']

remove("json_serialize")
print(list_all())
# []

remove() is idempotent—calling it with a name that does not exist does nothing.

You can check whether a plugin exists without risk of raising:

from errortools import has

if has("json_serialize"):
    ...

To remove all plugins at once (useful in test teardown):

from errortools import clear

clear()

Error handling

Situation

Behaviour

get("unknown")

Raises ValueError: Plugin 'unknown' is not registered

get("unknown", default=f)

Returns f

get("unknown", default=None)

Returns None (since 3.3.5)

run("unknown")

Raises ValueError (no default path)

has("unknown")

Returns False

Practical example: environment-based alert backend

# errortools_alerts/__init__.py
from errortools import register

@register("alert.send")
def _default_alert(message: str):
    print(f"[ALERT] {message}", file=sys.stderr)

A production package can override it:

# errortools_alerts_pagerduty/__init__.py
from errortools import Registry
from .client import PagerDutyClient

_registry = PagerDutyClient(api_key=...)
Registry.register("alert.send", _registry.send)

Application code stays the same regardless of which package is installed:

from errortools import run

run("alert.send", "Disk usage exceeded 90 %")

Thread safety

The internal registry is a plain dict. Concurrent registration / removal from multiple threads is not guaranteed to be safe; serialise such operations with a threading.Lock if necessary. Look-ups (get, run, list_all) are safe because CPython dict read operations are atomic.

API reference

register(name: str) -> Callable[[Callable], Callable]

Decorator that adds a function to the global registry under name.

Parameter

Type

Description

name

str

Unique identifier for the plugin.

Returns: A decorator that takes the target function and returns it unchanged.

get(name: str, default: Any = None) -> Any

Retrieve a registered plugin.

Parameter

Type

Description

name

str

Plugin identifier.

default

Any

Value returned when the plugin is missing. If not provided, a ValueError is raised instead.

Returns: The registered callable, or default.

Raises: ValueError — if the plugin does not exist and no default was supplied.

Note: Starting from 3.3.5, passing default=None is correctly honored and returns None instead of raising.

run(name: str, *args, **kwargs) -> Any

Look up and immediately call a plugin.

Parameter

Type

Description

name

str

Plugin identifier.

*args

Positional arguments forwarded to the plugin.

**kwargs

Keyword arguments forwarded to the plugin.

Returns: Whatever the plugin callable returns.

Raises: ValueError — if the plugin does not exist.

list_all() -> list[str]

Return a snapshot of currently registered plugin names.

Returns: list[str]

remove(name: str) -> None

Delete a plugin from the registry. No-op if the name is absent.

has(name: str) -> bool

Check whether a plugin is registered.

Parameter

Type

Description

name

str

Plugin identifier.

Returns: True if the plugin exists, otherwise False.

clear() -> None

Remove all plugins from the registry. This is useful for test isolation or resetting state.

class Registry

Programmatic equivalent of the top-level functions, exposed as a class for IDE discoverability.

Method

Equivalent to

Registry.register(name, func)

register(name)(func)

Registry.get(name)

get(name)

Registry.has(name)

has(name)

Registry.list_all()

list_all()

Registry.remove(name)

remove(name)

Registry.clear()

clear()

See also

  • BaseLogger.bind() — a different kind of extensibility where extra context fields are attached to every log record.