Source code for qsarkit.base.optional_deps

"""Lazy-import helper for optional, heavy dependencies (torch, transformers, ...).

A few qsarkit features rest on large third-party packages -- learned molecular
embeddings, SHAP/LIME attribution, UMAP projections. Modules that need one
import it lazily through :func:`require` inside ``__init__``/``fit`` rather
than at module import time, so that ``import qsarkit`` never pulls in
torch/transformers unless the corresponding feature is actually used.
"""

from __future__ import annotations

import importlib
from typing import Any

from qsarkit.base.exceptions import OptionalDependencyError

# Maps an import name to the pip extra that provides it (see pyproject.toml).
_EXTRA_FOR_MODULE = {
    "torch": "nlp",
    "transformers": "nlp",
    "gensim": "embeddings",
    "shap": "explainability",
    "lime": "explainability",
    "xgboost": "boosting",
    "lightgbm": "boosting",
    "umap": "embedding_viz",
    "kaleido": "reporting",
    "reportlab": "reporting",
    "skops": "persistence",
    "skops.io": "persistence",
    "imblearn": "balancing",
}


[docs] def require(module_name: str) -> Any: """Import and return ``module_name``, raising a helpful error if absent. Parameters ---------- module_name: Fully qualified module name, e.g. ``"torch"`` or ``"pdfminer.high_level"``. Returns ------- module The imported module object. Raises ------ OptionalDependencyError If the module is not installed. The error message names the pip extra (``qsarkit-learn[extra]``) that installs it. Examples -------- >>> from qsarkit.base import require >>> require("numpy").__name__ 'numpy' A missing dependency raises an error naming the extra that provides it, rather than an ``ImportError`` the caller has to interpret: >>> require("nonexistent_package_xyz") Traceback (most recent call last): ... qsarkit.base.exceptions.OptionalDependencyError: This feature requires... Call it inside ``__init__`` or ``fit``, never at module import time -- that is what keeps ``import qsarkit`` from pulling in PyTorch. """ try: return importlib.import_module(module_name) except ImportError as exc: top_level = module_name.split(".")[0] extra = _EXTRA_FOR_MODULE.get(module_name) or _EXTRA_FOR_MODULE.get(top_level) raise OptionalDependencyError(top_level, extra) from exc