Here's a production-grade solution, using importlib
and Pandas's import_optional_dependency as suggested by @dre-hh
from typing import *
import importlib, types
def module_exists(
*names: Union[List[str], str],
error: str = "ignore",
warn_every_time: bool = False,
__INSTALLED_OPTIONAL_MODULES: Dict[str, bool] = {}
) -> Optional[Union[Tuple[types.ModuleType, ...], types.ModuleType]]:
"""
Try to import optional dependencies.
Ref: https://stackoverflow.com/a/73838546/4900327
Parameters
----------
names: str or list of strings.
The module name(s) to import.
error: str {'raise', 'warn', 'ignore'}
What to do when a dependency is not found.
* raise : Raise an ImportError.
* warn: print a warning.
* ignore: If any module is not installed, return None, otherwise,
return the module(s).
warn_every_time: bool
Whether to warn every time an import is tried. Only applies when error="warn".
Setting this to True will result in multiple warnings if you try to
import the same library multiple times.
Returns
-------
maybe_module : Optional[ModuleType, Tuple[ModuleType...]]
The imported module(s), if all are found.
None is returned if any module is not found and `error!="raise"`.
"""
assert error in {"raise", "warn", "ignore"}
if isinstance(names, (list, tuple, set)):
names: List[str] = list(names)
else:
assert isinstance(names, str)
names: List[str] = [names]
modules = []
for name in names:
try:
module = importlib.import_module(name)
modules.append(module)
__INSTALLED_OPTIONAL_MODULES[name] = True
except ImportError:
modules.append(None)
def error_msg(missing: Union[str, List[str]]):
if not isinstance(missing, (list, tuple)):
missing = [missing]
missing_str: str = ' '.join([f'"{name}"' for name in missing])
dep_str = 'dependencies'
if len(missing) == 1:
dep_str = 'dependency'
msg = f'Missing optional {dep_str} {missing_str}. Use pip or conda to install.'
return msg
missing_modules: List[str] = [name for name, module in zip(names, modules) if module is None]
if len(missing_modules) > 0:
if error == "raise":
raise ImportError(error_msg(missing_modules))
if error == "warn":
for name in missing_modules:
## Ensures warning is printed only once
if warn_every_time is True or name not in __INSTALLED_OPTIONAL_MODULES:
print(f'Warning: {error_msg(name)}')
__INSTALLED_OPTIONAL_MODULES[name] = False
return None
if len(modules) == 1:
return modules[0]
return tuple(modules)
Usage: ignore errors (error="ignore"
, default behavior)
Suppose we want to run certain code only if the required libraries exists:
if module_exists("pydantic", "sklearn"):
from pydantic import BaseModel
from sklearn.metrics import accuracy_score
class AccuracyCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(accuracy_score(y_true, y_pred), self.num_decimals)
print("Defined AccuracyCalculator in global context")
If either dependencies pydantic
or skelarn
do not exist, then the class AccuracyCalculator
will not be defined and the print statement will not run.
Usage: raise ImportError (error="raise"
)
Alternatively, you can raise a error if any module does not exist:
if module_exists("pydantic", "sklearn", error="raise"):
from pydantic import BaseModel
from sklearn.metrics import accuracy_score
class AccuracyCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(accuracy_score(y_true, y_pred), self.num_decimals)
print("Defined AccuracyCalculator in global context")
Output:
line 60, in module_exists(error, __INSTALLED_OPTIONAL_MODULES, *names)
58 if len(missing_modules) > 0:
59 if error == "raise":
---> 60 raise ImportError(error_msg(missing_modules))
61 if error == "warn":
62 for name in missing_modules:
ImportError: Missing optional dependencies "pydantic" "sklearn". Use pip or conda to install.
Usage: print a warning (error="warn"
)
Alternatively, you can print a warning if the module does not exist.
if module_exists("pydantic", "sklearn", error="warn"):
from pydantic import BaseModel
from sklearn.metrics import accuracy_score
class AccuracyCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(accuracy_score(y_true, y_pred), self.num_decimals)
print("Defined AccuracyCalculator in global context")
if module_exists("pydantic", "sklearn", error="warn"):
from pydantic import BaseModel
from sklearn.metrics import roc_auc_score
class RocAucCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(roc_auc_score(y_true, y_pred), self.num_decimals)
print("Defined RocAucCalculator in global context")
Output:
Warning: Missing optional dependency "pydantic". Use pip or conda to install.
Warning: Missing optional dependency "sklearn". Use pip or conda to install.
Here, we ensure that only one warning is printed for each missing module, otherwise you would get a warning each time you try to import.
This is very useful for Python libraries where you might try to import the same optional dependencies many times, and only want to see one Warning.
You can pass warn_every_time=True
to always print the warning when you try to import.