Skip to content

Lifecycle and validation

This page covers the runtime behaviour a host relies on: how registration validates plugins, how removal and blocking work, and the threading model.

Registration is validated up front

register discovers a plugin's implementations, validates every one, and only then wires them in. If any implementation is invalid, the call raises and the plugin is not partially registered.

try:
    pm.register(plugin, name="acme")
except PluginValidationError as error:
    log.warning("skipping %s: %s", error.plugin_name, error)

Validation covers:

Problem Result
implements a hook that has no spec PluginValidationError (unless optional)
declares an argument the spec lacks PluginValidationError
duplicate plugin name ValueError
same plugin object registered twice ValueError

Removal and blocking

pm.unregister("acme")        # remove it and all its extensions
pm.set_blocked("acme")       # remove it AND refuse to register it again
pm.is_blocked("acme")        # True

Blocking is checked by both register and load_entrypoints, so a blocked name cannot sneak back in through entry-point discovery.

Introspection

pm.plugin_names()            # ['acme', 'widget']
pm.get_plugin("acme")        # the object, or None
pm.get_name(plugin)          # 'acme', or None
pm.get_hookcallers(plugin)   # the hooks this plugin contributes to
pm.caller(Specs.add_ingredients).implementations()   # impls in call order
repr(pm)                     # "<PluginManager 'kitchen' plugins=2>"

One-off implementations

call_extra runs additional implementations for a single call without registering them - handy in tests or for injecting a temporary behaviour:

def temporary_rule(record):
    return None if record.get("ok") else "not ok"

problems = pm.caller(Specs.check).call_extra([temporary_rule], {"record": record})

The extra implementations do not persist; the next ordinary call sees only the registered ones.

Threading model

Registry mutations - add_extension_points, register, unregister, set_blocked - are serialised by a re-entrant lock, so plugins can be loaded concurrently.

Hook calls are deliberately not locked. Locking every dispatch would serialise the whole application for no benefit in the common case, where plugins are loaded once at startup and called many times afterwards. If your host can register plugins while hooks are being called from other threads, guard those calls yourself.