Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] performance optimization for _check_soft_dependencies, speed up test collection time #6355

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion sktime/classification/deep_learning/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ def _convert_y_to_keras(self, y):
y = y.reshape(len(y), 1)

# in sklearn 1.2, sparse was renamed to sparse_output
if _check_soft_dependencies("sklearn>=1.2", severity="none"):
if _check_soft_dependencies(
"scikit-learn>=1.2",
severity="none",
package_import_alias={"scikit-learn": "sklearn"},
):
sparse_kw = {"sparse_output": False}
else:
sparse_kw = {"sparse": False}
Expand Down
23 changes: 16 additions & 7 deletions sktime/forecasting/compose/tests/test_reduce_global.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
"""Test extraction of features across (shifted) windows."""
__author__ = ["danbartl"]

from sktime.tests.test_switch import run_test_for_class
from sktime.utils.dependencies import _check_soft_dependencies

# HistGradientBoostingRegressor requires experimental flag in old sklearn versions
if _check_soft_dependencies("sklearn<1.0", severity="none"):
from sklearn.experimental import enable_hist_gradient_boosting # noqa

import random

import numpy as np
Expand All @@ -28,8 +21,24 @@
from sktime.forecasting.compose._reduce import _DirectReducer, _RecursiveReducer
from sktime.performance_metrics.forecasting import mean_absolute_percentage_error
from sktime.split import temporal_train_test_split
from sktime.tests.test_switch import run_test_for_class
from sktime.transformations.series.summarize import WindowSummarizer
from sktime.utils._testing.hierarchical import _make_hierarchical
from sktime.utils.dependencies import _check_soft_dependencies

# HistGradientBoostingRegressor requires experimental flag in old sklearn versions
sklearn_zero_x = _check_soft_dependencies(
"scikit-learn<1.4",
severity="none",
package_import_alias={"scikit-learn": "sklearn"},
)

if _check_soft_dependencies(
"scikit-learn<1.0",
severity="none",
package_import_alias={"scikit-learn": "sklearn"},
):
from sklearn.experimental import enable_hist_gradient_boosting # noqa


@pytest.fixture
Expand Down
10 changes: 8 additions & 2 deletions sktime/transformations/panel/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class ColumnTransformer(_ColumnTransformer, _PanelToPanelTransformer):

_tags = {
"authors": ["mloning", "sajaysurya", "fkiraly"],
"python_dependencies": ["scipy", "sklearn<1.4"],
"python_dependencies": ["scipy", "scikit-learn<1.4"],
}

def __init__(
Expand All @@ -133,7 +133,13 @@ def __init__(
"ColumnTransformer can simply be replaced by ColumnEnsembleTransformer."
)

if not _check_soft_dependencies("sklearn<1.4", severity="none"):
sklearn_lneq_14 = _check_soft_dependencies(
"scikit-learn<1.4",
severity="none",
package_import_alias={"scikit-learn": "sklearn"},
)

if not sklearn_lneq_14:
raise ModuleNotFoundError(
"ColumnTransformer is not fully compliant with the sktime interface "
"and distributed only for reasons of downwards compatibility. "
Expand Down
15 changes: 13 additions & 2 deletions sktime/utils/_maint/tests/test_show_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,22 @@ def test_deps_info():
assert isinstance(deps_info_default, dict)
assert set(deps_info_default.keys()) == set(DEFAULT_DEPS_TO_SHOW)

PKG_IMPORT_ALIAS = {"scikit-learn": "sklearn", "scikit-base": "skbase"}
KEY_ALIAS = {"sklearn": "scikit-learn", "skbase": "scikit-base"}

for key in DEFAULT_DEPS_TO_SHOW:
key_is_available = _check_soft_dependencies(key, severity="none")
pkg_name = KEY_ALIAS.get(key, key)
key_is_available = _check_soft_dependencies(
pkg_name,
severity="none",
package_import_alias=PKG_IMPORT_ALIAS,
)
assert (deps_info_default[key] is None) != key_is_available
if key_is_available:
assert _check_soft_dependencies(f"{key}=={deps_info_default[key]}")
assert _check_soft_dependencies(
f"{pkg_name}=={deps_info_default[key]}",
package_import_alias=PKG_IMPORT_ALIAS,
)
deps_single_key = _get_deps_info([key])
assert set(deps_single_key.keys()) == {key}

Expand Down
43 changes: 22 additions & 21 deletions sktime/utils/dependencies/_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

__author__ = ["fkiraly", "mloning"]

import io
import sys
import warnings
from importlib import import_module
from importlib.metadata import PackageNotFoundError, version
from importlib.util import find_spec
from inspect import isclass

from packaging.markers import InvalidMarker, Marker
Expand Down Expand Up @@ -128,20 +128,24 @@ def _check_soft_dependencies(
package_import_name = package_import_alias[package_name]
else:
package_import_name = package_name
# attempt import - if not possible, we know we need to raise warning/exception
try:
if suppress_import_stdout:
# setup text trap, import, then restore
sys.stdout = io.StringIO()
pkg_ref = import_module(package_import_name)
sys.stdout = sys.__stdout__
else:
pkg_ref = import_module(package_import_name)
# if package cannot be imported, make the user aware of installation requirement
except ModuleNotFoundError as e:

# optimized branching to check presence of import
# and presence of package distribution
# first we check import, then we check distribution
# because try/except consumes more runtime
pkg_spec = find_spec(package_import_name)
if pkg_spec is not None:
try:
pkg_env_version = version(package_name)
except PackageNotFoundError:
pkg_spec = None

# if package not present, make the user aware of installation reqs
if pkg_spec is None:
if obj is None and msg is None:
msg = (
f"{e}. '{package}' is a soft dependency and not included in the "
f"'{package}' not found. "
f"'{package}' is a soft dependency and not included in the "
f"base sktime installation. Please run: `pip install {package}` to "
f"install the {package} package. "
f"To install all soft dependencies, run: `pip install "
Expand All @@ -161,7 +165,7 @@ def _check_soft_dependencies(
# so if msg is passed it overrides the default messages

if severity == "error":
raise ModuleNotFoundError(msg) from e
raise ModuleNotFoundError(msg)
elif severity == "warning":
warnings.warn(msg, stacklevel=2)
return False
Expand All @@ -176,8 +180,6 @@ def _check_soft_dependencies(

# now we check compatibility with the version specifier if non-empty
if package_version_req != SpecifierSet(""):
pkg_env_version = pkg_ref.__version__

msg = (
f"{class_name} requires package '{package}' to be present "
f"in the python environment, with version {package_version_req}, "
Expand Down Expand Up @@ -237,12 +239,11 @@ def _check_dl_dependencies(msg=None, severity="error"):
"tensorflow is required for deep learning functionality in `sktime`. "
"To install these dependencies, run: `pip install sktime[dl]`"
)
try:
import_module("tensorflow")
if find_spec("tensorflow") is not None:
return True
except ModuleNotFoundError as e:
else:
if severity == "error":
raise ModuleNotFoundError(msg) from e
raise ModuleNotFoundError(msg)
elif severity == "warning":
warnings.warn(msg, stacklevel=2)
return False
Expand Down