Plugin System
API stability:
provisionalThe 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
Decorator (recommended)
Use register() as a decorator on the function you wish to expose:
from errortools import register
@register("json_serialize")
def _to_json(obj):
import json
return json.dumps(obj.__dict__, default=str)
If the name has already been registered the previous function is silently overwritten.
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 |
|---|---|
|
Raises |
|
Returns |
|
Returns |
|
Raises |
|
Returns |
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 |
|---|---|---|
|
|
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 |
|---|---|---|
|
|
Plugin identifier. |
|
|
Value returned when the plugin is missing. If not provided, a |
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 |
|---|---|---|
|
|
Plugin identifier. |
|
Positional arguments forwarded to the plugin. |
|
|
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 |
|---|---|---|
|
|
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
See also
BaseLogger.bind()— a different kind of extensibility where extra context fields are attached to every log record.