Best practices in Dash

Hi,

I am new to using Dash and Python to build web apps, can anyone suggest what is the best practice if I wish to separate a callback function from my pages (UI functions/files) by creating a new folder called “callbacks” and within that folder, I have for example a file called “a_callback.py” which I want to contain all callbacks and “backend” functions regarding the a.py UI file. E.g. in the a.py file I have a DateTime input where when user selects it, I want to save the selected value in a_callback.py and then call a function to load data.

Thanks

The way I do it, is as follows

Project_dir
|- backend
| |- ...
|- UI
  |- assets
  |- pages
  |  |- <dir_for_page_a>
  |  |  |- <dir_for_some_subpart>
  |  |  |- __init__.py
  |  |  |- layout_a.py
  |  |  |- callbacks_a.py
  |  |  |- ...
  |  |- <dir_for_page_b>
  |  |  |- ...
  |  |- __init__.py
  |  |- ...
  |- __init__.py
  |- app.py (containing main container)
  |- ...

This structure gives you a lot of freedom to nest everything as much as needed. If you have a small page, you can simply create a single file under the pages directory. More complex pages are created in its own directory. This will allow you to split the logic over multiple files but still keep it close to where it is used. You can then create subdirectories where needed. For example, when building page a, you put the layout in layout_a.py and put the general callbacks in callbacks_a.py. If you need a set of specific utility functions, you can create files or subdirectories for those.

When building components, there are three possible approaches that I take.

  1. Small components that don’t need specific logic and where I just need to define the component properties, like my_alert = dmc.Alert(id=''...", children=[]), I would put in a components.py file next to the layout. The layout can import the components from this file. This keeps the layout clean and focused on the high-level structure, rather than the specifics for each component
  2. Medium components, that are potentially build up from multiple components and/or where I would like to use multiple functions to render all the parts for the component. I create an individual file for this component. Whenever there are callbacks that only affect the internal components, I will include them in this file. Make sure to import the callbacks in the layout file, so they are registered.
  3. Large components. If you need more complexity than this, then you can also create a directory and split the component logic (and internal callbacks) in separate files.

One thing I have been applying for this approach is to create a function register_callbacks() and nest the callbacks under this function. This allows me to import all the files without registering the callbacks automatically. For example, I might have some utility functions that I want to use elsewhere. This will allow me to use them without registering the callbacks.
Registering the callbacks is done by calling the register_callbacks() function, so I fully control when the registering happens.

# callbacks.py
def register_callbacks():
    @callback(...)
    def callback_a(): ...

    @callback(...)
    def callback_b(): ...

and in layout.py

import callbacks

callbacks.register_callbacks()

def layout():
    return html.Div()

If you have more complex backend logic, I would put that logic in a separate folder next to the UI folder. This helps splitting the frontend logic from non-frontent logic.

On the other hand, if it is logic that is used by multiple pages, you put the logic in a folder next to pages, and import from there.

Hope this helps a bit. Let me know if you have more questions.

1 Like

Thanks Tobs! Just a question, will this impact the duration of the callbacks (i.e. make everything slower)? And by any chance, would you be able to give a sample for init.py, layout_a.py, and callbacks_a.py? That would be really helpful. Thank you

What do you mean by making callbacks slower? When initializing the server, Dash will register all callbacks in an internal register. Then it will pass a list of callback signatures to the browser, a mapping of sorts telling the browser for all callbacks what the inputs and outputs are. The browser will then monitor the inputs, and if one input is changed, the browser will collect the data stated in the callback input signature and send that to the server. Next, when the server returns an answer, the browser checks the mapping to understand which components were listed in the output. It will then update those properties with the newly received data.

So, however you arrange your callbacks in your code base would potentially only influence the initialization time, but arranging them in functions is not the bottleneck. It’s more important that they are called during initialization.

Under basic usage, the __init__.py files are empty, and only under specific conditions would I put something in here. with regards to samples for layout.py and callbacks.py, the examples in my post above are good samples. If you are looking for a small demo app, I would recommend visiting the Dash documentation and to pick any example that you come across. Actually, this is a nice exercise. Set up a basic folder structure, pick one example on the documentation pages and convert it into the above structure.

Hope that helps. You can always reply if you have are facing a more specific problem. If you can’t make it work, one suggestion would be to make a small demo github repository, and share it here. Then I can see if I can help you that way.

1 Like