diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index c8a7fef74b..7d4e7f464b 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -51,6 +51,7 @@ .. _@ajdawson: https://github.com/ajdawson .. _@bjlittle: https://github.com/bjlittle .. _@bouweandela: https://github.com/bouweandela +.. _@bsherratt: https://github.com/bsherratt .. _@corinnebosley: https://github.com/corinnebosley .. _@cpelley: https://github.com/cpelley .. _@djkirkham: https://github.com/djkirkham diff --git a/docs/src/community/index.rst b/docs/src/community/index.rst index a9e2a4d040..114cb96fe9 100644 --- a/docs/src/community/index.rst +++ b/docs/src/community/index.rst @@ -46,3 +46,13 @@ smoother interoperability: :hidden: iris_xarray + +Plugins +------- + +Iris can be extended with **plugins**! See below for further information: + +.. toctree:: + :maxdepth: 2 + + plugins diff --git a/docs/src/community/plugins.rst b/docs/src/community/plugins.rst new file mode 100644 index 0000000000..0d79d64623 --- /dev/null +++ b/docs/src/community/plugins.rst @@ -0,0 +1,68 @@ +.. _namespace package: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ + +.. _community_plugins: + +Plugins +======= + +Iris supports **plugins** under the ``iris.plugins`` `namespace package`_. +This allows packages that extend Iris' functionality to be developed and +maintained independently, while still being installed into ``iris.plugins`` +instead of a separate package. For example, a plugin may provide loaders or +savers for additional file formats, or alternative visualisation methods. + + +Using plugins +------------- + +Once a plugin is installed, it can be used either via the +:func:`iris.use_plugin` function, or by importing it directly: + +.. code-block:: python + + import iris + + iris.use_plugin("my_plugin") + # OR + import iris.plugins.my_plugin + + +Creating plugins +---------------- + +The choice of a `namespace package`_ makes writing a plugin relatively +straightforward: it simply needs to appear as a folder within ``iris/plugins``, +then can be distributed in the same way as any other package. An example +repository layout: + +.. code-block:: text + + + lib + + iris + + plugins + + my_plugin + - __init__.py + - (more code...) + - README.md + - pyproject.toml + - setup.cfg + - (other project files...) + +In particular, note that there must **not** be any ``__init__.py`` files at +higher levels than the plugin itself. + +The package name - how it is referred to by PyPI/conda, specified by +``metadata.name`` in ``setup.cfg`` - is recommended to include both "iris" and +the plugin name. Continuing this example, its ``setup.cfg`` should include, at +minimum: + +.. code-block:: ini + + [metadata] + name = iris-my-plugin + + [options] + packages = find_namespace: + + [options.packages.find] + where = lib diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9d9fa8b342..a38e426e6a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -32,7 +32,9 @@ This document explains the changes made to Iris for this release ✨ Features =========== -#. N/A +#. `@bsherratt`_ added support for plugins - see the corresponding + :ref:`documentation page` for further information. + (:pull:`5144`) 🐛 Bugs Fixed diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index a81d25add3..38465472ee 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -91,6 +91,7 @@ def callback(cube, field, filename): import contextlib import glob +import importlib import itertools import os.path import pathlib @@ -129,6 +130,7 @@ def callback(cube, field, filename): "sample_data_path", "save", "site_configuration", + "use_plugin", ] @@ -470,3 +472,22 @@ def sample_data_path(*path_to_join): "appropriate for general file access.".format(target) ) return target + + +def use_plugin(plugin_name): + """ + Convenience function to import a plugin + + For example:: + + use_plugin("my_plugin") + + is equivalent to:: + + import iris.plugins.my_plugin + + This is useful for plugins that are not used directly, but instead do all + their setup on import. In this case, style checkers would not know the + significance of the import statement and warn that it is an unused import. + """ + importlib.import_module(f"iris.plugins.{plugin_name}") diff --git a/lib/iris/io/format_picker.py b/lib/iris/io/format_picker.py index edf448e95b..a8e333c566 100644 --- a/lib/iris/io/format_picker.py +++ b/lib/iris/io/format_picker.py @@ -134,8 +134,9 @@ def get_spec(self, basename, buffer_obj): value = value[:50] + "..." printable_values[key] = value msg = ( - "No format specification could be found for the given buffer." - " File element cache:\n {}".format(printable_values) + "No format specification could be found for the given buffer. " + "Perhaps a plugin is missing or has not been loaded. " + "File element cache:\n {}".format(printable_values) ) raise ValueError(msg) diff --git a/lib/iris/plugins/README.md b/lib/iris/plugins/README.md new file mode 100644 index 0000000000..e8dee1de2c --- /dev/null +++ b/lib/iris/plugins/README.md @@ -0,0 +1,10 @@ +# Iris plugins + +`iris.plugins` is a [namespace package] allowing arbitrary plugins to be +installed alongside Iris. + +See [the Iris documentation][plugins] for more information. + + +[namespace package]: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ +[plugins]: https://scitools-iris.readthedocs.io/en/latest/community/plugins.html