PK apI] j j bowtie/__init__.py"""
Bowtie
"""
__version__ = '0.0.11'
from flask_socketio import emit
from bowtie._layout import Layout
PK $)I" bowtie/_compat.py# -*- coding: utf-8 -*-
"""
python 2/3 compatability
"""
import sys
from os import makedirs
IS_PY2 = sys.version_info < (3, 0)
IS_PY35 = sys.version_info >= (3, 5)
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
PK ^pIG6
bowtie/_component.py# -*- coding: utf-8 -*-
"""
Bowtie Component classes, all visual and control components inherit these
"""
from __future__ import unicode_literals
from builtins import bytes
import json
import msgpack
from datetime import datetime, date, time
import flask
from flask_socketio import emit
from future.utils import with_metaclass
from eventlet.event import Event
from eventlet.queue import LightQueue
from bowtie._compat import IS_PY35
def json_conversion(obj):
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):
return json.dumps(data, default=json_conversion)
def encoders(obj):
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):
return bytes(msgpack.packb(x, default=encoders))
def make_event(event):
@property
def actualevent(self):
name = event.__name__[3:]
return '{uuid}#{event}'.format(uuid=self._uuid, event=name)
if IS_PY35:
# can't set docstring on properties in python 2 like this
actualevent.__doc__ = event.__doc__
return actualevent
def is_event(attribute):
return attribute.startswith('on_')
def make_command(command):
def actualcommand(self, data):
name = command.__name__[3:]
signal = '{uuid}#{event}'.format(uuid=self._uuid, event=name)
if flask.has_request_context():
return emit(signal, {'data': pack(data)})
else:
sio = flask.current_app.extensions['socketio']
return sio.emit(signal, {'data': pack(data)})
actualcommand.__doc__ = command.__doc__
return actualcommand
def is_command(attribute):
return attribute.startswith('do_')
class _Maker(type):
def __new__(cls, 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, cls).__new__(cls, name, parents, dct)
class Component(with_metaclass(_Maker, object)):
_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, block=True, timeout=None):
event = LightQueue(1)
if flask.has_request_context():
emit('{}#get'.format(self._uuid),
callback=lambda x: event.put(msgpack.unpackb(bytes(x['data']))))
else:
sio = flask.current_app.extensions['socketio']
sio.emit('{}#get'.format(self._uuid),
callback=lambda x: event.put(msgpack.unpackb(bytes(x['data']))))
return event.get(timeout=10)
PK woI8! ! bowtie/_layout.py# -*- coding: utf-8 -*-
import os
from os import path
import stat
from subprocess import Popen
import inspect
from flask import Markup
from collections import namedtuple, defaultdict
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 NPMError(Exception):
pass
class WebpackError(Exception):
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 = [
'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-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()
self.templates = set()
self.imports = set()
self.visuals = [[]]
self.controllers = []
self.schedules = []
self.functions = []
def add_visual(self, visual,
width=None, height=None,
width_pixels=None, height_pixels=None,
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)
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.
"""
pass
assert isinstance(control, _Controller)
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.
"""
e = "'{}'".format(event)
self.subscriptions[e].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.
"""
env = Environment(loader=FileSystemLoader(
path.join(path.dirname(__file__), 'templates')
))
webpack = env.get_template('webpack.config.js')
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)
with open(path.join(self.directory, webpack.name), 'w') as f:
f.write(
webpack.render()
)
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)
)
# components = [env.get_template(t).render() for t in self.templates]
for template in self.templates:
temp = env.get_template(template)
with open(path.join(app, temp.name), 'w') as f:
f.write(
temp.render()
)
for i, visualrow in enumerate(self.visuals):
for j, visual in enumerate(visualrow):
self.visuals[i][j] = self.visuals[i][j]._instantiate()
# columns=len(visualrow),
# rows=len(self.visuals)
# )
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('npm init -f', shell=True, cwd=self.directory).wait()
if init != 0:
raise NPMError('Error running "npm init -f"')
self.packages.discard(None)
packages = ' '.join(self._packages + list(self.packages))
install = Popen('yarn add {}'.format(packages),
shell=True, cwd=self.directory).wait()
# install = Popen('npm install -S {}'.format(packages),
# shell=True, cwd='build').wait()
if install != 0:
raise NPMError('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'):
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
PK voI6옝 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):
_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):
pass
class DropDown(_Controller):
_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
def do_options(self, data):
pass
class Nouislider(_Controller):
_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):
pass
def on_slide(self):
pass
def on_set(self):
pass
def on_change(self):
pass
def on_start(self):
pass
def on_en(self):
pass
PK )SJIn n bowtie/visual.py# -*- coding: utf-8 -*-
"""
Visual components
"""
from bowtie._component import Component, jdumps
# 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.
"""
pass
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
)
def do_update(self, data):
pass
#
# TODO: 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):
super(Plotly, self).__init__()
if init is None:
init = dict(data=[], layout={'autosize': False})
self.init = init
def _instantiate(self):
return self._TAG.format(
uuid="'{}'".format(self._uuid),
init=jdumps(self.init),
)
## Events
def on_click(self):
"""Plotly click event.
Returns
-------
str
Name of click event.
"""
pass
def on_beforehover(self):
pass
def on_hover(self):
pass
def on_unhover(self):
pass
def on_select(self):
pass
## Commands
def do_all(self, data):
pass
def do_data(self, data):
pass
def do_layout(self, data):
pass
def do_config(self, data):
pass
PK oI1q bowtie/templates/button.jsximport React from 'react';
export default class SimpleButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
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 )gInmG G bowtie/templates/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
};
PK 0\pI
nbl l bowtie/templates/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};
this.state.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 (
);
}
}
DropDown.propTypes = {
uuid: React.PropTypes.string.isRequired,
socket: React.PropTypes.object.isRequired,
multi: React.PropTypes.bool.isRequired,
initOptions: React.PropTypes.array
};
PK )gI1=9
9
bowtie/templates/fixedtable.jsximport React from 'react';
import { DataTable } from 'react-jquery-datatables';
import 'fixed-data-table/dist/fixed-data-table.css';
import {Table, Column, Cell} from 'fixed-data-table';
function get_height_width() {
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
return [x, y];
}
export default class FixedTable extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.state.columns = ['id', 'first', 'last'];
this.state.data = [
[1, 2, 3],
['John', 'Bob', 'Bob'],
['Bobson', 'Mclaren', 'Mclaren']
];
}
render() {
var hw = get_height_width();
var height = Math.floor(hw[1] / this.props.rows);
var width = Math.floor((hw[0] * 9 / 10) / this.props.columns);
var headers = this.state.columns;
var columns = this.state.data;
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 0\pIA# # bowtie/templates/griddle.jsximport React from 'react';
import Griddle from 'griddle-react';
var msgpack = require('msgpack-lite');
function get_height_width() {
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
return [x, y];
}
export default class SmartGrid extends React.Component {
constructor(props) {
super(props);
this.state = {};
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(data)});
});
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
};
PK )gIE E bowtie/templates/index.html
{{ title }}
PK 0I' bowtie/templates/index.jsximport 'normalize.css';
import React from 'react';
import {render} from 'react-dom';
import io from 'socket.io-client';
{% 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 in visualrow %}
{{ visual }}
{% endfor %}
{% endfor %}
);
}
}
render(, document.getElementById('app'));
PK 0\pI-F`HH H bowtie/templates/nouislider.jsximport React from 'react';
// import 'nouislider-algolia-fork/src/nouislider.css';
// import 'nouislider-algolia-fork/src/nouislider.css';
import 'nouislider/src/nouislider.css';
// import Nouislider from 'react-nouislider';
// import nouislider from 'nouislider-algolia-fork';
import nouislider from 'nouislider';
var msgpack = require('msgpack-lite');
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
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', data);
});
// if (this.props.onChange) {
slider.on('change', function (data) {
socket.emit(uuid + '#change', data);
});
//
// // if (this.props.onSlide) {
slider.on('slide', function (data) {
socket.emit(uuid + '#slide', data);
});
// {..._objectWithoutProperties(this.props, ["uuid"])}
// if (this.props.onUpdate) {
}
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 0\pIsoPQ Q bowtie/templates/plotly.jsximport React from 'react';
import Plotly from 'plotly.js';
import cloneDeep from 'lodash.clonedeep';
var msgpack = require('msgpack-lite');
function get_height_width() {
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
return [x, y];
}
export default class PlotlyPlot extends React.Component {
selection = 'inital';
constructor(props) {
super(props);
this.state = this.props.initState;
this.resize = this.resize.bind(this);
}
shouldComponentUpdate(nextProps) {
return true;
}
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() {
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);
Plotly.newPlot(this.container, this.state.data, cloneDeep(this.state.layout),
{autosizable: false, displaylogo: false, fillFrame: true}); //, config);
// if (this.props.onClick)
var uuid = this.props.uuid;
var socket = this.props.socket;
// if (this.props.onClick)
socket.on(this.props.uuid + '#all', (data) => {
var arr = new Uint8Array(data['data']);
this.setState(msgpack.decode(arr));
});
// socket.on(this.props.uuid + '#' + 'get', (data) => {
// console.log('get command!!!');
// console.log(data);
// console.log(uuid + '#put');
// socket.emit(uuid + '#put', [3]); //this.state);
// console.log('done seding');
// });
socket.on(this.props.uuid + '#get', this.getSelection);
this.addListeners();
// console.log('get command!!!');
// console.log(data);
// console.log(uuid + '#put');
// socket.emit(uuid + '#put'); //this.state);
// fn(this.state.selection);
// console.log('done seding');
// });
// socket.emit(this.props.uuid + '#put', [3]);
}
// updateState(state) {
// console.log('updating...');
// this.state = state;
// }
// updateState = (ev) => this.setState({ text: ev.target.value });
componentDidUpdate() {
//TODO use minimal update for given changes
// this.container.data = this.state.data;
// this.container.layout = this.state.layout;
// var hw = get_height_width();
// this.state.layout = this.state.layout || {};
// this.state.layout['height'] = hw[1] / this.props.rows;
// this.state.layout['width'] = (hw[0] * 9 / 10) / this.props.columns;
//this.container.config = {autosizable: true, fillFrame: true, displaylogo: false};
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);
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() {
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
};
PK 0\pIm 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'])) 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 )gIy " bowtie/templates/webpack.config.jsvar webpack = require('webpack');
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')
]
}
};
module.exports = config;
PK ]=I bowtie/tests/__init__.pyPK 0\pIν bowtie/tests/test_compile.py#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import shutil
import pytest
from bowtie import Layout
from bowtie.control import Nouislider
from bowtie.visual import Plotly
@pytest.fixture
def remove_build(request):
yield
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build')
shutil.rmtree(path)
def callback(*args):
pass
def test_build(remove_build):
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()
PK oIW*( ( bowtie/tests/test_plotly.py#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import shutil
import subprocess
import pytest
from selenium.webdriver import PhantomJS, ActionChains
from bowtie import Layout
from bowtie.control import Nouislider
from bowtie.visual import Plotly
@pytest.fixture
def remove_build(request):
yield
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build')
shutil.rmtree(path)
def callback(*args):
pass
def test_plotly(remove_build):
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()
env = os.environ
env['PYTHONPATH'] = '{}:{}'.format(os.getcwd(), os.environ.get('PYTHONPATH', ''))
rv = subprocess.Popen(os.path.join(path, 'src/server.py'), env=env)
driver = PhantomJS()
driver.get('http://localhost:9991')
assert driver.title == 'Bowtie App'
rv.kill()
PK !H|&U