From 63c1460ad09f3b90165b7a54c7aea6484f0ebf4a Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Oct 2016 23:36:12 +0100 Subject: [PATCH 1/7] Improved error reporting on comms --- holoviews/plotting/comms.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/comms.py b/holoviews/plotting/comms.py index d9d73440cd..1ab82a3ce8 100644 --- a/holoviews/plotting/comms.py +++ b/holoviews/plotting/comms.py @@ -1,5 +1,8 @@ import json import uuid +import sys +import os +import traceback from ipykernel.comm import Comm as IPyComm from IPython import get_ipython @@ -70,10 +73,16 @@ def _handle_msg(self, msg): if it has been defined. """ try: + msg = self.decode(msg) if self._on_msg: - self._on_msg(self.decode(msg)) + self._on_msg(msg) except Exception as e: - msg = {'msg_type': "Error", 'traceback': str(e)} + frame =traceback.extract_tb(sys.exc_info()[2])[-2] + fname,lineno,fn,text = frame + error_kwargs = dict(type=type(e).__name__, fn=fn, fname=fname, + line=lineno, error=str(e)) + error = '{fname} {fn} L{line}\n\t{type}: {error}'.format(**error_kwargs) + msg = {'msg_type': "Error", 'traceback': error} else: msg = {'msg_type': "Ready"} self.comm.send(json.dumps(msg)) From 8ab54e98e13f9f1b882e4348ef2b2b40efe10e75 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 6 Oct 2016 02:49:19 +0100 Subject: [PATCH 2/7] Comms capture standard output and print it to the console --- holoviews/plotting/bokeh/callbacks.py | 3 +++ holoviews/plotting/comms.py | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index bda3bf1218..83bf78414a 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -79,6 +79,9 @@ class Callback(object): var comm = HoloViewsWidget.comms["{comms_target}"]; var comm_state = HoloViewsWidget.comm_state["{comms_target}"]; if (msg.msg_type == "Ready") {{ + if (msg.content) {{ + console.log("Python callback returned following output:", msg.content); + }} if (comm_state.event) {{ comm.send(comm_state.event); }} else {{ diff --git a/holoviews/plotting/comms.py b/holoviews/plotting/comms.py index 1ab82a3ce8..22b998ce27 100644 --- a/holoviews/plotting/comms.py +++ b/holoviews/plotting/comms.py @@ -3,11 +3,22 @@ import sys import os import traceback +from cStringIO import StringIO from ipykernel.comm import Comm as IPyComm from IPython import get_ipython +class Capturing(list): + def __enter__(self): + self._stdout = sys.stdout + sys.stdout = self._stringio = StringIO() + return self + def __exit__(self, *args): + self.extend(self._stringio.getvalue().splitlines()) + sys.stdout = self._stdout + + class Comm(object): """ Comm encompasses any uni- or bi-directional connection between @@ -73,18 +84,22 @@ def _handle_msg(self, msg): if it has been defined. """ try: + stdout = '' msg = self.decode(msg) if self._on_msg: - self._on_msg(msg) + with Capturing() as stdout: + self._on_msg(msg) except Exception as e: frame =traceback.extract_tb(sys.exc_info()[2])[-2] fname,lineno,fn,text = frame error_kwargs = dict(type=type(e).__name__, fn=fn, fname=fname, line=lineno, error=str(e)) + stdout = '\n\t'+'\n\t'.join(stdout) error = '{fname} {fn} L{line}\n\t{type}: {error}'.format(**error_kwargs) - msg = {'msg_type': "Error", 'traceback': error} + msg = {'msg_type': "Error", 'traceback': '\n'.join([stdout, error])} else: - msg = {'msg_type': "Ready"} + stdout = '\n\t'+'\n\t'.join(stdout) + msg = {'msg_type': "Ready", 'content': stdout} self.comm.send(json.dumps(msg)) From c20051a6f4c58add06eac472ab31bcae86d2c792 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 6 Oct 2016 03:09:50 +0100 Subject: [PATCH 3/7] Python3 fixes for Comm stdout handling --- holoviews/plotting/comms.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/holoviews/plotting/comms.py b/holoviews/plotting/comms.py index 22b998ce27..6bf34ca5a1 100644 --- a/holoviews/plotting/comms.py +++ b/holoviews/plotting/comms.py @@ -3,7 +3,7 @@ import sys import os import traceback -from cStringIO import StringIO +from io import BytesIO from ipykernel.comm import Comm as IPyComm from IPython import get_ipython @@ -12,7 +12,7 @@ class Capturing(list): def __enter__(self): self._stdout = sys.stdout - sys.stdout = self._stringio = StringIO() + sys.stdout = self._stringio = BytesIO() return self def __exit__(self, *args): self.extend(self._stringio.getvalue().splitlines()) @@ -84,7 +84,7 @@ def _handle_msg(self, msg): if it has been defined. """ try: - stdout = '' + stdout = [] msg = self.decode(msg) if self._on_msg: with Capturing() as stdout: @@ -98,8 +98,9 @@ def _handle_msg(self, msg): error = '{fname} {fn} L{line}\n\t{type}: {error}'.format(**error_kwargs) msg = {'msg_type': "Error", 'traceback': '\n'.join([stdout, error])} else: - stdout = '\n\t'+'\n\t'.join(stdout) - msg = {'msg_type': "Ready", 'content': stdout} + if stdout: + stdout = '\n\t'+'\n\t'.join(stdout) + msg = {'msg_type': "Ready", 'content': stdout if stdout else ''} self.comm.send(json.dumps(msg)) From e2c55ec9ae06198da1fc68554b4a93af6e028273 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 6 Oct 2016 12:45:22 +0100 Subject: [PATCH 4/7] Cleaned up standard output capturing on Comm --- holoviews/plotting/comms.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/holoviews/plotting/comms.py b/holoviews/plotting/comms.py index 6bf34ca5a1..f07e1ead12 100644 --- a/holoviews/plotting/comms.py +++ b/holoviews/plotting/comms.py @@ -9,11 +9,22 @@ from IPython import get_ipython -class Capturing(list): +class StandardOutput(list): + """ + Context manager to capture standard output for any code it + is wrapping and make it available as a list, e.g.: + + >>> with StandardOutput as stdout: + ... print("This gets captured") + ... print(stdout[0]) + 'This gets captured' + """ + def __enter__(self): self._stdout = sys.stdout sys.stdout = self._stringio = BytesIO() return self + def __exit__(self, *args): self.extend(self._stringio.getvalue().splitlines()) sys.stdout = self._stdout @@ -87,20 +98,21 @@ def _handle_msg(self, msg): stdout = [] msg = self.decode(msg) if self._on_msg: - with Capturing() as stdout: + with StandardOutput() as stdout: self._on_msg(msg) except Exception as e: frame =traceback.extract_tb(sys.exc_info()[2])[-2] fname,lineno,fn,text = frame error_kwargs = dict(type=type(e).__name__, fn=fn, fname=fname, line=lineno, error=str(e)) - stdout = '\n\t'+'\n\t'.join(stdout) error = '{fname} {fn} L{line}\n\t{type}: {error}'.format(**error_kwargs) - msg = {'msg_type': "Error", 'traceback': '\n'.join([stdout, error])} - else: if stdout: stdout = '\n\t'+'\n\t'.join(stdout) - msg = {'msg_type': "Ready", 'content': stdout if stdout else ''} + error = '\n'.join([stdout, error]) + msg = {'msg_type': "Error", 'traceback': error} + else: + stdout = '\n\t'+'\n\t'.join(stdout) if stdout else '' + msg = {'msg_type': "Ready", 'content': stdout} self.comm.send(json.dumps(msg)) From 96d6e0d1449d7487c228c62bda05030ef5b296e6 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 6 Oct 2016 12:58:54 +0100 Subject: [PATCH 5/7] Added comment to explain why stdout is captured in comms --- holoviews/plotting/comms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/holoviews/plotting/comms.py b/holoviews/plotting/comms.py index f07e1ead12..2118aca71d 100644 --- a/holoviews/plotting/comms.py +++ b/holoviews/plotting/comms.py @@ -98,6 +98,8 @@ def _handle_msg(self, msg): stdout = [] msg = self.decode(msg) if self._on_msg: + # Comm swallows standard output so we need to capture + # it and then send it to the frontend with StandardOutput() as stdout: self._on_msg(msg) except Exception as e: From c18b406305c02b553387a43d22e2c3e075181fe4 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 6 Oct 2016 13:43:42 +0100 Subject: [PATCH 6/7] Fixed StandardOutput doctest --- holoviews/plotting/comms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/holoviews/plotting/comms.py b/holoviews/plotting/comms.py index 2118aca71d..99dc984db4 100644 --- a/holoviews/plotting/comms.py +++ b/holoviews/plotting/comms.py @@ -14,10 +14,10 @@ class StandardOutput(list): Context manager to capture standard output for any code it is wrapping and make it available as a list, e.g.: - >>> with StandardOutput as stdout: - ... print("This gets captured") - ... print(stdout[0]) - 'This gets captured' + >>> with StandardOutput() as stdout: + ... print('This gets captured') + >>> print(stdout[0]) + This gets captured """ def __enter__(self): From 9cb1f144ce8bcfa2abcc9b1c22d50420fcc069b7 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 6 Oct 2016 14:14:20 +0100 Subject: [PATCH 7/7] Use StringIO to capture standard output --- holoviews/plotting/comms.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/comms.py b/holoviews/plotting/comms.py index 99dc984db4..21044125f3 100644 --- a/holoviews/plotting/comms.py +++ b/holoviews/plotting/comms.py @@ -3,7 +3,10 @@ import sys import os import traceback -from io import BytesIO +try: + from StringIO import StringIO +except: + from io import StringIO from ipykernel.comm import Comm as IPyComm from IPython import get_ipython @@ -22,7 +25,7 @@ class StandardOutput(list): def __enter__(self): self._stdout = sys.stdout - sys.stdout = self._stringio = BytesIO() + sys.stdout = self._stringio = StringIO() return self def __exit__(self, *args):