bento-dash¶
Introduction¶
Bento is a tool for building interactive dashboard applications, powered by a templating engine built on Plotly Dash. It lets you write a high-level description of your desired interactive dashboard and generates the application code for you. By providing a set of common building blocks and abstracting away some of the more complicated aspects, it aims to flatten the learning curve of dashboarding!
- Bento follows a few principles in its goal of improving user productivity:
Focus on one powerful, high-level object called the descriptor
Allow users a selection of ready-made building blocks called banks
Automatically handle component interactivity infrastructure
Provide a simple theming and layout experience
An analogy can be drawn between Bento and desktop computer hardware. A motherboard ties together several different components in a way such that assembly can be performed without advanced knowledge of each piece. The user is just tasked with understanding what they want out of the finished product. In a similar way, creating the Bento descriptor should be as simple as understanding what you want the dashboard to do and then finding the right banks from the catalog.
For some inspiration, check out some sample Bento apps in this gallery.
To get a sense of what Plotly Dash can do, visit their app gallery.
Quickstart¶
To get started with Bento, we’re going to walk through a few quick steps:
Installation
Run the demo dashboard
Modify the demo
Fall back to straight Plotly Dash
Clone Bento Builder
Installation¶
Dependencies: Python 3.7+
Bento is available on PyPI, and the latest version can be installed with:
$ pip3 install bento-dash
Run Demo¶
To make sure everything is working, try running the demo app:
$ bento-demo
And navigate in your browser to localhost:8050.
Modify Demo¶
To get a quick taste of the Bento experience, now run the following:
$ python3 -m bento.dashboards.demo
$ python3 -m bento.launcher
The first will write out a copy of the demo descriptor to your current dir. You can then open that up and modify it while the server from the second command is up.
- It’ll be hard to know what to change, so here’s a few quick things to try:
Comment out the
"theme": "dark"
line (Refresh your browser so the CSS updates!)Comment out and of the pages from the descriptor top-level
Reorder the layouts
Comment out any bank
Disconnect a bank by removing it from the
connections
Fallback¶
At any point, if you’ve started an app with Bento but can’t figure out how to
go further, you can always continue by editing the base app code. After either
of the last steps, you will have a bento_app.py
file plus associated files
in assets/
(check the log output while running Bento). These can be edited,
and then you can run the changes with:
$ python3 bento_app.py
Note
Bento does make use of several utility functions included in the bento Python package. To fully be independent, you’d need to copy those over as well.
Bento Builder¶
If you’ve jumped through the above hoops and are looking for a way to develop efficiently, I recommend cloning/templating from the Bento Builder repo! Some quickstart steps are available in the readme.
Concepts within Bento¶
Data¶
Everything begins with data, and Bento doesn’t try to assist with the data processing. It is assumed you will supply your dashboard with prepared data in a pandas DataFrame. This helps decouple your visualization code from any data preparation code. However, Bento does inform the dashboard by what’s in the data. For example, dropdown menus for filters can be automatically populated by what is in the dataframe.
You define all datasets used in the dashboard up front in the “data” key of the descriptor.
Each dataset needs a unique ID (uid) and a module that contains the code to load it.
In the Simple example, we loaded the data by the uid “covid” via the module
bento.sample_data.covid
which contains a method “load” that returns an object containing the dataframe.
In your generated Bento app, the data is stored in a global variable and accessed by key. This tends to be simplest at the page level, through the “dataid” key. This can be overridden at the bank level, however, if needed.
Pages¶
To Bento, a page is just about what you’d expect: everything associated with a given URL. Bento tries to make page navigation easy out of the box, with an autogenerated appbar. The downside to this: you still have to define your “pages” even if you want a single-page app.
Overall, the page definition is rather easy–each page needs a unique ID. The main descriptor “pages” key defines a dictionary with these unique IDs as keys and the individual page descriptors as values.
The Simple example demonstrates a single page while the Demo has multiple.
Banks¶
The banks are the building blocks of your Bento app.
You define your banks under a page’s “banks” key, each with a uid and a dictionary containing at least the “type” key, which would be chosen from the list below.
Connections¶
One of the trickiest parts of making an interactive dashboard in Plotly Dash is getting components to properly work together. At the same time, it’s one of the areas which tends to be the most repetitive once you understand it. This is a big part of what makes Bento useful. Bento abstracts away any need for writing callbacks by defining “connections”: which banks feed which other banks. If you want a bank to affect another one, connect them.
In a page descriptor, the “connections” key is just the set of “source” bank uids which defines, for each, the set of “sink” bank uids. In the Simple example, our axis_controls named “axis” feeds our graph called ”trend”.
Layout¶
This is the easiest part. Just write a 2D-list of the bank ids, imagining a grid. Each page descriptor should have this in a key “layout”, such as in Simple where we have two rows, one bank per row.
Bento uses the CSS Grid system to size the banks, and also comes with a sense of how big each bank should be depending on its inputs. For example, an axis_control bank might be 2 rows by 2 columns (2x2) for 1 axis, and it will try for 2x6 if you have 3 axes defined. If you try to pack in a lot in a row, Bento will trim from each until they fit.
Beginner Tutorial¶
This is intended as a fast, but not too furious, introduction to the Bento system. You’re going build a functional, interactive dashboard in a matter of minutes. Not to worry, you will be handed all of the pieces you need with instructions on where they go and a brief explanation of how the pieces fit. And, each step will give a visual result which you can check against some of the checkpoint images. The goal is to convey the fundamentals of the system without going into details.
As a straightforward example, let’s build a dashboard that visualizes historical stock
prices for a few large tech companies. As perhaps one of the most commonly experienced
chart types in the world, most people should have some idea of what to expect here. You
can see what we’re gonna end up with by running $ bento-demo
and clicking to the
stock page on the app bar. (Make sure you’ve followed the Bento Quickstart)
- We can break this project up into four stages:
Plan the basic features
Prepare the data we are using
Set up our Bento dev environment
Write the descriptor (which we’ll break out into pieces)
Note
For the foreseeable future (how long is that, really?), only ‘Nix environments are gonna be supported (namely Mac and Linux) so we’ll take some liberties in assuming that.
Features¶
What should our dashboard do? It’s a tough question if you are trying to predict the end product–there are a lot of unknowns (Who is the target user? What interactions will work well? Will the dashboard get cluttered? What does Bento even support doing?) so let’s just try to start with a good first draft. We’d like to at least be able to plot comparative traces of stock gains. For this, we’ll need the ability to select the ticker symbols to show. A way to choose a period in time for comparison would also be useful. Lastly, we would also need a way to normalize the prices into percentage gains from the start of our interval.
- This translates into 4 total Bento banks:
A
selector
– a multi-selection dropdown tied stock ticker symbolsA
date_control
– a two-sided slider bar, letting one choose a date intervalAn
analytics_set
– a toolkit that contains a normalization optionLastly, a standard
graph
for the plotting of the time series
Data¶
Bento leaves the data preparation to the user, by design. But for this example, we will simply use the sample stock dataset packaged with Bento (I suppose it is a stock stock dataset…apologies). Not only is this data prepared, but it is also supplied in the expected structure:
{
"df": your_dataframe_object,
"keys": ["key_column_1", "key_column_2"],
"types": {"measurement_column_1": int}
}
The structure above contains the data as a Pandas DataFrame and adds descriptive info: identifying the columns that are keys (which help locate rows of interest) and for those that contain metrics with their types (usually date, float, int). The metadata helps Bento supply automated defaults, helping ensure your app works right away. For example, sample stock data metadata looks something like
{
"df": <the DataFrame object>,
"keys": ["symbol"],
"types": {"open": float, "close": float, "volume": int}
}
Can you guess how Bento incorporates this?
Note
Exploring a sample dataset is straightforward, for example try:
>>> from bento.sample_data import stock
>>> dataset = stock.load()
>>> print(dataset['df'])
Ultimately, Bento requires a module that can be imported to load the data. So in
this case that is bento.sample_data.stock
, which we’ll use shortly!
Environment¶
Now that you’re palpably excited from visualizing the dashboard, it’s best to channel some
of that energy into setting up your development environment. This will save a lot of
time and headache down the road. I recommend going through the Quickstart steps
at the Bento Builder repo.
Make sure $ ./build.py simple_example -dbu
works, because we’ll be using the
same flags.
Once that is done, make a new directory called beginner and open a new file
descriptor.py
for editing in your favorite editor:
$ mkdir beginner
$ vi beginner/descriptor.py
Note
If you want some extra credit, add a version file to avoid a later warning
$ echo ‘__version__ = “0.0.1”’ > beginner/_version.py
Descriptor¶
And now for the main course, let’s write the Bento descriptor. This is the piece that really ties the room together.
Step 1¶
We’re going to start with the most basic, functional skeleton to start. This entails declaring the data source (the stock sample data, as above) and defines a single page containing a single bank (of type “graph”).
# beginner/descriptor.py
descriptor = {
"data": {"stock": {"module": "bento.sample_data.stock"}},
"pages": {"main_page": {"dataid": "stock", "banks": {"traces": {"type": "graph"}}}}
}
This should get us a graph that displays our DataFrame blindly. Go ahead and paste that into the descriptor file. Now you can run the build script:
$ ./build.py beginner -dbu
And if all is good, you can go to localhost:7777 and see something (ugly) like this:

Don’t worry, this will clean up pretty quick.
- So here’s what’s important to know about what we did:
We named the key in
data
something unique and relevant (“stock”)The key matches the value of the
dataid
for our pageThe
module
for our data entry is set to the sample dataOur page has a
banks
key with a valid dictionary of our single bankThe
main_page
andtraces
strings just represent unique names we can make up
Step 2¶
- Let’s add a few quick aesthetic improvements:
Add an
appbar
to the descriptor, which sets a title/subtitle and contains any nav linksChange the theme to
dark
Set the graph trace mode to lines
Also let’s break out the page definition from the main dictionary
Try them in any order by looking at the completed version below:
# beginner/descriptor.py
main_page = {
"dataid": "stock",
"banks": {
"traces": {"type": "graph", "mode": "lines"},
},
}
descriptor = {
"name": "beginner_tutorial",
"theme": "dark",
"appbar": {
"title": "Tech Stock Prices",
"subtitle": "A simple Bento starting point",
},
"data": {"stock": {"module": "bento.sample_data.stock"}},
"pages": {"main": main_page},
}
The full result should look like:

Step 3¶
Now let’s start cooking with gas. First, we’ll add all the banks we had planned. Simply
add these lines to the banks
dict:
"analytics": {"type": "analytics_set"},
"interval": {"type": "date_slider", "variant": "range"},
"symbols": {"type": "selector", "columns": ["symbol"]},
You should now get some new blocks showing up, but they aren’t very well-organized. As in, they are just stacked on top of each other, rather lazily. We can fix that by supplying a layout. This should be intuitive, just add this to the page dict and see if it makes sense:
"layout": [["symbols", "interval", "analytics"], ["traces"]],
Currently, Bento expects a 2-D array of bank IDs, but a generalization to N-D could be in the cards.
Perhaps you’re also frustrated that these don’t do anything yet. That’s because we haven’t told the app to connect the banks. This part is, I think, delightfully straightforward–just define the many-to-many graph of connections between banks. In this case, it’s just the following new dictionary keyed into the page:
"connections": {
"analytics": {"traces"},
"interval": {"traces"},
"symbols": {"traces"},
},
Now stuff should happen. And there was much rejoicing. If not, double-check against the full descriptor below
See the full descriptor
main_page = {
"dataid": "stock",
"banks": {
"traces": {"type": "graph", "mode": "lines"},
"analytics": {"type": "analytics_set"},
"interval": {"type": "date_slider", "variant": "range"},
"symbols": {"type": "selector", "columns": ["symbol"]},
},
"layout": [["symbols", "interval", "analytics"], ["traces"]],
"connections": {
"interval": {"traces"},
"symbols": {"traces"},
"analytics": {"traces"},
},
}
descriptor = {
"name": "beginner_tutorial",
"theme": "dark",
"appbar": {
"title": "Tech Stock Prices",
"subtitle": "A simple Bento starting point",
},
"data": {"stock": {"module": "bento.sample_data.stock"}},
"pages": {"main": main_page},
}

API Reference¶
Bento class¶
The fundamental Bento class that ties all of Bento’s functionality together.
-
class
bento.bento.
Bento
(descriptor: Dict, init_only: bool = False)[source]¶ Acts as the gateway to all Bento functionality.
- descriptordict
The descriptor contains all of the user-supplied information defining the desired dashboard. See the user guide for creating a descriptor.
- init_onlybool
Supply True in order to halt the automatic processing of the descriptor. This can be useful for debugging or modifying the standard app-creation process.
-
desc
¶ This stores the normalized version of the input descriptor.
- Type
dict
-
data
¶ Contains the data as a dictionary. TODO
- Type
dict
-
valid
¶ Whether the descriptor meets the schema. If False, will block processing.
- Type
bool
-
context
¶ Specifies the app for consumption by a Jinja template.
- Type
dict
Examples
- Simple:
>>> bento = Bento(my_descriptor) >>> bento.write()
- Advanced:
>>> bento = Bento(my_descriptor, init_only=True) >>> test_page = bento.desc["pages"]["test"] >>> bento.create_page("test", test_page) >>> bento.context["pages"]["test"] = alternate_grid_method(test_page) >>> bento.connect_page(test_page) >>> bento.write(app_output="modified_test_layout.py")
-
connect_page
(page: Dict)[source]¶ Applies the requested connections for the page to the Jinja context
A page is a set of connected banks, and the connections are defined by a supplied directed graph (dict of sets) of bank_ids. For example, {‘axes’: {‘map’, ‘counter’}, ‘colors’: {‘map’}} tells us the axes bank should feed both the map and counter banks, while our colors bank should feed just the map.
-
create_page
(pageid: str, page: Dict)[source]¶ Generates and lays out all banks defined for a page and updates the context
A page is composed of a set of banks, arranged based on a supplied layout object (an array of 2+ dim).
-
is_valid
(descriptor: Dict) → bool[source]¶ Ensures the descriptor meets the Cerberus schema (see schema.py)
Descriptor schema¶
- To get a quick sense of the schema, I recommend looking at the following examples:
A bare-bones dash - Run
python3 -m bento.dashboards.simple
thenpython3 bento_app.py
to viewThe demo dash - Run
bento-demo
to see it in action
The full schema follows, which, for now, is a simple dump of the Cerberus schema:
# Page and bank ids, are strings of word characters only with length 2+
# No trailing, leading, or double underscores
bento_uid_regex = r"^(?!_|.*_$|.*__.*)[a-z0-9_]{2,}$"
# One or more words separated by a space
words_regex = r"^\w+( \w+)*$"
page_schema = {
"banks": {
"type": "dict",
"required": True,
"allow_unknown": True,
"minlength": 1,
"keysrules": {"type": "string", "regex": bento_uid_regex},
"valuesrules": {
"type": "dict",
"allow_unknown": True,
"schema": {
"type": {"type": "string", "required": True, "regex": bento_uid_regex},
"width": {"type": "integer"},
"args": {"type": "dict"},
},
},
},
"connections": {"type": "dict"},
"dataid": {"type": "string"},
"layout": {"type": "list"},
"sidebar": {"type": "list"},
"title": {"type": "string"},
"subtitle": {"type": "string"},
}
descriptor_schema = {
"name": {"type": "string"},
"theme": {"type": "string", "regex": words_regex},
"theme_dict": {"type": "dict"},
"appbar": {
"type": "dict",
"schema": {
"title": {"type": "string"},
"subtitle": {"type": "string"},
"image": {"type": "string"},
},
},
"data": {"type": "dict"},
"show_help": {"type": "boolean"},
"pages": {
"type": "dict",
"allow_unknown": True,
"required": True,
"minlength": 1,
"keysrules": {"type": "string", "regex": bento_uid_regex},
"valuesrules": {"type": "dict", "schema": page_schema},
},
}