Source code for pyrokinetics.plugins

"""
This modules contains utility functions for adding plugins to Pyrokinetics.
This is achieved using entry-points.

If you want to register your own :class:`~pyrokinetics.equilibrium.Equilibrium`
reader, it should inherit :class:`pyrokinetics.file_utils.FileReader`,
and its ``read_from_file()`` function should return an ``Equilibrium``. To add
this plugin to Pyrokinetics, you should add the following to your
``pyproject.toml`` file::

    [project.entry-points."pyrokinetics.equilibrium"]
    my_eq = "my_project.my_module:MyEqReader"

This will register the class ``MyEqReader``, and within Pyrokinetics the
equilibrium type will be ``"my_eq"``. Note that here, ``"pyrokinetics.equilibrium"`` is
an entry point group name, not a module. The group names for each Pyrokinetics
file reader are:

- ``"pyrokinetics.gk_input"``
- ``"pyrokinetics.gk_output"``
- ``"pyrokinetics.equilibrium"``
- ``"pyrokinetics.kinetics"``

For more information, please see:

- `PyPA entry points specifications
  <https://packaging.python.org/en/latest/specifications/entry-points/>`_
- `Setuptools entry points tutorial
  <https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_
"""

from importlib.metadata import entry_points
from platform import python_version_tuple
from textwrap import dedent
from typing import Type

from .file_utils import FileReader, ReadableFromFile

__all__ = ["register_file_reader_plugins"]


[docs] def register_file_reader_plugins( group_name: str, Readable: Type[ReadableFromFile] ) -> None: """ Defines an entry point group for classes that implement the :class:`~pyrokinetics.file_utils.FileReader` and registers any user-defined plugins with that group. Parameters ---------- group_name The name of the entry points group. This will be prepended with ``"pyrokinetics."`` if it is not already. Readable The type of class returned by the registered file readers. """ # Ensure group name has the correct prefix prefix = "pyrokinetics." if group_name[: len(prefix)] != prefix: group_name = prefix + group_name # Get group, returning early if there are no plugins if int(python_version_tuple()[1]) >= 10: group = entry_points(group=group_name) if len(group) == 0: return else: try: group = entry_points()[group_name] except KeyError: return # Register all plugins in the user's environment for entry in group: # Simply loading the entry point should register the plugin if it # inherits FileReader cls = entry.load() # Check that it is of the correct type if not issubclass(cls, FileReader): raise TypeError( f"Plugin class {cls.__qualname__} should subclass {cls.__qualname__}" ) # Check that the registered file type matches the entry point name if entry.name not in Readable.supported_file_types(): err_msg = dedent(f"""\ Entry point name {entry.name} does not match any registered file types for the class {Readable.__qualname__}. Registered types include '{"', '".join(Readable.supported_file_types())}'. """) raise RuntimeError(err_msg)