-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Define _get_backends_cls function inside apiv2.py to read engines from plugins.py * Read open_backends_dataset_* from entrypoints. * Add backend entrypoints in setup.cfg * Pass apiv2.py isort and black formatting tests. * add dependencies * add backend entrypoints and check on conflicts * black * removed global variable EMGINES add class for entrypointys * black isort * add detect_engines in __all__ init.py * removed entrypoints in py36-bare-minimum.yml and py36-min-all-deps.yml * add entrypoints in IGNORE_DEPS * Plugins test (#20) - replace entrypoints with pkg_resources - add tests * fix typo Co-authored-by: keewis <keewis@users.noreply.github.com> * style Co-authored-by: keewis <keewis@users.noreply.github.com> * style * Code style * Code style * fix: updated plugins.ENGINES with plugins.list_engines() * fix * One more correctness fix of the latest merge from master Co-authored-by: TheRed86 <m.rossetti@bopen.eu> Co-authored-by: keewis <keewis@users.noreply.github.com> Co-authored-by: Alessandro Amici <a.amici@bopen.eu>
- Loading branch information
1 parent
8915058
commit 74dffff
Showing
9 changed files
with
204 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,78 @@ | ||
import inspect | ||
import typing as T | ||
|
||
from . import cfgrib_, h5netcdf_, zarr | ||
|
||
ENGINES: T.Dict[str, T.Dict[str, T.Any]] = { | ||
"h5netcdf": { | ||
"open_dataset": h5netcdf_.open_backend_dataset_h5necdf, | ||
}, | ||
"zarr": { | ||
"open_dataset": zarr.open_backend_dataset_zarr, | ||
}, | ||
"cfgrib": { | ||
"open_dataset": cfgrib_.open_backend_dataset_cfgrib, | ||
}, | ||
} | ||
|
||
|
||
for engine in ENGINES.values(): | ||
if "signature" not in engine: | ||
parameters = inspect.signature(engine["open_dataset"]).parameters | ||
for name, param in parameters.items(): | ||
if param.kind in ( | ||
inspect.Parameter.VAR_KEYWORD, | ||
inspect.Parameter.VAR_POSITIONAL, | ||
): | ||
raise TypeError( | ||
f'All the parameters in {engine["open_dataset"]!r} signature should be explicit. ' | ||
"*args and **kwargs is not supported" | ||
) | ||
engine["signature"] = set(parameters) | ||
import itertools | ||
import warnings | ||
from functools import lru_cache | ||
|
||
import pkg_resources | ||
|
||
|
||
class BackendEntrypoint: | ||
__slots__ = ("open_dataset", "open_dataset_parameters") | ||
|
||
def __init__(self, open_dataset, open_dataset_parameters=None): | ||
self.open_dataset = open_dataset | ||
self.open_dataset_parameters = open_dataset_parameters | ||
|
||
|
||
def remove_duplicates(backend_entrypoints): | ||
|
||
# sort and group entrypoints by name | ||
backend_entrypoints = sorted(backend_entrypoints, key=lambda ep: ep.name) | ||
backend_entrypoints_grouped = itertools.groupby( | ||
backend_entrypoints, key=lambda ep: ep.name | ||
) | ||
# check if there are multiple entrypoints for the same name | ||
unique_backend_entrypoints = [] | ||
for name, matches in backend_entrypoints_grouped: | ||
matches = list(matches) | ||
unique_backend_entrypoints.append(matches[0]) | ||
matches_len = len(matches) | ||
if matches_len > 1: | ||
selected_module_name = matches[0].module_name | ||
all_module_names = [e.module_name for e in matches] | ||
warnings.warn( | ||
f"\nFound {matches_len} entrypoints for the engine name {name}:" | ||
f"\n {all_module_names}.\n It will be used: {selected_module_name}.", | ||
RuntimeWarning, | ||
) | ||
return unique_backend_entrypoints | ||
|
||
|
||
def detect_parameters(open_dataset): | ||
signature = inspect.signature(open_dataset) | ||
parameters = signature.parameters | ||
for name, param in parameters.items(): | ||
if param.kind in ( | ||
inspect.Parameter.VAR_KEYWORD, | ||
inspect.Parameter.VAR_POSITIONAL, | ||
): | ||
raise TypeError( | ||
f"All the parameters in {open_dataset!r} signature should be explicit. " | ||
"*args and **kwargs is not supported" | ||
) | ||
return tuple(parameters) | ||
|
||
|
||
def create_engines_dict(backend_entrypoints): | ||
engines = {} | ||
for backend_ep in backend_entrypoints: | ||
name = backend_ep.name | ||
backend = backend_ep.load() | ||
engines[name] = backend | ||
return engines | ||
|
||
|
||
def set_missing_parameters(engines): | ||
for name, backend in engines.items(): | ||
if backend.open_dataset_parameters is None: | ||
open_dataset = backend.open_dataset | ||
backend.open_dataset_parameters = detect_parameters(open_dataset) | ||
|
||
|
||
@lru_cache(maxsize=1) | ||
def list_engines(): | ||
entrypoints = pkg_resources.iter_entry_points("xarray.backends") | ||
backend_entrypoints = remove_duplicates(entrypoints) | ||
engines = create_engines_dict(backend_entrypoints) | ||
set_missing_parameters(engines) | ||
return engines |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
from unittest import mock | ||
|
||
import pkg_resources | ||
import pytest | ||
|
||
from xarray.backends import plugins | ||
|
||
|
||
def dummy_open_dataset_args(filename_or_obj, *args): | ||
pass | ||
|
||
|
||
def dummy_open_dataset_kwargs(filename_or_obj, **kwargs): | ||
pass | ||
|
||
|
||
def dummy_open_dataset(filename_or_obj, *, decoder): | ||
pass | ||
|
||
|
||
@pytest.fixture | ||
def dummy_duplicated_entrypoints(): | ||
specs = [ | ||
"engine1 = xarray.tests.test_plugins:backend_1", | ||
"engine1 = xarray.tests.test_plugins:backend_2", | ||
"engine2 = xarray.tests.test_plugins:backend_1", | ||
"engine2 = xarray.tests.test_plugins:backend_2", | ||
] | ||
eps = [pkg_resources.EntryPoint.parse(spec) for spec in specs] | ||
return eps | ||
|
||
|
||
def test_remove_duplicates(dummy_duplicated_entrypoints): | ||
entrypoints = plugins.remove_duplicates(dummy_duplicated_entrypoints) | ||
assert len(entrypoints) == 2 | ||
|
||
|
||
def test_remove_duplicates_warnings(dummy_duplicated_entrypoints): | ||
|
||
with pytest.warns(RuntimeWarning) as record: | ||
_ = plugins.remove_duplicates(dummy_duplicated_entrypoints) | ||
|
||
assert len(record) == 2 | ||
message0 = str(record[0].message) | ||
message1 = str(record[1].message) | ||
assert "entrypoints" in message0 | ||
assert "entrypoints" in message1 | ||
|
||
|
||
@mock.patch("pkg_resources.EntryPoint.load", mock.MagicMock(return_value=None)) | ||
def test_create_engines_dict(): | ||
specs = [ | ||
"engine1 = xarray.tests.test_plugins:backend_1", | ||
"engine2 = xarray.tests.test_plugins:backend_2", | ||
] | ||
entrypoints = [pkg_resources.EntryPoint.parse(spec) for spec in specs] | ||
engines = plugins.create_engines_dict(entrypoints) | ||
assert len(engines) == 2 | ||
assert engines.keys() == set(("engine1", "engine2")) | ||
|
||
|
||
def test_set_missing_parameters(): | ||
backend_1 = plugins.BackendEntrypoint(dummy_open_dataset) | ||
backend_2 = plugins.BackendEntrypoint(dummy_open_dataset, ("filename_or_obj",)) | ||
engines = {"engine_1": backend_1, "engine_2": backend_2} | ||
plugins.set_missing_parameters(engines) | ||
|
||
assert len(engines) == 2 | ||
engine_1 = engines["engine_1"] | ||
assert engine_1.open_dataset_parameters == ("filename_or_obj", "decoder") | ||
engine_2 = engines["engine_2"] | ||
assert engine_2.open_dataset_parameters == ("filename_or_obj",) | ||
|
||
|
||
def test_set_missing_parameters_raise_error(): | ||
|
||
backend = plugins.BackendEntrypoint(dummy_open_dataset_args) | ||
with pytest.raises(TypeError): | ||
plugins.set_missing_parameters({"engine": backend}) | ||
|
||
backend = plugins.BackendEntrypoint( | ||
dummy_open_dataset_args, ("filename_or_obj", "decoder") | ||
) | ||
plugins.set_missing_parameters({"engine": backend}) | ||
|
||
backend = plugins.BackendEntrypoint(dummy_open_dataset_kwargs) | ||
with pytest.raises(TypeError): | ||
plugins.set_missing_parameters({"engine": backend}) | ||
|
||
backend = plugins.BackendEntrypoint( | ||
dummy_open_dataset_kwargs, ("filename_or_obj", "decoder") | ||
) | ||
plugins.set_missing_parameters({"engine": backend}) |