Source code for klea_utils.imports

#!/usr/bin/env python3
"""
Helpers for optional-dependency import checks.

File: klea_utils/imports.py

Copyright 2026 Ankur Sinha
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
"""

import importlib.util
import logging

logger = logging.getLogger(__name__)


[docs] def require_extra( import_names: str | list[str], extra: str, dist: str = "klea_utils", ) -> None: """Raise a helpful ``ImportError`` when optional imports are missing. Checks each *import_names* entry with :func:`importlib.util.find_spec` (no module code is executed) and, if any are not findable, raises a single ``ImportError`` that tells the user how to install the Klea ``[extra]`` (``pip install klea_utils[extra]``). The message is extra-level only -- the caller passes the *import* names (e.g. ``"bs4"``, not ``"beautifulsoup4"``) but the fix is the extra, which is the user-facing knob in ``setup.cfg``. Accepts a single import name or a list so callers can batch ``["bs4", "anydoc"]`` into one call and get one combined hint instead of N separate ``try`` blocks. :param import_names: Import name or list of import names to check (e.g. ``"bs4"`` or ``["bs4", "anydoc"]``). :param extra: Extra name in ``setup.cfg`` (e.g. ``"mcp"``, ``"chroma"``). :param dist: Distribution name that owns the extra (default ``"klea_utils"``). :raises ImportError: when any of *import_names* is not findable. """ if isinstance(import_names, str): names = [import_names] else: names = list(import_names) missing = [m for m in names if importlib.util.find_spec(m) is None] if not missing: logger.debug(f"Optional imports present for [{extra}]: {names}") return logger.debug(f"Optional imports missing for [{extra}]: {missing}") if len(missing) == 1: msg = ( f"{missing[0]} is required for the [{extra}] feature. " f"Install with: pip install {dist}[{extra}]" ) else: msg = ( f"{', '.join(missing)} are required for the [{extra}] feature. " f"Install with: pip install {dist}[{extra}]" ) raise ImportError(msg) from None