Repository: deepchecks/deepchecks

Issue:
[FEAT] Add `save_html` on CheckResult
Currently, we have `save_html` only on SuiteResult. As a user I want to have it also on a single CheckResult


Discussion:

Resolving patch:
diff --git a/deepchecks/core/check_result.py b/deepchecks/core/check_result.py
--- a/deepchecks/core/check_result.py
+++ b/deepchecks/core/check_result.py
@@ -24,7 +24,6 @@
 import ipywidgets as widgets
 import plotly.graph_objects as go
 import plotly
-from ipywidgets.embed import embed_minimal_html, dependency_state
 from plotly.basedatatypes import BaseFigure
 from matplotlib import pyplot as plt
 from IPython.display import display_html
@@ -34,7 +33,7 @@
 from deepchecks.core.display_pandas import dataframe_to_html, get_conditions_table
 from deepchecks.core.errors import DeepchecksValueError
 from deepchecks.utils.dataframes import un_numpy
-from deepchecks.utils.strings import get_docs_summary
+from deepchecks.utils.strings import create_new_file_name, get_docs_summary, widget_to_html
 from deepchecks.utils.ipython import is_notebook
 from deepchecks.utils.wandb_utils import set_wandb_run_state
 
@@ -188,11 +187,26 @@ def display_check(self, unique_id: str = None, as_widget: bool = False,
     def _repr_html_(self):
         """Return html representation of check result."""
         html_out = io.StringIO()
-        widgeted_output = self.display_check(as_widget=True)
-        embed_minimal_html(html_out, views=[widgeted_output], requirejs=False,
-                           embed_url=None, state=dependency_state(widgeted_output))
+        self.save_as_html(html_out, False)
         return html_out.getvalue()
 
+    def save_as_html(self, file=None, requirejs: bool = True):
+        """Save output as html file.
+
+        Parameters
+        ----------
+        file : filename or file-like object
+            The file to write the HTML output to. If None writes to output.html
+        requirejs: bool , default: True
+            If to save with all javascript dependencies
+        """
+        if file is None:
+            file = 'output.html'
+        widgeted_output = self.display_check(as_widget=True)
+        if isinstance(file, str):
+            file = create_new_file_name(file, 'html')
+        widget_to_html(widgeted_output, html_out=file, title=self.get_header(), requirejs=requirejs)
+
     def _display_to_json(self) -> List[Tuple[str, str]]:
         displays = []
         old_backend = matplotlib.get_backend()
diff --git a/deepchecks/core/display_suite.py b/deepchecks/core/display_suite.py
--- a/deepchecks/core/display_suite.py
+++ b/deepchecks/core/display_suite.py
@@ -11,7 +11,6 @@
 """Handle display of suite result."""
 import os
 import sys
-import re
 from typing import List, Union
 import warnings
 
@@ -22,11 +21,10 @@
 from IPython.display import display, display_html
 from IPython import get_ipython
 import ipywidgets as widgets
-from ipywidgets.embed import embed_minimal_html, dependency_state
 
 from deepchecks.core import errors
 from deepchecks.utils.ipython import is_widgets_enabled
-from deepchecks.utils.strings import create_new_file_name, get_random_string
+from deepchecks.utils.strings import create_new_file_name, get_random_string, widget_to_html
 from deepchecks.core.check_result import CheckResult, CheckFailure
 from deepchecks.core.display_pandas import (
     dataframe_to_html, get_conditions_table,
@@ -181,14 +179,7 @@ def _display_suite_widgets(summary: str,
     if html_out:
         if isinstance(html_out, str):
             html_out = create_new_file_name(html_out, 'html')
-        curr_path = os.path.dirname(os.path.abspath(__file__))
-        with open(os.path.join(curr_path, 'resources', 'suite_output.html'), 'r', encoding='utf8') as html_file:
-            html_formatted = re.sub('{', '{{', html_file.read())
-            html_formatted = re.sub('}', '}}', html_formatted)
-            html_formatted = re.sub('html_title', '{title}', html_formatted)
-            html_formatted = re.sub('widget_snippet', '{snippet}', html_formatted)
-            embed_minimal_html(html_out, views=[page], title='Suite Output', template=html_formatted,
-                               requirejs=requirejs, embed_url=None, state=dependency_state(page))
+        widget_to_html(page, html_out=html_out, title='Suite Output', requirejs=requirejs)
     else:
         display(page)
 
diff --git a/deepchecks/utils/strings.py b/deepchecks/utils/strings.py
--- a/deepchecks/utils/strings.py
+++ b/deepchecks/utils/strings.py
@@ -20,6 +20,8 @@
 from decimal import Decimal
 from copy import copy
 
+from ipywidgets import Widget
+from ipywidgets.embed import embed_minimal_html, dependency_state
 import pandas as pd
 from pandas.core.dtypes.common import is_numeric_dtype
 
@@ -27,6 +29,11 @@
 from deepchecks import core
 from deepchecks.utils.typing import Hashable
 
+try:
+    from importlib.resources import files
+except ImportError:
+    from importlib_resources import files
+
 
 __all__ = [
     'string_baseform',
@@ -43,7 +50,8 @@
     'get_docs_summary',
     'get_ellipsis',
     'to_snake_case',
-    'create_new_file_name'
+    'create_new_file_name',
+    'widget_to_html',
 ]
 
 
@@ -92,6 +100,30 @@ def get_docs_summary(obj, with_doc_link: bool = True):
     return summary
 
 
+def widget_to_html(widget: Widget, html_out: t.Any, title: str = None, requirejs: bool = True):
+    """Save widget as html file.
+
+    Parameters
+    ----------
+    widget: Widget
+        The widget to save as html.
+    html_out: filename or file-like object
+        The file to write the HTML output to.
+    title: str , default: None
+        The title of the html file.
+    requirejs: bool , default: True
+        If to save with all javascript dependencies
+    """
+    my_resources = files('deepchecks.core')
+    with open(os.path.join(my_resources, 'resources', 'suite_output.html'), 'r', encoding='utf8') as html_file:
+        html_formatted = re.sub('{', '{{', html_file.read())
+        html_formatted = re.sub('}', '}}', html_formatted)
+        html_formatted = re.sub('html_title', '{title}', html_formatted)
+        html_formatted = re.sub('widget_snippet', '{snippet}', html_formatted)
+        embed_minimal_html(html_out, views=[widget], title=title, template=html_formatted,
+                           requirejs=requirejs, embed_url=None, state=dependency_state(widget))
+
+
 def _generate_check_docs_link_html(check):
     """Create from check object a link to its example page in the docs."""
     if not isinstance(check, core.BaseCheck):


Test patch: