-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨(backends) import backends dynamically
We want to automatically discover backends in the data/backends sub-directories for CLI and LRS usage. We also now handle import failures gracefully, thus backends with unmet dependencies are excluded.
- Loading branch information
Showing
34 changed files
with
509 additions
and
539 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 was deleted.
Oops, something went wrong.
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 |
---|---|---|
@@ -0,0 +1,124 @@ | ||
"""Ralph backend loader.""" | ||
|
||
import logging | ||
import pkgutil | ||
from functools import lru_cache | ||
from importlib import import_module | ||
from importlib.util import find_spec | ||
from inspect import getmembers, isabstract, isclass | ||
from typing import Dict, Tuple, Type | ||
|
||
from ralph.backends.data.base import ( | ||
AsyncListable, | ||
AsyncWritable, | ||
BaseAsyncDataBackend, | ||
BaseDataBackend, | ||
Listable, | ||
Writable, | ||
) | ||
from ralph.backends.http.base import BaseHTTPBackend | ||
from ralph.backends.lrs.base import BaseAsyncLRSBackend, BaseLRSBackend | ||
from ralph.backends.stream.base import BaseStreamBackend | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@lru_cache() | ||
def get_backends(packages: Tuple[str], base_backends: Tuple[Type]) -> Dict[str, Type]: | ||
"""Return sub-classes of `base_backends` found in sub-modules of `packages`. | ||
Args: | ||
packages (tuple of str): A tuple of dotted package names. | ||
Ex.: ("ralph.backends.data", "ralph.backends.lrs"). | ||
base_backends (tuple of type): A tuple of base backend classes to search for. | ||
Ex.: ("BaseDataBackend",) | ||
Return: | ||
dict: A dictionary of found non-abstract backend classes by their name property. | ||
Ex.: {"fs": FSDataBackend} | ||
""" | ||
module_specs = [] | ||
for package in packages: | ||
try: | ||
module_spec = find_spec(package) | ||
except ModuleNotFoundError: | ||
module_spec = None | ||
|
||
if not module_spec: | ||
logger.info("Could not find '%s' package; skipping it", package) | ||
continue | ||
|
||
module_specs.append(module_spec) | ||
|
||
modules = [] | ||
for module_spec in module_specs: | ||
paths = module_spec.submodule_search_locations | ||
for module_info in pkgutil.iter_modules(paths, prefix=f"{module_spec.name}."): | ||
modules.append(module_info.name) | ||
|
||
backend_classes = set() | ||
for module in modules: | ||
try: | ||
backend_module = import_module(module) | ||
except Exception as error: # pylint:disable=broad-except | ||
logger.info("Failed to import %s module: %s", module, error) | ||
continue | ||
for _, class_ in getmembers(backend_module, isclass): | ||
if issubclass(class_, base_backends) and not isabstract(class_): | ||
backend_classes.add(class_) | ||
|
||
return { | ||
backend.name: backend | ||
for backend in sorted(backend_classes, key=lambda x: x.name, reverse=True) | ||
} | ||
|
||
|
||
@lru_cache(maxsize=1) | ||
def get_cli_backends() -> Dict[str, Type]: | ||
"""Return Ralph's backend classes for cli usage.""" | ||
dotted_paths = ( | ||
"ralph.backends.data", | ||
"ralph.backends.http", | ||
"ralph.backends.stream", | ||
) | ||
base_backends = ( | ||
BaseAsyncDataBackend, | ||
BaseDataBackend, | ||
BaseHTTPBackend, | ||
BaseStreamBackend, | ||
) | ||
return get_backends(dotted_paths, base_backends) | ||
|
||
|
||
@lru_cache(maxsize=1) | ||
def get_cli_write_backends() -> Dict[str, Type]: | ||
"""Return Ralph's backends classes for cli write usage.""" | ||
backends = get_cli_backends() | ||
return { | ||
name: backend | ||
for name, backend in backends.items() | ||
if issubclass(backend, (Writable, AsyncWritable, BaseHTTPBackend)) | ||
} | ||
|
||
|
||
@lru_cache(maxsize=1) | ||
def get_cli_list_backends() -> Dict[str, Type]: | ||
"""Return Ralph's backends classes for cli list usage.""" | ||
backends = get_cli_backends() | ||
return { | ||
name: backend | ||
for name, backend in backends.items() | ||
if issubclass(backend, (Listable, AsyncListable)) | ||
} | ||
|
||
|
||
@lru_cache(maxsize=1) | ||
def get_lrs_backends() -> Dict[str, Type]: | ||
"""Return Ralph's backend classes for LRS usage.""" | ||
return get_backends( | ||
("ralph.backends.lrs",), | ||
( | ||
BaseAsyncLRSBackend, | ||
BaseLRSBackend, | ||
), | ||
) |
Oops, something went wrong.