How to define callbacks in separate files? (plotly dash) - python

Background
Dash web applications have a dash application instance, usually named app, and initiated like this:
app = dash.Dash(__name__)
Then, callbacks are added to the application using a callback decorator:
#app.callback(...)
def my_function(...):
# do stuff.
In most of the tutorials you find, the callbacks are defined with all of the application layout in the app.py. This of course is just the MWE way of doing things. In a real application, separating code to modules and packages would greatly improve readability and maintainability, but naively separating the callbacks to and layouts just results into circular imports.
Question
What would be the correct way to separate callbacks and layouts from the app.py in a single page app?
MWE
Here is a minimal (non-)working example with the problem
File structure
.
├── my_dash_app
│   ├── app.py
│   └── views
│   ├── first_view.py
│   └── __init__.py
└── setup.py
setup.py
import setuptools
setuptools.setup(
name='dash-minimal-realworld',
version='1.0.0',
install_requires=['dash>=1.12.0'],
packages=setuptools.find_packages(),
)
app.py
import dash
from my_dash_app.views.first_view import make_layout
app = dash.Dash(__name__)
app.layout = make_layout()
if __name__ == '__main__':
app.run_server(debug=True)
first_view.py
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
from my_dash_app.app import app
def make_layout():
return html.Div([
dcc.Input(id='my-id', value='initial value', type='text'),
html.Div(id='my-div')
])
#app.callback(Output(component_id='my-div', component_property='children'),
[Input(component_id='my-id', component_property='value')])
def update_output_div(input_value):
return 'You\'ve entered "{}"'.format(input_value)
Running python ./my_dash_app/app.py results into circular dependency:
ImportError: cannot import name 'make_layout' from 'my_dash_app.views.first_view' (c:\tmp\dash_minimal_realworld\my_dash_app\views\first_view.py)

I don't think (but I might be wrong) that there's a correct way of doing it per se, but what you could do it have a central module (maindash.py) around your startup code app = dash.Dash(__name__), and have different callbacks simply import app from my_dash_app.maindash. This would set up the callbacks in their own separate modules but re-use that one central module for the app instance.
It's easiest to show an overview of it like this:
app.py being the main script called to start everything up. maindash.py is in charge of creating the main app instance. first_view.py is where the decorators are defined to set up all the callbacks.
Here's the result:
.
├── my_dash_app
│ ├── app.py
│ ├── maindash.py
│ └── views
│ ├── first_view.py
│ └── __init__.py
└── setup.py
Since imports are re-used in Python, there's no real harm in doing from my_dash_app.maindash import app several times from different other modules, such as event handlers and the main script. They'll share the same import instance - thus re-using the dash.Dash() instance as well.
Just make sure you import the central module before setting up the handlers, and you should be good to go.
Here's the code snippets separated for testing:
app.py
from my_dash_app.maindash import app
from my_dash_app.views.first_view import make_layout
if __name__ == '__main__':
app.layout = make_layout()
app.run_server(debug=True)
maindash.py
import dash
app = dash.Dash(__name__)
first_view.py
from my_dash_app.maindash import app
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
def make_layout():
return html.Div([
dcc.Input(id='my-id', value='initial value', type='text'),
html.Div(id='my-div')
])
#app.callback(Output(component_id='my-div', component_property='children'),
[Input(component_id='my-id', component_property='value')])
def update_output_div(input_value):
return 'You\'ve entered "{}"'.format(input_value)

A little late to the party but I have found this to be a straightforward way of doing it:
You create a separate script called 'callbacks.py' (for example)
Define a function within callbacks.py which takes a dash.Dash object (i.e. the app) as a parameter (you can of course pass more arguments if necessary) and within which you define all your callbacks as you normally would in the main script:
def get_callbacks(app):
#app.callback([Output("figure1", "figure")],
[Input("child1", "value")])
def callback1(figure):
return
#app.callback([Output("figure2", "figure")],
[Input("child2", "value")])
def callback2(figure):
return
Within the main script, simply import the function and call it after instantiating the dash.Dash object passing the same object into it:
import dash
from callbacks import get_callbacks
import layout
app = dash.Dash(__name__)
app.layout = layout.layout
get_callbacks(app)

I know it is too late to answer your question here, but maybe someone else will find it useful.
I wanted to be able to create callbacks in separate files, however I think that although importing an app from main dash module works well, it may be unclear for other people who read the code.
I created a callback manager used to initialize callbacks. This manager is attached to an app in the main app module.
callbacks_manager.py
from dataclasses import dataclass, field
from typing import Callable, List, Union
from dash.dependencies import handle_callback_args
from dash.dependencies import Input, Output, State
#dataclass
class Callback:
func: Callable
outputs: Union[Output, List[Output]]
inputs: Union[Input, List[Input]]
states: Union[State, List[State]] = field(default_factory=list)
kwargs: dict = field(default_factory=lambda: {"prevent_initial_call": False})
class CallbackManager:
def __init__(self):
self._callbacks = []
def callback(self, *args, **kwargs):
output, inputs, state, prevent_initial_call = handle_callback_args(
args, kwargs
)
def wrapper(func):
self._callbacks.append(Callback(func,
output,
inputs,
state,
{"prevent_initial_callback": prevent_initial_call}))
return wrapper
def attach_to_app(self, app):
for callback in self._callbacks:
app.callback(
callback.outputs, callback.inputs, callback.states, **callback.kwargs
)(callback.func)
callbacks.py
import dash
from callback_manager import CallbackManager
callback_manager = CallbackManager()
#callback_manager.callback(
dash.dependencies.Output('label', 'children'),
[dash.dependencies.Input('call_btn', 'n_clicks')])
def update_label(n_clicks):
if n_clicks > 0:
return "Callback called!"
app.py
import dash
import dash_html_components as html
from callbacks import callback_manager
app = dash.Dash(__name__)
callback_manager.attach_to_app(app)
app.layout = html.Div([
html.Div(id="label"),
html.Button('Call callback', id='call_btn', n_clicks=0),
])
if __name__ == '__main__':
app.run_server(debug=True)
Note that you can have multiple files with callbacks and import them with as keyword:
from callbacks1 import callback_manager as callback_manager1
from callbacks2 import callback_manager as callback_manager2
app = dash.Dash(__name__)
callback_manager1.attach_to_app(app)
callback_manager2.attach_to_app(app)
I believe doing it this way is more explicit.

You can just use the decorator #dash.callback instead of #app.callback. Then remove the line from my_dash_app.app import app in first_view.py and you'll get rid of the circular dependency.
From the documentation: #dash.callback is an alternative to #app.callback (where app = dash.Dash()) introduced in Dash 2.0. It allows you to register callbacks without defining or importing the app object. The call signature is identical and it can be used instead of app.callback in all cases.

This is a rather late response to an older post, but here's a very simple way to completely separate layout, callbacks, and app, without introducing additional complexity.
app.py
from dash import Dash
from layouts import my_layout
from callbacks import my_callback, my_callback_inputs, my_callback_outputs
if __name__ == "__main__":
app = Dash(__name__)
app.layout = my_layout
app.callback(my_callback_outputs, my_callback_inputs)(my_callback)
app.run_server()
It works by not using the decorator syntax, which is supposed to be "syntactic sugar" to make things simpler. However, for this specific problem, many of the proposed solutions actually increase coupling and complexity while retaining the decorator syntax. In this case, it's simpler just not to use the decorator.
In callbacks.py, my_callback is defined without any decorator:
callbacks.py
my_callback_inputs = []
my_callback_outputs = []
def my_callback():
return

Related

Properly log/display error from callbacks when Dash is running within Flask

I am working on a Flask application that uses multiple Dash dashboards. I've been able to set up a multi-page Dash application in Flask. This required a lot of extra steps to do so. I am using a Blueprint, as well as some other atypical steps for a Dash application.
The problem I am trying to replicate in this dummy example (see below) is how to properly log errors in a callback when dash is being used within Flask. If I were to strip out some of the Flask parts to this application, the debug environment in Dash will catch the callback error (test_me). Currently, I have not found a solution to correctly trigger errors when I am embedding a Dash application within Flask.
I have tried doing try...Except around parts of the code, as well as tinkering with the .enable_dev_tools() to no avail. It seems like putting the callbacks within a function is the crux of why errors are not being propagated as one would expect. I can explain why the code has to be structured this way.
I am using dash = 2.0.0 and flask = 2.0.2. This is being run in a Conda environment that is using Python 3.9.7.
I looked around googling and some other questions on SO but could not find something helpful enough. Please let me know if my example is not reproducible or if I need more information.
import dash
from dash import html, dcc
import pandas as pd
from werkzeug.serving import run_simple
from flask import (Flask, Blueprint)
server_bp = Blueprint('main', __name__)
bottom_thing = html.Div(
[dcc.Dropdown(id='filter',options = [{'label':'temp','value':'temp'},
{'label':'not_temp','value':'not_temp'}],
value = 'temp'),
html.H6(id = 'made-up-text',children = [])]
)
main_page = html.Div(children = [
html.H1(children = 'Dash Example'),
bottom_thing
])
def dumb_function(x = 1):
return x
##NOTE: This should fail as I am passing an extra argument to it.
def generate_callbacks(app):
#app.callback(dash.dependencies.Output('made-up-text','children'),
dash.dependencies.Input('filter','value'))
def test_me(filter_val):
dumb_function(x=100, y = 100)
if filter_val == 'temp':
return 'This is a temp string'
return 'This is not a temp string'
return app
def create_layout(app:dash.Dash, main_dash_path = '/'):
'''
Returns the view for a specific url. Note that the pathnames must match
the navbar defined elsewhere.
Parameters: pathname (str). Pathname is passed through via a callback.
not explicitly passed
Returns: A view which is a html.Div object
'''
# Update app layout
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
#Add path names for the particular view.
#app.callback(dash.dependencies.Output('page-content', 'children'),
[dash.dependencies.Input('url','pathname')])
def display_page(pathname):
'''Returns the view for a specific url.'''
if pathname == main_dash_path:
return main_page
callbacks = generate_callbacks(app)
return app.layout
##Configure the various layouts here
def register_dashapps(app: Flask, title:str, url_pathname = '/'):
dashapp = dash.Dash(__name__,
server=app,
url_base_pathname=url_pathname)
with app.app_context():
dashapp.title = title
dashapp.layout = create_layout(dashapp)
##NOTE: This is only for development
dashapp.enable_dev_tools(debug=True)
def register_blueprints(server):
server.register_blueprint(server_bp)
def create_app():
server = Flask(__name__)
register_dashapps(server, title = 'wee')
register_blueprints(server)
return server
if __name__ == '__main__':
server = create_app()
run_simple('127.1.1.1', 1111, server, use_reloader=True, use_debugger=True)

How do I use Flask.route with Flask

A brief introduction to the directory structure is as follows:
__init__.py contain the application factory.
page.py
from app import app
# a simple page that says hello
#app.route('/hello')
def hello():
return 'Hello, World!'
app.py
from flaskr import create_app
app = create_app()
if __name__ == '__main__':
app.run()
When I start the server and go to the '/hello', it says 404.
What's the problem?
Here is a short solution which should run.
page.py
from flaskr import app
#app.route('/hello')
def hello():
return 'Hello'
__index__.py
from flask import Flask
app = Flask(__name__)
from flaskr import page
app.py
from flaskr import app
To run this you just need to define a Environment Variable on the Commandline like this:
export FLASK_APP=microblog.py
And then run it with
flask run
The way you have structured your code is not right. That's the reason you are not able to access your "/hello" API.
Let's assume that you run the app.py file. In this case, you haven't imported page.py right? So, how will the app.py know that there is a route defined in page.py?
Alternatively, let's assume you run page.py. In this case, when you do an "from app import app" , the main definition does not get executed. Here too, the route will now be present, but the app will not be run, thereby you will be unable to access your API.
The simplest solution would be to combine the contents of app.py and page.py into a single file.

Cannot import name 'socker'

Initial note: I know there are many of these type posts for python but I've tried many of the solutions and they have not worked for me.
File structure:
/nickdima
__init__.py
/test_pong
__init__.py
pong.py
/nickdima/__init__.py:
from flask_socketio import Socketio
socker = SocketIO()
from test_pong import pong
def create_app():
app = Flask(__name__)
socker.init_app(app)
return app
/nickdima/test_pong/pong.py
from __main__ import socker
#socker.on('connect')
def handle_connect():
print('connected')
When I run this code on Heroku I get the error:
from __main__ import socker
ImportError: cannot import name 'socker'
I'm fairly certain this is related to a circular import but I cannot solve it.
I've tried putting: from test_pong import pong
inside the create_app() function to "delay the import" locally but to no avail and I get the same error cannot import name 'socker'
Ok, so after further inspection here's what I suggest:
/nickdima
__init__.py
socker.py
/test_pong
__init__.py
pong.py
/nickdima/socker.py
from flask_socketio import Socketio
socker = SocketIO()
/nickdima/__init__.py:
from nickidima.socker import socker
from nickidima.test_pong import pong
def create_app():
app = Flask(__name__)
socker.init_app(app)
return app
/nickdima/test_pong/pong.py
from nickidima.socker import socker
#socker.on('connect')
def handle_connect():
print('connected')
This way you no longer have circular dependencies!
Imports are relative to the root directory where nickidima is placed. I'm not sure how Heroku works with this kind of importing (actually I've never used Heroku) but I hope you get the idea and will be able to tweak it to your needs.
The most important lesson: circular dependencies are almost always a sign of a bad design and almost always can be replaced with non-circular dependencies. And when they can: do it.
Side note: I'm following your naming convention (socker?) but seriously, you should fix it. :)

Flask not finding routes in imported modules

I'm having a problem with Flask wherein routes declared in imported modules are not be registered and always result in a 404. I am running the latest version Flask on Python 2.7.
I have the following directory structure:
run.py has the following code:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
import views.home
if __name__ == '__main__':
app.run()
home.py has the following code:
from run import app
#app.route('/test')
def test():
return "test"
When I run run.py the route declared in home.py (http://localhost:5000/test) always returns a 404 even though run.py imports views.home. The root view (http://localhost:5000) declared in run.py works fine.
I have written a function that prints out all the registered routes and /test is not in there (get a list of all routes defined in the app).
Any idea why?
I have discovered that switching the import statement in run.py from
import views.home
to
from views.home import *
makes everything work, which gave me the clue as to why the modules are not being registered using import views.home.
Basically, when run.py is run as a script it is given the name __main__ and this is the name given to the module in sys.modules (Importing modules: __main__ vs import as module)
Then when I import app from run.py in views.home.py a new instance of run.py is registered in sys.modules with the name run. As this point, the reference to app in run.py and views.home.py are two different references hence the routes not being registered.
The solution was to move the creation of the app variable out of run.py and in to a separate python file (I called it web_app.py) that is imported into run.py. This guarantees that the Flask app variable declared inside web_app.py is the always referenced correctly wherever web_app.py is imported.
So run.py now looks like this:
from web_app import app
if __name__ == '__main__':
app.run()
and web_app.py looks like this:
from flask import Flask
app = Flask(__name__)
import view.home
You can do it by reorganizing your code as it is described here Larger Applications, but it is more recommended to divide them into smaller groups where each group is implemented with the help of a blueprint. For a gentle introduction into this topic refer to the Modular Applications with Blueprints chapter of the documentation.
Modular Applications with Blueprints

Flask app does not use routes defined in another module

I can't get it to work to use one module that creates the Flask application object and runs it, and one module that implements the views (routes and errorhandlers). The modules are not contained in a Python package.
app.py
from flask import Flask
app = Flask('graphlog')
import config
import views
if __name__ == '__main__':
app.run(host=config.host, port=config.port, debug=config.debug)
views.py
from app import app
#app.route('/')
def index():
return 'Hello!'
config.py
host = 'localhost'
port = 8080
debug = True
I always get Flask's default "404 Not Found" page. If I move the contents of view.py to app.py however, it works. What's the problem here?
You have four modules here:
__main__, the main script, the file you gave to the Python command to run.
config, loaded from the config.py file.
views, loaded from the views.py file.
app, loaded from app.py when you use import app.
Note that the latter is separate from the first! The initial script is not loaded as app and Python sees it as different. You have two Flask objects, one referenced as __main__.app, the other as app.app.
Create a separate file to be the main entry point for your script; say run.py:
from app import app
import config
if __name__ == '__main__':
app.run(host=config.host, port=config.port, debug=config.debug)
and remove the import config line from app.py, as well as the last two lines.
Alternatively (but much uglier), use from __main__ import app in views.py.

Categories

Resources