Black Lives Matter. Please consider donating to Black Girls Code today.

Need to store data in session, is there an alternative?

Is it possible to store data in a user session? I didn’t find any example, and from the following scenario, I think I need it. Thanks in advance for any help!

I’m developing a dashboard where people can upload a csv file, and based on that data, charts are displayed. And users can then also apply filters to update the charts.

I have all charting and filtering working fine when I work on a dataframe in stored in a global variable, and I am now struggling to move that to non-local variables. I have the upload component triggering a callback that parses the csv and creates the dataframe as needed. But then I’m stuck because the function that updates the charts is a callback on the filtering fields and these need to access data generated by the callback on the upload component.

To make it short, I want to get the example here working when the function update_output is not a callback of the upload component, but a callback on a form element giving a value for a filtering criteria.

Here is more info though:

First, here is the callback chain:

charts ---watch--> <div id="signal">  <---updates--- filter_data <---triggers---filter_form_fields

upload_componenent ----triggers----> handle_upload

My problem is that I don’t see how I can get the dataframe built in handle_upload in the first chain. filter_data is accessing the dataframe via a global_store function, but it currenly uses a global variable to access the dataframe. It is that access to the global variable that I need to avoid. But I don’t see how.

I explored several suggestions I found in the doc:

  1. I could store the dataframe serialised in a div as explained on https://dash.plot.ly/sharing-data-between-callbacks , but all example are about callback on the div accessing the data from the div. However in my case, I don’t want callback on this div to access the data, I want callbacks on form fields to access this data.

  2. There’s a suggestion to Uses Redis via Flask-Cache for storing “global variables”. This data is accessed through a function, the output of which is cached and keyed by its input arguments., however, I don’t see how to get it to work as the return of the function is the dataframe constructed in the callback of the upload component.

Finally I found a way to get it working by storing data in a div as suggested in the doc. To spare some time to others, here is a skeleton illustrating how I did it:

# -*- coding: utf-8 -*-
import dash
import base64
import io
import dash_core_components as dcc
import dash_html_components as html

import pandas as pd
import numpy as np
import csv
import json

from flask_caching import Cache
app = dash.Dash(__name__)
from datetime import datetime as dt

app.layout = html.Div(children=[
                # upload component
                dcc.Upload(
                        id='upload-data',
                        children=html.Div([
                            'Drag and Drop or ',
                            html.A('Select Files')
                        ]),
                        multiple=False
                    ),
                dcc.Input( id = "maximum-amount", placeholder = "Maximum amount", type = 'numeric',),
                html.Div(id='scatter_of_expenses'),

                # signaling divs, meant to be hidden
                html.Div(id='signal'),
                html.Div(id='uploaded'),
                ])

@app.callback(dash.dependencies.Output('uploaded', 'children'),
              [dash.dependencies.Input('upload-data', 'contents')],
)
# this function is handling the uploaded file and storing the result in the div #uploaded
def handle_upload(type_and_content):
    # return empty dataframe if no file uploaded yet
    if type_and_content == None:
        return pd.DataFrame({}).to_json()
    content_type, content_string = type_and_content.split(',')
    content_csv = base64.b64decode(content_string)
    dateparse = lambda x: pd.datetime.strptime(x, '%d/%m/%Y')
    e = pd.read_csv(io.StringIO(content_csv.decode('utf-8')))
    return e.to_json()

@app.callback(
    dash.dependencies.Output('signal', 'children'),
    [dash.dependencies.Input('maximum-amount', 'value'),
    ],
    # FIXME
    # next line makes that the handle_upload callback is not called
    state = [dash.dependencies.State('uploaded', 'children'), ]
)
# this function is triggered by the search form, and reads the data from the div #uploaded as
# it gets it via a state variable
def filter_expenses(maximum_amount, df):
    data = df.read_json(df)
    res = data[data.amount<maximum_amount]
    return res.to_json
#--------------------------------------------------------------------------------



@app.callback(dash.dependencies.Output('scatter_of_expenses', 'children'),
              [dash.dependencies.Input('signal', 'children')],
              state = [dash.dependencies.State('uploaded', 'children')]
)
# This function displays a chart of the filtered data. It gets triggered by the filter_expenses function which
# puts the filtered data in the div #signal
def scatter_of_expenses(filter, df):
     return dcc.Graph(...)


if __name__ == '__main__':
    app.run_server(debug=True)
1 Like