diff --git a/plotly_resampler/figure_resampler/figure_resampler.py b/plotly_resampler/figure_resampler/figure_resampler.py index 7c6da5d4..7ab20712 100644 --- a/plotly_resampler/figure_resampler/figure_resampler.py +++ b/plotly_resampler/figure_resampler/figure_resampler.py @@ -13,9 +13,12 @@ import warnings from typing import Tuple, List +import uuid +import base64 import dash import plotly.graph_objects as go from dash import Dash +from flask_cors import cross_origin from jupyter_dash import JupyterDash from plotly.basedatatypes import BaseFigure from trace_updater import TraceUpdater @@ -25,6 +28,159 @@ from .utils import is_figure, is_fr +class JupyterDashPersistentInlineOutput(JupyterDash): + """Extension of the JupyterDash class to support the custom inline output for + ``FigureResampler`` figures. + + Specifically we embed a div in the notebook to display the figure inline. + + - In this div the figure is shown as an iframe when the server (of the dash app) + is alive. + - In this div the figure is shown as an image when the server (of the dash app) + is dead. + + As the HTML & javascript code is embedded in the notebook output, which is loaded + each time you open the notebook, the figure is always displayed (either as iframe + or just an image). + Hence, this extension enables to maintain always an output in the notebook. + + .. Note:: + This subclass is only used when the mode is set to ``"inline_persistent"`` in + the :func:`FigureResampler.show_dash ` + method. However, the mode should be passed as ``"inline"`` since this subclass + overwrites the inline behavior. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._uid = str(uuid.uuid4()) # A new unique id for each app + + # Mimic the _alive_{token} endpoint but with cors + @self.server.route(f"/_is_alive_{self._uid}", methods=["GET"]) + @cross_origin(origin=["*"], allow_headers=["Content-Type"]) + def broadcast_alive(): + return "Alive" + + def _display_inline_output(self, dashboard_url, width, height): + """Display the dash app persistent inline in the notebook. + + The figure is displayed as an iframe in the notebook if the server is reachable, + otherwise as an image. + """ + # TODO: check whether an error gets logged in case of crash + # TODO: add option to opt out of this + from IPython.display import display + + # Get the image from the dashboard and encode it as base64 + fig = self.layout.children[0].figure # is stored there in the show_dash method + f_width = 1000 if fig.layout.width is None else fig.layout.width + fig_base64 = base64.b64encode( + fig.to_image(format="png", width=f_width, scale=1, height=fig.layout.height) + ).decode("utf8") + + # The unique id of this app + # This id is used to couple the output in the notebook with this app + # A fetch request is performed to the _is_alive_{uid} endpoint to check if the + # app is still alive. + uid = self._uid + + # The html (& javascript) code to display the app / figure + display( + { + "text/html": f""" +
+ + """ + }, + raw=True, + clear=True, + display_id=uid, + ) + + def _display_in_jupyter(self, dashboard_url, port, mode, width, height): + """Override the display method to retain some output when displaying inline + in jupyter. + """ + if mode == "inline": + self._display_inline_output(dashboard_url, width, height) + else: + super()._display_in_jupyter(dashboard_url, port, mode, width, height) + + class FigureResampler(AbstractFigureAggregator, go.Figure): """Data aggregation functionality for ``go.Figures``.""" @@ -84,7 +240,7 @@ def __init__( verbose: bool, optional Whether some verbose messages will be printed or not, by default False. show_dash_kwargs: dict, optional - A dict that will be used as default kwargs for the :func:`show_dash` method. + A dict that will be used as default kwargs for the :func:`show_dash` method. Note that the passed kwargs will be take precedence over these defaults. """ @@ -109,7 +265,7 @@ def __init__( f._grid_ref = figure._grid_ref f.add_traces(figure.data) elif isinstance(figure, dict) and ( - "data" in figure or "layout" in figure # or "frames" in figure # TODO + "data" in figure or "layout" in figure # or "frames" in figure # TODO ): # A figure as a dict, can be; # - a plotly figure as a dict (after calling `fig.to_dict()`) @@ -131,7 +287,9 @@ def __init__( # A single trace dict or a list of traces f.add_traces(figure) - self._show_dash_kwargs = show_dash_kwargs if show_dash_kwargs is not None else {} + self._show_dash_kwargs = ( + show_dash_kwargs if show_dash_kwargs is not None else {} + ) super().__init__( f, @@ -184,6 +342,15 @@ def show_dash( web browser. * ``"inline"``: The app will be displayed inline in the notebook output cell in an iframe. + * ``"inline_persistent"``: The app will be displayed inline in the + notebook output cell in an iframe, if the app is not reachable a static + image of the figure is shown. Hence this is a persistent version of the + ``"inline"`` mode, allowing users to see a static figure in other + environments, browsers, etc. + + .. note:: + This mode requires the ``kaleido`` package. + * ``"jupyterlab"``: The app will be displayed in a dedicated tab in the JupyterLab interface. Requires JupyterLab and the ``jupyterlab-dash`` extension. @@ -206,10 +373,21 @@ def show_dash( constructor via the ``show_dash_kwargs`` argument. """ + available_modes = ["external", "inline", "inline_persistent", "jupyterlab"] + assert ( + mode is None or mode in available_modes + ), f"mode must be one of {available_modes}" graph_properties = {} if graph_properties is None else graph_properties assert "config" not in graph_properties.keys() # There is a param for config # 1. Construct the Dash app layout - app = JupyterDash("local_app") + if mode == "inline_persistent": + # Inline persistent mode: we display a static image of the figure when the + # app is not reachable + # Note: this is the "inline" behavior of JupyterDashInlinePersistentOutput + mode = "inline" + app = JupyterDashPersistentInlineOutput("local_app") + else: + app = JupyterDash("local_app") app.layout = dash.html.Div( [ dash.dcc.Graph( @@ -223,10 +401,7 @@ def show_dash( self.register_update_graph_callback(app, "resample-figure", "trace-updater") # 2. Run the app - if ( - mode == "inline" - and "height" not in kwargs - ): + if mode == "inline" and "height" not in kwargs: # If app height is not specified -> re-use figure height for inline dash app # Note: default layout height is 450 (whereas default app height is 650) # See: https://plotly.com/python/reference/layout/#layout-height diff --git a/poetry.lock b/poetry.lock index 8c74824e..bbe50473 100644 --- a/poetry.lock +++ b/poetry.lock @@ -250,7 +250,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.4.1" +version = "6.4.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -435,6 +435,18 @@ python-versions = "*" brotli = "*" flask = "*" +[[package]] +name = "flask-cors" +version = "3.0.10" +description = "A Flask extension adding a decorator for CORS support" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Flask = ">=0.9" +Six = "*" + [[package]] name = "h11" version = "0.13.0" @@ -834,6 +846,14 @@ category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +[[package]] +name = "kaleido" +version = "0.2.1" +description = "Static image export for web-based visualization libraries with zero dependencies" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "line-profiler" version = "3.5.1" @@ -2058,7 +2078,7 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "1.1" python-versions = "^3.7.1,<3.11" -content-hash = "801a6c76200c5f8f3fba1f3b2e84ac837c049429bdb57bfdc5d1640d131c220a" +content-hash = "7afd3983a097476349d6aabfaec3cf00d1608b62ba68a46bac2b3802bf3af636" [metadata.files] alabaster = [ @@ -2171,9 +2191,6 @@ brotli = [ {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, @@ -2185,18 +2202,12 @@ brotli = [ {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, @@ -2204,9 +2215,6 @@ brotli = [ {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, @@ -2214,9 +2222,6 @@ brotli = [ {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, @@ -2306,47 +2311,47 @@ colorama = [ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] coverage = [ - {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, - {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, - {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, - {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, - {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, - {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, - {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, - {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, - {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, - {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, - {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, - {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, - {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, + {file = "coverage-6.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e"}, + {file = "coverage-6.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c"}, + {file = "coverage-6.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8"}, + {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39"}, + {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0"}, + {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee"}, + {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d"}, + {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc"}, + {file = "coverage-6.4.2-cp310-cp310-win32.whl", hash = "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386"}, + {file = "coverage-6.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0"}, + {file = "coverage-6.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46"}, + {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07"}, + {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039"}, + {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996"}, + {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f"}, + {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e"}, + {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083"}, + {file = "coverage-6.4.2-cp37-cp37m-win32.whl", hash = "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7"}, + {file = "coverage-6.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120"}, + {file = "coverage-6.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"}, + {file = "coverage-6.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32"}, + {file = "coverage-6.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae"}, + {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8"}, + {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1"}, + {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63"}, + {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933"}, + {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de"}, + {file = "coverage-6.4.2-cp38-cp38-win32.whl", hash = "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783"}, + {file = "coverage-6.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6"}, + {file = "coverage-6.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f"}, + {file = "coverage-6.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f"}, + {file = "coverage-6.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe"}, + {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29"}, + {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55"}, + {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b"}, + {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978"}, + {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c"}, + {file = "coverage-6.4.2-cp39-cp39-win32.whl", hash = "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd"}, + {file = "coverage-6.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf"}, + {file = "coverage-6.4.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97"}, + {file = "coverage-6.4.2.tar.gz", hash = "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe"}, ] cryptography = [ {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, @@ -2381,15 +2386,12 @@ dash-bootstrap-components = [ {file = "dash_bootstrap_components-1.2.0-py3-none-any.whl", hash = "sha256:d7fd69cb2b1e86f9cc4bcee4036302e5d534d0bf102d331b29392a3c355d776a"}, ] dash-core-components = [ - {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, ] dash-html-components = [ - {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, ] dash-table = [ - {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, ] debugpy = [ @@ -2444,6 +2446,10 @@ flask-compress = [ {file = "Flask-Compress-1.12.tar.gz", hash = "sha256:e2159499f39d618a4d56ba0484e7b58b57956b9a2c6d3510f095f5bb14b7afc5"}, {file = "Flask_Compress-1.12-py3-none-any.whl", hash = "sha256:9f4e40211755e86f85e5eb7d414856ef1e8751912caa78d62853169400335f0c"}, ] +flask-cors = [ + {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, + {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, +] h11 = [ {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, @@ -2551,6 +2557,14 @@ kaitaistruct = [ {file = "kaitaistruct-0.10-py2.py3-none-any.whl", hash = "sha256:a97350919adbf37fda881f75e9365e2fb88d04832b7a4e57106ec70119efb235"}, {file = "kaitaistruct-0.10.tar.gz", hash = "sha256:a044dee29173d6afbacf27bcac39daf89b654dd418cfa009ab82d9178a9ae52a"}, ] +kaleido = [ + {file = "kaleido-0.2.1-py2.py3-none-macosx_10_11_x86_64.whl", hash = "sha256:ca6f73e7ff00aaebf2843f73f1d3bacde1930ef5041093fe76b83a15785049a7"}, + {file = "kaleido-0.2.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:bb9a5d1f710357d5d432ee240ef6658a6d124c3e610935817b4b42da9c787c05"}, + {file = "kaleido-0.2.1-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:aa21cf1bf1c78f8fa50a9f7d45e1003c387bd3d6fe0a767cfbbf344b95bdc3a8"}, + {file = "kaleido-0.2.1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:845819844c8082c9469d9c17e42621fbf85c2b237ef8a86ec8a8527f98b6512a"}, + {file = "kaleido-0.2.1-py2.py3-none-win32.whl", hash = "sha256:ecc72635860be616c6b7161807a65c0dbd9b90c6437ac96965831e2e24066552"}, + {file = "kaleido-0.2.1-py2.py3-none-win_amd64.whl", hash = "sha256:4670985f28913c2d063c5734d125ecc28e40810141bdb0a46f15b76c1d45f23c"}, +] line-profiler = [ {file = "line_profiler-3.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:409e32944176d4004df4308cc37674c1e48ea7444918c129edf5da68ded305c6"}, {file = "line_profiler-3.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d675732d221b5a4bfe48f57bd0ed2f759ad919e650890f4f5f1cf6536c1bc23"}, diff --git a/pyproject.toml b/pyproject.toml index c09ce9b9..df53fc2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ orjson = "^3.6.8" # faster json serialization pandas = "^1.3.5" trace-updater = ">=0.0.8" numpy = ">=1.21.0" +Flask-Cors = "^3.0.10" [tool.poetry.dev-dependencies] jupyterlab = "^3.3.0" @@ -37,6 +38,7 @@ sphinx-autodoc-typehints = "^1.17.0" ipywidgets = "^7.7.0" memory-profiler = "^0.60.0" line-profiler = "^3.5.1" +kaleido = "0.2.1" [build-system] requires = ["poetry-core>=1.0.0", "setuptools"] diff --git a/tests/conftest.py b/tests/conftest.py index f954b955..777d1b4f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,11 +44,14 @@ def driver(): from seleniumwire import webdriver from webdriver_manager.chrome import ChromeDriverManager, ChromeType from selenium.webdriver.chrome.options import Options + from selenium.webdriver.common.desired_capabilities import DesiredCapabilities import time time.sleep(1) options = Options() + d = DesiredCapabilities.CHROME + d['goog:loggingPrefs'] = {'browser': 'ALL'} if not TESTING_LOCAL: if headless: options.add_argument("--headless") @@ -56,12 +59,14 @@ def driver(): driver = webdriver.Chrome( ChromeDriverManager(chrome_type=ChromeType.GOOGLE).install(), options=options, + desired_capabilities=d, ) else: options.add_argument("--remote-debugging-port=9222") driver = webdriver.Chrome( options=options, - # executable_path="/home/jonas/git/gIDLaB/plotly-dynamic-resampling/chromedriver", + # executable_path="/home/jeroen/chromedriver", + desired_capabilities=d, ) # driver = webdriver.Firefox(executable_path='/home/jonas/git/gIDLaB/plotly-dynamic-resampling/geckodriver') return driver diff --git a/tests/fr_selenium.py b/tests/fr_selenium.py index 2593d763..aca41414 100644 --- a/tests/fr_selenium.py +++ b/tests/fr_selenium.py @@ -14,7 +14,6 @@ import sys import json import time -from datetime import datetime, timedelta from typing import List, Union from seleniumwire import webdriver @@ -186,7 +185,14 @@ def go_to_page(self): time.sleep(1) self.driver.get("http://localhost:{}".format(self.port)) self.on_page = True - if not_on_linux(): time.sleep(10) # bcs serialization of multiprocessing + if not_on_linux(): time.sleep(7) # bcs serialization of multiprocessing + max_nb_tries = 3 + for _ in range(max_nb_tries): + try: + self.driver.find_element_by_id("resample-figure") + break + except: + time.sleep(5) def clear_requests(self, sleep_time_s=1): time.sleep(sleep_time_s) diff --git a/tests/test_figure_resampler.py b/tests/test_figure_resampler.py index 299cae2c..04bd87e1 100644 --- a/tests/test_figure_resampler.py +++ b/tests/test_figure_resampler.py @@ -4,6 +4,7 @@ import pytest +import time import numpy as np import pandas as pd import multiprocessing @@ -603,13 +604,65 @@ def test_stop_server_inline(): fr.stop_server() proc = multiprocessing.Process(target=fr.show_dash, kwargs=dict(mode="inline")) proc.start() - import time time.sleep(3) fr.stop_server() proc.terminate() +def test_stop_server_inline_persistent(): + # mostly written to test the check_update_figure_dict whether the inline + height + # line option triggers + fr = FigureResampler(go.Figure()) + n = 100_000 + x = np.arange(n) + y = np.sin(x) + fr.add_trace(go.Scattergl(name="test"), hf_x=x, hf_y=y) + fr.update_layout(height=900) + fr.stop_server() + proc = multiprocessing.Process(target=fr.show_dash, kwargs=dict(mode="inline_persistent")) + proc.start() + + time.sleep(3) + fr.stop_server() + proc.terminate() + + +def test_manual_jupyterdashpersistentinline(): + # Manually call the JupyterDashPersistentInline its method + # This requires some gimmicky stuff to mimmick the behaviour of a jupyter notebook. + + fr = FigureResampler(go.Figure()) + n = 100_000 + x = np.arange(n) + y = np.sin(x) + fr.add_trace(go.Scattergl(name="test"), hf_x=x, hf_y=y) + + # no need to start the app (we just need the FigureResampler object) + + from plotly_resampler.figure_resampler.figure_resampler import JupyterDashPersistentInlineOutput + import dash + app = JupyterDashPersistentInlineOutput("manual_app") + assert hasattr(app, "_uid") + + # Mimmick what happens in the .show_dash method + # note: this is necessary because the figure gets accessed in the J + # JupyterDashPersistentInline its _display_inline_output method (to create the img) + app.layout = dash.html.Div( + [ + dash.dcc.Graph( + id="resample-figure", figure=fr + ), + # no need to add traceupdater for this dummy app + ] + ) + + # call the method (as it would normally be called) + app._display_in_jupyter(f"", port="", mode="inline", width='100%', height=500) + # call with a different mode (as it normally never would be called) + app._display_in_jupyter(f"", port="", mode="external", width='100%', height=500) + + def test_stop_server_external(): fr = FigureResampler(go.Figure()) n = 100_000 @@ -620,7 +673,6 @@ def test_stop_server_external(): fr.stop_server() proc = multiprocessing.Process(target=fr.show_dash, kwargs=dict(mode="external")) proc.start() - import time time.sleep(3) fr.stop_server() diff --git a/tests/test_figure_resampler_selenium.py b/tests/test_figure_resampler_selenium.py index 6f8df509..31dbc45f 100644 --- a/tests/test_figure_resampler_selenium.py +++ b/tests/test_figure_resampler_selenium.py @@ -69,6 +69,13 @@ def test_multiple_tz(driver, multiple_tz_figure): assert len(autoscale_requests) == 1 assert autoscale_requests[0].response.status_code == 204 + if len(driver.get_log("browser")) > 0: # Check no errors in the browser + for entry in driver.get_log("browser"): + print(entry) + if not entry["level"] == "INFO": + # Only WebGL warnings are allowed + assert entry["level"] == "warning" + assert entry["message"].contains("WebGL") except Exception as e: raise e finally: @@ -173,6 +180,13 @@ def test_basic_example_gui(driver, example_figure): n_updated_traces=5, ) + if len(driver.get_log("browser")) > 0: # Check no errors in the browser + for entry in driver.get_log("browser"): + print(entry) + if not entry["level"] == "INFO": + # Only WebGL warnings are allowed + assert entry["level"] == "warning" + assert entry["message"].contains("WebGL") except Exception as e: raise e finally: @@ -280,6 +294,13 @@ def test_basic_example_gui_existing(driver, example_figure_fig): n_updated_traces=5, ) + if len(driver.get_log("browser")) > 0: # Check no errors in the browser + for entry in driver.get_log("browser"): + print(entry) + if not entry["level"] == "INFO": + # Only WebGL warnings are allowed + assert entry["level"] == "warning" + assert entry["message"].contains("WebGL") except Exception as e: raise e finally: @@ -382,6 +403,14 @@ def test_gsr_gui(driver, gsr_figure): fr.reset_axes() time.sleep(0.2) + + if len(driver.get_log("browser")) > 0: # Check no errors in the browser + for entry in driver.get_log("browser"): + print(entry) + if not entry["level"] == "INFO": + # Only WebGL warnings are allowed + assert entry["level"] == "warning" + assert entry["message"].contains("WebGL") except Exception as e: raise e finally: @@ -456,6 +485,13 @@ def test_cat_gui(driver, cat_series_box_hist_figure): n_updated_traces=1, ) + if len(driver.get_log("browser")) > 0: # Check no errors in the browser + for entry in driver.get_log("browser"): + print(entry) + if not entry["level"] == "INFO": + # Only WebGL warnings are allowed + assert entry["level"] == "warning" + assert entry["message"].contains("WebGL") except Exception as e: raise e finally: @@ -531,6 +567,13 @@ def test_shared_hover_gui(driver, shared_hover_figure): assert len(autoscale_requests) == 1 assert autoscale_requests[0].response.status_code == 204 + if len(driver.get_log("browser")) > 0: # Check no errors in the browser + for entry in driver.get_log("browser"): + print(entry) + if not entry["level"] == "INFO": + # Only WebGL warnings are allowed + assert entry["level"] == "warning" + assert entry["message"].contains("WebGL") except Exception as e: raise e finally: @@ -603,6 +646,13 @@ def test_multi_trace_go_figure(driver, multi_trace_go_figure): assert len(autoscale_requests) == 1 assert autoscale_requests[0].response.status_code == 204 + if len(driver.get_log("browser")) > 0: # Check no errors in the browser + for entry in driver.get_log("browser"): + print(entry) + if not entry["level"] == "INFO": + # Only WebGL warnings are allowed + assert entry["level"] == "warning" + assert entry["message"].contains("WebGL") except Exception as e: raise e finally: