From 6a907bb9c3456632f2fceb066f0949999cd2d6b5 Mon Sep 17 00:00:00 2001 From: David Igandan Date: Fri, 24 Apr 2026 11:11:45 +0100 Subject: [PATCH 1/2] Add docs for writing your own plugin --- docs/siteconfig.rst | 115 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/docs/siteconfig.rst b/docs/siteconfig.rst index 66fe75e2..48bb1e94 100644 --- a/docs/siteconfig.rst +++ b/docs/siteconfig.rst @@ -243,7 +243,120 @@ tbd. Writing your own plugins ======================== -tbd. +Zocalo discovers configuration plugins via Python entry points and loads them +when an environment is activated. A plugin typically does three things: + +- Validate its configuration +- Perform a one-time setup when activated +- Return a useful object that becomes available on the configuration handle + (``zc.``) + +Overview +-------- + +- Discovery: Plugins are discovered from the ``zocalo.configuration.plugins`` + entry-point group declared in the pyproject.toml of the python-workflows and python-zocalo distributions. + The entry-point name is the plugin name you reference in + configuration.yaml (the value of ``plugin:``). +- Naming: Plugin names must be valid Python identifiers and must not collide + with reserved names (``activated``, ``environments``, ``plugin_configurations``). +- Activation: When you call ``zc.activate_environment("env")`` or + ``zc.activate([...])``, Zocalo loads each referenced plugin in order. The + plugin's ``activate()`` is called and its return value is exposed on the + configuration object as ``zc.``. See the loader in + [modules/python-zocalo/src/zocalo/configuration/__init__.py](modules/python-zocalo/src/zocalo/configuration/__init__.py:57). + +Minimal plugin skeleton +----------------------- + +Define a class named after your plugin in a module importable by +the entry point defined in the pyproject.toml. Provide a nested ``Schema`` that extends +``zocalo.configuration.PluginSchema`` to validate configuration values in configuration.yaml, and implement a +static ``activate()`` method. Zocalo will pass the parsed plugin configuration +dictionary as ``configuration`` and, if requested, the full configuration object +as ``config_object``. + +.. code-block:: python + + from marshmallow import fields + from zocalo.configuration import PluginSchema + + class MyPlugin: + class Schema(PluginSchema): + host = fields.Str(required=True) + port = fields.Int(load_default=4318) + timeout = fields.Int(load_default=10) + + @staticmethod + def activate(configuration, config_object=None): + # Do setup using validated config + # Optionally interact with other plugin state via config_object + # Return a value that will be exposed as zc.myplugin + return {"endpoint": f"{configuration['host']}:{configuration['port']}"} + + +Schema validation +----------------- + +If your class defines an inner ``Schema`` subclass of ``PluginSchema`` using +Marshmallow fields, Zocalo validates plugin configuration at parse time. Invalid +configs raise ``zocalo.ConfigurationError``. + +Registering your plugin +----------------------- + +Add an entry to your package metadata so Zocalo can discover it. For example in +``pyproject.toml``: + +.. code-block:: toml + + [project.entry-points."zocalo.configuration.plugins"] + myplugin = "yourpackage.module:MyPlugin" + +Then reference it in the configuration.yaml using the entry-point name: + +.. code-block:: yaml + + my-setup: + plugin: myplugin + host: example.com + port: 4318 + +Return values and access +------------------------ + +Whatever your plugin returns from ``activate()`` is attached to the configuration +as an attribute named after the plugin. For ``plugin: logging`` this shows up as +``zc.logging`` (see +[modules/python-zocalo/src/zocalo/configuration/plugin_logging.py](modules/python-zocalo/src/zocalo/configuration/plugin_logging.py)). +Return a simple dictionary or a richer helper object. + +Example: Distributed tracing plugin +----------------------------------- + +As a real example, a site plugin can enable distributed tracing via +OpenTelemetry. A minimal configuration might look like: + +.. code-block:: yaml + + tracing: + plugin: opentelemetry + host: otel.tracing.diamond.ac.uk + port: 4318 + timeout: 10 + +When this plugin activates Zocalo can initialise an OpenTelemetry TracerProvider and +exporter pointing at the endpoint configured by the plugin. Once tracing is enabled, Zocalo +services can enrich spans with useful attributes; for example the dispatcher +adds a ``dcid`` attribute to the current span when available (see +[modules/python-zocalo/src/zocalo/service/dispatcher.py](modules/python-zocalo/src/zocalo/service/dispatcher.py)). + +Tips and patterns +----------------- + +- Prefer small, focused plugins that each set up one concern (e.g. logging, + storage, tracing), and compose them via environments to control activation + order. .. _Python Logging Configuration Schema: https://docs.python.org/3/library/logging.config.html#dictionary-schema-details .. _Python entry points: https://amir.rachum.com/blog/2017/07/28/python-entry-points/ From 37877ee35d3a2b441cd45649088063431d4c50b7 Mon Sep 17 00:00:00 2001 From: David Igandan Date: Mon, 11 May 2026 13:52:17 +0100 Subject: [PATCH 2/2] Add diagram about necessary file changes to create a custom zocalo plugin --- docs/final2-creating-plugin.svg | 1 + docs/siteconfig.rst | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 docs/final2-creating-plugin.svg diff --git a/docs/final2-creating-plugin.svg b/docs/final2-creating-plugin.svg new file mode 100644 index 00000000..32231fa7 --- /dev/null +++ b/docs/final2-creating-plugin.svg @@ -0,0 +1 @@ +DIALS/modules......Python Workflows[project.entrypoints."zocalo.configuration.plugins"]myplugin = path.to.file_containing:MyPluginpyproject.tomlclass MyPlugin: class Schema(PluginSchema): ...@staticmethoddef activate(configuration): config = {} ... ... return configconfiguration.yamlmy_plugin_config = getattr(self.config)use(my_plugin_config)my-setup: plugin: myplugin host: example.com port: 4318 custom_behaviour: xyzutil/zocalo/configurations.pycommon_service.pyzocalo-config/zocalo... \ No newline at end of file diff --git a/docs/siteconfig.rst b/docs/siteconfig.rst index 48bb1e94..8a04f863 100644 --- a/docs/siteconfig.rst +++ b/docs/siteconfig.rst @@ -264,8 +264,14 @@ Overview ``zc.activate([...])``, Zocalo loads each referenced plugin in order. The plugin's ``activate()`` is called and its return value is exposed on the configuration object as ``zc.``. See the loader in - [modules/python-zocalo/src/zocalo/configuration/__init__.py](modules/python-zocalo/src/zocalo/configuration/__init__.py:57). + `src/zocalo/configuration/__init__.py:57 `_. + +.. image:: final2-creating-plugin.svg + :alt: Zocalo Plugin + :align: center + + Minimal plugin skeleton -----------------------