Bowtie: Interactive Dashboard Toolkit

Bowtie helps you visualize your data interactively. No Javascript required, you build your dashboard in pure Python. Easy to deploy so you can share results with others.

New to Bowtie? The quickstart guide will get you running your first app. It takes about 10 minutes to go through.

Notable Features

  • Ships with many useful widgets including charts, tables, dropdown menus, sliders, and markdown.
  • All widgets come equipped with events and commands for interaction.
  • Hook into Plotly charts with click, selection, and hover events.
  • Jupyter integration allows you to prototype your dashboards.
  • Schedule functions to run on a timer.
  • Store and fetch data with the client (browser).
  • Built in progress indicators for all visual widgets.
  • Powerful Pythonic grid API to layout components, not in HTML and CSS.
  • Compiles a single Javascript bundle speeding up load times and removes CDN dependencies.
  • Powerful programming model let’s you listen to multiple events and update multiple widgets with single functions.

Contents

Quick Start

This quick start will show how to do the following:

  • Install everything needed to use Bowtie.
  • Write an app connecting a slider to a plot.
  • How to deploy to Heroku.

Install Bowtie

If you use conda, you can install with:

conda install -c conda-forge bowtie-py

If you use pip, you can install with:

pip install bowtie

To install bleeding edge you can use flit, to install:

flit install

Install Yarn

Bowtie uses Yarn to manage the Javascript libraries We need to install it before we can use Bowtie. If you installed Bowtie with conda, Yarn was installed as a dependency and you can move on to Creating Your First App.

Conda Install

Yarn is available through conda-forge:

conda install -c conda-forge yarn
MacOS Install

Yarn is available through Homebrew:

brew install yarn
Other Environments

For other environments please follow the install instructions on the official website.

Creating Your First App

We will be creating a slider that controls the frequency of a sinusoid and visualizing the sine wave. First we’ll import the App class and two components we will use:

from bowtie import App
from bowtie.visual import Plotly
from bowtie.control import Slider
import numpy as np

I imported Numpy to generate sine waves. Now we instantiate the App and the components and configure them:

app = App(sidebar=True)
chart = Plotly()
slider = Slider(minimum=1, maximum=10, start=5, step=0.1)

Next we add these components to the app so they will be displayed on the web page. We place the slider in the sidebar and place the sine chart in the main view:

app.add_sidebar(slider)
app.add(chart)

Next we’ll create a listener that generates a plot on slider changes:

@app.subscribe(slider.on_change)
def plot_sine(freq):
    t = np.linspace(0, 10, 100)
    chart.do_all({
        'data': [{
            'type': 'scatter',
            'mode': 'lines+markers',
            'x': t,
            'y': np.sin(freq * t)
        }]
    })

The bowtie.control.Slider component sends its values as a list of strings so we had to cast it to a float.

Lastly we need to build the application by laying out the components and connecting listeners to events. The App class handles this and we put this logic into a function. Bowtie provides a decorator, command, which we’ll use to make a simple command line interface. To finish, we simply wrap the function with the command decorator:

from bowtie import command
@command
def main():
    return app

Now take a look at the CLI we just created by running this script:

python app.py

The output should look something like this:

Usage: app.py [--help] COMMAND [ARGS]...

  Bowtie CLI to help build and run your app.

Options:
  --help  Show this message and exit.

Commands:
  build  Writes the app, downloads the packages, and...
  dev    Recompiles the app for development.
  prod   Recompiles the app for production.
  run    Build the app and serve it.
  serve  Serves the Bowtie app locally.

To construct the app, we run the script with the build command:

python app.py build

This will construct the app, install the JavaScript libraries and compile your project. Once it’s done you should be able to run the following to launch your app:

python app.py serve

That will launch the app locally and you should be able to access it at http://localhost:9991.

Deploy to Heroku

This isn’t well documented, but you can try the following. For example, this was done to create bowtie-demo so you may refer to that.

  • Create the Procfile, try the following:

    web: python app.py serve -p $PORT
    
  • Create requirements files, again see bowtie-demo for an example.

  • Rebuild with production settings with webpack, by default Bowtie makes a development build:

    python app.py prod
    
  • We need to add the Javascript, so commit the following file:

    git add build/bundle.js.gz
    
  • Finally push your repo to Heroku!:

    git push heroku master
    

App

The App class defines the structure of the application. It contains a root view, what you get when you go to /. It subscribes functions to component events and builds the application.

class bowtie.App(name='__main__', app=None, rows: int = 1, columns: int = 1, sidebar: bool = False, title: str = 'Bowtie App', theme: Optional[str] = None, background_color: str = 'White', socketio: str = '', debug: bool = False)[source]

Core class to layout, connect, build a Bowtie app.

Create a Bowtie App.

Parameters:
  • name (str, optional) – Use __name__ or leave as default if using a single module. Consult the Flask docs on “import_name” for details on more complex apps.
  • app (Flask app, optional) – If you are defining your own Flask app, pass it in here. You only need this if you are doing other stuff with Flask outside of bowtie.
  • row (int, optional) – Number of rows in the grid.
  • columns (int, optional) – Number of columns in the grid.
  • sidebar (bool, optional) – Enable a sidebar for control components.
  • title (str, optional) – Title of the HTML.
  • theme (str, optional) – Color for Ant Design components.
  • background_color (str, optional) – Background color of the control pane.
  • socketio (string, optional) – Socket.io path prefix, only change this for advanced deployments.
  • debug (bool, optional) – Enable debugging in Flask. Disable in production!
add(component: bowtie._component.Component) → None[source]

Add a widget to the grid in the next available cell.

Searches over columns then rows for available cells.

Parameters:component (bowtie._Component) – A Bowtie component instance.
add_route(view: bowtie._app.View, path: str, exact: bool = True) → None[source]

Add a view to the app.

Parameters:
  • view (View) –
  • path (str) –
  • exact (bool, optional) –
add_sidebar(widget: bowtie._component.Component) → None[source]

Add a widget to the sidebar.

Parameters:widget (bowtie._Component) – Add this widget to the sidebar, it will be appended to the end.
load(func: Callable) → Callable[source]

Call a function on page load.

Parameters:func (callable) – Function to be called.
schedule(seconds: float)[source]

Call a function periodically.

Parameters:
  • seconds (float) – Minimum interval of function calls.
  • func (callable) – Function to be called.
subscribe(*events) → Callable[source]

Call a function in response to an event.

If more than one event is given, func will be given as many arguments as there are events.

If the pager calls notify, the decorated function will be called.

Parameters:*event (event or pager) – Bowtie event, must have at least one.

Examples

Subscribing a function to multiple events.

>>> from bowtie.control import Dropdown, Slider
>>> app = App()
>>> dd = Dropdown()
>>> slide = Slider()
>>> @app.subscribe(dd.on_change, slide.on_change)
... def callback(dd_item, slide_value):
...     pass
>>> @app.subscribe(dd.on_change)
... @app.subscribe(slide.on_change)
... def callback2(value):
...     pass

Using the pager to run a callback function.

>>> from bowtie.pager import Pager
>>> app = App()
>>> pager = Pager()
>>> @app.subscribe(pager)
... def callback():
...     pass
>>> def scheduledtask():
...     pager.notify()
wsgi_app(environ, start_response)[source]

Support uwsgi and gunicorn.

View

Views are responsible for laying components out on a webpage. Each view defines a grid and optional sidebar. Each app comes with one root view and you can add as many additional routes and view as you want.

class bowtie.View(rows: int = 1, columns: int = 1, sidebar: bool = False, background_color: str = 'White')[source]

Grid of components.

Create a new grid.

Parameters:
  • rows (int, optional) – Number of rows in the grid.
  • columns (int, optional) – Number of columns in the grid.
  • sidebar (bool, optional) – Enable a sidebar for control components.
  • background_color (str, optional) – Background color of the control pane.
add(component: Union[bowtie._component.Component, Sequence[bowtie._component.Component]]) → None[source]

Add a widget to the grid in the next available cell.

Searches over columns then rows for available cells.

Parameters:components (bowtie._Component) – A Bowtie widget instance.
add_sidebar(component: bowtie._component.Component) → None[source]

Add a widget to the sidebar.

Parameters:component (bowtie._Component) – Add this component to the sidebar, it will be appended to the end.

Size

Each row and column in the app is a Size object. The space used by each row and column can be changed through the following methods.

class bowtie._app.Size[source]

Size of rows and columns in grid.

This is accessed through .rows and .columns from App and View instances.

This uses CSS’s minmax function.

The minmax() CSS function defines a size range greater than or equal to min and less than or equal to max. If max < min, then max is ignored and minmax(min,max) is treated as min. As a maximum, a <flex> value sets the flex factor of a grid track; it is invalid as a minimum.

Examples

Laying out an app with the first row using 1/3 of the space and the second row using 2/3 of the space.

>>> app = App(rows=2, columns=3)
>>> app.rows[0].fraction(1)
1fr
>>> app.rows[1].fraction(2)
2fr

Create a default row or column size with fraction = 1.

auto() → bowtie._app.Size[source]

Set the size to auto or content based.

ems(value: float) → bowtie._app.Size[source]

Set the size in ems.

fraction(value: float) → bowtie._app.Size[source]

Set the fraction of free space to use.

min_auto() → bowtie._app.Size[source]

Set the minimum size to auto or content based.

min_ems(value: float) → bowtie._app.Size[source]

Set the minimum size in ems.

min_percent(value: float) → bowtie._app.Size[source]

Set the minimum percentage of free space to use.

min_pixels(value: float) → bowtie._app.Size[source]

Set the minimum size in pixels.

percent(value: float) → bowtie._app.Size[source]

Set the percentage of free space to use.

pixels(value: float) → bowtie._app.Size[source]

Set the size in pixels.

Gap

Set the margin between the cells of the grid in the App.

class bowtie._app.Gap[source]

Margin between rows or columns of the grid.

This is accessed through .row_gap and .column_gap from App and View instances.

Examples

Create a gap of 5 pixels between all rows.

>>> app = App()
>>> app.row_gap.pixels(5)
5px

Create a default margin of zero.

ems(value: int) → bowtie._app.Gap[source]

Set the margin in ems.

percent(value) → bowtie._app.Gap[source]

Set the margin as a percentage.

pixels(value: int) → bowtie._app.Gap[source]

Set the margin in pixels.

Components

Find the list of supported components here and how to use them.

Visuals

Plotly
class bowtie.visual.Plotly(init: Optional[Dict] = None)[source]

Plotly component.

Useful for many kinds of plots.

Create a Plotly component.

Parameters:init (dict, optional) – Initial Plotly data to plot.
do_all(plot)[source]

Replace the entire plot.

Parameters:plot (dict) – Dict that can be plotted with Plotly. It should have this structure: {data: [], layout: {}}.
Returns:
Return type:None
do_config(config)[source]

Update the configuration of the plot.

Parameters:config (dict) – Plotly config information.
Returns:
Return type:None
do_data(data)[source]

Replace the data portion of the plot.

Parameters:data (list of traces) – List of data to replace the old data.
Returns:
Return type:None
do_layout(layout)[source]

Update the layout.

Parameters:layout (dict) – Contains layout information.
Returns:
Return type:None
get(timeout=10)

Get the current selection of points.

Returns:
Return type:list
get_click(timeout=10)

Get the current selection of points.

Returns:
Return type:list
get_hover(timeout=10)

Get the current selection of points.

Returns:
Return type:list
get_layout(timeout=10)

Get the current layout.

Returns:
Return type:list
get_select(timeout=10)

Get the current selection of points.

Returns:
Return type:list
on_beforehover

Emit an event before hovering over a point.

Payload: TODO.
Returns:Name of event.
Return type:str
on_click

React to Plotly click event.

Payload: TODO.
Returns:Name of event.
Return type:str
on_hover

Emit an event after hovering over a point.

Payload: TODO.
Returns:Name of event.
Return type:str
on_relayout

Emit an event when the chart axes change.

Payload: TODO.
Returns:Name of event.
Return type:str
on_select

Emit an event when points are selected with a tool.

Payload: TODO.
Returns:Name of event.
Return type:str
on_unhover

Emit an event when hover is removed.

Payload: TODO.
Returns:Name of event.
Return type:str
Table
class bowtie.visual.Table(data=None, columns: Optional[List[Union[int, str]]] = None, results_per_page: int = 10)[source]

Ant Design table with filtering and sorting.

Create a table and optionally initialize the data.

Parameters:
  • data (pd.DataFrame, optional) –
  • columns (list, optional) – List of column names to display.
  • results_per_page (int, optional) – Number of rows on each pagination of the table.
do_columns(columns)[source]

Update the columns of the table.

Parameters:columns (array-like) – List of strings.
Returns:
Return type:None
do_data(data)[source]

Replace the columns and data of the table.

Parameters:data (pandas.DataFrame) –
Returns:
Return type:None
SVG
class bowtie.visual.SVG(preserve_aspect_ratio: bool = False)[source]

SVG image.

Mainly for matplotlib plots.

Create SVG component.

Parameters:preserve_aspect_ratio (bool, optional) – If True it preserves the aspect ratio otherwise it will stretch to fill up the space available.
do_image(image)[source]

Replace the image.

Parameters:image (str) – Generated by savefig from matplotlib with format=svg.
Returns:
Return type:None

Examples

This shows how to update an SVG widget with Matplotlib.

>>> 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('<svg')
...     s = s[idx:]
...     image.do_image(s)
SmartGrid
class bowtie.visual.SmartGrid[source]

Griddle table with filtering and sorting.

Create the table, optionally set the columns.

do_update(data)[source]

Update the data of the table.

Parameters:data (pandas.DataFrame) –
Returns:
Return type:None
get(timeout=10)

Get the table data.

Returns:Each entry in the list is a dict of labels and values for a row.
Return type:list

Progress

Progress component.

Not for direct use by user.

class bowtie._progress.Progress[source]

Circle progress indicator.

Create a progress indicator.

This component is used by all visual components. It 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

Using the progress widget to provide feedback to the user.

>>> from bowtie.visual import Plotly
>>> 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)

References

https://ant.design/components/progress/

do_active()[source]

Reset the progress to active (in progress) status.

Returns:
Return type:None
do_error()[source]

Display an error in the progress indicator.

Returns:
Return type:None
do_inc(inc)[source]

Increment the progress indicator.

Parameters:inc (number) – Value to increment the progress.
Returns:
Return type:None
do_percent(percent)[source]

Set the percentage of the progress.

Parameters:percent (number) – Sets the progress to this percentage.
Returns:
Return type:None
do_success()[source]

Display the progress indicator as done.

Returns:
Return type:None
do_visible(visible)[source]

Hide and shows the progress indicator.

Parameters:visible (bool) – If True shows the progress indicator otherwise it is hidden.
Returns:
Return type:None

Controllers

Button
class bowtie.control.Button(label: str = '')[source]

An Ant design button.

Create a button.

Parameters:label (str, optional) – Label on the button.
on_click

Emit an event when the button is clicked.

There is no getter associated with this event.

Payload: None.
Returns:Name of click event.
Return type:str
Switch
class bowtie.control.Switch(initial: bool = False)[source]

Toggle switch.

Create a toggle switch.

Parameters:initial (bool, optional) – Starting state of the switch.
get(timeout=10)

Get the state of the switch.

Returns:True if the switch is enabled.
Return type:bool
on_switch

Emit an event when the switch is toggled.

Payload: bool status of the switch.
Returns:Name of event.
Return type:str
DatePicker
class bowtie.control.DatePicker[source]

A Date Picker.

Let’s you choose an individual day.

Create a date picker.

get(timeout=10)

Get the currently selected date.

Returns:Date in the format “YYYY-MM-DD”
Return type:str
on_change

Emit an event when a date is selected.

Payload: str of the form "yyyy-mm-dd".
Returns:Name of event.
Return type:str
MonthPicker
class bowtie.control.MonthPicker[source]

A Month Picker.

Let’s you choose a month and year.

Create month picker.

get(timeout=10)

Get the currently selected month.

Returns:Month in the format “YYYY-MM”
Return type:str
on_change

Emit an event when a month is selected.

Payload: str of the form "yyyy-mm".
Returns:Name of event.
Return type:str
RangePicker
class bowtie.control.RangePicker[source]

A Date Range Picker.

Choose two dates to use as a range.

Create a date range picker.

get(timeout=10)

Get the currently selected date range.

Returns:A list of two strings ["yyyy-mm-dd", "yyyy-mm-dd"].
Return type:list
on_change

Emit an event when a range is selected.

Payload: list of two dates ["yyyy-mm-dd", "yyyy-mm-dd"].
Returns:Name of event.
Return type:str
Textbox
class bowtie.control.Textbox(placeholder: str = 'Enter text', size: str = 'default', area: bool = False, autosize: bool = False, disabled: bool = False)[source]

A single line text box.

Create a text box.

Parameters:
  • placeholder (str, optional) – Initial text that appears.
  • size ('default', 'large', 'small', optional) – Size of the text box.
  • area (bool, optional) – Create a text area if True else create a single line input.
  • autosize (bool, optional) – Automatically size the widget based on the content.
  • disabled (bool, optional) – Disable input to the widget.

References

https://ant.design/components/input/

do_text(text)[source]

Set the text of the text box.

Parameters:text (str) – String of the text box.
get(timeout=10)

Get the current text.

Returns:
Return type:str
on_change

Emit an event when the text is changed.

Payload: str
Returns:Name of event.
Return type:str
on_enter

Emit an event when enter is pressed in the text box.

Payload: str
Returns:Name of event.
Return type:str
Number
class bowtie.control.Number(start: int = 0, minimum: float = -1e+100, maximum: float = 1e+100, step: int = 1, size: str = 'default')[source]

A number input widget with increment and decrement buttons.

Create a number input.

Parameters:
  • start (number, optional) – Starting number
  • minimum (number, optional) – Lower bound
  • maximum (number, optional) – Upper bound
  • size ('default', 'large', 'small', optional) – Size of the text box.

References

https://ant.design/components/input/

get(timeout=10)

Get the current number.

Returns:
Return type:number
on_change

Emit an event when the number is changed.

Payload: number
Returns:Name of event.
Return type:str
Slider
class bowtie.control.Slider(start: Union[float, Sequence[float], None] = None, ranged: bool = False, minimum: float = 0, maximum: float = 100, step: float = 1, vertical: bool = False)[source]

Ant Design slider.

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.
  • vertical (bool, optional) – If True, the slider will be vertical

References

https://ant.design/components/slider/

do_inc(value=1)[source]

Increment value of slider by given amount.

Parameters:value (int) – Number to change value of slider by.
do_max(value)[source]

Replace the max value of the slider.

Parameters:value (int) – Maximum value of the slider.
do_min(value)[source]

Replace the min value of the slider.

Parameters:value (int) – Minimum value of the slider.
do_min_max_value(minimum, maximum, value)[source]

Set the minimum, maximum, and value of slider simultaneously.

Parameters:
  • minimum (int) – Minimum value of the slider.
  • maximum (int) – Maximum value of the slider.
  • value (int) – Value of the slider.
do_value(value)[source]

Set the value of the slider.

Parameters:value (int) – Value of the slider.
get(timeout=10)

Get the currently selected value(s).

Returns:List if it’s a range slider and gives two values.
Return type:list or number
on_after_change

Emit an event when the slider control is released.

Payload: number or list of values.
Returns:Name of event.
Return type:str
on_change

Emit an event when the slider’s value changes.

Payload: number or list of values.
Returns:Name of event.
Return type:str
NoUISlider
class bowtie.control.Nouislider(start: Union[int, Sequence[int]] = 0, minimum: int = 0, maximum: int = 100, tooltips: bool = True)[source]

A lightweight JavaScript range slider library.

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.

References

https://refreshless.com/nouislider/events-callbacks/

get(timeout=10)

Get the currently selected value(s).

Returns:List if it’s a range slider and gives two values.
Return type:list or number
on_change

Emit an event when the slider is moved.

https://refreshless.com/nouislider/events-callbacks/

Payload: list of values.
Returns:Name of event.
Return type:str
on_end

Emit an event when the slider is moved.

https://refreshless.com/nouislider/events-callbacks/

Payload: list of values.
Returns:Name of event.
Return type:str
on_set

Emit an event when the slider is moved.

https://refreshless.com/nouislider/events-callbacks/

Payload: list of values.
Returns:Name of event.
Return type:str
on_slide

Emit an event when the slider is moved.

https://refreshless.com/nouislider/events-callbacks/

Payload: list of values.
Returns:Name of event.
Return type:str
on_start

Emit an event when the slider is moved.

https://refreshless.com/nouislider/events-callbacks/

Payload: list of values.
Returns:Name of event.
Return type:str
on_update

Emit an event when the slider is moved.

https://refreshless.com/nouislider/events-callbacks/

Payload: list of values.
Returns:Name of event.
Return type:str
Upload
class bowtie.control.Upload(multiple=True)[source]

Draggable file upload widget.

Create the widget.

Note: the handler parameter may be changed in the future.

Parameters:multiple (bool, optional) – If true, you can upload multiple files at once. Even with this set to true, the handler will get called once per file uploaded.
on_upload

Emit an event when the selection changes.

There is no getter associated with this event.

Payload: tuple with a str (name) and BytesIO (stream).

The user is responsible for storing the object in this function if they want it for later use. To indicate an error, return True, otherwise a return value of None or False indicate success.

Checkbox
class bowtie.control.Checkbox(labels: Optional[Sequence[str]] = None, values: Optional[Sequence[Union[str, int]]] = None, defaults: Optional[Sequence[Union[str, int]]] = None)[source]

Ant Design checkboxes.

Create checkboxes.

Parameters:
  • labels (optional, sequence of stings) –
  • values (optional, sequence of strings or ints) –
  • defaults (optional, sequence of strings or ints) –

References

https://ant.design/components/checkbox/

do_check(values: Sequence[Union[str, int]])[source]

Set the value of the slider.

Parameters:values (Sequence of int, str) – The radio value to select.
do_options(labels: Sequence[str], values: Sequence[Union[str, int]]) → Sequence[Dict][source]

Replace the checkbox options.

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:

Return type:

None

do_values(*value)[source]

Set the value(s) of the slider.

Parameters:value (int) – Value of the slider.
get(timeout=10)

Get the checked values.

Returns:List of values
Return type:list
on_change

Emit an event when the slider’s value changes.

Payload: number or list of values.
Returns:Name of event.
Return type:str
Radio
class bowtie.control.Radio(labels: Optional[Sequence[str]] = None, values: Optional[Sequence[Union[str, int]]] = None, default: Union[str, int, None] = None)[source]

Ant Design Radio Group.

Create radio boxes.

Parameters:
  • labels (optional, sequence of stings) –
  • values (optional, sequence of strings or ints) –
  • default (optional, string or int) –

References

https://ant.design/components/radio/

do_options(labels, values)[source]

Replace the radio button options.

Parameters:
  • labels (Sequence) – List of strings which will be visible to the user.
  • values (Sequence) – List of values associated with the labels that are hidden from the user.
Returns:

Return type:

None

do_select(value: Union[str, int])[source]

Set the value of the slider.

Parameters:value (int, str) – The radio value to select.
get(timeout=10)

Get the currently selected value(s).

Returns:List if it’s a range slider and gives two values.
Return type:list or number
on_change

Emit an event when the slider’s value changes.

Payload: number or list of values.
Returns:Name of event.
Return type:str

HTML

Markdown
class bowtie.html.Markdown(initial: str = '')[source]

Display Markdown.

Create a Markdown widget.

Parameters:initial (str, optional) – Default markdown for the widget.
do_text(text)[source]

Replace widget with this text.

Parameters:test (str) – Markdown text as a string.
Returns:
Return type:None
get(timeout=10)

Get the current text.

Returns:
Return type:String of html.
Div
class bowtie.html.Div(text: str = '')[source]

Div tag.

Create header text with a size.

Parameters:text (str) – Text of the header tag.
do_text(text)[source]

Replace widget with this text.

Parameters:test (str) – Markdown text as a string.
Returns:
Return type:None
get(timeout=10)

Get the current text.

Returns:
Return type:String of html.

Feedback

There will be a few ways to message users.

Message

Messages provide a temporary message that will disappear after a few seconds.

Reference

https://ant.design/components/message/

bowtie.feedback.message.error(content)[source]

Error message.

Parameters:content (str) – Message to show user.
bowtie.feedback.message.info(content)[source]

Info message.

Parameters:content (str) – Message to show user.
bowtie.feedback.message.loading(content)[source]

Load message.

Parameters:content (str) – Message to show user.
bowtie.feedback.message.success(content)[source]

Success message.

Parameters:content (str) – Message to show user.
bowtie.feedback.message.warning(content)[source]

Warning message.

Parameters:content (str) – Message to show user.

Cache

Bowtie provides a simple key value store where you can store data with the client. Keep in mind that if you store a large amount of data it will get transferred to and from the client which could result in a poor user experience. That being said, it can be very useful to store results from expensive computations.

class bowtie._cache._Cache[source]

Store data in the browser.

This cache uses session storage so data will stay in the browser until the tab is closed. All data must be serializable, which means if the serialization transforms the data it won’t be the same when it is fetched.

Examples

>>> from bowtie import cache
>>> cache['a'] = True  # doctest: +SKIP
>>> cache['a']  # doctest: +SKIP
True
>>> cache['b'] = np.arange(5)  # doctest: +SKIP
>>> cache['b']  # doctest: +SKIP
[1, 2, 3, 4, 5]

Authentication

Bowtie provides simple basic authentication out of the box. Better support for other authentication methods is planned for future releases. Adding basic authentication to your app is designed to be easy and simple. Simply create a BasicAuth instance and pass it the Bowtie app and a dictionary with usernames as keys and passwords as values:

from bowtie import App
from bowtie.auth import BasicAuth
app = App(__name__)
basic_auth = BasicAuth(app, {
    'alice': 'secret1',
    'bob': 'secret2',
})

To provide your own authentication you can implement the interface required by the abstract Auth class. This is not well supported or well documented at this time unfortunately.

class bowtie.auth.Auth(app: bowtie._app.App)[source]

Abstract Authentication class.

Create Auth class to protect flask routes and socketio connect.

before_request()[source]

Determine if a user is allowed to view this route.

Name is subject to change.

Returns:
Return type:None, if no protection is needed.
socketio_auth() → bool[source]

Determine if a user is allowed to establish socketio connection.

Name is subject to change.

class bowtie.auth.BasicAuth(app: bowtie._app.App, credentials: Dict[str, str])[source]

Basic Authentication.

Create basic auth with credentials.

Parameters:credentials (dict) – Usernames and passwords should be passed in as a dictionary.

Examples

>>> from bowtie import App
>>> from bowtie.auth import BasicAuth
>>> app = App(__name__)
>>> auth = BasicAuth(app, {'alice': 'secret1', 'bob': 'secret2'})
before_request() → Optional[flask.wrappers.Response][source]

Determine if a user is allowed to view this route.

socketio_auth() → bool[source]

Determine if a user is allowed to establish socketio connection.

Flask Integration

Todo

discuss how to use bowtie within existing flask app

Todo

show example

Deploy

This section is under development. It will discuss options for deploying Bowtie apps and scaling them.

Todo

gunicorn example

Todo

NGINX example

Jupyter Integration

Bowtie can run a dashboard defined in a Jupyter notebook. First load the extension:

%load_ext bowtie

Create your app in the same fashion you would in a script. Instead of using a main function decorated with @command, we use an IPython magic:

app = App()
%bowtie app

This will run the Bowtie app and create an iframe to view the dashboard. When you want to stop the Bowtie app use the following magic:

%bowtie_stop

Using Docker

Bowtie depends on yarn to manage Node packages. If you would prefer to not install this on your system you can use the provided Dockerfile to build your Bowtie app. The file provides a conda environment with python 3.6.

Docker Hub

The Docker image is hosted on Docker Hub. To pull the bleeding edge release:

docker pull jwkvam/bowtie

To pull a specific version:

docker pull jwkvam/bowtie:0.6.0

Building

If you prefer to build the Docker yourself:

docker build . -t bowtie

Usage

I recommend running the Docker interactively:

docker run -ti -p 9991:9991 -v (pwd):/work -rm bowtie bash

This runs Docker in your current working directory. Run this command in the same directory as your bowtie project. This forwards the Docker port 9991 to the host, so you can access the dashboard from the host machine.

You may find it convenient to make this command an alias:

alias bowtie='docker run -ti -p 9991:9991 -v (pwd):/work -rm bowtie bash'

Let’s say your dashboard is in app.py and you have a requirements.txt file:

$ bowtie
# now inside the docker
bowtie $ pip install -r requirements.txt
bowtie $ python app.py run

After a few moments you should be able to access the website from your machine.

Exceptions

Here is a list of all exceptions defined by Bowtie. Generally there shouldn’t be a need to catch these but they are provided here for completeness.

Bowtie exceptions.

exception bowtie.exceptions.GridIndexError[source]

Invalid index into the grid layout.

exception bowtie.exceptions.MissingRowOrColumn[source]

Missing a row or column.

exception bowtie.exceptions.NoSidebarError[source]

Cannot add to the sidebar when it doesn’t exist.

exception bowtie.exceptions.NoUnusedCellsError[source]

All cells are used.

exception bowtie.exceptions.NotStatefulEvent[source]

Event is not stateful and cannot be paired with other events.

exception bowtie.exceptions.SerializationError[source]

Cannot serialize the data for command.

exception bowtie.exceptions.SizeError[source]

Size values must be a number.

exception bowtie.exceptions.SpanOverlapError[source]

Spans may not overlap.

exception bowtie.exceptions.WebpackError[source]

Errors from Webpack.

exception bowtie.exceptions.YarnError[source]

Errors from Yarn.

Architecture

Read this section if you are interested in hacking on Bowtie or understanding how it works. Essentially, Bowtie works by using SocketIO to communicate between React and Python.

digraph foo { "Bowtie App" -> {"Browser 1";"Browser 2"} [dir="both",label="socket.io"]; bgcolor="transparent"; }

React

All the available components are associated with a React class. At a minimum each class must have a uuid and socket prop. The uuid prop is a unique identifier which is used to name the message being sent. The socket prop is a socketio connection.

Flask

Bowtie attempts to abstract away the Flask interface. Admittedly Flask is not difficult to learn but ultimately I wanted a library which required little boilerplate.

If you want to tinker with the Flask app, you can edit the server.py file that gets generated during the build phase.

SocketIO

SocketIO binds the Python backend code to the React frontend. Python uses the Flask-SocketIO extension and the frontend uses socket.io-client.

Events

Almost every component has events associated with it. For example, a slider generates events when the user moves the slider. In Bowtie, these events are class attributes with the prefix on_. With the App class you can subscribe callbacks to events, so when an event happens the callback is called with an argument that is related to the event.

Commands

Many components have commands to update their state. For example you can update the drop down list or update a plot. The commands are instance functions that are prefixed do_. For example, to update a Plotly chart in Bowtie you can call do_all(dict), and Plotly will update it’s chart with the data and layout options defined in the dictionary.

Bowtie Application

There are a few key parts to any Bowtie application.

  1. Define the app, components, and global state:

    app = App(rows=1, columns=1, sidebar=True)
    plotly = Plotly()
    dropdown = Dropdown(*config)
    global_dataset = SQLQuery(cool_data)
    
  2. Layout the app, you can also do this globally, but this can keep the code better organized:

    def layout():
        app.add_sidebar(dropdown)
        app.add(plotly)
    app.layout = layout
    
  1. Create what should happen in response to events:

    @app.subscribe(dropdown.on_change)
    def callback(dropdown_item):
        # compute something cool and plot it
        data = cool_stuff(global_dataset, dropdown_item)
        plotly.do_all(data)
    
  2. Create a main function that simply returns the app. Then Bowtie knows how to build and serve the app:

    from bowtie import command
    @command
    def main():
        return app
    

Create New Components

Bowtie is designed to not make it terribly onerous to make new components. We need to write two new classes.

  1. Create new React class
  2. Create visual or control class in Python

To walk through this process I’ll use the dropdown component, since that touches on many interesting pieces.

React Class

The dropdown component leverages a popular react dropdown component to do the heavy lifting. First we start by importing react, msgpack and the component:

import React from 'react';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
var msgpack = require('msgpack-lite');

We also imported the default styling so it looks reasonable. We can do this because we’re using webpack to compile the application.

Next we will define the properties that the React class will hold. This defines how the Python code can initialize the component. We always need uuid and socket properties since they make it possible for the Python backend to communicate with the React object. This component supports multiple selection and that will be a bool property. We’ll also make an initOptions property which will let us set an initial list of options to populate the dropdown. Now that we have that defined let’s write it in code:

Dropdown.propTypes = {
    uuid: React.PropTypes.string.isRequired,
    socket: React.PropTypes.object.isRequired,
    multi: React.PropTypes.bool.isRequired,
    initOptions: React.PropTypes.array
};

Now we will create the class:

export default class Dropdown extends React.Component {
    ...
}

Everything from now we’ll write as functions in the class body. First we’ll look at the render function:

render () {
    return (
        <Select
            multi={this.props.multi}
            value={this.state.value}
            options={this.state.options}
            onChange={this.handleChange}
        />
    );
}

This instantiates the component and allows us to set configuration options for the underlying component. Note that this.state is mutable and this.prop is fixed. For example, multiple selection cannot be changed but the drop down options can be changed.

Now we’ll tell it how to communicate. We do this after the component is created in the componentDidMount function:

componentDidMount() {
    var socket = this.props.socket;
    var uuid = this.props.uuid;
    socket.on(uuid + '#get', this.getValue);
    socket.on(uuid + '#options', (data) => {
        var arr = new Uint8Array(data['data']);
        this.setState({value: null, options: msgpack.decode(arr)});
    });
}

Note that we have defined a command to be used through Python with do_options and a get function so Python can get it’s current state. Next we define the constructor which initializes the state and binds this to those handlers:

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);
}

Lastly we define the handlers referenced above:

handleChange(value) {
    this.setState({value});
    this.props.socket.emit(this.props.uuid + '#change', value);
}

getValue(data, fn) {
    fn(this.state.value);
}

Python Class

Now that we have the React component defined, let’s write the Python half. We don’t need to write much here, it’s a little glue code.

First we’ll define the class:

class Dropdown(_Controller):
    _TEMPLATE = 'dropdown.jsx'
    _COMPONENT = 'Dropdown'
    _PACKAGE = 'react-select'
    _TAG = ('<Dropdown initOptions={{{options}}} '
            'multi={{{multi}}}'
            'socket={{socket}} '
            'uuid={{{uuid}}} '
            '/>')

We have defined a few component specific constants:

  • _TEMPLATE: Name of the file where the React class is defined.
  • _COMPONENT: Name of the React class (used to import the class).
  • _PACKAGE: Name of the NPM package used by the component.
  • _TAG: String used to instantiate the component.

We write the constructor who’s main responsibility is creating the string to instantiate the component in Javascript. In Bowtie, this gets assigned to the _instantiate field:

def __init__(self, options, multi=False):
    super(Dropdown, self).__init__()

    self._instantiate = self._TAG.format(
        options=json.dumps(options),
        multi='true' if multi else 'false',
        uuid="'{}'".format(self._uuid)
    )

Lastly we have one event (named “change”), one command (named “options”), and one getter (named “get”). We can create those by defining functions with the appropriate name and arguments, metaclasses handle the rest:

def on_change(self):
    pass

For the commands, we have the ability to just pass the data through to the React component:

def do_options(self, data):
    return data

We can also preprocess the data to present an easier interface for the programmer:

def do_options(self, labels, values):
    return [dict(label=l, value=v) for l, v in zip(labels, values)]

The main caveat here is we must ensure the data is serializable by msgpack.

For the getter we can write:

def get(self, data):
    return data

We can use this getter to do post processing, but here I just return the data as given to me from the React component.

Metaclass Parsing

A note about how commands, events, and getters are transformed into messages.

Events

Anything function that begins with on_ is an event. The message that ends up getting sent is on_{name} is {uuid}#name.

Commands

Anything function that begins with do_ is a command. The message that ends up getting sent is do_{name} is {uuid}#name.

Getters

Anything function that begins with get_ or is get is a getter. The message that ends up getting sent is get_{name} is {uuid}#get or {uuid}#get_{name}.

Comparison with Other Tools

Bowtie is designed to have a simple API to create dashboard applications quickly. That being said let’s compare this against similar Python libraries. This section could use some help especially if you are familiar with one of the libraries listed.

Dash

From Plotly is Dash Even though both Bowtie and Dash allow you to develop a dashboard we have very different designs.

Layout API

Dash uses HTML. In order to layout your dash app you need to know a little HTML. This can be a pro or con depending on your comfort with HTML.

Bowtie uses its own Pythonic API. You don’t need to know any HTML. On the other hand you need to read the API to understand how to use it.

Events

In my opinion, the callback and event system is much easier to use and more powerful in Bowtie. The API is Pythonic so you don’t have to memorize special strings. You can distinguish between events and state. You can update multiple components in a single callback. Bowtie tries very hard to be simple to use and powerful.

Style

One area that Bowtie lacks in is styling. Dash has powerful styling techniques. If you need custom styling in Bowtie, you’ll need to edit the generated HTML by hand.

Building and Running

Running a Bowtie app consists of two steps: first “building” and then “running” the app. The build process figures out what Javascript libraries are needed, creates HTML and Javascript files, and finally uses Webpack to assemble it all into one Javascript file, bundle.js. Once the Javascript bundle is built the Bowtie app can be run.

Dash uses minified Javascript files and it doesn’t pack them. Overall this likely results in a better user experience for Dash users since packing the Javascript doesn’t result in a much smaller file or much better Javascript performance.

Dynamic Layout

Bowtie’s layouts are currently defined at build time. Dash can dynamically change the layout. This is a feature that I would like to add into Bowtie.

Source Code

Bowtie is a monolithic repo. All the Python, Javascript, and HTML is in a single repo. Dash is modular and has multiple repos for its core functionality, React components, and HTML components.

I believe Bowtie is easier to understand and maintain because everything is self-contained. Instead it’s harder to use custom components with Bowtie since they need to be included in the library itself. This is decoupled in Dash so custom components are easier to develop.

Other

This has touched on some of the major differences. There are many more however that I’ll try to address eventually.

Bokeh

This is the oldest dashboard tool in Python I’m aware of. I think it hasn’t been adopted much because of poor visibility and documentation. To be fair I haven’t used it a lot and only discovered it after I created Bowtie.

Shiny

Not a Python library but is the gold standard.

Todo

should try using shiny so I know what it’s like