Forums

Flask and Dash Integration

I have two applications working together (Flask and Dash). The Flask application receives and processes data, calls the function that creates the Dash application, and sends a request with the id_session used to access the correct user data. The Dash application receives the request and updates the Dash layout. This integration works correctly in a localhost environment, but on PythonAnywhere, I am having issues with the correct updating of the graphs in the Dash layout. From what I can identify, it seems that the update signal reception in the callback is not working correctly. The request is received, and the data is accessed correctly within the “update-data” function, but the callback does not respond correctly every time. Sometimes the graphs are updated with incorrect data.

FLASK CODE:

flask_app = Flask(__name__)
flask_app.secret_key = 'supersecretkey'

import dash_app
dash_app.create_dash_app(flask_app)

@flask_app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':

        session_id = str(uuid.uuid4())
        session['session_id'] = session_id

        upload_folder = os.path.join('uploads', session_id)
        os.makedirs(upload_folder, exist_ok=True)
        database_file = request.files['database_file']
        data = pd.read_csv(database_file)

         # Data Manipulation Code

        data_path = os.path.join(upload_folder, 'temp_data.csv')
        data.to_csv(data_path, index=False)

        requests.post('http://my_domain/update-data', data={'session_id': session_id})

        return redirect("http://my_domain/dashapp/")

    return render_template('index.html')

DASH CODE:

def create_dash_app(flask_app):

FONT_AWESOME = ["https://use.fontawesome.com/releases/v5.10.2/css/all.css"]
dash_app = Dash(server=flask_app, url_base_pathname='/dashapp/', external_stylesheets=FONT_AWESOME)
server = dash_app.server

dash_app.layout = dbc.Container(children=[
    dcc.Store(id='data-update-signal', data=0),
     ... Rest of the Layout Code...
     ])

global data
data = pd.DataFrame({})

@server.route('/update-data', methods=['POST'])
def update_data():
    session_id = request.form.get('session_id')
    if not session_id:
        return "Session ID missing", 400

    download_folder = os.path.join('uploads', session_id)

    global data
    data_path = os.path.join(download_folder, 'temp_data.csv')
    data = pd.read_csv(data_path)

    current_signal = dash_app.layout.children[0].data
    new_signal = current_signal + 1
    dash_app.layout.children[0].data = new_signal

    return "Data updated", 200

@dash_app.callback(
    Output('graph1', 'figure'),
    Output('graph3', 'figure'),
    Output('graph4', 'figure'),
    Input(ThemeSwitchAIO.ids.switch("theme"), "value"),
    [Input('data-update-signal', 'data')]
)
def card1(toggle, data_update_signal):
    template_theme1 = "flatly"
    template_theme2 = "darkly"

    template = template_theme1 if toggle else template_theme2

    fig1 = go.Figure()
    ... Rest of the Fig Code ...

    fig3 = go.Figure()
     ... Rest of the Fig Code ...

    fig4 = go.Figure()
     ... Rest of the Fig Code ...

    return fig1, fig3, fig4

return dash_app

Add some logging to your code (prints to stderr will appear in your error log) so you can see what is happening when the graph returns the incorrect data.

Both applications have detailed prints, and no errors occur in either when the graph updates fail in the Dash application. I worked on the code to avoid any inconsistencies in the data used, but the problem appears after making some requests or refreshing the page. In these cases, the graph updates fail because the “session_id” parameter in the “card1” function is the session code used previously, thus the data from the previous session is used to generate the graphs.

The Dash endpoint works correctly, the update signal and the session code to access the data are correctly stored in the dcc.Store component of the Dash layout.

DASH APP CODE

def create_dash_app(app):
data_lock = threading.Lock()

FONT_AWESOME = ["https://use.fontawesome.com/releases/v5.10.2/css/all.css"]
dash_app = Dash(server=app, url_base_pathname='/dashapp/', external_stylesheets=FONT_AWESOME)
server = dash_app.server

# =========  Layout  =========== #
dash_app.layout = dbc.Container(children=[
    dcc.Store(id='session-id-store', data=''),
    dcc.Store(id='data-update-signal', data=0),
    # Rest of the Layout Code
], fluid=True, style={'height': '100vh'})

# =========  Endpoint  =========== #
@server.route('/update-data', methods=['POST'])
def update_data():
    session_id = request.form.get('session_id')
    if not session_id:
        return "Session ID missing", 400

    with data_lock:
        try:
            dash_app.layout.children[0].data = str(session_id)

            current_signal = dash_app.layout.children[1].data
            new_signal = current_signal + 1
            dash_app.layout.children[1].data = new_signal

            return "Data updated", 200
        except Exception:
            return "Erro", 500

@dash_app.callback(
    Output('graph1', 'figure'),
    Output('graph3', 'figure'),
    Output('graph4', 'figure'),
    Input(ThemeSwitchAIO.ids.switch("theme"), "value"),
    Input('session-id-store', 'data'),
    Input('data-update-signal', 'data')
)
def card1 (toggle, session_id, data_update_signal):
    template = "flatly" if toggle else "darkly"

    with data_lock: 
        try:
            download_folder = os.path.join('uploads', session_id)
            data_path = os.path.join(download_folder, 'temp_data.csv')

        except:
            return go.Figure(), go.Figure(), go.Figure()

    data = pd.read_csv(data_path)

    fig1 = go.Figure()
    # Rest of the Fig Code

    fig3 = go.Figure()
    # Rest of the Fig Code

    fig4 = go.Figure()
    # Rest of the Fig Code

    return fig1, fig3, fig4

return dash_app

Might this be being caused by multiple worker processes? If I'm understanding your code structure correctly, you have a Flask app and a Dash app both running as the same running site, let's say at https://www.yourdomain.com/, and the Flask app is accepting incoming requests and doing requests.post calls back into https://www.yourdomain.com/ in order to get the Dash portion to do some work.

On PythonAnywhere, your site runs as a number of separate processes (this differs from the Flask debug server that you would use locally). So if you have any dependency on global variables, these will differ between processes. For example, if you were to set a global session_id in one process and then make a request back to the site, the request might be handled by a different process which had a different global session_id.

I understand, and I also believe that this issue in my code is being caused by multiple work processes. However, the code has already been fixed and there is no longer any dependency on global variables (code from my last post). In a way, the errors have decreased after the code corrections, but they still occur on certain occasions. I don’t know if it’s a fragility of Dash Plotly, the way PythonAnywhere servers work, or the fact that I’m running a Flask application and a Dash application as the same running site. I can’t identify what might be causing this.

Are you able to narrow down the issue to those specific occasions when the app is misbehaving? Is there a pattern?

It doesn’t seem to follow a pattern. What I can identify is that every time it happens, it is because the function where the graphs are generated, “card1”, cannot access the correct value of the “session_id” parameter, even though all previous processes occur correctly. This is verified by the logs inserted in the code.

The same happens when the page is refreshed. For example, the code ran correctly, but a page refresh (dash application) can update the graphs incorrectly, and the reason is that the “session_id” parameter was not passed correctly. Either the parameter is not accessed (empty), generating blank graphs, or a parameter from another request is accessed, generating incorrect graphs.

That definitely sounds like the kind of pattern I'd expect if there was still some kind of global state that's different between the different processes. Perhaps there's something internal to Dash and/or Flask? But that would be strange, because they certainly work normally when they're running on their own. So maybe a combination of multiple processes and Dash and Flask in the same website could be the explanation.

Might it be worth trying splitting them out into two separate sites, https://www.yourdomain.com/ for Flask and https://dash-backend.yourdomain.com/ for Dash?