Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion docs/src/user_guide/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,19 @@ shaping for the MRAF algorithm using different initial phase type and mixing par

.. note::
The metrics (NRMSE, Fidelity, Efficiency) are **automatically saved** in the ``.h5beam`` file under
``/RawData/Algorithm/``. See :numref:`sec-h5beam-structure` for details.
``/RawData/Algorithm/``. See :numref:`sec-h5beam-structure` for details.

Algorithms provided by other packages
-------------------------------------

Another Python package can add algorithms to the list of BeamShaping. It writes an algorithm class derived from
``AlgoBase``, registers it with the decorator ``AlgorithmFactory.register_algorithm()`` and declares the module
containing it in the entry point group ``pymodaq_plugins_beam_shaping.algorithms`` of its ``pyproject.toml``:

.. code-block:: toml

[project.entry-points."pymodaq_plugins_beam_shaping.algorithms"]
my_algorithms = "my_package.algorithms"

BeamShaping imports these modules when it starts, after its own algorithms. A package that cannot be imported is
reported in the log and ignored.
26 changes: 25 additions & 1 deletion src/pymodaq_plugins_beam_shaping/algorithms/factory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from importlib import import_module
from importlib.metadata import entry_points
from pathlib import Path

from typing import Callable
Expand All @@ -10,8 +11,30 @@
logger = set_logger(get_module_name(__file__))


ENTRY_POINT_GROUP = 'pymodaq_plugins_beam_shaping.algorithms'


def register_external_algorithms(group: str = ENTRY_POINT_GROUP):
""" Load the modules declared by other packages in the entry point group of the algorithms

A package providing algorithms declares, in the entry point group *pymodaq_plugins_beam_shaping.algorithms*, a
module whose import registers its algorithms with the decorator *AlgorithmFactory.register_algorithm*. A package
failing to load is reported in the log and ignored.
"""
modules = []
for entry_point in entry_points(group=group):
try:
modules.append(entry_point.load())
except Exception as e: # a broken third party package should not prevent BeamShaping to start
logger.warning(f'Could not load the algorithms of {entry_point.value} ({entry_point.name}): {e!r}')
return modules


def register_algorithms(parent_module_name: str = 'pymodaq_plugins_beam_shaping'):
""" Browse modules containing algorithms for optical shaping and register them in the factory"""
""" Browse modules containing algorithms for optical shaping and register them in the factory

The algorithms provided by other packages through the entry point group *ENTRY_POINT_GROUP* are registered last.
"""
algorithms = []
try:
algorithm_module = import_module(f'{parent_module_name}.algorithms.algorithms')
Expand All @@ -27,6 +50,7 @@ def register_algorithms(parent_module_name: str = 'pymodaq_plugins_beam_shaping'
except ModuleNotFoundError as e:
logger.warning(str(e))
finally:
algorithms.extend(register_external_algorithms())
return algorithms


Expand Down
54 changes: 54 additions & 0 deletions tests/algorithm_entry_points_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from types import SimpleNamespace

import pytest

from pymodaq_plugins_beam_shaping.algorithms import factory
from pymodaq_plugins_beam_shaping.algorithms.factory import AlgorithmFactory, register_external_algorithms
from pymodaq_plugins_beam_shaping.algorithms.utils import LensSetup


def fake_entry_point(name, load):
return SimpleNamespace(name=name, value=f'fake_package.{name}', load=load)


@pytest.fixture
def clean_registry():
before = dict(AlgorithmFactory.algorithms_registry)
yield
AlgorithmFactory.algorithms_registry.clear()
AlgorithmFactory.algorithms_registry.update(before)


def test_external_algorithm_is_registered(monkeypatch, clean_registry):
def load():
@AlgorithmFactory.register_algorithm()
class External(factory.AlgoBase):
ALGO_NAME = 'External test algorithm'
SETUP_TYPE = LensSetup.TwoF
return External

monkeypatch.setattr(factory, 'entry_points', lambda group: [fake_entry_point('external', load)])
assert 'External test algorithm' not in AlgorithmFactory.algorithms_registry

register_external_algorithms()

assert 'External test algorithm' in AlgorithmFactory.algorithms_registry


def test_entry_point_group_is_the_documented_one(monkeypatch):
groups = []
monkeypatch.setattr(factory, 'entry_points', lambda group: groups.append(group) or [])
register_external_algorithms()
assert groups == ['pymodaq_plugins_beam_shaping.algorithms']


def test_broken_package_does_not_prevent_the_others(monkeypatch, clean_registry):
def broken():
raise ModuleNotFoundError('missing dependency')

def good():
return 'module'

monkeypatch.setattr(factory, 'entry_points',
lambda group: [fake_entry_point('broken', broken), fake_entry_point('good', good)])
assert register_external_algorithms() == ['module']
Loading