diff --git a/pysteps/__init__.py b/pysteps/__init__.py index 43fa20533..b06870292 100644 --- a/pysteps/__init__.py +++ b/pysteps/__init__.py @@ -218,3 +218,4 @@ def load_config_file(params_file=None, verbose=False, dryrun=False): # After the sub-modules are loaded, register the discovered importers plugin. io.interface.discover_importers() +nowcasts.interface.discover_nowcasts() diff --git a/pysteps/nowcasts/__init__.py b/pysteps/nowcasts/__init__.py index 71d5e73c8..30d9720a9 100644 --- a/pysteps/nowcasts/__init__.py +++ b/pysteps/nowcasts/__init__.py @@ -1,3 +1,3 @@ """Implementations of deterministic and ensemble nowcasting methods.""" -from pysteps.nowcasts.interface import get_method +from pysteps.nowcasts.interface import discover_nowcasts, nowcasts_info, get_method diff --git a/pysteps/nowcasts/interface.py b/pysteps/nowcasts/interface.py index 19b01a523..8a659250e 100644 --- a/pysteps/nowcasts/interface.py +++ b/pysteps/nowcasts/interface.py @@ -30,6 +30,9 @@ get_method """ +from pprint import pprint +from pysteps import nowcasts +import importlib from pysteps.extrapolation.interface import eulerian_persistence from pysteps.nowcasts import ( anvil, @@ -39,7 +42,10 @@ steps, sseps, ) + from pysteps.nowcasts import lagrangian_probability +import os + _nowcast_methods = dict() _nowcast_methods["anvil"] = anvil.forecast @@ -54,6 +60,91 @@ _nowcast_methods["steps"] = steps.forecast +def discover_nowcasts(): + """ + Search for installed nowcasts plugins in the entrypoint 'pysteps.plugin.nowcasts' + + The nowcasts method found are added to the `pysteps.nowcasts.interface_nowcasts_methods` + dictionary containing the available nowcasts_methods. + """ + + # The pkg resources needs to be reload to detect new packages installed during + # the execution of the python application. For example, when the plugins are + # installed during the tests + import pkg_resources + + importlib.reload(pkg_resources) + + for entry_point in pkg_resources.iter_entry_points( + group="pysteps.plugins.nowcasts", name=None + ): + _module = entry_point.load() + nowcast_module_name = entry_point.name + + if nowcast_module_name not in _nowcast_methods: + _nowcast_methods[nowcast_module_name] = _module + + else: + RuntimeWarning( + f"The Nowcasts methode '{nowcast_module_name}' is already available in" + "'pysteps.nowcasts._nowcasts_methods'.\n" + f"Skipping {entry_point.module_name}:{'.'.join(entry_point.attrs)}" + ) + if hasattr(nowcasts, nowcast_module_name): + RuntimeWarning( + f"The nowcasts method '{nowcast_module_name}' is already an attribute" + "of 'pysteps.nowcasts'.\n" + f"Skipping {entry_point.module_name}:{'.'.join(entry_point.attrs)}" + ) + else: + setattr(nowcasts, nowcast_module_name, _module) + + +def nowcasts_info(): + """Print all the available nowcast methods.""" + + # nowcasts methods available in the `nowcasts` package + available_nowcasts = [ + attr.split(".")[0] + for attr in os.listdir(" ".join(nowcasts.__path__)) + if not attr.startswith("__") and attr != "interface.py" + ] + print("\nMethods available in the pysteps.nowcasts") + pprint(available_nowcasts) + # nowcasts declared in the pysteps.nowcast interface + + nowcasts_in_the_interface = [f for f in _nowcast_methods.keys()] + + print("\nMethods available in the pysteps.nowcasts.get_method interface") + pprint([(short_name, f.__name__) for short_name, f in _nowcast_methods.items()]) + + # Let's use sets to find out if there are importers present in the importer module + # but not declared in the interface, and viceversa. + available_nowcasts = set(available_nowcasts) + nowcasts_in_the_interface = set(nowcasts_in_the_interface) + + difference = available_nowcasts ^ nowcasts_in_the_interface + if len(difference) > 0: + print("\nIMPORTANT:") + _diff = available_nowcasts - nowcasts_in_the_interface + if len(_diff) > 0: + print( + "\nIMPORTANT:\nThe following importers are available in pysteps.nowcasts module " + "but not in the pysteps.nowcasts.get_method interface" + ) + pprint(_diff) + _diff = nowcasts_in_the_interface - available_nowcasts + if len(_diff) > 0: + print( + "\nWARNING:\n" + "The following importers are available in the pysteps.nowcasts.get_method " + "interface but not in the pysteps.nowcasts module" + ) + pprint(_diff) + + return available_nowcasts, nowcasts_in_the_interface + + def get_method(name): """ Return a callable function for computing nowcasts. @@ -90,6 +181,7 @@ def get_method(name): | sseps | short-space ensemble prediction system (SSEPS). | | | Essentially, this is a localization of STEPS | +-----------------+-------------------------------------------------------+ + """ if isinstance(name, str): name = name.lower() diff --git a/pysteps/tests/test_interfaces.py b/pysteps/tests/test_interfaces.py index c490b2539..fd0cf1337 100644 --- a/pysteps/tests/test_interfaces.py +++ b/pysteps/tests/test_interfaces.py @@ -260,6 +260,20 @@ def test_nowcasts_interface(): for i in range(num_timesteps): assert numpy.all(forecast[i] == precip) + # Test for invalid method types + with pytest.raises(ValueError): + pysteps.nowcasts.interface.get_method("linear") + + assert isinstance( + pysteps.nowcasts.interface.nowcasts_info()[0], + set, + ) + + assert isinstance( + pysteps.nowcasts.interface.nowcasts_info()[1], + set, + ) + def test_utils_interface(): """Test utils module interface.""" diff --git a/pysteps/tests/test_plugins_support.py b/pysteps/tests/test_plugins_support.py index bbf3bcf5b..91f9f8de5 100644 --- a/pysteps/tests/test_plugins_support.py +++ b/pysteps/tests/test_plugins_support.py @@ -10,7 +10,6 @@ import sys import tempfile - __ = pytest.importorskip("cookiecutter") from cookiecutter.main import cookiecutter