Source code for bd103.decorators

"""Contains useful decorators that can be applied to various objects (usually functions).

One of the most common variants of decorators are prefixed with ``requires_``. They only allow the function to run if a specific requirement is met.
"""

import functools
import importlib.util
import platform
import typing as t
from collections import abc


[docs]def requires_os( os_name: t.Union[str, t.List[str]], silent: bool = False ) -> abc.Callable: """Only allows the use of the function if running on a certain operating system. OS detection is done through :func:`platform.system()`. Args: os_name: A string or list of strings that say either "windows", "ubuntu", or "darwin". (Or anything that :func:`platform.system()` might return.) silent: If false and on the wrong OS, it will raise an :exc:`OSError`. If true, it will skip the function and continue. Examples: .. code-block:: python @requires_os("windows") def only_windows(): print("I CAN SEE THROUGH WINDOWS") .. code-block:: python @requires_os(["linux", "darwin"]) def access_home() -> str: with open("~/myfile.txt", "rt") as fp: return fp.read() Raises: OSError: Wrong OS type! """ def decorator(func: abc.Callable) -> abc.Callable: @functools.wraps(func) def wrapper(*args, **kwargs): if isinstance(os_name, str): if platform.system().lower() == os_name.lower(): return func(*args, **kwargs) elif not silent: raise OSError(f"OS {platform.system()} does not equal {os_name}") elif isinstance(os_name, list): if platform.system().lower() in [i.lower() for i in os_name]: return func(*args, **kwargs) elif not silent: raise OSError(f"OS {platform.system()} is not in {os_name}") else: raise TypeError(f"os_name type {type(os_name)} is not string or list") return wrapper return decorator
[docs]def requires_module( module: str, package: str = None, silent: bool = False ) -> abc.Callable: """Only allows the use of the function if a certain module or package is installed. This uses :mod:`importlib` to detect what modules are installed. A common example of this decorator would be in the :mod:`bd103.ext` package. Args: module: The name of the module that needs to be installed. This also works on subpackages. package: Finds module relative to the given package name. See :func:`importlib.util.find_spec`. silent: If false, this function will raise a :exc:`ModuleNotFoundError`. If true, it will skip this function and continue. Examples: .. code-block:: python try: import colorama except ImportError: colorama = None @requires_module("colorama") def do_color_stuff(): colorama.init() print("\\x1b[34mHELLO\\x1b[0m") colorama.deinit() Raises: ModuleNotFoundError: Package is most likely not installed. """ def decorator(func: abc.Callable) -> abc.Callable: @functools.wraps(func) def wrapper(*args, **kwargs): if importlib.util.find_spec(module, package=package): return func(*args, **kwargs) elif not silent: if package is None: raise ModuleNotFoundError( f"{repr(func)} requires module {module} to run" ) else: raise ModuleNotFoundError( f"{repr(func)} requires module {module} from package {package} to run" ) return wrapper return decorator