Overview¶
The entire functionality of dash_building_blocks
drives on the
Block
and
Store
classes.
Block¶
The Block
is the basic “building unit”
of object-oriented Dash code.
Inheriting Block
provides us with some
simple but useful tools for
creating a custom “block” class that encapsulates a layout of components
and any relevant callbacks.
Note that the Block
class itself is an abstract class and cannot be
instantiated.
>>> import dash_building_blocks as dbb
>>> block = dbb.Block()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
...
NotImplementedError
This is because all Block
-inheriting
classes require at minimum a
layout
method to be defined. At initialization, whatever is returned by
the defined layout
method will become the permanent layout
attribute
of the instantiated block object. This is a purposefully restricting feature
that ensures that any Dash component ids defined within the block layout
are globally unique. More on that later.
Let’s take a look at a minimal block implementation:
import dash_html_components as html
import dash_building_blocks as dbb
class MyBlock(dbb.Block):
def layout(self):
return html.Div('hello world')
We can then instantiate it:
block = MyBlock()
print(block.layout)
Output:
Div('hello world')
As you can see, the html.Div('hello world')
we implemented
the MyBlock.layout
method to return became the layout
attribute at
initialization of Block
. We could
potentially assign this simple div as
the layout of a Dash
app object:
import dash
app = dash.Dash()
app.layout = block.layout
if __name__ == '__main__':
app.run_server()
Ok, but so what? We aren’t being very productive here. We just used a few extra lines of code (to define MyBlock) yet did not accomplish anything extra … Let’s look at a slightly more productive example.
Say we want to create a block of components and callbacks that will be
repeated a few times. In this case let’s make a simple Parrot
block
that repeats whatever a Human
says.
First, let’s try this idea with pure Dash:
import dash
from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
app = dash.Dash()
human_div = html.Div('Hello World', id='human-says')
parrot_div = html.Div(id='parrot-says')
app.layout = html.Div(
[human_div, parrot_div]
)
@app.callback(
Output('parrot-says', 'children'),
[Input('human-says', 'children')]
)
def update_what_parrot_says(whatever_human_says):
return whatever_human_says
if __name__ == '__main__':
app.run_server()
...
This is easy enough. But there is only one parrot. What if we want more parrots? In order to keep the code DRY, we can do something along these lines:
app = dash.Dash()
human_div = html.Div('Hello World', id='human-says')
def create_parrot(name):
# create div
parrot_id = 'parrot-{}-says'.format(name)
parrot_div = html.Div(id=parrot_id)
# create dependencies
dependencies = {
'output': Output(parrot_id, 'children'),
'inputs': [Input('human-says', 'children')]
}
# define callback function
def callback_f(whatever_human_says):
return '{} says: {}'.format(name, whatever_human_says)
return {
'div': parrot_div,
'dependencies': dependencies,
'callback': callback_f
}
parrot_names = ['iago', 'zazu', 'skully']
parrots = [create_parrot(name) for name in parrot_names]
app.layout = html.Div(
[human_div] + [parrot['div'] for parrot in parrots]
)
for parrot in parrots:
app.callback(**parrot['dependencies'])(parrot['callback'])
This is not too bad but our code is starting to be less readable and we need to use string formatting to ensure that Dash component ids are all globally unique; as a project becomes large and complex this can be a daunting task.
Let’s run with this idea but instead leverage dash_building_blocks
.
There will only be one Human
block, so we don’t need to worry about its
reusability. Still we can use the dbb.Block
to encapsulate the Human
-coupled components, for organization and readibility sakes; as well as the
possibility that we will extend its functionality in the future with, say,
Human
-coupled callbacks.
Let’s define our Human
block class.
class Human(dbb.Block):
def layout(self):
return html.Div('Hello World', id=self.register('says'))
Note the use of self.register('says')
. Inherited from
Block
,
this function allows us to define a localized id, which is created, stored
internally, and returned by the function for convenience. Behind the scenes,
every Block
subclass object maintains
a mapping of localized id to its
globally unique counterpart. This means don’t have to worry about global ids
getting mixed up (unless we explicitly mess them up). More on that later, but
for now, just know that self.register('says')
will return an id like
“human-<id>-says”, where id is a random alphanumerical string by
default unless explicitly specified during block initialization.
Now let’s define our Parrot
block class.
class Parrot(dbb.Block):
def layout(self):
return html.Div(id=self.register('says'))
def callbacks(self, human):
@self.app.callback(
self.output('says', 'children'),
[human.input('says', 'children')]
)
def update_what_i_say(whatever_human_says):
return '{} says: {}'.format(self.data.name,
whatever_human_says)
Because all parrots should have the ability to repeat what some human says,
we defined a callbacks
method that expects as input a Human
block
and creates the appropriate callback. You may have noticed that self.app
and self.data
were used and wondered where they came from. These will be
available as we will pass them as arguments when initializing the block.
You may also have noticed the self.output
and human.input
calls.
These convenience methods are inherited from
Block
and return the
Dash dependency respective to the localized component id and property
provided. To illustrate, let’s quickly use the MyBlock
we
implemented earlier:
>>> block = MyBlock()
>>> block.layout
Div('Hello World')
>>> block.id
'my-block-ze7V9nTWCJ6thubV'
>>> block.register('helloworld')
'my-block-ze7V9nTWCJ6thubV-helloworld'
>>> dep = block.input('helloworld', 'children')
>>> dep
<dash.dependencies.Input at 0x11dec14e0>
>>> dep.component_id
'my-block-ze7V9nTWCJ6thubV-helloworld'
>>> dep.component_property
'children'
>>> block.output('helloworld', 'children')
<dash.dependencies.Output at 0x11dec1518>
>>> block.state('helloworld', 'children')
<dash.dependencies.State at 0x11dec12e8>
See the API documentation for more detail.
With our Human
and Parrot
block classes defined, we can put them in
action. We must make sure that we pass in data={'name': name}
when
initializing our Parrot
s so that self.data.name
is available as
expected in our definition of the parrot update_what_i_say
callback.
Let’s create the app:
app = dash.Dash()
human = Human()
parrot_names = ['iago', 'zazu', 'skully']
parrots = [Parrot(app=app, data={'name': name})
for name in parrot_names]
app.layout = html.Div(
[ human.layout ] + [ parrot.layout for parrot in parrots ]
)
for parrot in parrots:
parrot.callbacks(human)
And run it:
if __name__ == '__main__':
app.run_server()
The high-level definition of the app is now decoupled from the block-level definitions, improving readibility.
Store¶
Let’s extend our simple human & parrot app to be a little more dynamic. Instead of the human saying a static phrase that all parrots repeat, let’s say that we want the human to select a parrot and command it (and not the others) to repeat his current phrase. A good approach here may be to package the selected parrot name and current human phrase into a hidden div that acts as intermediate data storage. Sharing data between callbacks using hidden divs is very common and suggested in the Dash user guide.
To streamline the creation and use of these hidden storage divs,
dash_building_blocks
provides the Store
class. Its interface
is similar to that of Block
s but
slightly
different due to its more specific function. It is hidden by default but may
be made visible by specifying the hide=False
option at initialization,
which can come in handy when debugging.
Before we get to using the Store
, let’s
refactor what we already have as necessary.
First, the Human
. Instead of holding a static phrase, we want the human
to select between available parrots and have an editable phrase; a
dropdown, text-input, and submit button will do the trick.
class Human(dbb.Block):
def layout(self):
return html.Div([
dcc.Dropdown(
options=self.data.options,
id=self.register('select')
),
dcc.Input(id=self.register('says')),
html.Button('Speak!', id=self.register('command-button'))
])
Next, the Parrot
. Let’s put the object-orientation to work and create
a self.template
attribute to hold the template for the parrot’s message;
that way we can set it in layout
during initialization and have it
available when updating in the callback as well (keeping things DRY). Since
we are planning on getting our callback dependency data from some hidden
storage div, we change the Parrot.callbacks()
parameter to some
intermediate_input
that we will expect to be of type dash.dependencies.
.Input
.
# we use these in the callback, make sure we import them
from dash.exceptions import PreventUpdate
import json
class Parrot(dbb.Block):
def layout(self):
self.template = self.data.name + ' says: {}'
says = self.template.format('squawk!')
return html.Div(says, id=self.register('says'))
def callbacks(self, intermediate_input):
@self.app.callback(
self.output('says', 'children'),
[intermediate_input]
)
def update_what_i_say(intermediate_data):
intermediate_data = json.loads(intermediate_data)
if intermediate_data['name'] == self.data.name:
return self.template.format(intermediate_data['say'])
else:
raise PreventUpdate
Note that this means Parrot could easily integrate with raw dash
code
now as it no longer relies on another block as previously. For example:
...
parrot.callback(Input('intermediate_div', 'children'))
...
This is the kind
of design choice that is completely up to the user; the goal of
dash_building_blocks
has always been for it to work with dash
without pigeonholing the user into a specific design framework. In fact,
throughout this user guide we
will use callbacks
as the standard name for the function where we define
a block’s callbacks, but this is nowhere set in stone. Besides the required
layout
method, how you define the block class is completely up to you.
Back to our refactoring. Using the store object and the modifications to our little parrots example, we can achieve the extra dynamic functionality while keeping our code organized and clean.
app = dash.Dash()
store = dbb.Store(app)
store.register('human-command')
parrot_names = ['iago', 'zazu', 'skully']
options = [{'label': name, 'value': name}
for name in parrot_names]
human = Human(data={'options': options})
parrots = [Parrot(app=app, data={'name': name})
for name in parrot_names]
app.layout = html.Div(
[
human.layout
] + [
parrot.layout for parrot in parrots
] + [
store.layout
]
)
@app.callback(
store.output('human-command'),
inputs=[human.input('command-button', 'n_clicks')],
state=[human.state('select', 'value'),
human.state('says', 'value')]
)
def command_parrot(n_clicks, selected_parrot, what_to_say):
return json.dumps({'name': selected_parrot,
'say': what_to_say})
for parrot in parrots:
parrot.callbacks(store.input('human-command'))
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})
if __name__ == '__main__':
app.run_server()
Important to note is the use of store
. We first registered
'human-command'
so the store
knew to create the respective hidden
storage div. We then assigned a callback to update human-command
without specifying the ‘children’ property in
store.output('human-command')
because unlike blocks, the store
depedencies always contain the 'children'
implictly.
Note
Block
depedencies do default
the component_property to 'children'
if not provided, though the
pythonic way is to be explicit.