From f73f2f934549b05ef59f499750a40f2e5daed36a Mon Sep 17 00:00:00 2001 From: Julien Barnier Date: Sun, 8 Oct 2023 17:23:38 +0200 Subject: [PATCH] feat: add path argument to save jsdom result to file See #21 --- src/pyobsplot/obsplot.py | 83 ++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/src/pyobsplot/obsplot.py b/src/pyobsplot/obsplot.py index 90be694..9180fd7 100644 --- a/src/pyobsplot/obsplot.py +++ b/src/pyobsplot/obsplot.py @@ -5,9 +5,13 @@ import shutil import os import signal +from pathlib import Path from subprocess import Popen, PIPE, SubprocessError -from IPython.display import display +from IPython.display import display, SVG, HTML from typing import Any +import warnings +from ipywidgets.embed import embed_minimal_html + from .widget import ObsplotWidget from .jsdom import ObsplotJsdom @@ -153,10 +157,37 @@ def __call__(self, *args, **kwargs) -> ObsplotWidget: """ Method called when an instance is called. """ + path = None + if "path" in kwargs: + path = kwargs["path"] + del kwargs["path"] spec = self.get_spec(*args, **kwargs) - return ObsplotWidget( + res = ObsplotWidget( spec, theme=self._theme, default=self._default, debug=self._debug ) # type: ignore + if path is not None: + ObsplotWidgetCreator.save_to_file(path, res) + else: + return res + + @staticmethod + def save_to_file(path: str, res: ObsplotWidget) -> None: + """ + Save an Obsplot object generated by a widget creator to a file. + + Args: + path (str): path to output file. + res (ObsplotWidget): result of a call to Obsplot(). + + Raises: + RuntimeWarning: if the file extension doesn't match the Obsplot type. + """ + extension = Path(path).suffix.lower() + if extension not in [".html", ".htm"]: + warnings.warn( + "Output file extension should be one of 'html' or 'htm'", RuntimeWarning + ) + embed_minimal_html(path, views=[res]) class ObsplotJsdomCreator(ObsplotCreator): @@ -179,16 +210,22 @@ def __call__(self, *args, **kwargs) -> None: raise RuntimeError( "Server has ended, please recreate your plot generator object." ) + path = None + if "path" in kwargs: + path = kwargs["path"] + del kwargs["path"] spec = self.get_spec(*args, **kwargs) - display( - ObsplotJsdom( - spec, - port=self._port, - theme=self._theme, - default=self._default, - debug=self._debug, - ).plot() - ) + res = ObsplotJsdom( + spec, + port=self._port, + theme=self._theme, + default=self._default, + debug=self._debug, + ).plot() + if path is None: + display(res) + else: + ObsplotJsdomCreator.save_to_file(path, res) def start_server(self): """ @@ -234,3 +271,27 @@ def close(self): """ if self._proc is not None: os.killpg(os.getpgid(self._proc.pid), signal.SIGTERM) + + @staticmethod + def save_to_file(path: str, res: SVG | HTML) -> None: + """ + Save an Obsplot object generated by a Jsdom creator to a file. + + Args: + path (str): path to output file. + res (SVG | HTML): result of a call to Obsplot(). + + Raises: + RuntimeWarning: if the file extension doesn't match the Obsplot type. + """ + extension = Path(path).suffix.lower() + if extension not in [".html", ".svg", ".htm"]: + warnings.warn( + "Output file extension should be one of 'html' or 'svg'", RuntimeWarning + ) + if isinstance(res, HTML) and extension == ".svg": + warnings.warn( + f"Output is HTML but file extension is '{extension}'", RuntimeWarning + ) + with open(path, "w") as f: + f.write(res.data)