Skip to content

Core API

grappler

The grappler module contains everything you need to get started using grappler. The main entry point is the Hook class.

Package

Bases: NamedTuple

A logical collection of plugins

name: str class-attribute

A name for the package which may be displayed to a human.

version: str class-attribute

A version number for the package.

id: str class-attribute

A unique identifier for the package.

platform: Optional[str] class-attribute

The package platform (sys.platform compatible value, if any)

Hook(topic: str, *, grappler: Optional[Grappler] = None) -> None

Bases: Generic[T]

An abstraction for loading plugins providing a specific behavior.

Hooks provide an iterable interface for loaded plugin values. This allows to define a hook with a topic to load plugins for, and then iterate over the hook in order to access the externally loaded objects. Furthermore, the iterated objects can be further restricted by providing a type argument to the hook; when this is done, any externally loaded object that is not an instance of the given type is skipped.

Type arguments to Hook must be usable in isinstance check

The type argument given to the hook class should be usable as the second argument to Python's isinstance. Failing this constraint will result in a type error upon iteration (notable exception: Any may be used). This means that some types which are otherwise valid are not usable in this context (e.g. None or Callable[...]).

Parameters:

Name Type Description Default
topic str

An arbitrary string identifier for the behavior that your hook will load plugins for. The hook will then only load plugins that advertise the topic given here.

required
grappler Optional[Grappler]

When given, it should be a Grappler which will be used to find and load plugins. If not given, then an unmodified instance of EntryPointGrappler is used. See the Customising Loading section of the user guide, which gives an explanation of how to setup a grappler for more complex loading behavior.

None

Usage:

# iterate the hook to load all plugins from the topic.
from grappler import Hook
objs = list(Hook("topic.counter-functions"))

# use a type argument to restrict the objects that are returned.
from typing import Any, Protocol, runtime_checkable
from grappler import Hook

@runtime_checkable
class CounterFunction(Protocol):
    def __call__(self, items: Any) -> int: ...

counter_functions = list(Hook[CounterFunction]("topic.counter-functions"))
# (note: argument specs are not checked for callable Protocol
# instance checks; the mere presence of a `__call__` method will satisfy
# the hook's filter. This is due to how `isinstance` works in Python.)
Source code in grappler/_hook.py
def __init__(self, topic: str, *, grappler: Optional[Grappler] = None) -> None:
    self.topic = topic
    self.grappler = grappler or EntryPointGrappler()
    self._loaded: Set[Plugin] = set()

__iter__() -> Iterator[T]

Return an iterator to loaded plugin objects from the hook's topic.

If the hook was instantiated with a type argument, then only objects which pass isinstance(obj, T) are included in the iterator.

Source code in grappler/_hook.py
def __iter__(self) -> Iterator[T]:
    """Return an iterator to loaded plugin objects from the hook's topic.

    If the hook was instantiated with a type argument, then only objects
    which pass `isinstance(obj, T)` are included in the iterator.
    """
    return self._iter_grappler(self.grappler)

loaded_plugins() -> Collection[Plugin] property

Return a collection containing all plugins loaded by the hook so far.

Source code in grappler/_hook.py
@property
def loaded_plugins(self) -> Collection[Plugin]:
    """
    Return a collection containing all plugins loaded by the hook
    so far.
    """
    return list(self._loaded)

Plugin dataclass

An external, loadable Python object

grappler_id: str class-attribute

The id of the grappler that the plugin came from.

plugin_id: str class-attribute

A unique identifier for the plugin.

package: Package class-attribute

A struct containing more information about the package containing the plugin

topics: Tuple[str, ...] class-attribute

A tuple of topics that the plugin advertises.

name: Optional[str] class-attribute

A name for the plugin which may be displayed to a human.

Grappler

Bases: Protocol

General protocol for an object that can find and load plugins.

id() -> str property

A globally unique identifier for the grappler.

Source code in grappler/_types.py
@property
def id(self) -> str:
    """A globally unique identifier for the grappler."""

find(topic: Optional[str] = None) -> ContextManager[Iterator[Plugin]]

Return a context managed iterator of plugins that this grappler can load.

Implementors of this protocol only need to make sure that the plugins can be loaded when the returned context manager is still open; once the context closes, then it is not required for the returned plugins to still be loadable.

Source code in grappler/_types.py
def find(self, topic: Optional[str] = None) -> ContextManager[Iterator[Plugin]]:
    """
    Return a context managed iterator of plugins that this grappler
    can load.

    Implementors of this protocol only need to make sure that the
    plugins can be loaded when the returned context manager is still
    open; once the context closes, then it is not required for the
    returned plugins to still be loadable.
    """

load(plugin: Plugin) -> Any

Load an object out of an plugin.

May raise an UnknownPluginError if the plugin type is not recognised by the grappler. (This may happen even when supplied a plugin which originated in the grappler, but it's find context is already closed.)

Source code in grappler/_types.py
def load(self, plugin: Plugin) -> Any:
    """Load an object out of an plugin.

    May raise an UnknownPluginError if the plugin type is not recognised
    by the grappler.
    (This may happen even when supplied a plugin which originated in
    the grappler, but it's find context is already closed.)
    """

UnknownPluginError(plugin: Plugin, grappler: Grappler) -> None

Bases: LookupError

Raised when a grappler is asked to load an plugin it doesn't know how to.

Source code in grappler/_types.py
def __init__(self, plugin: Plugin, grappler: Grappler) -> None:
    super().__init__(
        self,
        f"Grappler (id={repr(grappler.id)}) does not know "
        f"how to load plugin: {plugin}",
    )
    self.plugin = plugin
    self.grappler = grappler