diff --git a/docs/api/trinity/api.cli.rst b/docs/api/trinity/api.cli.rst new file mode 100644 index 0000000000..8e65a9bb48 --- /dev/null +++ b/docs/api/trinity/api.cli.rst @@ -0,0 +1,46 @@ +Command Line Interface (CLI) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: shell + + usage: trinity [-h] [--version] [--trinity-root-dir TRINITY_ROOT_DIR] + [-l {debug,info}] [--network-id NETWORK_ID | --ropsten] + [--sync-mode {full,light} | --light] [--data-dir DATA_DIR] + [--nodekey NODEKEY] [--nodekey-path NODEKEY_PATH] + {console,attach} ... + + positional arguments: + {console,attach} + console run the chain and start the trinity REPL + attach open an REPL attached to a currently running chain + + optional arguments: + -h, --help show this help message and exit + + sync mode: + --version show program's version number and exit + --trinity-root-dir TRINITY_ROOT_DIR + The filesystem path to the base directory that trinity + will store it's information. Default: + $XDG_DATA_HOME/.local/share/trinity + + logging: + -l {debug,info}, --log-level {debug,info} + Sets the logging level + + network: + --network-id NETWORK_ID + Network identifier (1=Mainnet, 3=Ropsten) + --ropsten Ropsten network: pre configured proof-of-work test + network. Shortcut for `--networkid=3` + + sync mode: + --sync-mode {full,light} + --light Shortcut for `--sync-mode=light` + + chain: + --data-dir DATA_DIR The directory where chain data is stored + --nodekey NODEKEY Hexadecimal encoded private key to use for the nodekey + --nodekey-path NODEKEY_PATH + The filesystem path to the file which contains the + nodekey diff --git a/docs/api/trinity/extensibility/api.extensibility.events.rst b/docs/api/trinity/extensibility/api.extensibility.events.rst new file mode 100644 index 0000000000..46139d19b7 --- /dev/null +++ b/docs/api/trinity/extensibility/api.extensibility.events.rst @@ -0,0 +1,8 @@ +Events +====== + +.. autoclass:: trinity.extensibility.events.PluginStartedEvent + :members: + +.. autoclass:: trinity.extensibility.events.ResourceAvailableEvent + :members: diff --git a/docs/api/trinity/extensibility/api.extensibility.exceptions.rst b/docs/api/trinity/extensibility/api.extensibility.exceptions.rst new file mode 100644 index 0000000000..67a93a6282 --- /dev/null +++ b/docs/api/trinity/extensibility/api.extensibility.exceptions.rst @@ -0,0 +1,9 @@ +Exceptions +========== + +.. autoclass:: trinity.extensibility.exceptions.EventBusNotReady + :members: + +.. autoclass:: trinity.extensibility.exceptions.UnsuitableShutdownError + :members: + diff --git a/docs/api/trinity/extensibility/api.extensibility.plugin.rst b/docs/api/trinity/extensibility/api.extensibility.plugin.rst new file mode 100644 index 0000000000..ce4af26073 --- /dev/null +++ b/docs/api/trinity/extensibility/api.extensibility.plugin.rst @@ -0,0 +1,39 @@ +Plugin +====== + + +PluginContext +------------- + +.. autoclass:: trinity.extensibility.plugin.PluginContext + :members: + +BasePlugin +---------- + +.. autoclass:: trinity.extensibility.plugin.BasePlugin + :members: + +BaseSyncStopPlugin +------------------ + +.. autoclass:: trinity.extensibility.plugin.BaseSyncStopPlugin + :members: + +BaseAsyncStopPlugin +------------------- + +.. autoclass:: trinity.extensibility.plugin.BaseAsyncStopPlugin + :members: + +BaseMainProcessPlugin +--------------------- + +.. autoclass:: trinity.extensibility.plugin.BaseMainProcessPlugin + :members: + +BaseIsolatedPlugin +------------------ + +.. autoclass:: trinity.extensibility.plugin.BaseIsolatedPlugin + :members: \ No newline at end of file diff --git a/docs/api/trinity/extensibility/api.extensibility.plugin_manager.rst b/docs/api/trinity/extensibility/api.extensibility.plugin_manager.rst new file mode 100644 index 0000000000..5f98a25aa3 --- /dev/null +++ b/docs/api/trinity/extensibility/api.extensibility.plugin_manager.rst @@ -0,0 +1,27 @@ +PluginManager +============= + +BaseManagerProcessScope +----------------------- + +.. autoclass:: trinity.extensibility.plugin_manager.BaseManagerProcessScope + :members: + +MainAndIsolatedProcessScope +--------------------------- + +.. autoclass:: trinity.extensibility.plugin_manager.MainAndIsolatedProcessScope + :members: + +SharedProcessScope +------------------ + +.. autoclass:: trinity.extensibility.plugin_manager.SharedProcessScope + :members: + +PluginManager +------------- + +.. autoclass:: trinity.extensibility.plugin_manager.PluginManager + :members: + diff --git a/docs/api/trinity/extensibility/index.rst b/docs/api/trinity/extensibility/index.rst new file mode 100644 index 0000000000..92d6fc2c6e --- /dev/null +++ b/docs/api/trinity/extensibility/index.rst @@ -0,0 +1,16 @@ +Extensibility +============= + +.. warning:: + + The extensibility API isn't stable yet. Expect breaking changes. + +.. toctree:: + :maxdepth: 4 + :name: toc-eth-api-extensibility + + api.extensibility.events.rst + api.extensibility.exceptions.rst + api.extensibility.plugin.rst + api.extensibility.plugin_manager.rst + diff --git a/docs/api/trinity/index.rst b/docs/api/trinity/index.rst index a8492927c6..2062e59cd6 100644 --- a/docs/api/trinity/index.rst +++ b/docs/api/trinity/index.rst @@ -3,49 +3,12 @@ Trinity This section aims to provide a detailed description of all APIs. If you are looking for something more hands-on or higher-level check out the existing :doc:`Trinity guides `. -Command Line Interface (CLI) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. toctree:: + :maxdepth: 4 + :name: toc-api-trinity + :caption: API -.. code-block:: shell + api.cli + extensibility/index - usage: trinity [-h] [--version] [--trinity-root-dir TRINITY_ROOT_DIR] - [-l {debug,info}] [--network-id NETWORK_ID | --ropsten] - [--sync-mode {full,light} | --light] [--data-dir DATA_DIR] - [--nodekey NODEKEY] [--nodekey-path NODEKEY_PATH] - {console,attach} ... - positional arguments: - {console,attach} - console run the chain and start the trinity REPL - attach open an REPL attached to a currently running chain - - optional arguments: - -h, --help show this help message and exit - - sync mode: - --version show program's version number and exit - --trinity-root-dir TRINITY_ROOT_DIR - The filesystem path to the base directory that trinity - will store it's information. Default: - $XDG_DATA_HOME/.local/share/trinity - - logging: - -l {debug,info}, --log-level {debug,info} - Sets the logging level - - network: - --network-id NETWORK_ID - Network identifier (1=Mainnet, 3=Ropsten) - --ropsten Ropsten network: pre configured proof-of-work test - network. Shortcut for `--networkid=3` - - sync mode: - --sync-mode {full,light} - --light Shortcut for `--sync-mode=light` - - chain: - --data-dir DATA_DIR The directory where chain data is stored - --nodekey NODEKEY Hexadecimal encoded private key to use for the nodekey - --nodekey-path NODEKEY_PATH - The filesystem path to the file which contains the - nodekey diff --git a/trinity/extensibility/__init__.py b/trinity/extensibility/__init__.py index 2b03807f7b..33c602592d 100644 --- a/trinity/extensibility/__init__.py +++ b/trinity/extensibility/__init__.py @@ -9,6 +9,7 @@ BaseSyncStopPlugin, DebugPlugin, PluginContext, + TrinityBootInfo, ) from trinity.extensibility.plugin_manager import ( # noqa: F401 BaseManagerProcessScope, diff --git a/trinity/extensibility/exceptions.py b/trinity/extensibility/exceptions.py index d61c25cbb9..20751ca257 100644 --- a/trinity/extensibility/exceptions.py +++ b/trinity/extensibility/exceptions.py @@ -3,18 +3,21 @@ ) -class UnsuitableShutdownError(BaseTrinityError): +class EventBusNotReady(BaseTrinityError): """ - Raised when `shutdown` was called on a ``PluginManager`` instance that operates - in the ``MainAndIsolatedProcessScope`` or when ``shutdown_blocking`` was called on a - ``PluginManager`` instance that operates in the ``SharedProcessScope``. + Raised when a plugin tried to access an :class:`~lahja.eventbus.EventBus` before the plugin + had received its :meth:`~trinity.extensibility.plugin.BasePlugin.ready` call. """ pass -class EventBusNotReady(BaseTrinityError): +class UnsuitableShutdownError(BaseTrinityError): """ - Raised when a plugin tried to access an EventBus before the plugin - had received its ``ready`` call. + Raised when :meth:`~trinity.extensibility.plugin_manager.PluginManager.shutdown` was called on + a :class:`~trinity.extensibility.plugin_manager.PluginManager` instance that operates in the + :class:`~trinity.extensibility.plugin_manager.MainAndIsolatedProcessScope` or when + :meth:`~trinity.extensibility.plugin.PluginManager.shutdown_blocking` was called on a + :class:`~trinity.extensibility.plugin_manager.PluginManager` instance that operates in the + :class:`~trinity.extensibility.plugin_manager.SharedProcessScope`. """ pass diff --git a/trinity/extensibility/plugin.py b/trinity/extensibility/plugin.py index 2fc9a36114..a80ec9cdc5 100644 --- a/trinity/extensibility/plugin.py +++ b/trinity/extensibility/plugin.py @@ -15,6 +15,7 @@ from typing import ( Any, Dict, + NamedTuple ) from lahja import ( @@ -49,27 +50,64 @@ ) +class TrinityBootInfo(NamedTuple): + args: Namespace + trinity_config: TrinityConfig + boot_kwargs: Dict[str, Any] = None + + class PluginContext: """ - The ``PluginContext`` holds valuable contextual information such as the parsed - arguments that were used to launch ``Trinity``. It also provides access to APIs - such as the ``EventBus``. - - Each plugin gets a ``PluginContext`` injected during startup. + The :class:`~trinity.extensibility.plugin.PluginContext` holds valuable contextual information + and APIs to be used by a plugin. This includes the parsed arguments that were used to launch + ``Trinity`` as well as an :class:`~lahja.endpoint.Endpoint` that the plugin can use to connect + to the central :class:`~lahja.eventbus.EventBus`. + + The :class:`~trinity.extensibility.plugin.PluginContext` is set during startup and is + guaranteed to exist by the time that a plugin receives its + :meth:`~trinity.extensibility.plugin.BasePlugin.ready` call. """ - def __init__(self, endpoint: Endpoint) -> None: - self.event_bus = endpoint - self.boot_kwargs: Dict[str, Any] = None - self.args: Namespace = None - self.trinity_config: TrinityConfig = None + def __init__(self, endpoint: Endpoint, boot_info: TrinityBootInfo) -> None: + self._event_bus = endpoint + self._args: Namespace = boot_info.args + self._trinity_config: TrinityConfig = boot_info.trinity_config + # Leaving boot_kwargs as an undocumented public member as it will most likely go away + self.boot_kwargs: Dict[str, Any] = boot_info.boot_kwargs def shutdown_host(self, reason: str) -> None: + """ + Shutdown ``Trinity`` by broadcasting a :class:`~trinity.events.ShutdownRequest` on the + :class:`~lahja.eventbus.EventBus`. The actual shutdown routine is executed and coordinated + by the main application process who listens for this event. + """ self.event_bus.broadcast( ShutdownRequest(reason), BroadcastConfig(filter_endpoint=MAIN_EVENTBUS_ENDPOINT) ) + @property + def args(self) -> Namespace: + """ + Return the parsed arguments that were used to launch the application + """ + return self._args + + @property + def event_bus(self) -> Endpoint: + """ + Return the :class:`~lahja.endpoint.Endpoint` that the plugin uses to connect to the + central :class:`~lahja.eventbus.EventBus` + """ + return self._event_bus + + @property + def trinity_config(self) -> TrinityConfig: + """ + Return the :class:`~trinity.config.TrinityConfig` + """ + return self._trinity_config + class BasePlugin(ABC): @@ -80,18 +118,23 @@ class BasePlugin(ABC): @abstractmethod def name(self) -> str: """ - Describe the name of the plugin + Describe the name of the plugin. """ - raise NotImplementedError( - "Must be implemented by subclasses" - ) + pass @property def logger(self) -> logging.Logger: + """ + Get the :class:`~logging.Logger` for this plugin. + """ return logging.getLogger('trinity.extensibility.plugin.BasePlugin#{0}'.format(self.name)) @property def event_bus(self) -> Endpoint: + """ + Get the :class:`~lahja.endpoint.Endpoint` that this plugin uses to connect to the + :class:`~lahja.eventbus.EventBus` + """ if self.context is None: raise EventBusNotReady("Tried accessing ``event_bus`` before ``ready`` was called") @@ -105,19 +148,24 @@ def set_context(self, context: PluginContext) -> None: def ready(self) -> None: """ - Called after the plugin received its context and is ready to bootstrap itself. + Notify the plugin that it is ready to bootstrap itself. Plugins can rely + on the :class:`~trinity.extensibility.plugin.PluginContext` to be set + after this method has been called. """ pass def configure_parser(self, arg_parser: ArgumentParser, subparser: _SubParsersAction) -> None: """ - Called at startup, giving the plugin a chance to amend the Trinity CLI argument parser + Give the plugin a chance to amend the Trinity CLI argument parser. This hook is called + before :meth:`~trinity.extensibility.plugin.BasePlugin.ready` """ pass def start(self) -> None: """ - Prepare the plugin to get started and eventually cause ``start`` to get called. + Delegate to :meth:`~trinity.extensibility.plugin.BasePlugin._start` and set ``running`` + to ``True``. Broadcast a :class:`~trinity.extensibility.events.PluginStartedEvent` on the + :class:`~lahja.eventbus.EventBus` and hence allow other plugins to act accordingly. """ self.running = True self._start() @@ -128,15 +176,19 @@ def start(self) -> None: def _start(self) -> None: """ - The ``start`` method is called only once when the plugin is started. In the case - of an `BaseIsolatedPlugin` this method will be launched in a separate process. + Perform the actual plugin start routine. In the case of a `BaseIsolatedPlugin` this method + will be called in a separate process. + + This method should usually be overwritten by subclasses with the exception of plugins that + set ``func`` on the ``ArgumentParser`` to redefine the entire host program. """ pass class BaseSyncStopPlugin(BasePlugin): """ - A ``BaseSyncStopPlugin`` unwinds synchronoulsy, hence blocks until shut down is done. + A :class:`~trinity.extensibility.plugin.BaseSyncStopPlugin` unwinds synchronoulsy, hence blocks + until the shutdown is done. """ def _stop(self) -> None: """ @@ -146,8 +198,8 @@ def _stop(self) -> None: def stop(self) -> None: """ - Stop the plugin by delegating to - :meth:`~trinity.extensibility.plugin.BaseSyncStopPlugin._stop` + Delegate to :meth:`~trinity.extensibility.plugin.BaseSyncStopPlugin._stop` causing the + plugin to stop and setting ``running`` to ``False``. """ self._stop() self.running = False @@ -155,7 +207,8 @@ def stop(self) -> None: class BaseAsyncStopPlugin(BasePlugin): """ - A ``BaseAsyncStopPlugin`` unwinds asynchronoulsy, hence needs to be awaited. + A :class:`~trinity.extensibility.plugin.BaseAsyncStopPlugin` unwinds asynchronoulsy, hence + needs to be awaited. """ async def _stop(self) -> None: @@ -166,8 +219,8 @@ async def _stop(self) -> None: async def stop(self) -> None: """ - Asynchronously stop the plugin by delegating to - :meth:`~trinity.extensibility.plugin.BaseAsyncStopPlugin._stop` + Delegate to :meth:`~trinity.extensibility.plugin.BaseAsyncStopPlugin._stop` causing the + plugin to stop asynchronously and setting ``running`` to ``False``. """ await self._stop() self.running = False @@ -175,18 +228,21 @@ async def stop(self) -> None: class BaseMainProcessPlugin(BasePlugin): """ - A ``BaseMainProcessPlugin`` overtakes the whole main process before most of the Trinity boot - process had a chance to start. In that sense it redefines the whole meaning of the ``trinity`` - process. + A :class:`~trinity.extensibility.plugin.BaseMainProcessPlugin` overtakes the whole main process + early before any of the subsystems started. In that sense it redefines the whole meaning of the + ``trinity`` command. """ pass class BaseIsolatedPlugin(BaseSyncStopPlugin): """ - A ``BaseIsolatedPlugin`` runs in an isolated process and doesn't dictate whether its - implementation is based on non-blocking asyncio or synchronous calls. When an isolated - plugin is stopped it will first receive a SIGINT followed by a SIGTERM soon after. + A :class:`~trinity.extensibility.plugin.BaseIsolatedPlugin` runs in an isolated process and + hence provides security and flexibility by not making assumptions about its internal + operations. + + Such plugins are free to use non-blocking asyncio as well as synchronous calls. When an + isolated plugin is stopped it does first receive a SIGINT followed by a SIGTERM soon after. It is up to the plugin to handle these signals accordingly. """ @@ -194,7 +250,7 @@ class BaseIsolatedPlugin(BaseSyncStopPlugin): def start(self) -> None: """ - Prepare the plugin to get started and eventually cause ``start`` to get called. + Prepare the plugin to get started and eventually call ``_start`` in a separate process. """ self.running = True self._process = ctx.Process( diff --git a/trinity/extensibility/plugin_manager.py b/trinity/extensibility/plugin_manager.py index 7c57be5827..d6669f5d83 100644 --- a/trinity/extensibility/plugin_manager.py +++ b/trinity/extensibility/plugin_manager.py @@ -37,12 +37,17 @@ BasePlugin, BaseSyncStopPlugin, PluginContext, + TrinityBootInfo, ) class BaseManagerProcessScope(ABC): """ - Define the operational model under which a ``PluginManager`` runs. + Define the operational model under which a + :class:`~trinity.extensibility.plugin_manager.PluginManager` works. Subclasses + define whether a :class:`~trinity.extensibility.plugin_manager.PluginManager` is + responsible to manage a specific plugin and how its + :class:`~trinity.extensibility.plugin.PluginContext` is created. """ endpoint: Endpoint @@ -50,21 +55,19 @@ class BaseManagerProcessScope(ABC): @abstractmethod def is_responsible_for_plugin(self, plugin: BasePlugin) -> bool: """ - Define whether a ``PluginManager`` operating under this scope is responsible - for a given plugin or not. + Define whether a :class:`~trinity.extensibility.plugin_manager.PluginManager` operating + under this scope is responsible to manage the given ``plugin``. """ - raise NotImplementedError("Must be implemented by subclasses") + pass @abstractmethod def create_plugin_context(self, plugin: BasePlugin, - args: Namespace, - trinity_config: TrinityConfig, - boot_kwargs: Dict[str, Any]) -> PluginContext: + boot_info: TrinityBootInfo) -> None: """ - Create the ``PluginContext`` for a given plugin. + Create the :class:`~trinity.extensibility.plugin.PluginContext` for the given ``plugin``. """ - raise NotImplementedError("Must be implemented by subclasses") + pass class MainAndIsolatedProcessScope(BaseManagerProcessScope): @@ -74,27 +77,29 @@ def __init__(self, event_bus: EventBus, main_proc_endpoint: Endpoint) -> None: self.endpoint = main_proc_endpoint def is_responsible_for_plugin(self, plugin: BasePlugin) -> bool: + """ + Return ``True`` if if the plugin instance is a subclass of + :class:`~trinity.extensibility.plugin.BaseIsolatedPlugin` or + :class:`~trinity.extensibility.plugin.BaseMainProcessPlugin` + """ return isinstance(plugin, BaseIsolatedPlugin) or isinstance(plugin, BaseMainProcessPlugin) def create_plugin_context(self, plugin: BasePlugin, - args: Namespace, - trinity_config: TrinityConfig, - boot_kwargs: Dict[str, Any]) -> PluginContext: + boot_info: TrinityBootInfo) -> None: + """ + Create a :class:`~trinity.extensibility.plugin.PluginContext` that holds a reference to a + dedicated new :class:`~lahja.endpoint.Endpoint` to enable plugins which run in their own + isolated processes to connect to the central :class:`~lahja.endpoint.EventBus` that Trinity + uses to enable application wide event-driven communication even across process boundaries. + """ if isinstance(plugin, BaseIsolatedPlugin): # Isolated plugins get an entirely new endpoint to be passed into that new process - context = PluginContext( - self.event_bus.create_endpoint(plugin.name) - ) - context.args = args - context.trinity_config = trinity_config - context.boot_kwargs = boot_kwargs - return context - - # A plugin that overtakes the main process never gets far enough to even get a context. - # For now it should be safe to just return `None`. Maybe reconsider in the future. - return None + plugin.set_context(PluginContext( + self.event_bus.create_endpoint(plugin.name), + boot_info, + )) class SharedProcessScope(BaseManagerProcessScope): @@ -103,20 +108,24 @@ def __init__(self, shared_proc_endpoint: Endpoint) -> None: self.endpoint = shared_proc_endpoint def is_responsible_for_plugin(self, plugin: BasePlugin) -> bool: + """ + Return ``True`` if if the plugin instance is a subclass of + :class:`~trinity.extensibility.plugin.BaseAsyncStopPlugin`. + """ return isinstance(plugin, BaseAsyncStopPlugin) def create_plugin_context(self, plugin: BasePlugin, - args: Namespace, - trinity_config: TrinityConfig, - boot_kwargs: Dict[str, Any]) -> PluginContext: - + boot_info: TrinityBootInfo) -> None: + """ + Create a :class:`~trinity.extensibility.plugin.PluginContext` that uses the + :class:`~lahja.endpoint.Endpoint` of the + :class:`~trinity.extensibility.plugin_manager.PluginManager` to communicate with the + central :class:`~lahja.endpoint.EventBus` that Trinity uses to enable application wide, + event-driven communication even across process boundaries. + """ # Plugins that run in a shared process all share the endpoint of the plugin manager - context = PluginContext(self.endpoint) - context.args = args - context.trinity_config = trinity_config - context.boot_kwargs = boot_kwargs - return context + plugin.set_context(PluginContext(self.endpoint, boot_info)) class PluginManager: @@ -124,6 +133,17 @@ class PluginManager: The plugin manager is responsible to register, keep and manage the life cycle of any available plugins. + A :class:`~trinity.extensibility.plugin_manager.PluginManager` is tight to a specific + :class:`~trinity.extensibility.plugin_manager.BaseManagerProcessScope` which defines which + plugins are controlled by this specific manager instance. + + This is due to the fact that Trinity currently allows plugins to either run in a shared + process, also known as the "networking" process, as well as in their own isolated + processes. + + Trinity uses two different :class:`~trinity.extensibility.plugin_manager.PluginManager` + instances to govern these different categories of plugins. + .. note:: This API is very much in flux and is expected to change heavily. @@ -137,7 +157,9 @@ def __init__(self, scope: BaseManagerProcessScope) -> None: @property def event_bus_endpoint(self) -> Endpoint: """ - Return the ``Endpoint`` that the ``PluginManager`` uses to connect to the ``EventBus`` + Return the :class:`~lahja.endpoint.Endpoint` that the + :class:`~trinity.extensibility.plugin_manager.PluginManager` instance uses to connect to + the central :class:`~lahja.eventbus.EventBus`. """ return self._scope.endpoint @@ -165,21 +187,27 @@ def prepare(self, trinity_config: TrinityConfig, boot_kwargs: Dict[str, Any] = None) -> None: """ - Create a ``PluginContext`` for every plugin that this plugin manager instance - is responsible for. + Create and set the :class:`~trinity.extensibility.plugin.PluginContext` and call + :meth:`~trinity.extensibility.plugin.BasePlugin.ready` on every plugin that this + plugin manager instance is responsible for. """ for plugin in self._plugin_store: if not self._scope.is_responsible_for_plugin(plugin): continue - context = self._scope.create_plugin_context(plugin, args, trinity_config, boot_kwargs) - plugin.set_context(context) + self._scope.create_plugin_context( + plugin, + TrinityBootInfo(args, trinity_config, boot_kwargs) + ) plugin.ready() def shutdown_blocking(self) -> None: """ - Synchronously shut down all started plugins. + Synchronously shut down all running plugins. Raises an + :class:`~trinity.extensibility.exceptions.UnsuitableShutdownError` if called on a + :class:`~trinity.extensibility.plugin_manager.PluginManager` that operates in the + :class:`~trinity.extensibility.plugin_manager.SharedProcessScope`. """ if isinstance(self._scope, SharedProcessScope): @@ -201,7 +229,10 @@ def shutdown_blocking(self) -> None: async def shutdown(self) -> None: """ - Asynchronously shut down all started plugins. + Asynchronously shut down all running plugins. Raises an + :class:`~trinity.extensibility.exceptions.UnsuitableShutdownError` if called on a + :class:`~trinity.extensibility.plugin_manager.PluginManager` that operates in the + :class:`~trinity.extensibility.plugin_manager.MainAndIsolatedProcessScope`. """ if isinstance(self._scope, MainAndIsolatedProcessScope): raise UnsuitableShutdownError("Use `shutdown_blocking` for instances of this scope")