From 4ffb4ed4efa4f6d670762710be7cfb89a3b51296 Mon Sep 17 00:00:00 2001 From: John Kern Date: Tue, 26 Mar 2013 07:46:25 -0700 Subject: [PATCH 01/10] #57: re-factor to make it easy to deploy and support other web technologies. - the displayable module knows how to display figures - the deployable module knows how to deploy figures - favor os.sep of hardcoding / in path - replaced string replace calls with jinja2 template module --- d3py/deployable.py | 53 +++++++++++++ d3py/displayable.py | 137 +++++++++++++++++++++++++++++++++ d3py/figure.py | 154 ++++++++----------------------------- d3py/networkx_figure.py | 3 +- d3py/pandas_figure.py | 9 +-- examples/d3py_area.py | 16 ++-- examples/d3py_bar.py | 19 ++--- examples/d3py_graph.py | 10 ++- examples/d3py_line.py | 12 +-- examples/d3py_multiline.py | 17 ++-- examples/d3py_scatter.py | 13 ++-- examples/util.py | 25 ++++++ 12 files changed, 296 insertions(+), 172 deletions(-) create mode 100644 d3py/deployable.py create mode 100644 d3py/displayable.py create mode 100644 examples/util.py diff --git a/d3py/deployable.py b/d3py/deployable.py new file mode 100644 index 0000000..71eef60 --- /dev/null +++ b/d3py/deployable.py @@ -0,0 +1,53 @@ +from abc import ABCMeta +from exceptions import NotImplementedError + +import logging +import os + +class deployable(): + """ + Given a d3py.figure, deployable stores it persistently. Concrete classes + may simply save the files to the file system or be used to deploy them + in the cloud. + """ + + __metaclass__ = ABCMeta + + def save(self, fig): + raise NotImplementedError + +class FileSystem(deployable): + """ + Concrete class which simply saves the files to the file system + """ + def __init__(self, fig, dest_dir, host="localhost", port=8000, logging=False): + self.fig = fig + self.host = host + self.port = port + self.dest_dir = dest_dir + + def save(self): + """ + Save the figure to dest_dir + """ + self.fig.update() + self.fig.save() + self.fig.renderHtml(self.host, self.port) + + if self.dest_dir is None: + raise Exception("Destination directory not defined") + if not os.path.exists(self.dest_dir): + raise IOError("Destination directory, {d} , does not exist.".format(d=self.dest_dir)) + os.chdir(self.dest_dir) + static_dir = self.dest_dir + os.sep + "static" + if not os.path.exists(static_dir): + os.mkdir(static_dir) + for k_filename in self.fig.filemap: + f = self.dest_dir + os.sep + k_filename + with open(f, 'w',0644) as fd_out: + fd_in = self.fig.filemap[k_filename]["fd"] + fd_in.seek(0) + # import pdb; pdb.set_trace() + for line in fd_in.readlines(): + fd_out.write(line) + fd_out.close() diff --git a/d3py/displayable.py b/d3py/displayable.py new file mode 100644 index 0000000..ba1f9ad --- /dev/null +++ b/d3py/displayable.py @@ -0,0 +1,137 @@ +from abc import ABCMeta +from exceptions import NotImplementedError + +import logging + +# in support of SimplyServer +import threading +import webbrowser +from HTTPHandler import CustomHTTPRequestHandler, ThreadedHTTPServer + +# requires IPython 0.11.0 or higher +import IPython.core.display + +class displayable(): + """ + Given a d3py.figure, displayables present the graph to the user. + The first displayable is based on python's SimpleHTTPServer class. + + These classes should know nothing of html, css or javascript which + live in the figure class. + """ + __metaclass__ = ABCMeta + + def show(self, fig): + raise NotImplementedError + +class SimplyServer(displayable): + """ + Use Python's SimpleHTTPServer class to present this resulting d3 output + to the user. + """ + def __init__(self, fig, host="localhost", port=8000, + interactive=False, logging=False): + + self.fig = fig + self.host = host + self.port = port + self._server_thread = None + self.httpd = None + # interactive is True by default as this is designed to be a command line tool + # we do not want to block interaction after plotting. + self.interactive = interactive + + def ion(self): + """ + Turns interactive mode on ala pylab + """ + self.interactive = True + + def ioff(self): + """ + Turns interactive mode off + """ + self.interactive = False + + def show(self, interactive=None): + self.fig.update() + self.fig.save() + self.fig.renderHtml(self.host, self.port) + if interactive is not None: + blocking = not interactive + else: + blocking = not self.interactive + + if blocking: + self._serve(blocking=True) + else: + # if not blocking, we serve the + self._serve(blocking=False) + # fire up a browser + webbrowser.open_new_tab("http://%s:%s/%s.html"%(self.host,self.port, self.name)) + + def _serve(self, blocking=True): + """ + start up a server to serve the files for this vis. + """ + msgparams = (self.host, self.port, self.fig.name) + url = "http://%s:%s/%s.html"%msgparams + if self._server_thread is None or self._server_thread.active_count() == 0: + Handler = CustomHTTPRequestHandler + Handler.filemap = self.fig.filemap + Handler.logging = self.fig.logging + try: + self.httpd = ThreadedHTTPServer(("", self.port), Handler) + except Exception, e: + print "Exception %s"%e + return False + if blocking: + logging.info('serving forever on port: %s'%msgparams[1]) + msg = "You can find your chart at " + url + print msg + print "Ctrl-C to stop serving the chart and quit!" + self._server_thread = None + self.httpd.serve_forever() + else: + logging.info('serving asynchronously on port %s'%msgparams[1]) + self._server_thread = threading.Thread( + target=self.httpd.serve_forever + ) + self._server_thread.daemon = True + self._server_thread.start() + msg = "You can find your chart at " + url + print msg + + + def __enter__(self): + self.interactive = False + return self + + def __exit__(self, ex_type, ex_value, ex_tb): + if ex_tb is not None: + print "Cleanup after exception: %s: %s"%(ex_type, ex_value) + self._cleanup() + + def __del__(self): + self._cleanup() + + def _cleanup(self): + try: + if self.httpd is not None: + print "Shutting down httpd" + self.httpd.shutdown() + self.httpd.server_close() + except Exception, e: + print "Error in clean-up: %s"%e + +class IPython(displayable): + """ + IPython integration + """ + def __init__(self): + pass + + def show(self, fig): + html = "