diff --git a/skore/src/skore/sklearn/train_test_split/train_test_split.py b/skore/src/skore/sklearn/train_test_split/train_test_split.py index 48b54bb54d..11ccbfd26b 100644 --- a/skore/src/skore/sklearn/train_test_split/train_test_split.py +++ b/skore/src/skore/sklearn/train_test_split/train_test_split.py @@ -2,6 +2,8 @@ from __future__ import annotations +import contextlib +import re import warnings from typing import TYPE_CHECKING, Any, Optional, Union @@ -10,12 +12,56 @@ from skore.sklearn.find_ml_task import _find_ml_task from skore.sklearn.train_test_split.warning import TRAIN_TEST_SPLIT_WARNINGS +from skore.utils._environment import is_environment_html_capable if TYPE_CHECKING: ArrayLike = Any from skore.project import Project +# use variables for HTML warnings +# pick vscode color then fallback to jupyter color +# then fallback to orange +HTML_WARNING_TEMPLATE = """ + +
+
{warning_class}
+
{warning}
+
+""" + + def train_test_split( *arrays: ArrayLike, X: Optional[ArrayLike] = None, @@ -158,10 +204,36 @@ class labels. ml_task=ml_task, ) + to_display = [] for warning_class in TRAIN_TEST_SPLIT_WARNINGS: warning = warning_class.check(**kwargs) if warning is not None: + to_display.append((warning, warning_class)) + + if is_environment_html_capable(): + with contextlib.suppress(ImportError): + from IPython.core.interactiveshell import InteractiveShell + from IPython.display import HTML, display + from markdown import markdown + + if InteractiveShell.initialized(): + markup = "".join( + [ + HTML_WARNING_TEMPLATE.format( + warning=markdown(warning), + warning_class=re.sub( + "([A-Z][a-z]+)", + r" \1", + re.sub("([A-Z]+)", r" \1", warning_class.__name__), + ).lstrip(), + ) + for warning, warning_class in to_display + ] + ) + display(HTML(markup)) + else: + for warning, warning_class in to_display: warnings.warn(message=warning, category=warning_class, stacklevel=1) return output diff --git a/skore/src/skore/utils/_environment.py b/skore/src/skore/utils/_environment.py new file mode 100644 index 0000000000..aff9e6f6b7 --- /dev/null +++ b/skore/src/skore/utils/_environment.py @@ -0,0 +1,52 @@ +import os +import sys + + +def get_environment_info(): + """Detect the current Python execution environment. + + Returns a dictionary with information about the environment. + """ + env_info = { + "is_jupyter": False, + "is_vscode": False, + "is_interactive": False, + "environment_name": "standard_python", + "details": {}, + } + + # Check for interactive mode + env_info["is_interactive"] = hasattr(sys, "ps1") + + # Check for Jupyter + try: + shell = get_ipython().__class__.__name__ # type: ignore + env_info["details"]["ipython_shell"] = shell + + if shell == "ZMQInteractiveShell": # Jupyter notebook/lab + env_info["is_jupyter"] = True + env_info["environment_name"] = "jupyter" + elif shell == "TerminalInteractiveShell": # IPython terminal + env_info["environment_name"] = "ipython_terminal" + except NameError: + pass + + # Check for VSCode + if "VSCODE_PID" in os.environ: + env_info["is_vscode"] = True + if env_info["is_interactive"]: + env_info["environment_name"] = "vscode_interactive" + else: + env_info["environment_name"] = "vscode_script" + + # Add additional environment details + env_info["details"]["python_executable"] = sys.executable + env_info["details"]["python_version"] = sys.version + + return env_info + + +def is_environment_html_capable() -> bool: + """Return `True` if the execution context dcan render HTML. `False` otherwise.""" + info = get_environment_info() + return info["is_vscode"] or info["is_jupyter"]