--- a +++ b/dosma/utils/env.py @@ -0,0 +1,150 @@ +import logging +import os +from importlib import util + +_SUPPORTED_PACKAGES = {} + +_FILE_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) + +__all__ = ["debug", "get_version", "package_available"] + + +def package_available(name: str): + """Returns if package is available. + + Args: + name (str): Name of the package. + + Returns: + bool: Whether module exists in environment. + """ + global _SUPPORTED_PACKAGES + if name not in _SUPPORTED_PACKAGES: + _SUPPORTED_PACKAGES[name] = util.find_spec(name) is not None + return _SUPPORTED_PACKAGES[name] + + +def get_version(package_or_name) -> str: + """Returns package version. + + Args: + package_or_name (``module`` or ``str``): Module or name of module. + This package must have the version accessible through ``<module>.__version__``. + + Returns: + str: The package version. + + Examples: + >>> get_version("numpy") + "1.20.0" + """ + if isinstance(package_or_name, str): + if not package_available(package_or_name): + raise ValueError(f"Package {package_or_name} not available") + spec = util.find_spec(package_or_name) + package_or_name = util.module_from_spec(spec) + spec.loader.exec_module(package_or_name) + version = package_or_name.__version__ + return version + + +def debug(value: bool = None) -> bool: + """Return (and optionally set) debug mode. + + Args: + value (bool, optional): If specified, sets the debug status. + If not specified, debug mode is not set, only returned. + + Returns: + bool: If ``True``, debug mode is active. + + Raises: + ValueError: If ``value`` is not a supported value. + + Note: + Changing the debug state changes the stream handler logging level + for the default dosma logger. If debug state is turned off, logging + level is set to ``logging.INFO``. If debug state is turned on, + logging level is set to ``logging.DEBUG``. + + Examples: + >>> debug() # get debug status, defaults to False + False + >>> debug(True) # turn on debug mode + True + >>> debug() # get debug status + True + """ + + def _is_debug(): + return os.environ.get("DOSMA_DEBUG", "") in ["True", "true"] + + def _toggle_debug(_old_value, _new_value): + from dosma.defaults import preferences + + # TODO: Toggle dosma logging to debug mode. + if _old_value == _new_value: + return + + _dm_logger = logging.getLogger("dosma") + if _new_value: + preferences.set("nipype", value="stream", prefix="logging") + _dm_logger.setLevel(logging.DEBUG) + for h in _dm_logger.handlers: + h.setLevel(logging.DEBUG) + else: + preferences.set("nipype", value="file_stderr", prefix="logging") + _dm_logger.setLevel(logging.DEBUG) # the root logger should always log at DEBUG level + for h in _dm_logger.handlers: + if isinstance(h, logging.StreamHandler): + h.setLevel(logging.INFO) + + if value is not None: + old_value = _is_debug() + if isinstance(value, bool): + os.environ["DOSMA_DEBUG"] = str(value) + elif isinstance(value, str) and value.lower() in ("true", "false", ""): + os.environ["DOSMA_DEBUG"] = value + else: + raise ValueError(f"Unknown value for debug: '{value}'") + + _toggle_debug(old_value, _is_debug()) + + return _is_debug() + + +def sitk_available(): + return package_available("SimpleITK") + + +def cupy_available(): + if "cupy" not in _SUPPORTED_PACKAGES: + try: + import cupy # noqa + except ImportError: + _SUPPORTED_PACKAGES["cupy"] = False + return package_available("cupy") + + +def sigpy_available(): + return package_available("sigpy") + + +def torch_available(): + return package_available("torch") + + +def resources_dir() -> str: + return os.path.abspath(os.path.join(_FILE_DIRECTORY, "../resources")) + + +def output_dir() -> str: + return os.path.abspath(os.path.join(_FILE_DIRECTORY, "../../.dosma")) + + +def temp_dir() -> str: + return os.path.join(output_dir(), "temp") + + +def log_file_path() -> str: + return os.path.join(output_dir(), "dosma.log")