Source code for law.contrib

# coding: utf-8

"""
Main entry point and dynamic loading of contrib packages.
"""

from __future__ import annotations

__all__ = ["available_packages", "loaded_packages", "load", "load_all"]

import os
import glob

import law
from law.util import law_src_path, flatten
from law.logger import get_logger
from law._types import ModuleType


logger = get_logger(__name__)

this_dir = os.path.dirname(os.path.abspath(__file__))


#: List of names of available contrib packages.
available_packages = [
    os.path.basename(os.path.dirname(contrib_init))
    for contrib_init in glob.glob(os.path.join(this_dir, "*", "__init__.py"))
]

#: Dictionary of names to modules of already loaded contrib packages.
loaded_packages: dict[str, ModuleType] = {}


[docs] def load(*packages: str) -> ModuleType | list[ModuleType]: """ Loads contrib *packages* and adds them to the law namespace. Example: .. code-block:: python import law law.contrib.load("docker") law.docker.DockerSandbox(...) It is ensured that packages are loaded only once. """ for pkg in flatten(packages): if pkg in loaded_packages: logger.debug(f"skip contrib package '{pkg}', already loaded") continue if not os.path.exists(law_src_path("contrib", pkg, "__init__.py")): raise Exception(f"contrib package '{pkg}' does not exist") if getattr(law, pkg, None): raise Exception( f"cannot load contrib package '{pkg}', attribute with that name already exists in " "the law module", ) # load the module mod = __import__(f"law.contrib.{pkg}", globals(), locals(), [pkg]) setattr(law, pkg, mod) law.__all__.append(pkg) loaded_packages[pkg] = mod logger.debug(f"loaded contrib package '{pkg}'") # optionally call its auto_load hook when existing auto_load = getattr(mod, "auto_load", None) if callable(auto_load): auto_load() logger.debug(f"invoked auto_load hook of contrib package '{pkg}'") modules = [loaded_packages[pkg] for pkg in packages] return modules[0] if len(packages) == 1 else modules
[docs] def load_all() -> list[str]: """ Loads all available contrib packages via :py:func:`load`. A package is skipped when an ImportError was raised. The list of names of loaded packages is returned. """ loaded_packages = [] for name in available_packages: try: load(name) except ImportError: continue loaded_packages.append(name) return loaded_packages