diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c58b1e4bd..d36c200f695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [4.14.3] - 2021-01-12 ### Fixed +- Plotly.js cdn url will now be versioned by default for: + `include_plotlyjs='cdn'` a new `include_plotlyjs='cdn-latest'` option + has the original behaviour. Prevents likelihood of htmls generated with older + `plotly.js` versions breaking with version bumps. + [2961](https://github.com/plotly/plotly.py/pull/2961) - `px.timeline()` now allows `hover_data` formatting of start and end times [3018](https://github.com/plotly/plotly.py/pull/3018) - Small change to packaging of `plotlywidget` extension for JupyterLab 3 [3021](https://github.com/plotly/plotly.py/pull/3021) diff --git a/packages/python/plotly/plotly/io/_base_renderers.py b/packages/python/plotly/plotly/io/_base_renderers.py index f5aec7e61cd..8eb947efe8d 100644 --- a/packages/python/plotly/plotly/io/_base_renderers.py +++ b/packages/python/plotly/plotly/io/_base_renderers.py @@ -7,9 +7,10 @@ from os.path import isdir import six -from plotly.io import to_json, to_image, write_image, write_html from plotly import utils, optional_imports +from plotly.io import to_json, to_image, write_image, write_html from plotly.io._orca import ensure_server +from plotly.io._utils import plotly_cdn_url from plotly.offline.offline import _get_jconfig, get_plotlyjs from plotly.tools import return_figure_from_figure_or_data @@ -289,7 +290,7 @@ def activate(self): require.undef("plotly"); requirejs.config({{ paths: {{ - 'plotly': ['https://cdn.plot.ly/plotly-latest.min'] + 'plotly': ['{plotly_cdn}'] }} }}); require(['plotly'], function(Plotly) {{ @@ -298,7 +299,9 @@ def activate(self): }} """.format( - win_config=_window_plotly_config, mathjax_config=_mathjax_config + win_config=_window_plotly_config, + mathjax_config=_mathjax_config, + plotly_cdn=plotly_cdn_url().rstrip(".js"), ) else: diff --git a/packages/python/plotly/plotly/io/_html.py b/packages/python/plotly/plotly/io/_html.py index 550a96f1d86..b279d91fccc 100644 --- a/packages/python/plotly/plotly/io/_html.py +++ b/packages/python/plotly/plotly/io/_html.py @@ -6,7 +6,7 @@ import six from _plotly_utils.optional_imports import get_module -from plotly.io._utils import validate_coerce_fig_to_dict +from plotly.io._utils import validate_coerce_fig_to_dict, plotly_cdn_url from plotly.offline.offline import _get_jconfig, get_plotlyjs from plotly import utils @@ -61,10 +61,16 @@ def to_html( fully self-contained and can be used offline. If 'cdn', a script tag that references the plotly.js CDN is included - in the output. HTML files generated with this option are about 3MB - smaller than those generated with include_plotlyjs=True, but they - require an active internet connection in order to load the plotly.js - library. + in the output. The url used is versioned to match the bundled plotly.js. + HTML files generated with this option are about 3MB smaller than those + generated with include_plotlyjs=True, but they require an active + internet connection in order to load the plotly.js library. + + If 'cdn-latest', a script tag that always references the latest plotly.js + CDN is included in the output. + HTML files generated with this option are about 3MB smaller than those + generated with include_plotlyjs=True, but they require an active + internet connection in order to load the plotly.js library. If 'directory', a script tag is included that references an external plotly.min.js bundle that is assumed to reside in the same @@ -266,12 +272,15 @@ def to_html( require_start = 'require(["plotly"], function(Plotly) {' require_end = "});" - elif include_plotlyjs == "cdn": + elif include_plotlyjs == "cdn" or include_plotlyjs == "cdn-latest": + cdn_url = plotly_cdn_url() + if include_plotlyjs == "cdn-latest": + cdn_url = plotly_cdn_url(cdn_ver="latest") load_plotlyjs = """\ {win_config} - \ + \ """.format( - win_config=_window_plotly_config + win_config=_window_plotly_config, cdn_url=cdn_url ) elif include_plotlyjs == "directory": @@ -417,10 +426,16 @@ def write_html( fully self-contained and can be used offline. If 'cdn', a script tag that references the plotly.js CDN is included - in the output. HTML files generated with this option are about 3MB - smaller than those generated with include_plotlyjs=True, but they - require an active internet connection in order to load the plotly.js - library. + in the output. The url used is versioned to match the bundled plotly.js. + HTML files generated with this option are about 3MB smaller than those + generated with include_plotlyjs=True, but they require an active + internet connection in order to load the plotly.js library. + + If 'cdn-latest', a script tag that always references the latest plotly.js + CDN is included in the output. + HTML files generated with this option are about 3MB smaller than those + generated with include_plotlyjs=True, but they require an active + internet connection in order to load the plotly.js library. If 'directory', a script tag is included that references an external plotly.min.js bundle that is assumed to reside in the same diff --git a/packages/python/plotly/plotly/io/_utils.py b/packages/python/plotly/plotly/io/_utils.py index 289ffa2c1a7..5bc03271148 100644 --- a/packages/python/plotly/plotly/io/_utils.py +++ b/packages/python/plotly/plotly/io/_utils.py @@ -2,6 +2,7 @@ import plotly import plotly.graph_objs as go +from plotly.offline import get_plotlyjs_version def validate_coerce_fig_to_dict(fig, validate): @@ -42,3 +43,8 @@ def validate_coerce_output_type(output_type): Must be one of: 'Figure', 'FigureWidget'""" ) return cls + + +def plotly_cdn_url(cdn_ver=get_plotlyjs_version()): + """Return a valid plotly CDN url.""" + return "https://cdn.plot.ly/plotly-{cdn_ver}.min.js".format(cdn_ver=cdn_ver,) diff --git a/packages/python/plotly/plotly/tests/test_core/test_offline/test_offline.py b/packages/python/plotly/plotly/tests/test_core/test_offline/test_offline.py index 249827a6744..0b5368113de 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_offline/test_offline.py +++ b/packages/python/plotly/plotly/tests/test_core/test_offline/test_offline.py @@ -12,6 +12,8 @@ import plotly import plotly.io as pio +from plotly.io._utils import plotly_cdn_url + import json packages_root = os.path.dirname( @@ -39,7 +41,7 @@ """ -cdn_script = '" +cdn_script = ''.format(cdn_url=plotly_cdn_url()) directory_script = '' diff --git a/packages/python/plotly/plotly/tests/test_io/test_html.py b/packages/python/plotly/plotly/tests/test_io/test_html.py new file mode 100644 index 00000000000..026677c0d58 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_io/test_html.py @@ -0,0 +1,52 @@ +import sys + +import pytest +import numpy as np + + +import plotly.graph_objs as go +import plotly.io as pio +from plotly.io._utils import plotly_cdn_url + + +if sys.version_info >= (3, 3): + import unittest.mock as mock + from unittest.mock import MagicMock +else: + import mock + from mock import MagicMock + +# fixtures +# -------- +@pytest.fixture +def fig1(request): + return go.Figure( + data=[ + { + "type": "scatter", + "y": np.array([2, 1, 3, 2, 4, 2]), + "marker": {"color": "green"}, + } + ], + layout={"title": {"text": "Figure title"}}, + ) + + +# HTML +# ---- +def assert_latest_cdn_connected(html): + assert plotly_cdn_url(cdn_ver="latest") in html + + +def assert_locked_version_cdn_connected(html): + assert plotly_cdn_url() in html + + +def test_latest_cdn_included(fig1): + html_str = pio.to_html(fig1, include_plotlyjs="cdn-latest") + assert_latest_cdn_connected(html_str) + + +def test_versioned_cdn_included(fig1): + html_str = pio.to_html(fig1, include_plotlyjs="cdn") + assert_locked_version_cdn_connected(html_str) diff --git a/packages/python/plotly/plotly/tests/test_io/test_renderers.py b/packages/python/plotly/plotly/tests/test_io/test_renderers.py index 27e1e6ea6de..ee11c412fb7 100644 --- a/packages/python/plotly/plotly/tests/test_io/test_renderers.py +++ b/packages/python/plotly/plotly/tests/test_io/test_renderers.py @@ -11,6 +11,7 @@ import plotly.graph_objs as go import plotly.io as pio from plotly.offline import get_plotlyjs +from plotly.io._utils import plotly_cdn_url if sys.version_info >= (3, 3): import unittest.mock as mock @@ -134,8 +135,8 @@ def assert_not_full_html(html): assert not html.startswith(" \n " - ' ' + ' ' '