diff --git a/pycaret/containers/models/classification.py b/pycaret/containers/models/classification.py index 1f140b5d0..8c3ce5c58 100644 --- a/pycaret/containers/models/classification.py +++ b/pycaret/containers/models/classification.py @@ -263,7 +263,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.linear_model import LogisticRegression elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.linear_model import LogisticRegression else: from sklearn.linear_model import LogisticRegression @@ -324,7 +326,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.neighbors import KNeighborsClassifier elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.neighbors import KNeighborsClassifier else: from sklearn.neighbors import KNeighborsClassifier @@ -580,7 +584,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.svm import SVC elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.svm import SVC else: from sklearn.svm import SVC diff --git a/pycaret/containers/models/clustering.py b/pycaret/containers/models/clustering.py index 5f83ddf2c..0ee10d0f8 100644 --- a/pycaret/containers/models/clustering.py +++ b/pycaret/containers/models/clustering.py @@ -191,7 +191,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.cluster import KMeans elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.cluster import KMeans else: from sklearn.cluster import KMeans @@ -340,7 +342,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.cluster import DBSCAN elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.cluster import DBSCAN else: from sklearn.cluster import DBSCAN diff --git a/pycaret/containers/models/regression.py b/pycaret/containers/models/regression.py index 0bf4389a5..b3d44c2b0 100644 --- a/pycaret/containers/models/regression.py +++ b/pycaret/containers/models/regression.py @@ -233,7 +233,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.linear_model import LinearRegression elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.linear_model import LinearRegression else: from sklearn.linear_model import LinearRegression @@ -287,7 +289,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.linear_model import Lasso elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.linear_model import Lasso else: from sklearn.linear_model import Lasso @@ -344,7 +348,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.linear_model import Ridge elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.linear_model import Ridge else: from sklearn.linear_model import Ridge @@ -401,7 +407,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.linear_model import ElasticNet elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.linear_model import ElasticNet else: from sklearn.linear_model import ElasticNet @@ -1004,7 +1012,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.svm import SVR elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.svm import SVR else: from sklearn.svm import SVR @@ -1065,7 +1075,9 @@ def __init__(self, experiment): if self.engine == "sklearn": from sklearn.neighbors import KNeighborsRegressor elif self.engine == "sklearnex": - if _check_soft_dependencies("sklearnex", extra=None, severity="warning"): + if _check_soft_dependencies( + "scikit-learn-intelex", extra=None, severity="warning" + ): from sklearnex.neighbors import KNeighborsRegressor else: from sklearn.neighbors import KNeighborsRegressor diff --git a/pycaret/containers/models/time_series.py b/pycaret/containers/models/time_series.py index 01d3469dc..841e43ba7 100644 --- a/pycaret/containers/models/time_series.py +++ b/pycaret/containers/models/time_series.py @@ -1495,7 +1495,7 @@ def return_regressor_class(self): if self.engine == "sklearn": from sklearn.linear_model import LinearRegression elif self.engine == "sklearnex": - _check_soft_dependencies("sklearnex", extra=None, severity="error") + _check_soft_dependencies("scikit-learn-intelex", extra=None, severity="error") # noqa: E501 from sklearnex.linear_model import LinearRegression if self.gpu_param == "force": @@ -1552,7 +1552,7 @@ def return_regressor_class(self): if self.engine == "sklearn": from sklearn.linear_model import ElasticNet elif self.engine == "sklearnex": - _check_soft_dependencies("sklearnex", extra=None, severity="error") + _check_soft_dependencies("scikit-learn-intelex", extra=None, severity="error") # noqa: E501 from sklearnex.linear_model import ElasticNet if self.gpu_param == "force": @@ -1613,7 +1613,7 @@ def return_regressor_class(self): if self.engine == "sklearn": from sklearn.linear_model import Ridge elif self.engine == "sklearnex": - _check_soft_dependencies("sklearnex", extra=None, severity="error") + _check_soft_dependencies("scikit-learn-intelex", extra=None, severity="error") # noqa: E501 from sklearnex.linear_model import Ridge if self.gpu_param == "force": @@ -1673,7 +1673,7 @@ def return_regressor_class(self): if self.engine == "sklearn": from sklearn.linear_model import Lasso elif self.engine == "sklearnex": - _check_soft_dependencies("sklearnex", extra=None, severity="error") + _check_soft_dependencies("scikit-learn-intelex", extra=None, severity="error") # noqa: E501 from sklearnex.linear_model import Lasso if self.gpu_param == "force": @@ -2040,7 +2040,7 @@ def return_regressor_class(self): if self.engine == "sklearn": from sklearn.neighbors import KNeighborsRegressor elif self.engine == "sklearnex": - _check_soft_dependencies("sklearnex", extra=None, severity="error") + _check_soft_dependencies("scikit-learn-intelex", extra=None, severity="error") # noqa: E501 from sklearnex.neighbors import KNeighborsRegressor if self.gpu_param == "force": diff --git a/pycaret/utils/_dependencies.py b/pycaret/utils/_dependencies.py index 6d4769e55..e20d775d8 100644 --- a/pycaret/utils/_dependencies.py +++ b/pycaret/utils/_dependencies.py @@ -1,95 +1,20 @@ # Adapted from # https://github.com/sktime/sktime/blob/v0.11.0/sktime/utils/validation/_dependencies.py -import sys -from distutils.version import LooseVersion -from importlib import import_module -from typing import Dict, Optional, Union +from typing import Optional -from importlib_metadata import distributions +from skbase.utils.dependencies import _check_soft_dependencies as _skbase_csd +from skbase.utils.dependencies._dependencies import _get_installed_packages -from pycaret.internal.logging import get_logger, redirect_output +from pycaret.internal.logging import get_logger logger = get_logger() -INSTALLED_MODULES = None - - -def _try_import_and_get_module_version( - modname: str, -) -> Optional[Union[LooseVersion, bool]]: - """Returns False if module is not installed, None if version is not available""" - try: - if modname in sys.modules: - mod = sys.modules[modname] - else: - if logger: - with redirect_output(logger): - mod = import_module(modname) - else: - mod = import_module(modname) - try: - ver = mod.__version__ - except AttributeError: - # Version could not be obtained - ver = None - except ImportError: - ver = False - if ver: - ver = LooseVersion(ver) - return ver - - -# Based on packages_distributions() from importlib_metadata -def get_installed_modules() -> Dict[str, Optional[LooseVersion]]: - """ - Get installed modules and their versions from pip metadata. - """ - global INSTALLED_MODULES - if not INSTALLED_MODULES: - # Get all installed modules and their versions without - # needing to import them. - module_versions = {} - # top_level.txt contains information about modules - # in the package. It is not always present, in which case - # the assumption is that the package name is the module name. - # https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html - for dist in distributions(): - for pkg in (dist.read_text("top_level.txt") or "").split(): - try: - ver = LooseVersion(dist.metadata["Version"]) - except Exception: - ver = None - module_versions[pkg] = ver - INSTALLED_MODULES = module_versions - return INSTALLED_MODULES - - -def _get_module_version(modname: str) -> Optional[Union[LooseVersion, bool]]: - """Will cache the version in INSTALLED_MODULES - - Returns False if module is not installed.""" - installed_modules = get_installed_modules() - if modname not in installed_modules: - # Fallback. This should never happen unless module is not present - installed_modules[modname] = _try_import_and_get_module_version(modname) - return installed_modules[modname] - - -def get_module_version(modname: str) -> Optional[LooseVersion]: - """Raises a ValueError if module is not installed""" - version = _get_module_version(modname) - if version is False: - raise ValueError(f"Module '{modname}' is not installed.") - return version - -def is_module_installed(modname: str) -> bool: - try: - get_module_version(modname) - return True - except ValueError: - return False +def get_module_version_str(modname: str) -> str: + """Raises a ValueError if module is not installed""" + versions = _get_installed_packages() + return versions.get(modname, "Not installed") def _check_soft_dependencies( @@ -103,18 +28,28 @@ def _check_soft_dependencies( Parameters ---------- - package : str - Package to check - severity : str, optional - Whether to raise an error ("error") or just a warning message ("warning"), - by default "error" + packages : str + package name to check + str should be package name and/or package version specifications to check. + Must be a PEP 440 compatible specifier string, for a single package. + For instance, the PEP 440 compatible package name such as ``"pandas"``; + or a package requirement specifier string such as ``"pandas>1.2.3"``. + + severity : str, "error" (default), "warning", "none" + whether the check should raise an error, a warning, or nothing + + * "error" - raises a ``ModuleNotFoundError`` if one of packages is not installed + * "warning" - raises a warning if one of packages is not installed + function returns False if one of packages is not installed, otherwise True + * "none" - does not raise exception or warning + function returns False if one of packages is not installed, otherwise True + extra : Optional[str], optional The 'extras' that will install this package, by default "all_extras". If None, it means that the dependency is not available in optional requirements file and must be installed by the user on their own. - install_name : Optional[str], optional - The package name to install, by default None - If none, the name in `package` argument is used + + install_name : ignored, present only for backwards compatibility Returns ------- @@ -130,33 +65,22 @@ def _check_soft_dependencies( RuntimeError Is the severity argument is not one of the allowed values """ - install_name = install_name or package + msg = ( + f"\n'{package}' is a soft dependency and not included in the " + f"pycaret installation. Please run: `pip install {package}` to install. " + ) + if extra is not None: + msg += ( + f"\nAlternately, you can install {package} by running " + f"`pip install pycaret[{extra}]`" + ) - package_available = is_module_installed(package) + package_available = _skbase_csd(package, severity=severity, msg=msg) if package_available: - ver = get_module_version(package) + ver = get_module_version_str(package) logger.info( "Soft dependency imported: {k}: {stat}".format(k=package, stat=str(ver)) ) - else: - msg = ( - f"\n'{package}' is a soft dependency and not included in the " - f"pycaret installation. Please run: `pip install {install_name}` to install." - ) - if extra is not None: - msg += f"\nAlternately, you can install this by running `pip install pycaret[{extra}]`" - - if severity == "error": - logger.exception(f"{msg}") - raise ModuleNotFoundError(msg) - elif severity == "warning": - logger.warning(f"{msg}") - package_available = False - else: - raise RuntimeError( - "Error in calling _check_soft_dependencies, severity " - f'argument must be "error" or "warning", found "{severity}".' - ) return package_available diff --git a/pycaret/utils/_show_versions.py b/pycaret/utils/_show_versions.py index 11ceab2dc..1c9becb5d 100644 --- a/pycaret/utils/_show_versions.py +++ b/pycaret/utils/_show_versions.py @@ -11,7 +11,7 @@ import sys from typing import Optional -from pycaret.utils._dependencies import get_module_version +from pycaret.utils._dependencies import get_module_version_str required_deps = [ "pip", @@ -127,7 +127,7 @@ def _get_deps_info(optional: bool = False, logger: Optional[logging.Logger] = No for modname in deps: try: - ver = get_module_version(modname) + ver = get_module_version_str(modname) if not ver: ver = "Installed but version unavailable" except ValueError: diff --git a/pyproject.toml b/pyproject.toml index c5cbe1197..40c4e2b37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,8 @@ classifiers = [ # this set should be kept minimal! dependencies = [ # Base + "packaging", + "scikit-base>=0.6.1", "ipython>=5.5.0", "ipywidgets>=7.6.5", "tqdm>=4.62.0",