PKJXImJJbowtie/__init__.py""" Bowtie """ __version__ = '0.0.15' from bowtie._layout import Layout PK4rIbowtie/_compat.py# -*- coding: utf-8 -*- """ python 2/3 compatability """ import sys from os import makedirs IS_PY2 = sys.version_info < (3, 0) if IS_PY2: # pylint: disable=invalid-name makedirs_lib = makedirs # pylint: disable=function-redefined,missing-docstring def makedirs(name, mode=0o777, exist_ok=False): try: makedirs_lib(name, mode=mode) except OSError: if not exist_ok: raise PKIݡӆbowtie/_component.py# -*- coding: utf-8 -*- """ Bowtie Component classes, all visual and control components inherit these """ # need this for get commands on python2 from __future__ import unicode_literals # pylint: disable=redefined-builtin from builtins import bytes from functools import wraps import json from datetime import datetime, date, time import msgpack import flask from flask_socketio import emit from future.utils import with_metaclass import eventlet from eventlet.queue import LightQueue def json_conversion(obj): """ Encode additional objects to JSON. """ try: # numpy isn't an explicit dependency of bowtie # so we can't assume it's available import numpy as np if isinstance(obj, np.ndarray): return obj.tolist() except ImportError: pass if isinstance(obj, datetime) or isinstance(obj, time) or isinstance(obj, date): return obj.isoformat() raise TypeError('Not sure how to serialize {} of type {}'.format(obj, type(obj))) def jdumps(data): """ Encoding Python object to JSON string with additional encoders. """ return json.dumps(data, default=json_conversion) def encoders(obj): """ Convert objects to msgpack encodable ones. """ try: # numpy isn't an explicit dependency of bowtie # so we can't assume it's available import numpy as np if isinstance(obj, np.ndarray): return obj.tolist() except ImportError: pass if isinstance(obj, datetime) or isinstance(obj, time) or isinstance(obj, date): return obj.isoformat() return obj def pack(x): """ Encode ``x`` into msgpack with additional encoders. """ return bytes(msgpack.packb(x, default=encoders)) def unpack(x): """ Decode ``x`` from msgpack into a string. """ return msgpack.unpackb(bytes(x['data']), encoding='utf8') def make_event(event): """ Creates an event from a method signature. """ # pylint: disable=missing-docstring @property @wraps(event) def actualevent(self): name = event.__name__[3:] # pylint: disable=protected-access return '{uuid}#{event}'.format(uuid=self._uuid, event=name) return actualevent def is_event(attribute): """ Test if a method is an event. """ return attribute.startswith('on_') def make_command(command): """ Creates an command from a method signature. """ # pylint: disable=missing-docstring @wraps(command) def actualcommand(self, *args, **kwds): data = command(self, *args, **kwds) name = command.__name__[3:] # pylint: disable=protected-access signal = '{uuid}#{event}'.format(uuid=self._uuid, event=name) if flask.has_request_context(): emit(signal, {'data': pack(data)}) else: sio = flask.current_app.extensions['socketio'] sio.emit(signal, {'data': pack(data)}) eventlet.sleep() return actualcommand def is_command(attribute): """ Test if a method is an command. """ return attribute.startswith('do_') class _Maker(type): def __new__(mcs, name, parents, dct): for k in dct: if is_event(k): dct[k] = make_event(dct[k]) if is_command(k): dct[k] = make_command(dct[k]) return super(_Maker, mcs).__new__(mcs, name, parents, dct) class Component(with_metaclass(_Maker, object)): """ All visual and control classes subclass this so their events and commands get transformed by the metaclass. """ _NEXT_UUID = 0 @classmethod def _next_uuid(cls): cls._NEXT_UUID += 1 return cls._NEXT_UUID def __init__(self): # wanted to put "self" instead of "Component" # was surprised that didn't work self._uuid = Component._next_uuid() super(Component, self).__init__() def get(self, timeout=10): """ Sends a ``get`` command to the react component. Returns the the retreived data. """ event = LightQueue(1) if flask.has_request_context(): emit('{}#get'.format(self._uuid), callback=lambda x: event.put(unpack(x))) else: sio = flask.current_app.extensions['socketio'] sio.emit('{}#get'.format(self._uuid), callback=lambda x: event.put(unpack(x))) return event.get(timeout=timeout) PKI~X<4!4!bowtie/_layout.py# -*- coding: utf-8 -*- """ Defines the Layout class. """ import os from os import path import inspect import shutil import stat from collections import namedtuple, defaultdict from subprocess import Popen from flask import Markup from jinja2 import Environment, FileSystemLoader from markdown import markdown from bowtie._compat import makedirs from bowtie.control import _Controller from bowtie.visual import _Visual _Import = namedtuple('_Import', ['module', 'component']) _Control = namedtuple('_Control', ['instantiate', 'caption']) _Schedule = namedtuple('_Control', ['seconds', 'function']) class YarnError(Exception): """ Errors from ``Yarn``. """ pass class WebpackError(Exception): """ Errors from ``Webpack``. """ pass class Layout(object): """Create a Bowtie App. Parameters ---------- title : str, optional Title of the HTML. description : str, optional Describe the app in Markdown, inserted in control pane. basic_auth : bool, optional Enable basic authentication. username : str, optional Username for basic authentication. password : str, optional Password for basic authentication. background_color : str, optional Background color of the control pane. directory : str, optional Location where app is compiled. host : str, optional Host IP address. port : int, optional Host port number. debug : bool, optional Enable debugging in Flask. Disable in production! """ _packages = [ 'antd', 'babel-core', 'babel-loader', 'babel-plugin-transform-object-rest-spread', 'babel-polyfill', 'babel-preset-es2015', 'babel-preset-react', 'babel-preset-stage-0', 'classnames', 'core-js', 'css-loader', 'extract-text-webpack-plugin', 'less', 'less-loader', 'lodash.clonedeep', 'msgpack-lite', 'node-sass', 'normalize.css', 'postcss-modules-values', 'react', 'react-dom', 'sass-loader', 'socket.io-client', 'style-loader', 'webpack@1.13.2' ] def __init__(self, title='Bowtie App', description='Bowtie App\n---', basic_auth=False, username='username', password='password', background_color='White', directory='build', host='0.0.0.0', port=9991, debug=False): self.title = title self.description = Markup(markdown(description)) self.basic_auth = basic_auth self.username = username self.password = password self.background_color = background_color self.directory = directory self.host = host self.port = port self.debug = debug self.subscriptions = defaultdict(list) self.packages = set(['rc-progress']) self.templates = set(['progress.jsx']) self.imports = set() self.visuals = [[]] self.controllers = [] self.schedules = [] self.functions = [] def add_visual(self, visual, next_row=False): """Add a visual to the layout. Parameters ---------- visual : bowtie._Visual A Bowtie visual instance. next_row : bool, optional Add this visual to the next row. """ assert isinstance(visual, _Visual) # pylint: disable=protected-access self.packages.add(visual._PACKAGE) self.templates.add(visual._TEMPLATE) self.imports.add(_Import(component=visual._COMPONENT, module=visual._TEMPLATE[:visual._TEMPLATE.find('.')])) if next_row and self.visuals[-1]: self.visuals.append([]) self.visuals[-1].append(visual) def add_controller(self, control): """Add a controller to the layout. Parameters ---------- control : bowtie._Controller A Bowtie controller instance. """ assert isinstance(control, _Controller) # pylint: disable=protected-access self.packages.add(control._PACKAGE) self.templates.add(control._TEMPLATE) self.imports.add(_Import(component=control._COMPONENT, module=control._TEMPLATE[:control._TEMPLATE.find('.')])) self.controllers.append(_Control(instantiate=control._instantiate, caption=control.caption)) def subscribe(self, event, func): """Call a function in response to an event. Parameters ---------- event : str Name of the event. func : callable Function to be called. """ quoted = "'{}'".format(event) self.subscriptions[quoted].append(func.__name__) def schedule(self, seconds, func): """Call a function periodically. Parameters ---------- seconds : float Minimum interval of function calls. func : callable Function to be called. """ self.schedules.append(_Schedule(seconds, func.__name__)) def build(self): """Compiles the Bowtie application. """ file_dir = path.dirname(__file__) env = Environment(loader=FileSystemLoader( path.join(file_dir, 'templates') )) server = env.get_template('server.py') index = env.get_template('index.html') react = env.get_template('index.jsx') src, app, templates = create_directories(directory=self.directory) webpack_src = path.join(file_dir, 'src/webpack.config.js') shutil.copy(webpack_src, self.directory) server_path = path.join(src, server.name) # [1] grabs the parent stack and [1] grabs the filename source_filename = inspect.stack()[1][1] with open(server_path, 'w') as f: f.write( server.render( basic_auth=self.basic_auth, username=self.username, password=self.password, source_module=os.path.basename(source_filename)[:-3], subscriptions=self.subscriptions, schedules=self.schedules, host="'{}'".format(self.host), port=self.port, debug=self.debug ) ) perms = os.stat(server_path) os.chmod(server_path, perms.st_mode | stat.S_IEXEC) with open(path.join(templates, index.name), 'w') as f: f.write( index.render(title=self.title) ) for template in self.templates: template_src = path.join(file_dir, 'src', template) shutil.copy(template_src, app) for i, visualrow in enumerate(self.visuals): for j, visual in enumerate(visualrow): # pylint: disable=protected-access self.visuals[i][j] = visual._instantiate(), visual.progress._instantiate() with open(path.join(app, react.name), 'w') as f: f.write( react.render( description=self.description, background_color=self.background_color, components=self.imports, controls=self.controllers, visuals=self.visuals ) ) init = Popen('yarn init -y', shell=True, cwd=self.directory).wait() if init != 0: raise YarnError('Error running "yarn init -y"') self.packages.discard(None) packages = ' '.join(self._packages + list(self.packages)) install = Popen('yarn add {}'.format(packages), shell=True, cwd=self.directory).wait() if install != 0: raise YarnError('Error install node packages') dev = Popen('webpack -d', shell=True, cwd=self.directory).wait() if dev != 0: raise WebpackError('Error building with webpack') def create_directories(directory='build'): """ Create all the necessary subdirectories for the build. """ src = path.join(directory, 'src') templates = path.join(src, 'templates') app = path.join(src, 'app') makedirs(app, exist_ok=True) makedirs(templates, exist_ok=True) return src, app, templates PKJXIu bowtie/_progress.py# -*- coding: utf-8 -*- """ Progress component """ from bowtie._component import Component class Progress(Component): """This component is used by all visual components and is not meant to be used alone. By default, it is not visible. It is an opt-in feature and you can happily use Bowtie without using the progress indicators at all. It is useful for indicating progress to the user for long-running processes. It can be accessed through the ``.progress`` accessor. Examples -------- >>> plotly = Plotly() >>> def callback(x): >>> plotly.progress.do_visible(True) >>> plotly.progress.do_percent(0) >>> compute1() >>> plotly.progress.do_inc(50) >>> compute2() >>> plotly.progress.do_visible(False) """ _TEMPLATE = 'progress.jsx' _COMPONENT = 'CProgress' _PACKAGE = 'antd' _TAG = ('') def _instantiate(self): return self._TAG.format( uuid="'{}'".format(self._uuid) ) # pylint: disable=no-self-use def do_percent(self, percent): """Set the percentage of the progress. Parameters ---------- percent : number Sets the progress to this percentage. Returns ------- None """ return percent def do_inc(self, inc): """Increments the progress indicator. Parameters ---------- inc : number Value to increment the progress. Returns ------- None """ return inc def do_visible(self, visible): """Hides and shows the progress indicator. Parameters ---------- visible : bool If ``True`` shows the progress indicator otherwise it is hidden. Returns ------- None """ return visible def do_active(self): """Hides and shows the progress indicator. Returns ------- None """ pass def do_success(self): """Hides and shows the progress indicator. Returns ------- None """ pass def do_error(self): """Hides and shows the progress indicator. Returns ------- None """ pass PKJXI?--bowtie/control.py# -*- coding: utf-8 -*- """ Control components """ from collections import Iterable from bowtie._component import Component, jdumps # pylint: disable=too-few-public-methods class _Controller(Component): """ Used to test if a an object is a controller. All controllers must inherit this class. """ pass class Button(_Controller): """Create a button. Parameters ---------- label : str, optional Label on the button. caption : str, optional Heading text. """ _TEMPLATE = 'button.jsx' _COMPONENT = 'SimpleButton' _PACKAGE = None _TAG = ('') def __init__(self, label='', caption=''): super(Button, self).__init__() self._instantiate = self._TAG.format( label="'{}'".format(label), uuid="'{}'".format(self._uuid) ) self.caption = caption def on_click(self): """Emits an event when the button is clicked. | **Payload:** ``None``. Returns ------- str Name of click event. """ pass class DropDown(_Controller): """Create a drop down. Parameters ---------- labels : array-like, optional List of strings which will be visible to the user. values : array-like, optional List of values associated with the labels that are hidden from the user. multi : bool, optional If multiple selections are allowed. caption : str, optional Heading text. """ _TEMPLATE = 'dropdown.jsx' _COMPONENT = 'DropDown' _PACKAGE = 'react-select' _TAG = ('') def __init__(self, labels=None, values=None, multi=False, caption=''): super(DropDown, self).__init__() if labels is None and values is None: labels = [] values = [] options = [dict(value=value, label=str(label)) for value, label in zip(values, labels)] self._instantiate = self._TAG.format( options=jdumps(options), multi='true' if multi else 'false', uuid="'{}'".format(self._uuid) ) self.caption = caption def on_change(self): """Emits an event when the selection changes. | **Payload:** ``dict`` with keys "value" and "label". """ pass # pylint: disable=no-self-use def do_options(self, labels, values): """Replaces the drop down fields. Parameters ---------- labels : array-like List of strings which will be visible to the user. values : array-like List of values associated with the labels that are hidden from the user. Returns ------- None """ return [dict(label=l, value=v) for l, v in zip(labels, values)] def _jsbool(x): """Convert Python bool to JS bool. """ return repr(x).lower() class Switch(_Controller): """Specific Date Pickers inherit this class. """ _TEMPLATE = 'switch.jsx' _COMPONENT = 'Toggle' _PACKAGE = 'antd' _TAG = ('') def __init__(self, initial=False, caption=''): super(Switch, self).__init__() self._instantiate = self._TAG.format( uuid="'{}'".format(self._uuid), defaultChecked=_jsbool(initial) ) self.caption = caption def on_switch(self): """Emits an event when the switch is toggled. | **Payload:** ``bool`` status of the switch. Returns ------- str Name of event. """ pass class _DatePickers(_Controller): """Specific Date Pickers inherit this class. """ _TEMPLATE = 'date.jsx' _COMPONENT = 'PickDates' _PACKAGE = 'antd' _TAG = ('') def __init__(self, date_type=False, month_type=False, range_type=False, caption=''): super(_DatePickers, self).__init__() self._instantiate = self._TAG.format( uuid="'{}'".format(self._uuid), date_type=_jsbool(date_type), month_type=_jsbool(month_type), range_type=_jsbool(range_type) ) self.caption = caption class DatePicker(_DatePickers): """Date Picker Parameters ---------- caption : str, optional Heading text. """ def __init__(self, caption=''): super(DatePicker, self).__init__(date_type=True, caption=caption) def on_change(self): """Emits an event when a date is selected. | **Payload:** ``str`` of the form ``"yyyy-mm-dd"``. Returns ------- str Name of event. """ pass class MonthPicker(_DatePickers): """Date Picker Parameters ---------- caption : str, optional Heading text. """ def __init__(self, caption=''): super(MonthPicker, self).__init__(month_type=True, caption=caption) def on_change(self): """Emits an event when a month is selected. | **Payload:** ``str`` of the form ``"yyyy-mm"``. Returns ------- str Name of event. """ pass class RangePicker(_DatePickers): """Date Picker Parameters ---------- caption : str, optional Heading text. """ def __init__(self, caption=''): super(RangePicker, self).__init__(range_type=True, caption=caption) def on_change(self): """Emits an event when a range is selected. | **Payload:** ``list`` of two dates ``["yyyy-mm-dd", "yyyy-mm-dd"]``. Returns ------- str Name of event. """ pass class Slider(_Controller): """Create a slider. Parameters ---------- start : number or list with two values, optional Determines the starting value. If a list of two values are given it will be a range slider. ranged : bool, optional If this is a range slider. minimum : number, optional Minimum value of the slider. maximum : number, optional Maximum value of the slider. step : number, optional Step size. caption : str, optional Heading text. References ---------- https://ant.design/components/slider/ """ _TEMPLATE = 'slider.jsx' _COMPONENT = 'AntSlider' _PACKAGE = 'antd' _TAG = ('') def __init__(self, start=None, ranged=False, minimum=0, maximum=100, step=1, caption=''): super(Slider, self).__init__() if not start: start = [0, 0] if ranged else 0 elif isinstance(start, Iterable): start = list(start) ranged = True self._instantiate = self._TAG.format( uuid="'{}'".format(self._uuid), range=_jsbool(ranged), minimum=minimum, maximum=maximum, start=start, step=step, marks={minimum: str(minimum), maximum: str(maximum)} ) self.caption = caption def on_change(self): """Emits an event when the slider's value changes. | **Payload:** ``number`` or ``list`` of values. Returns ------- str Name of event. """ pass def on_after_change(self): """Emits an event when the slider control is released. | **Payload:** ``number`` or ``list`` of values. Returns ------- str Name of event. """ pass class Nouislider(_Controller): """Create a slider. Parameters ---------- start : number or list with two values, optional Determines the starting value. If a list of two values are given it will be a range slider. minimum : number, optional Minimum value of the slider. maximum : number, optional Maximum value of the slider. tooltips : bool, optional Show a popup text box. caption : str, optional Heading text. References ---------- https://refreshless.com/nouislider/events-callbacks/ """ _TEMPLATE = 'nouislider.jsx' _COMPONENT = 'Nouislider' _PACKAGE = 'nouislider' _TAG = ('') def __init__(self, start=0, minimum=0, maximum=100, tooltips=True, caption=''): super(Nouislider, self).__init__() if not isinstance(start, Iterable): start = [start] else: start = list(start) self._instantiate = self._TAG.format( uuid="'{}'".format(self._uuid), min=minimum, max=maximum, start=start, tooltips='true' if tooltips else 'false' ) self.caption = caption def on_update(self): """Emits an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ pass def on_slide(self): """Emits an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ pass def on_set(self): """Emits an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ pass def on_change(self): """Emits an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ pass def on_start(self): """Emits an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ pass def on_end(self): """Emits an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ pass PKJXIL "  bowtie/visual.py# -*- coding: utf-8 -*- """ Visual components """ from bowtie._component import Component, jdumps from bowtie._progress import Progress # pylint: disable=too-few-public-methods class _Visual(Component): """ Used to test if a an object is a controller. All controllers must inherit this class. """ def __init__(self): self.progress = Progress() super(_Visual, self).__init__() class SmartGrid(_Visual): """Table Component with filtering and sorting Parameters ---------- columns : list, optional List of column names to display. results_per_page : int, optional Number of rows on each pagination of the table. """ _TEMPLATE = 'griddle.jsx' _COMPONENT = 'SmartGrid' _PACKAGE = 'griddle-react' _TAG = ('') def __init__(self, columns=None, results_per_page=10): if columns is None: columns = [] self.columns = columns self.results_per_page = results_per_page super(SmartGrid, self).__init__() def _instantiate(self): return self._TAG.format( uuid="'{}'".format(self._uuid), columns=jdumps(self.columns), results_per_page=self.results_per_page ) # pylint: disable=no-self-use def do_update(self, data): """Updates the data of the table Parameters ---------- data : list of dicts Each entry in the list must be a dict with the same keys which are the columns of the table. Returns ------- None """ return data class SVG(_Visual): """SVG image, mainly for matplotlib plots. Parameters ---------- preserve_aspect_ratio : bool, optional If ``True`` it preserves the aspect ratio otherwise it will stretch to fill up the space available. """ _TEMPLATE = 'svg.jsx' _COMPONENT = 'SVG' _PACKAGE = None _TAG = ('') def __init__(self, preserve_aspect_ratio=False): self.preserve_aspect_ratio = preserve_aspect_ratio super(SVG, self).__init__() def _instantiate(self): return self._TAG.format( uuid="'{}'".format(self._uuid), preserve_aspect_ratio='true' if self.preserve_aspect_ratio else 'false' ) # pylint: disable=no-self-use def do_image(self, image): """Replaces the image. Parameters ---------- image : str Generated by ``savefig`` from matplotlib with ``format=svg``. Returns ------- None Examples -------- >>> from io import StringIO >>> import matplotlib >>> matplotlib.use('Agg') >>> import matplotlib.pyplot as plt >>> image = SVG() >>> >>> def callback(x): >>> sio = StringIO() >>> plt.plot(range(5)) >>> plt.savefig(sio, format='svg') >>> sio.seek(0) >>> s = sio.read() >>> idx = s.find('>> s = s[idx:] >>> image.do_image(s) """ return image # These visuals are partially implemented # # class FixedTable(_Visual): # _TEMPLATE = 'fixedtable.jsx' # _COMPONENT = 'FixedTable' # _PACKAGE = 'fixed-data-table' # _TAG = ('') # # def __init__(self): # super(FixedTable, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # rows=rows, # columns=columns # ) # # # class DataTable(_Visual): # _TEMPLATE = 'datatables.jsx' # _COMPONENT = 'JTable' # _PACKAGE = 'react-jquery-datatables' # _TAG = ('') # # def __init__(self): # super(DataTable, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # ) # # # class Grid(_Visual): # _TEMPLATE = 'dazzlegrid.jsx' # _COMPONENT = 'Grid' # _PACKAGE = 'react-data-grid' # _TAG = ('') # # def __init__(self): # super(Grid, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # ) # # # class Table(_Visual): # _TEMPLATE = 'datagrid.jsx' # _COMPONENT = 'Table' # _PACKAGE = 'react-datagrid' # _TAG = ('') # # def __init__(self): # super(Table, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # ) class Plotly(_Visual): """ Plotly component. """ _TEMPLATE = 'plotly.jsx' _COMPONENT = 'PlotlyPlot' _PACKAGE = 'plotly.js' _TAG = ('') def __init__(self, init=None): if init is None: init = dict(data=[], layout={'autosize': False}) self.init = init super(Plotly, self).__init__() def _instantiate(self): return self._TAG.format( uuid="'{}'".format(self._uuid), init=jdumps(self.init), ) ## Events def on_click(self): """Plotly click event. | **Payload:** TODO. Returns ------- str Name of event. """ pass def on_beforehover(self): """Emits an event before hovering over a point. | **Payload:** TODO. Returns ------- str Name of event. """ pass def on_hover(self): """Emits an event after hovering over a point. | **Payload:** TODO. Returns ------- str Name of event. """ pass def on_unhover(self): """Emits an event when hover is removed. | **Payload:** TODO. Returns ------- str Name of event. """ pass def on_select(self): """Emits an event when points are selected with a tool. | **Payload:** TODO. Returns ------- str Name of event. """ pass ## Commands # pylint: disable=no-self-use def do_all(self, plot): """Replaces the entire plot. Parameters ---------- plot : dict Dict that can be plotted with Plotly. Returns ------- None """ return plot def do_data(self, data): """Replaces the data portion of the plot. Parameters ---------- data : list of traces List of data to replace the old data. Returns ------- None """ return data def do_layout(self, layout): """Updates the layout. Parameters ---------- layout : dict Contains layout information. Returns ------- None """ return layout def do_config(self, config): """Updates the configuration of the plot. Parameters ---------- config : dict Plotly config information. Returns ------- None """ return config PKJXI*Ơbowtie/src/button.jsximport React from 'react'; import { Button } from 'antd'; import 'antd/dist/antd.css'; export default class SimpleButton extends React.Component { constructor(props) { super(props); } handleClick = event => { this.props.socket.emit(this.props.uuid + '#click'); } render() { return ( ); } } SimpleButton.propTypes = { label: React.PropTypes.string.isRequired, uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired }; PKЮ}InmGGbowtie/src/datagrid.jsximport 'react-datagrid/index.css'; import React from 'react'; import DataGrid from 'react-datagrid'; export default class Table extends React.Component { render() { var data = [ { id: '1', firstName: 'John', lastName: 'Bobson'}, { id: '2', firstName: 'Bob', lastName: 'Mclaren'}, { id: '3', firstName: 'Bob', lastName: 'Mclaren'}, { id: '4', firstName: 'Bob', lastName: 'Mclaren'}, { id: '5', firstName: 'Bob', lastName: 'Mclaren'}, { id: '6', firstName: 'Bob', lastName: 'Mclaren'}, { id: '7', firstName: 'Bob', lastName: 'Mclaren'}, { id: '8', firstName: 'Bob', lastName: 'Mclaren'}, { id: '9', firstName: 'Bob', lastName: 'Mclaren'}, { id: '10', firstName: 'Bob', lastName: 'Mclaren'}, { id: '11', firstName: 'Bob', lastName: 'Mclaren'}, { id: '12', firstName: 'Bob', lastName: 'Mclaren'}, { id: '13', firstName: 'Bob', lastName: 'Mclaren'}, { id: '14', firstName: 'Bob', lastName: 'Mclaren'}, { id: '15', firstName: 'Bob', lastName: 'Mclaren'}, { id: '16', firstName: 'Bob', lastName: 'Mclaren'}, { id: '17', firstName: 'Bob', lastName: 'Mclaren'}, { id: '18', firstName: 'Bob', lastName: 'Mclaren'}, { id: '19', firstName: 'Bob', lastName: 'Mclaren'}, { id: '20', firstName: 'Bob', lastName: 'Mclaren'}, { id: '21', firstName: 'Bob', lastName: 'Mclaren'}, { id: '22', firstName: 'Bob', lastName: 'Mclaren'}, { id: '23', firstName: 'Bob', lastName: 'Mclaren'}, { id: '24', firstName: 'Bob', lastName: 'Mclaren'} ]; var columns = [ { name: 'firstName'}, { name: 'lastName'} ]; return ( ); } } Table.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired }; PKI bowtie/src/date.jsximport React from 'react'; import { DatePicker, LocaleProvider } from 'antd'; import enUS from 'antd/lib/locale-provider/en_US'; import 'antd/dist/antd.css'; // import 'antd/lib/date-picker/style/index.css'; const { MonthPicker, RangePicker } = DatePicker; var msgpack = require('msgpack-lite'); export default class PickDates extends React.Component { constructor(props) { super(props); this.state = {value: null}; } handleChange = (moment, ds) => { this.setState({value: moment}); this.props.socket.emit(this.props.uuid + '#change', msgpack.encode(ds)); } componentDidMount() { var socket = this.props.socket; var uuid = this.props.uuid; socket.on(uuid + '#get', this.getValue); } getValue = (data, fn) => { fn(msgpack.encode(this.state.value)); } render () { if (this.props.date) { return ( ); } else if (this.props.month) { return ( ); } else { return ( ); } } } PickDates.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, date: React.PropTypes.bool.isRequired, month: React.PropTypes.bool.isRequired, range: React.PropTypes.bool.isRequired, }; PKЮ}IXXXbowtie/src/dropdown.jsximport React from 'react'; import Select from 'react-select'; // Be sure to include styles at some point, probably during your bootstrapping import 'react-select/dist/react-select.css'; var msgpack = require('msgpack-lite'); export default class DropDown extends React.Component { constructor(props) { super(props); this.state = {value: null, options: this.props.initOptions}; this.handleChange = this.handleChange.bind(this); // this.getValue = this.getValue.bind(this); this.newOptions = this.newOptions.bind(this); } handleChange(value) { this.setState({value}); this.props.socket.emit(this.props.uuid + '#change', msgpack.encode(value)); } newOptions(data) { var arr = new Uint8Array(data['data']); this.setState({value: null, options: msgpack.decode(arr)}); } componentDidMount() { var socket = this.props.socket; var uuid = this.props.uuid; socket.on(uuid + '#get', this.getValue); socket.on(uuid + '#options', this.newOptions); } getValue = (data, fn) => { fn(msgpack.encode(this.state.value)); } render () { return (
{headers.map(function(name, i){ return ( {name}} cell={} width={100} /> ); })}
); } } // Col 1} // cell={Column 1 static content} // width={100} // /> // Col 2} // cell={Column 2 static content} // width={100} // /> // Col 3} // cell={({rowIndex, ...props}) => ( // // Data for column 3: {this.state.data[rowIndex][2]} // // )} // width={100} // /> FixedTable.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, rows: React.PropTypes.number, columns: React.PropTypes.number }; PKЮ}Izbowtie/src/griddle.jsximport React from 'react'; import Griddle from 'griddle-react'; var msgpack = require('msgpack-lite'); export default class SmartGrid extends React.Component { constructor(props) { super(props); this.state = {data: []}; this.getData = this.getData.bind(this); } getData(data, fn) { fn(msgpack.encode(this.state.data)); } componentDidMount() { var socket = this.props.socket; socket.on(this.props.uuid + '#update', (data) => { var arr = new Uint8Array(data['data']); this.setState({data: msgpack.decode(arr)}); }); socket.on(this.props.uuid + '#get', this.getData); } render() { return ( ); } } SmartGrid.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, columns: React.PropTypes.array.isRequired, resultsPerPage: React.PropTypes.number.isRequired }; PK4rIeebowtie/src/nouislider.jsximport React from 'react'; import 'nouislider/src/nouislider.css'; import nouislider from 'nouislider'; var msgpack = require('msgpack-lite'); export default class Nouislider extends React.Component { constructor(props) { super(props); this.createSlider = this.createSlider.bind(this); this.getValue = this.getValue.bind(this); } createSlider() { var slider = this.slider = nouislider.create(this.sliderContainer, {...this.props} ); var uuid = this.props.uuid; var socket = this.props.socket; slider.on('update', function (data) { socket.emit(uuid + '#update', msgpack.encode(data)); }); slider.on('change', function (data) { socket.emit(uuid + '#change', msgpack.encode(data)); }); slider.on('slide', function (data) { socket.emit(uuid + '#slide', msgpack.encode(data)); }); slider.on('set', function (data) { socket.emit(uuid + '#set', msgpack.encode(data)); }); slider.on('start', function (data) { socket.emit(uuid + '#start', msgpack.encode(data)); }); slider.on('end', function (data) { socket.emit(uuid + '#end', msgpack.encode(data)); }); } componentDidMount() { if (this.props.disabled) this.sliderContainer.setAttribute('disabled', true); else this.sliderContainer.removeAttribute('disabled'); this.createSlider(); var uuid = this.props.uuid; var socket = this.props.socket; socket.on(uuid + '#get', this.getValue); } getValue(data, fn) { fn(msgpack.encode(this.slider.get())); } componentDidUpdate() { if (this.props.disabled) this.sliderContainer.setAttribute('disabled', true); else this.sliderContainer.removeAttribute('disabled'); this.slider.destroy(); this.createSlider(); } componentWillUnmount() { this.slider.destroy(); } render() { return (
this.sliderContainer = slider} /> ); } } Nouislider.propTypes = { // http://refreshless.com/nouislider/slider-options/#section-animate uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, animate: React.PropTypes.bool, // http://refreshless.com/nouislider/behaviour-option/ behaviour: React.PropTypes.string, // http://refreshless.com/nouislider/slider-options/#section-Connect connect: React.PropTypes.oneOfType([ React.PropTypes.oneOf(['lower', 'upper']), React.PropTypes.bool ]), // http://refreshless.com/nouislider/slider-options/#section-cssPrefix cssPrefix: React.PropTypes.string, // http://refreshless.com/nouislider/slider-options/#section-orientation direction: React.PropTypes.oneOf(['ltr', 'rtl']), // http://refreshless.com/nouislider/more/#section-disable disabled: React.PropTypes.bool, // http://refreshless.com/nouislider/slider-options/#section-limit limit: React.PropTypes.number, // http://refreshless.com/nouislider/slider-options/#section-margin margin: React.PropTypes.number, // http://refreshless.com/nouislider/events-callbacks/#section-change // onChange: React.PropTypes.func, // http://refreshless.com/nouislider/events-callbacks/#section-update // onSlide: React.PropTypes.func, // http://refreshless.com/nouislider/events-callbacks/#section-slide // onUpdate: React.PropTypes.func, // http://refreshless.com/nouislider/slider-options/#section-orientation orientation: React.PropTypes.oneOf(['horizontal', 'vertical']), // http://refreshless.com/nouislider/pips/ pips: React.PropTypes.object, // http://refreshless.com/nouislider/slider-values/#section-range range: React.PropTypes.object.isRequired, // http://refreshless.com/nouislider/slider-options/#section-start start: React.PropTypes.arrayOf(React.PropTypes.number).isRequired, // http://refreshless.com/nouislider/slider-options/#section-step step: React.PropTypes.number, // http://refreshless.com/nouislider/slider-options/#section-tooltips tooltips: React.PropTypes.oneOfType([ React.PropTypes.bool, React.PropTypes.arrayOf( React.PropTypes.shape({ to: React.PropTypes.func }) ) ]) }; PK`If  bowtie/src/plotly.jsximport React from 'react'; import Plotly from 'plotly.js'; import cloneDeep from 'lodash.clonedeep'; var msgpack = require('msgpack-lite'); export default class PlotlyPlot extends React.Component { constructor(props) { super(props); this.selection = null; this.state = this.props.initState; this.resize = this.resize.bind(this); this.props.socket.on(this.props.uuid + '#all', (data) => { var arr = new Uint8Array(data['data']); this.setState(msgpack.decode(arr)); }); this.props.socket.on(this.props.uuid + '#get', this.getSelection); } setSelection = data => { this.selection = data; this.props.socket.emit(this.props.uuid + '#select', msgpack.encode(data)); } getSelection = (data, fn) => { fn(msgpack.encode(this.selection)); } resize() { Plotly.Plots.resize(this.container); } addListeners() { var uuid = this.props.uuid; var socket = this.props.socket; this.container.on('plotly_click', function (data) { var p0 = data.points[0]; var datum = { n: p0.pointNumber, x: p0.x, y: p0.y, }; socket.emit(uuid + '#click', msgpack.encode(datum)); }); // if (this.props.onBeforeHover) // this.container.on('plotly_beforehover', function (data) { // socket.emit(uuid + '#beforehover', data); // }); // // if (this.props.onHover) // this.container.on('plotly_hover', function (data) { // socket.emit(uuid + '#hover', data); // }); // // if (this.props.onUnHover) // this.container.on('plotly_unhover', function (data) { // socket.emit(uuid + '#unhover', data); // }); // if (this.props.onSelected) this.container.on('plotly_selected', this.setSelection); } componentDidMount() { // let {data, layout, config} = this.props; var parent = window.getComputedStyle(this.container.parentElement); // this.state.layout['autosize'] = false; // this.state.layout['height'] = parseFloat(parent.height); // this.state.layout['width'] = parseFloat(parent.width); var layout = this.state.layout; layout['autosize'] = false; layout['height'] = parseFloat(parent.height); layout['width'] = parseFloat(parent.width); Plotly.newPlot(this.container, this.state.data, cloneDeep(layout), {autosizable: false, displaylogo: false, fillFrame: true}); //, config); this.addListeners(); // this.setState({layout: layout}); } componentDidUpdate() { //TODO use minimal update for given changes // this.container.data = this.state.data; // this.container.layout = this.state.layout; //this.container.config = {autosizable: true, fillFrame: true, displaylogo: false}; var parent = window.getComputedStyle(this.container.parentElement); var layout = this.state.layout; layout['autosize'] = false; layout['height'] = parseFloat(parent.height); layout['width'] = parseFloat(parent.width); // this.setState({layout: layout}); Plotly.newPlot(this.container, this.state.data, cloneDeep(this.state.layout), {autosizable: false, displaylogo: false, fillFrame: true}); //, config); this.addListeners(); // window.addEventListener('resize', this.resize); } componentWillUnmount() { this.props.socket.off(this.props.uuid + '#all'); this.props.socket.off(this.props.uuid + '#get'); Plotly.purge(this.container); } render() { return (
this.container=node} /> ); } } PlotlyPlot.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, initState: React.PropTypes.object, rows: React.PropTypes.number, columns: React.PropTypes.number }; PKJXIg7RRbowtie/src/progress.jsximport React from 'react'; import { Progress } from 'antd'; import 'antd/dist/antd.css'; var msgpack = require('msgpack-lite'); export default class CProgress extends React.Component { constructor(props) { super(props); this.state = {percent: 0, visible: false, status: 'active'}; } percent = data => { var arr = new Uint8Array(data['data']); this.setState({percent: msgpack.decode(arr)}); } increment = data => { var arr = new Uint8Array(data['data']); this.setState({percent: this.state.percent + msgpack.decode(arr)}); } visible = data => { var arr = new Uint8Array(data['data']); this.setState({visible: msgpack.decode(arr)}); } active = data => { this.setState({status: 'active'}); } success = data => { this.setState({status: 'success'}); } error = data => { this.setState({status: 'exception'}); } componentDidMount() { var uuid = this.props.uuid; var socket = this.props.socket; socket.on(uuid + '#percent', this.percent); socket.on(uuid + '#visible', this.visible); socket.on(uuid + '#inc', this.increment); socket.on(uuid + '#active', this.active); socket.on(uuid + '#success', this.success); socket.on(uuid + '#error', this.error); } render() { if (this.state.visible) { return (
); } else { return ( this.props.children ); } } } CProgress.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, children: React.PropTypes.any }; PKJXIeٚbowtie/src/slider.jsximport React from 'react'; import { Slider } from 'antd'; import 'antd/dist/antd.css'; var msgpack = require('msgpack-lite'); export default class AntSlider extends React.Component { constructor(props) { super(props); this.state = {value: this.props.start}; } componentDidMount() { var uuid = this.props.uuid; var socket = this.props.socket; socket.on(uuid + '#get', this.getValue); } getValue = (data, fn) => { fn(msgpack.encode(this.state.value)); } onChange = value => { this.setState({value: value}); this.props.socket.emit(this.props.uuid + '#change', msgpack.encode(value)); } onAfterChange = value => { this.props.socket.emit(this.props.uuid + '#after_change', msgpack.encode(value)); } render() { return ( ); } } AntSlider.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, min: React.PropTypes.number.isRequired, max: React.PropTypes.number.isRequired, range: React.PropTypes.bool.isRequired, start: React.PropTypes.oneOfType([ React.PropTypes.number, React.PropTypes.array ]).isRequired, step: React.PropTypes.number.isRequired, marks: React.PropTypes.object.isRequired, }; PKiI6bowtie/src/svg.jsximport React from 'react'; var msgpack = require('msgpack-lite'); export default class SVG extends React.Component { constructor(props) { super(props); this.state = {value: null}; } handleChange(value) { this.setState({value}); this.props.socket.emit(this.props.uuid + '#change', msgpack.encode(value)); } image = data => { var arr = new Uint8Array(data['data']); var str = msgpack.decode(arr); if (!this.props.preserveAspectRatio) { var idx = str.indexOf(' '); str = str.slice(0, idx) + ' preserveAspectRatio="none"' + str.slice(idx); } this.setState({value: str}); } componentDidMount() { var socket = this.props.socket; var uuid = this.props.uuid; socket.on(uuid + '#image', this.image); } render () { return ( ); } } SVG.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, preserveAspectRatio: React.PropTypes.object.isRequired, }; PK Id bowtie/src/switch.jsximport React from 'react'; import { Switch, LocaleProvider } from 'antd'; import enUS from 'antd/lib/locale-provider/en_US'; import 'antd/dist/antd.css'; var msgpack = require('msgpack-lite'); export default class Toggle extends React.Component { constructor(props) { super(props); this.state = {checked: this.props.defaultChecked}; } handleChange = (checked) => { this.setState({checked: checked}); this.props.socket.emit(this.props.uuid + '#switch', msgpack.encode(checked)); } componentDidMount() { var socket = this.props.socket; var uuid = this.props.uuid; socket.on(uuid + '#get', this.getValue); } getValue = (data, fn) => { fn(msgpack.encode(this.state.checked)); } render () { return ( ); } } Toggle.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, defaultChecked: React.PropTypes.bool.isRequired, }; PKIZ䗀{{bowtie/src/webpack.config.jsconst webpack = require('webpack'); const prod = process.argv.indexOf('-p') !== -1; var path = require('path'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var extractCSS = new ExtractTextPlugin('stylesheets/[name].css'); var extractLESS = new ExtractTextPlugin('stylesheets/[name].less'); var BUILD_DIR = path.resolve(__dirname, 'src/static'); var APP_DIR = path.resolve(__dirname, 'src/app'); var config = { entry: APP_DIR + '/index.jsx', output: { path: BUILD_DIR, filename: 'bundle.js' }, module: { loaders: [ { test: /\.jsx?/, include: APP_DIR, loader: 'babel', exclude: /nodemodules/, query: { presets: ['es2015', 'react', 'stage-0'], plugins: ['transform-object-rest-spread'] } }, { test: /\.scss$/, loaders: ['style', 'css', 'sass'], exclude: /flexboxgrid/ }, { test: /\.css$/, loader: extractCSS.extract(['css', 'sass']), exclude: /flexboxgrid/ }, { test: /\.less$/, loader: extractLESS.extract(['less', 'sass']), }, { test: /\.(css|scss)$/, loader: 'style!css?modules', include: /flexboxgrid/ } ], noParse: [ /plotly\.js$/ ] }, plugins: [ extractCSS, extractLESS, ], resolve: { extensions: ['', '.jsx', '.js', '.json'], modulesDirectories: [ 'node_modules', path.resolve(__dirname, './node_modules') ] } }; // for production // https://github.com/webpack/webpack/issues/2537#issuecomment-250950677 if (prod) { config.plugins.push( // https://facebook.github.io/react/docs/optimizing-performance.html new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }) ); } module.exports = config; PK)gIEEbowtie/templates/index.html {{ title }}
PKJXIT*bowtie/templates/index.jsximport 'normalize.css'; import React from 'react'; import {render} from 'react-dom'; import io from 'socket.io-client'; import 'antd/dist/antd.css' import CProgress from './progress'; {% for component in components %} import {{ component.component }} from './{{ component.module }}'; {% endfor %} var socket = io(); class Dashboard extends React.Component { render() { return (
{{ description }} {% for control in controls %}
{{ control.caption }}
{{ control.instantiate }}
{% endfor %}
{% for visualrow in visuals %}
{% for visual, progress in visualrow %}
{{ progress }} {{ visual }}
{% endfor %}
{% endfor %}
); } } render(, document.getElementById('app')); PK3 xI{ bowtie/templates/server.py#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import traceback from functools import wraps from builtins import bytes import click import msgpack from flask import Flask, render_template, copy_current_request_context from flask import request, Response from flask_socketio import SocketIO, emit import eventlet def check_auth(username, password): """This function is called to check if a username / password combination is valid. """ return username == '{{ username }}' and password == '{{ password }}' def authenticate(): """Sends a 401 response that enables basic auth""" return Response( 'Could not verify your access level for that URL.\n' 'You have to login with proper credentials', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}) def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) return decorated # import the user created module sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import {{source_module}} app = Flask(__name__) app.debug = {{ debug|default(False) }} socketio = SocketIO(app, binary=True) def context(func): def foo(): with app.app_context(): func() return foo class Scheduler(object): def __init__(self, seconds, func): self.seconds = seconds self.func = func self.thread = None def start(self): self.thread = eventlet.spawn(self.run) def run(self): ret = eventlet.spawn(context(self.func)) eventlet.sleep(self.seconds) try: ret.wait() except: traceback.print_exc() self.thread = eventlet.spawn(self.run) def stop(self): if self.thread: self.thread.cancel() @app.route('/') {% if basic_auth %} @requires_auth {% endif %} def index(): return render_template('index.html') {% if login %} @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': success = {{ source_module }}.{{ login }}() if success: return redirect(url_for('index')) else: return redirect(url_for('login')) return {{ loginpage }} {% endif %} {% for event, functions in subscriptions.items() %} @socketio.on({{ event }}) def _(*args): {% for func in functions %} foo = copy_current_request_context({{ source_module }}.{{ func }}) eventlet.spawn(foo, *(msgpack.unpackb(bytes(a['data']), encoding='utf8') for a in args)) {% endfor %} {% endfor %} @click.command() @click.option('--host', '-h', default={{host}}, help='Host IP') @click.option('--port', '-p', default={{port}}, help='port number') def main(host, port): scheds = [] {% for schedule in schedules %} sched = Scheduler({{ schedule.seconds }}, {{ source_module }}.{{ schedule.function }}) scheds.append(sched) {% endfor %} for sched in scheds: sched.start() socketio.run(app, host=host, port=port) for sched in scheds: sched.stop() if __name__ == '__main__': main() PK]=Ibowtie/tests/__init__.pyPK4rI@@bowtie/tests/conftest.py#!/usr/bin/env python # -*- coding: utf-8 -*- """ Pytest configuration """ import os import shutil import pytest @pytest.fixture def remove_build(): """ Removes build directory after use. """ yield path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build') shutil.rmtree(path) PK4rIbowtie/tests/test_compile.py#!/usr/bin/env python # -*- coding: utf-8 -*- """ Compile tests """ import os from bowtie import Layout from bowtie.control import Nouislider from bowtie.visual import Plotly def callback(*args): """dummy function""" # pylint: disable=unused-argument pass # pylint: disable=unused-argument def test_build(remove_build): """ Tests the build process. """ ctrl = Nouislider() viz = Plotly() path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build') layout = Layout(directory=path) layout.add_controller(ctrl) layout.add_visual(viz) layout.subscribe(ctrl.on_change, callback) layout.build() PK4rIyy11bowtie/tests/test_plotly.py#!/usr/bin/env python # -*- coding: utf-8 -*- """ Plotly testing """ import os import subprocess from selenium.webdriver import PhantomJS # from selenium.webdriver import ActionChains from bowtie import Layout from bowtie.control import Nouislider from bowtie.visual import Plotly def callback(*args): """dummy function""" # pylint: disable=unused-argument pass # pylint: disable=unused-argument def test_plotly(remove_build): """ Tests plotly. """ viz = Plotly() ctrl = Nouislider() path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build') layout = Layout(directory=path) layout.add_visual(viz) layout.add_controller(ctrl) layout.subscribe(ctrl.on_change, callback) layout.build() env = os.environ env['PYTHONPATH'] = '{}:{}'.format(os.getcwd(), os.environ.get('PYTHONPATH', '')) server = subprocess.Popen(os.path.join(path, 'src/server.py'), env=env) driver = PhantomJS() driver.get('http://localhost:9991') assert driver.title == 'Bowtie App' server.kill() PK!H|&Ubbowtie-0.0.15.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,Q034 /, (-JLR()*M ILR(4KM̫#DPK!HlQ bowtie-0.0.15.dist-info/METADATAn0~ ߫$U=. $^peƒ`/t^h/24 )mET ܑѧjfDJlBa97=5Or\ξK2acow='f@֌nGѠ<\KLK+e#:֙=t+̝Z h~Lusww]}Ā.J>{>0kɫ6=Pchuve>^aJ!:lӭ[iF2{zPK!H*f bowtie-0.0.15.dist-info/RECORDuǶZy? &(" A0aArzz}Ä[;h1{^^}+0d>^pѮAv%¦jq(-5w jm)2ʕYԵE;6 ߠ뭅j\27zCqhQ\'p%}0lR#QF \(Ƶ[uk‰zW|W, FǾ)=O87N)@0I*3=k%T V 0Q/j·J*g  x7|4/ Q91P86m;L\T:\kfSӭ ɡ^4QNoϣ-W2b4'{9Yu gY{U߽xkXHb\f"lGkq[ذd Y}FvTDP@ ̆%9Y}߹焃2Govt}+Lp6 +xOe cV\ u7Ǚ>{Cv!\iru3CGq聜8FYXNn9Z@QHUCogBѭؖX[;ˆ 5=O<HhCo+\O򯕽,ڐ$N6B2-Sdk[4\m4`j]夞# Ve[ ƣEKլG9^]-u^9װʼgjC5^D kʫ[<ԒaPGs?^ba_5YrAO cK~X|H'sE9^C`#ʇn,+XoΙ*B8$ϵZcN< ?MFH^b3e ʗ5.OiGyLg4n$,sPKJXImJJbowtie/__init__.pyPK4rIzbowtie/_compat.pyPKIݡӆfbowtie/_component.pyPKI~X<4!4!bowtie/_layout.pyPKJXIu 5bowtie/_progress.pyPKJXI?--A?bowtie/control.pyPKJXIL "  큌lbowtie/visual.pyPKJXI*Ơӌbowtie/src/button.jsxPKЮ}InmGGbowtie/src/datagrid.jsxPKI bowtie/src/date.jsxPKЮ}IXXXbowtie/src/dropdown.jsxPKЮ}I-Q bowtie/src/fixedtable.jsxPKЮ}Izbowtie/src/griddle.jsxPK4rIee׶bowtie/src/nouislider.jsxPK`If  sbowtie/src/plotly.jsxPKJXIg7RRbowtie/src/progress.jsxPKJXIeٚ8bowtie/src/slider.jsxPKiI6bowtie/src/svg.jsxPK Id bowtie/src/switch.jsxPKIZ䗀{{/bowtie/src/webpack.config.jsPK)gIEEbowtie/templates/index.htmlPKJXIT*bbowtie/templates/index.jsxPK3 xI{ (bowtie/templates/server.pyPK]=I=bowtie/tests/__init__.pyPK4rI@@sbowtie/tests/conftest.pyPK4rIbowtie/tests/test_compile.pyPK4rIyy11큽bowtie/tests/test_plotly.pyPK!H|&Ub'bowtie-0.0.15.dist-info/WHEELPK!HlQ bowtie-0.0.15.dist-info/METADATAPK!H*f bowtie-0.0.15.dist-info/RECORDPK!