From da39de7797645d50d1d76117911979778eb6bb65 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 11 Dec 2016 21:12:26 +0000 Subject: [PATCH 1/4] Defined html and json mime types to fix py3 bytes issues --- holoviews/plotting/renderer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index a2eed14c2f..bdee63fec9 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -50,8 +50,8 @@ 'webm': 'video/webm', 'mp4': 'video/mp4', 'pdf': 'application/pdf', - 'html': None, - 'json': None + 'html': 'text/html', + 'json': 'text/json' } static_template = """ From feefa735fa27af6325552bda22779b5b21aa6003 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 18 Dec 2016 20:14:58 +0000 Subject: [PATCH 2/4] Fixed Renderer.export_widgets ensuring HTML is encoded --- holoviews/plotting/renderer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index bdee63fec9..a42583eaef 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -361,12 +361,13 @@ def export_widgets(self_or_cls, obj, filename, fmt=None, template=None, widget = obj html = self_or_cls.static_html(widget, fmt, template) + encoded = self_or_cls.encode((html, {'mime_type': 'text/html'})) if isinstance(filename, BytesIO): - filename.write(html) + filename.write(encoded) filename.seek(0) else: with open(filename, 'w') as f: - f.write(html) + f.write(encoded) @classmethod From 01cc5b8ad5befafa1fefaa99cd2f1031da14ca49 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 18 Dec 2016 20:15:47 +0000 Subject: [PATCH 3/4] Added tests ensuring Renderers correctly encode HTML data --- tests/testrenderclass.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tests/testrenderclass.py b/tests/testrenderclass.py index 4988be1b56..2dffda9777 100644 --- a/tests/testrenderclass.py +++ b/tests/testrenderclass.py @@ -7,8 +7,7 @@ from unittest import SkipTest import numpy as np -from holoviews.plotting.mpl.renderer import MPLRenderer -from holoviews import HoloMap, Image, ItemTable +from holoviews import HoloMap, Image, ItemTable, Store from holoviews.element.comparison import ComparisonTestCase from nose.plugins.attrib import attr @@ -18,9 +17,15 @@ try: # Standardize backend due to random inconsistencies from matplotlib import pyplot + from holoviews.plotting.mpl import MPLRenderer pyplot.switch_backend('agg') except: - pyplot = None + pass + +try: + from holoviews.plotting.bokeh import BokehRenderer +except: + pass def digest_data(data): hashfn = sha256() @@ -36,7 +41,7 @@ class MPLRendererTest(ComparisonTestCase): """ def setUp(self): - if pyplot is None: + if 'matplotlib' not in Store.renderers: raise SkipTest("Matplotlib required to test widgets") self.basename = 'no-file' @@ -104,9 +109,29 @@ def test_static_html_gif(self): data = self.renderer.static_html(self.map1, fmt='gif') self.assertEqual(digest_data(data), '9d43822e0f368f3c673b19aaf66d22252849947b7dc4a157306c610c42d319b5') + def test_export_widgets(self): bytesio = BytesIO() self.renderer.export_widgets(self.map1, bytesio, fmt='widgets') data = normalize(bytesio.read()) self.assertEqual(digest_data(data), '91bbc7b4efebd07b1ee595b902d9899b27f2c7e353dfc87c57c2dfd5d0404301') + + +class BokehRendererTest(ComparisonTestCase): + + def setUp(self): + if 'bokeh' not in Store.renderers: + raise SkipTest("Bokeh required to test widgets") + self.image1 = Image(np.array([[0,1],[2,3]]), label='Image1') + self.image2 = Image(np.array([[1,0],[4,-2]]), label='Image2') + self.map1 = HoloMap({1:self.image1, 2:self.image2}, label='TestMap') + self.renderer = BokehRenderer.instance() + + def test_save_html(self): + bytesio = BytesIO() + self.renderer.save(self.image1, bytesio) + + def test_export_widgets(self): + bytesio = BytesIO() + self.renderer.export_widgets(self.map1, bytesio, fmt='widgets') From f28098d05172683598acbbf5aaa569a72703cd2d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 18 Dec 2016 20:50:00 +0000 Subject: [PATCH 4/4] Fixes for utf encoding and added tests --- holoviews/plotting/renderer.py | 4 ++-- tests/testrenderclass.py | 20 +++++++++++++++++++- tests/testwidgets.py | 8 +++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index a42583eaef..005abf5518 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -478,11 +478,11 @@ def save(self_or_cls, obj, basename, fmt='auto', key={}, info={}, options=None, rendered = self_or_cls(plot, fmt) if rendered is None: return (data, info) = rendered + encoded = self_or_cls.encode(rendered) if isinstance(basename, BytesIO): - basename.write(data) + basename.write(encoded) basename.seek(0) else: - encoded = self_or_cls.encode(rendered) filename ='%s.%s' % (basename, info['file-ext']) with open(filename, 'wb') as f: f.write(encoded) diff --git a/tests/testrenderclass.py b/tests/testrenderclass.py index 2dffda9777..5c35421b2a 100644 --- a/tests/testrenderclass.py +++ b/tests/testrenderclass.py @@ -2,13 +2,17 @@ """ Test cases for rendering exporters """ +from __future__ import unicode_literals + from io import BytesIO from hashlib import sha256 from unittest import SkipTest import numpy as np from holoviews import HoloMap, Image, ItemTable, Store +from holoviews.core.util import unicode from holoviews.element.comparison import ComparisonTestCase +from holoviews.plotting import Renderer from nose.plugins.attrib import attr @@ -33,6 +37,20 @@ def digest_data(data): return hashfn.hexdigest() +class TestRenderer(ComparisonTestCase): + """ + Test the basic serializer and deserializer (i.e. using pickle), + including metadata access. + """ + + def test_renderer_encode_unicode_types(self): + mime_types = ['image/svg+xml', 'text/html', 'text/json'] + for mime in mime_types: + info = {'mime_type': mime} + encoded = Renderer.encode(('Testing «ταБЬℓσ»: 1<2 & 4+1>3', info)) + self.assertTrue(isinstance(encoded, bytes)) + + @attr(optional=1) class MPLRendererTest(ComparisonTestCase): """ @@ -107,7 +125,7 @@ def test_static_html_widgets(self): def test_static_html_gif(self): data = self.renderer.static_html(self.map1, fmt='gif') - self.assertEqual(digest_data(data), + self.assertEqual(digest_data(normalize(data)), '9d43822e0f368f3c673b19aaf66d22252849947b7dc4a157306c610c42d319b5') def test_export_widgets(self): diff --git a/tests/testwidgets.py b/tests/testwidgets.py index 6be00a2f29..da7b526ac7 100644 --- a/tests/testwidgets.py +++ b/tests/testwidgets.py @@ -17,6 +17,7 @@ raise SkipTest("Matplotlib required to test widgets") from holoviews import Image, HoloMap +from holoviews.core.util import unicode from holoviews.plotting.mpl import RasterPlot def digest_data(data): @@ -32,10 +33,15 @@ def digest_data(data): filters += [re.compile('new [A-Za-z]*SelectionWidget\([a-z0-9_, "]+')] def normalize(data): + if isinstance(data, bytes): + data = data.decode('utf8') for f in filters: data = re.sub(f, '[CLEARED]', data) # Hack around inconsistencies in jinja between Python 2 and 3 - return data.replace('0.0', '0').replace('1.0', '1') + data = data.replace('0.0', '0').replace('1.0', '1') + if isinstance(data, unicode): + data = data.encode('utf8') + return data @attr(optional=1) class TestWidgets(IPTestCase):