From 5955c20782e4a752ce469b6c36c68675de8f03d5 Mon Sep 17 00:00:00 2001 From: Mihir Trivedi Date: Mon, 19 Feb 2024 08:23:06 -0500 Subject: [PATCH] requires dependencies --- alto/images/docker_image.py | 5 ++++ alto/utils.py | 53 ++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/alto/images/docker_image.py b/alto/images/docker_image.py index cf4ed20..72021fe 100644 --- a/alto/images/docker_image.py +++ b/alto/images/docker_image.py @@ -31,6 +31,7 @@ _check_optional_key_in_conf ) from alto.output import OutputManager +from alto.utils import requires_dependencies ########## @@ -53,6 +54,10 @@ class Docker(BaseImage, ConfigMixin): CLIENT: Any image_version: Optional[str] = None + @requires_dependencies( + "docker", + "docker", + ) def __init__(self, alto_wkdir: Path, image_name: str, diff --git a/alto/utils.py b/alto/utils.py index 1aa8704..2301b6f 100644 --- a/alto/utils.py +++ b/alto/utils.py @@ -4,8 +4,10 @@ # Imports from dataclasses import dataclass -from typing import Any, Dict, Optional, List, Union +from functools import wraps +import importlib from pathlib import Path +from typing import Any, Dict, Callable, Optional, List, Union # Functions / Utils @@ -167,3 +169,52 @@ def paths_flattener(list_of_paths: List[Union[str, Path]]) -> List[Path]: flattened_paths.append(Path("/".join(spath))) return flattened_paths + + +def requires_dependencies( + dependencies: Union[str, List[str]], + extras: Optional[str] = None, +): + """ + Wrapper used to prompt the user to `pip install` a package and/or Prism extracts in + order to run a function. Borrowed heavily from the `unstructured` library: + https://github.com/Unstructured-IO/unstructured/blob/main/unstructured/utils.py + + args: + dependencies: required dependencies + extracts: list of Prism extras that the user can `pip install` + """ + if isinstance(dependencies, str): + dependencies = [dependencies] + + def decorator(func: Callable[..., Any]) -> Callable[..., Any]: + @wraps(func) + def wrapper(*args, **kwargs): + missing_deps: List[str] = [] + for dep in dependencies: + if not dependency_exists(dep): + missing_deps.append(dep) + if len(missing_deps) > 0: + raise ImportError( + f"""Following dependencies are missing: {', '.join(["`" + dep + "`" for dep in missing_deps])}. """ # noqa + + ( # noqa + f"""Please install them using `pip install "alto-dev[{extras}]"`.""" # noqa + if extras + else f"Please install them using `pip install {' '.join(missing_deps)}`." # noqa + ), + ) + return func(*args, **kwargs) + + return wrapper + return decorator + + +def dependency_exists(dependency: str): + try: + importlib.import_module(dependency) + except ImportError as e: + # Check to make sure this isn't some unrelated import error. + pkg = dependency.split(".")[0] + if pkg in repr(e): + return False + return True