diff --git a/skore/src/skore/persistence/item/__init__.py b/skore/src/skore/persistence/item/__init__.py index 81385bc94..2934d26ca 100644 --- a/skore/src/skore/persistence/item/__init__.py +++ b/skore/src/skore/persistence/item/__init__.py @@ -36,10 +36,12 @@ def object_to_item( if display_as is not None: if not isinstance(object, str): - raise TypeError("`object` must be a str if `display_as` is specified") + raise TypeError("`object` must be a string if `display_as` is specified") if display_as not in MediaType.__members__: - raise ValueError(f"`display_as` must be in {list(MediaType.__members__)}") + raise ValueError( + f"`display_as` must be one of {', '.join(MediaType.__members__)}" + ) return MediaItem.factory(object, MediaType[display_as].value, note=note) diff --git a/skore/src/skore/persistence/item/altair_chart_item.py b/skore/src/skore/persistence/item/altair_chart_item.py index a2a3aebb5..0b7fd570e 100644 --- a/skore/src/skore/persistence/item/altair_chart_item.py +++ b/skore/src/skore/persistence/item/altair_chart_item.py @@ -70,7 +70,7 @@ def chart(self) -> AltairChart: return altair.Chart.from_json(self.chart_str) def as_serializable_dict(self): - """Return item as a JSONable dict to export to frontend.""" + """Convert item to a JSON-serializable dict to used by frontend.""" import base64 chart_bytes = self.chart_str.encode("utf-8") diff --git a/skore/src/skore/persistence/item/cross_validation_reporter_item.py b/skore/src/skore/persistence/item/cross_validation_reporter_item.py index 35a8c7820..6ecbffb7b 100644 --- a/skore/src/skore/persistence/item/cross_validation_reporter_item.py +++ b/skore/src/skore/persistence/item/cross_validation_reporter_item.py @@ -204,7 +204,7 @@ def reporter(self) -> CrossValidationReporter: return joblib.load(stream) def as_serializable_dict(self): - """Get a serializable dict from the item.""" + """Convert item to a JSON-serializable dict to used by frontend.""" # Get tabular results (the cv results in a dataframe-like structure) cv_results = { key: value.tolist() diff --git a/skore/src/skore/persistence/item/item.py b/skore/src/skore/persistence/item/item.py index 9eb7a0930..8cde7ab84 100644 --- a/skore/src/skore/persistence/item/item.py +++ b/skore/src/skore/persistence/item/item.py @@ -117,10 +117,10 @@ def read_asset_content(path): return template.render(**context) def as_serializable_dict(self): - """Get a serializable dict from the item. + """Convert item to a JSON-serializable dict to used by frontend. - Derived class must call their super implementation - and merge the result with their output. + Derived class must call their super implementation and merge the result with + their output. """ return { "updated_at": self.updated_at, diff --git a/skore/src/skore/persistence/item/matplotlib_figure_item.py b/skore/src/skore/persistence/item/matplotlib_figure_item.py index b12979866..2f0853dbf 100644 --- a/skore/src/skore/persistence/item/matplotlib_figure_item.py +++ b/skore/src/skore/persistence/item/matplotlib_figure_item.py @@ -76,7 +76,7 @@ def figure(self) -> Figure: return joblib.load(stream) def as_serializable_dict(self) -> dict: - """Return item as a JSONable dict to export to frontend.""" + """Convert item to a JSON-serializable dict to used by frontend.""" with BytesIO() as stream: self.figure.savefig(stream, format="svg", bbox_inches="tight") diff --git a/skore/src/skore/persistence/item/media_item.py b/skore/src/skore/persistence/item/media_item.py index 7531456c2..521574a80 100644 --- a/skore/src/skore/persistence/item/media_item.py +++ b/skore/src/skore/persistence/item/media_item.py @@ -19,7 +19,7 @@ def lazy_is_instance(object: Any, cls_fullname: str) -> bool: class MediaType(Enum): - """Enum used to map aliases and media types.""" + """A media type of a string.""" HTML = "text/html" MARKDOWN = "text/markdown" @@ -63,7 +63,7 @@ def factory( cls, media: str, /, - media_type: Optional[str] = "text/markdown", + media_type: Optional[str] = MediaType.MARKDOWN.value, **kwargs, ) -> MediaItem: """ @@ -90,7 +90,7 @@ def factory( return cls(media, media_type, **kwargs) def as_serializable_dict(self): - """Return item as a JSONable dict to export to frontend.""" + """Convert item to a JSON-serializable dict to used by frontend.""" return super().as_serializable_dict() | { "media_type": self.media_type, "value": self.media, diff --git a/skore/src/skore/persistence/item/numpy_array_item.py b/skore/src/skore/persistence/item/numpy_array_item.py index 4dd096ce5..8e958a15c 100644 --- a/skore/src/skore/persistence/item/numpy_array_item.py +++ b/skore/src/skore/persistence/item/numpy_array_item.py @@ -59,21 +59,6 @@ def array(self) -> numpy.ndarray: return numpy.asarray(loads(self.array_json)) - def as_serializable_dict(self): - """Get a serializable dict from the item. - - Derived class must call their super implementation - and merge the result with their output. - """ - d = super().as_serializable_dict() - d.update( - { - "media_type": "text/markdown", - "value": self.array.tolist(), - } - ) - return d - @classmethod def factory(cls, array: numpy.ndarray, /, **kwargs) -> NumpyArrayItem: """ @@ -95,3 +80,10 @@ def factory(cls, array: numpy.ndarray, /, **kwargs) -> NumpyArrayItem: raise ItemTypeError(f"Type '{array.__class__}' is not supported.") return cls(array_json=dumps(array.tolist()), **kwargs) + + def as_serializable_dict(self): + """Convert item to a JSON-serializable dict to used by frontend.""" + return super().as_serializable_dict() | { + "media_type": "text/markdown", + "value": self.array.tolist(), + } diff --git a/skore/src/skore/persistence/item/pandas_dataframe_item.py b/skore/src/skore/persistence/item/pandas_dataframe_item.py index 4e4ecb8c6..1e8e3dda6 100644 --- a/skore/src/skore/persistence/item/pandas_dataframe_item.py +++ b/skore/src/skore/persistence/item/pandas_dataframe_item.py @@ -77,21 +77,6 @@ def dataframe(self) -> pandas.DataFrame: return dataframe - def as_serializable_dict(self): - """Get a serializable dict from the item. - - Derived class must call their super implementation - and merge the result with their output. - """ - d = super().as_serializable_dict() - d.update( - { - "media_type": "application/vnd.dataframe", - "value": self.dataframe.fillna("NaN").to_dict(orient="tight"), - } - ) - return d - @classmethod def factory(cls, dataframe: pandas.DataFrame, /, **kwargs) -> PandasDataFrameItem: """ @@ -146,3 +131,10 @@ def factory(cls, dataframe: pandas.DataFrame, /, **kwargs) -> PandasDataFrameIte dataframe_json=dataframe.to_json(orient=PandasDataFrameItem.ORIENT), **kwargs, ) + + def as_serializable_dict(self): + """Convert item to a JSON-serializable dict to used by frontend.""" + return super().as_serializable_dict() | { + "media_type": "application/vnd.dataframe", + "value": self.dataframe.fillna("NaN").to_dict(orient="tight"), + } diff --git a/skore/src/skore/persistence/item/pandas_series_item.py b/skore/src/skore/persistence/item/pandas_series_item.py index b74365c0c..8b943281d 100644 --- a/skore/src/skore/persistence/item/pandas_series_item.py +++ b/skore/src/skore/persistence/item/pandas_series_item.py @@ -82,21 +82,6 @@ def series(self) -> pandas.Series: return series - def as_serializable_dict(self): - """Get a serializable dict from the item. - - Derived class must call their super implementation - and merge the result with their output. - """ - d = super().as_serializable_dict() - d.update( - { - "value": self.series.fillna("NaN").to_list(), - "media_type": "text/markdown", - } - ) - return d - @classmethod def factory(cls, series: pandas.Series, /, **kwargs) -> PandasSeriesItem: """ @@ -138,3 +123,10 @@ def factory(cls, series: pandas.Series, /, **kwargs) -> PandasSeriesItem: series_json=series.to_json(orient=PandasSeriesItem.ORIENT), **kwargs, ) + + def as_serializable_dict(self): + """Convert item to a JSON-serializable dict to used by frontend.""" + return super().as_serializable_dict() | { + "value": self.series.fillna("NaN").to_list(), + "media_type": "text/markdown", + } diff --git a/skore/src/skore/persistence/item/pickle_item.py b/skore/src/skore/persistence/item/pickle_item.py index e99c56e6d..21a69cfad 100644 --- a/skore/src/skore/persistence/item/pickle_item.py +++ b/skore/src/skore/persistence/item/pickle_item.py @@ -17,9 +17,8 @@ class PickleItem(Item): """ - A class to represent any object item. + An item used to persist objects that cannot be otherwise, using binary protocols. - This class is generally used to persist objects that cannot be otherwise. It encapsulates the object with its pickle representaton, its creation and update timestamps. """ @@ -52,7 +51,7 @@ def __init__( @classmethod def factory(cls, object: Any, /, **kwargs) -> PickleItem: """ - Create a new PickleItem with any object. + Create a new PickleItem from ``object``. Parameters ---------- @@ -76,11 +75,11 @@ def object(self) -> Any: return joblib.load(stream) def as_serializable_dict(self): - """Get a JSON serializable representation of the item.""" + """Convert item to a JSON-serializable dict to used by frontend.""" try: object = self.object except Exception as e: - value = "UnpicklingError" + value = "Item cannot be displayed" traceback = "".join(format_exception(None, e, e.__traceback__)) note = "".join( ( diff --git a/skore/src/skore/persistence/item/pillow_image_item.py b/skore/src/skore/persistence/item/pillow_image_item.py index 68b906d83..8d3e5266f 100644 --- a/skore/src/skore/persistence/item/pillow_image_item.py +++ b/skore/src/skore/persistence/item/pillow_image_item.py @@ -87,7 +87,7 @@ def image(self) -> PIL.Image.Image: ) def as_serializable_dict(self): - """Return item as a JSONable dict to export to frontend.""" + """Convert item to a JSON-serializable dict to used by frontend.""" import base64 import io diff --git a/skore/src/skore/persistence/item/plotly_figure_item.py b/skore/src/skore/persistence/item/plotly_figure_item.py index d35f6b5b8..2aaae561f 100644 --- a/skore/src/skore/persistence/item/plotly_figure_item.py +++ b/skore/src/skore/persistence/item/plotly_figure_item.py @@ -11,7 +11,7 @@ from .media_item import lazy_is_instance if TYPE_CHECKING: - import plotly.basedatatypes as plotly + import plotly.basedatatypes class PlotlyFigureItem(Item): @@ -43,7 +43,12 @@ def __init__( self.figure_str = figure_str @classmethod - def factory(cls, figure: plotly.BaseFigure, /, **kwargs) -> PlotlyFigureItem: + def factory( + cls, + figure: plotly.basedatatypes.BaseFigure, + /, + **kwargs, + ) -> PlotlyFigureItem: """ Create a new PlotlyFigureItem instance from a Plotly figure. @@ -65,14 +70,14 @@ def factory(cls, figure: plotly.BaseFigure, /, **kwargs) -> PlotlyFigureItem: return cls(plotly.io.to_json(figure, engine="json"), **kwargs) @property - def figure(self) -> plotly.BaseFigure: + def figure(self) -> plotly.basedatatypes.BaseFigure: """The figure from the persistence.""" import plotly.io return plotly.io.from_json(self.figure_str) def as_serializable_dict(self): - """Return item as a JSONable dict to export to frontend.""" + """Convert item to a JSON-serializable dict to used by frontend.""" import base64 figure_bytes = self.figure_str.encode("utf-8") diff --git a/skore/src/skore/persistence/item/polars_dataframe_item.py b/skore/src/skore/persistence/item/polars_dataframe_item.py index de7d2562f..baa84723c 100644 --- a/skore/src/skore/persistence/item/polars_dataframe_item.py +++ b/skore/src/skore/persistence/item/polars_dataframe_item.py @@ -68,23 +68,6 @@ def dataframe(self) -> polars.DataFrame: dataframe = polars.read_json(df_stream) return dataframe - def as_serializable_dict(self): - """Get a serializable dict from the item. - - Derived class must call their super implementation - and merge the result with their output. - """ - d = super().as_serializable_dict() - d.update( - { - "value": self.dataframe.to_pandas() - .fillna("NaN") - .to_dict(orient="tight"), - "media_type": "application/vnd.dataframe", - } - ) - return d - @classmethod def factory(cls, dataframe: polars.DataFrame, /, **kwargs) -> PolarsDataFrameItem: """ @@ -115,3 +98,10 @@ def factory(cls, dataframe: polars.DataFrame, /, **kwargs) -> PolarsDataFrameIte raise PolarsToJSONError("Conversion to JSON failed") from e return cls(dataframe_json=dataframe_json, **kwargs) + + def as_serializable_dict(self): + """Convert item to a JSON-serializable dict to used by frontend.""" + return super().as_serializable_dict() | { + "value": self.dataframe.to_pandas().fillna("NaN").to_dict(orient="tight"), + "media_type": "application/vnd.dataframe", + } diff --git a/skore/src/skore/persistence/item/polars_series_item.py b/skore/src/skore/persistence/item/polars_series_item.py index f1114c497..172565d4a 100644 --- a/skore/src/skore/persistence/item/polars_series_item.py +++ b/skore/src/skore/persistence/item/polars_series_item.py @@ -65,21 +65,6 @@ def series(self) -> polars.Series: return series - def as_serializable_dict(self): - """Get a serializable dict from the item. - - Derived class must call their super implementation - and merge the result with their output. - """ - d = super().as_serializable_dict() - d.update( - { - "value": self.series.to_list(), - "media_type": "text/markdown", - } - ) - return d - @classmethod def factory(cls, series: polars.Series, /, **kwargs) -> PolarsSeriesItem: """ @@ -101,3 +86,10 @@ def factory(cls, series: polars.Series, /, **kwargs) -> PolarsSeriesItem: raise ItemTypeError(f"Type '{series.__class__}' is not supported.") return cls(series_json=series.to_frame().write_json(), **kwargs) + + def as_serializable_dict(self): + """Convert item to a JSON-serializable dict to used by frontend.""" + return super().as_serializable_dict() | { + "value": self.series.to_list(), + "media_type": "text/markdown", + } diff --git a/skore/src/skore/persistence/item/primitive_item.py b/skore/src/skore/persistence/item/primitive_item.py index 9bf3a7baf..a7d341c72 100644 --- a/skore/src/skore/persistence/item/primitive_item.py +++ b/skore/src/skore/persistence/item/primitive_item.py @@ -70,21 +70,6 @@ def __init__( self.primitive = primitive - def as_serializable_dict(self): - """Get a serializable dict from the item. - - Derived class must call their super implementation - and merge the result with their output. - """ - d = super().as_serializable_dict() - d.update( - { - "media_type": "text/markdown", - "value": self.primitive, - } - ) - return d - @classmethod def factory(cls, primitive: Primitive, /, **kwargs) -> PrimitiveItem: """ @@ -104,3 +89,10 @@ def factory(cls, primitive: Primitive, /, **kwargs) -> PrimitiveItem: raise ItemTypeError(f"Type '{primitive.__class__}' is not supported.") return cls(primitive=primitive, **kwargs) + + def as_serializable_dict(self): + """Convert item to a JSON-serializable dict to used by frontend.""" + return super().as_serializable_dict() | { + "media_type": "text/markdown", + "value": self.primitive, + } diff --git a/skore/src/skore/persistence/item/sklearn_base_estimator_item.py b/skore/src/skore/persistence/item/sklearn_base_estimator_item.py index 38a78e2ab..aef7d05ff 100644 --- a/skore/src/skore/persistence/item/sklearn_base_estimator_item.py +++ b/skore/src/skore/persistence/item/sklearn_base_estimator_item.py @@ -71,21 +71,6 @@ def estimator(self) -> sklearn.base.BaseEstimator: self.estimator_skops, trusted=self.estimator_skops_untrusted_types ) - def as_serializable_dict(self): - """Get a serializable dict from the item. - - Derived class must call their super implementation - and merge the result with their output. - """ - d = super().as_serializable_dict() - d.update( - { - "value": self.estimator_html_repr, - "media_type": "application/vnd.sklearn.estimator+html", - } - ) - return d - @classmethod def factory( cls, @@ -128,3 +113,10 @@ def factory( estimator_skops_untrusted_types=estimator_skops_untrusted_types, **kwargs, ) + + def as_serializable_dict(self): + """Convert item to a JSON-serializable dict to used by frontend.""" + return super().as_serializable_dict() | { + "value": self.estimator_html_repr, + "media_type": "application/vnd.sklearn.estimator+html", + } diff --git a/skore/src/skore/project/project.py b/skore/src/skore/project/project.py index d485e3f72..d1b15b0c4 100644 --- a/skore/src/skore/project/project.py +++ b/skore/src/skore/project/project.py @@ -93,7 +93,7 @@ def put( The value to associate with ``key`` in the Project. note : str, optional A note to attach with the item. - display_as : str, optional + display_as : {"HTML", "MARKDOWN", "SVG"}, optional Used in combination with a string value, it customizes the way the value is displayed in the interface. @@ -124,10 +124,11 @@ def get(self, key, *, latest=True, metadata=False): ---------- key : str The key corresponding to the item to get. - latest : boolean, optional - Get the latest value or all the values associated to ``key``, default True. - metadata : boolean, optional - Get the metadata in addition of the value, default False. + latest : boolean, default=True + If True, get the latest value, otherwise get all the values associated to + ``key``. + metadata : boolean, default=False + If True, get the metadata in addition to the value. Returns ------- diff --git a/skore/tests/integration/ui/test_ui.py b/skore/tests/integration/ui/test_ui.py index cd2d2eadf..03d7c571f 100644 --- a/skore/tests/integration/ui/test_ui.py +++ b/skore/tests/integration/ui/test_ui.py @@ -575,7 +575,7 @@ def test_get_items_with_pickle_item_and_unpickling_error( "updated_at": mock_nowstr, "name": "pickle", "media_type": "text/markdown", - "value": "UnpicklingError", + "value": "Item cannot be displayed", }, ], }, diff --git a/skore/tests/unit/item/test_matplotlib_figure_item.py b/skore/tests/unit/item/test_matplotlib_figure_item.py index d0a7212ec..ccbd3ba02 100644 --- a/skore/tests/unit/item/test_matplotlib_figure_item.py +++ b/skore/tests/unit/item/test_matplotlib_figure_item.py @@ -32,7 +32,7 @@ def test_factory(self, mock_nowstr, tmp_path): with io.BytesIO(item.figure_bytes) as stream: joblib.load(stream).savefig(tmp_path / "item.png") - assert not compare_images(tmp_path / "figure.png", tmp_path / "item.png", 0) + assert compare_images(tmp_path / "figure.png", tmp_path / "item.png", 0) is None assert item.created_at == mock_nowstr assert item.updated_at == mock_nowstr @@ -56,8 +56,12 @@ def test_figure(self, tmp_path): item1.figure.savefig(tmp_path / "item1.png") item2.figure.savefig(tmp_path / "item2.png") - assert not compare_images(tmp_path / "figure.png", tmp_path / "item1.png", 0) - assert not compare_images(tmp_path / "figure.png", tmp_path / "item2.png", 0) + assert ( + compare_images(tmp_path / "figure.png", tmp_path / "item1.png", 0) is None + ) + assert ( + compare_images(tmp_path / "figure.png", tmp_path / "item2.png", 0) is None + ) def test_as_serializable_dict(self, mock_nowstr): figure = FakeFigure() diff --git a/skore/tests/unit/project/test_project.py b/skore/tests/unit/project/test_project.py index f5f27a9cc..5d2bd51a0 100644 --- a/skore/tests/unit/project/test_project.py +++ b/skore/tests/unit/project/test_project.py @@ -115,7 +115,7 @@ def test_put_matplotlib_figure(in_memory_project, monkeypatch, tmp_path): figure.savefig(tmp_path / "figure.png") in_memory_project.get("figure").savefig(tmp_path / "item.png") - assert not compare_images(tmp_path / "figure.png", tmp_path / "item.png", 0) + assert compare_images(tmp_path / "figure.png", tmp_path / "item.png", 0) is None def test_put_altair_chart(in_memory_project):