Unable to change layout because I cannot access the session information

Hello everyone,

I started by creating a flask application and then did authentication for it.
In the authentication function I saved the user profile in the session.

However when I want to access it on any other file on the application I get the following error:
RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.

the simplified code is like this

from layout import layout
from flask import session

dash_app = dash.Dash("myapp",server=flask_app)
if session is None:
    layout = html.Div([dcc.Location(id='anId', pathname='/login')])
else:
    layout = layout

Please take a look at this old thread.

I am trying to get the flask session to work in my app, but it won’t work, I get the same error as OP: RuntimeError: Working outside of request context.

I looked at the old thread that was linked, but it did not give any useful info to get the app working as a whole. I included an example app below, with in comments my attempts of getting the flask session to work (which it didn’t). Can someone show me what to add in order to get the flask session working?

To start the dashboard, create a directory and add the file app.py to it. Then next to app.py create a subdirectory called locale and add general.se.yml and general.en.yml to it. Check the included requirements.txt to see what needs to be installed.

The example app works with a global variable LOCALE, which the callback alters. What I would like is to store and read the locale info from the flask session.

Some background context to this post:
I am trying to get an app working with localization/internationalization. The app should be able to run in the cloud where multiple workers and/or processes can work on requests coming from my dash app. The language info should be stored on a per client basis such that language selection by one client does not affect another clients interface that has the dash app open simultaneously in another browser.

The problem is that the web page must be refreshed in order to load the translations for the newly requested language. So to get the correct language loaded, we must store the language setting in a scope that all server workers/processes have access to, that does not get overwritten by reloading the app, and that is independent per client.

This means that any global variable in the code itself doesn’t work (information is not kept when a new worker starts), the internal storage that i18n provides or using environmental variables doesn’t work (it’s storage that is not client-independent).

As such I was thinking of using flask session storage, to store that info.

I am trying to avoid Dash pages for now, given that my app is quite big and it will be quite some work to rewrite my single-page app to new framework. The benefit of Dash pages however is that I would be able to pass queries in the url and pass localization via the url to the layout. Side question here: is there any way of accessing url query parameters in the single page app like in the example below?

app.py

import dash
import flask
from dash import dcc, html, Input, Output
from flask import session

import i18n
from pathlib import Path

i18n.set("load_path", [Path(__file__).parent / "locale"])

server = flask.Flask(__name__)
LOCALE = 'en'


def serve_layout(locale):
    return html.Div(
        [
            dcc.Location(id="url", refresh=True),
            i18n.t("general.language_dropdown", locale=locale),
            dcc.Dropdown(
                id="ldropdown", options=["se", "en"], placeholder="Select language", persistence='session'
            ),
            i18n.t("general.welcome", locale=locale)
        ]
    )


# @server.route("/")
def get_layout():
    # LOCALE = session.get('locale', 'en')
    return serve_layout(LOCALE)


app = dash.Dash(__name__, server=server)
app.layout = get_layout


@app.callback(
    Output("url", "href"),
    Input(component_id="ldropdown", component_property="value"),
    prevent_initial_call=True,
)
def change_dashboard_language(language_code):
    global LOCALE
    LOCALE = language_code
    # session['locale'] = language_code
    return "/"


if __name__ == "__main__":
    app.run_server(debug=True)

locale/general.en.yml

---
en:
  language_dropdown: Select language
  welcome: Welcome to this dashboard

locale/general.se.yml

---
se:
  language_dropdown: Välj språk
  welcome: Välkommen till den här instrumentpanelen

requirements.txt

dash
python-i18n[YAML]

In your case there is one alternative to completely avoid the Flask session. You can use persistence="memory" and render just your content block. This should work as long as the dropdown has always a selection. The memory persistence works effectively as a cookie and won’t go away even if the browser is closed. Note that the dropdown value can be used in other callbacks, e. g. if you need the locale info to render a specific chart in a specific language.

# def serve_layout.... (just like you did)

app.layout = html.Div(
        [
            dcc.Location(id="url", refresh=True),
            dcc.Dropdown(
                id="ldropdown", value="en", options=["se", "en"], placeholder="Select language", persistence='memory'
            ),        
           html.Div(id="page-content"),    
        ]
    )

@app.callback(
    Output("page-content", "children"),
    Input(ponent_id="ldropdown", component_property="value"),
)
def render_layout(locale):
    return serve_layout(locale)

The canonical way to pass session data in Dash is via dcc.Store, which can also be used in this case (although it is not needed). The persistence level can also be set as in the dropdown.

Side question here: is there any way of accessing url query parameters in the single page app like in the example below?

You can do it via the search prop in dcc.Location (more info in the reference page).

2 Likes

It took me a while to realize that that was indeed an option as well. Yesterday, I got this idea as well after reading some other StackOverflow post on this topic. I don’t know why I didn’t think of this option before.

Furthermore, I do agree with you that it’s the best to use dcc.Store to store and pass information. But that data is only accessible in callbacks, and not when the Dash Renderer creates the app layout. The same with the url parameters, if you are parsing it by interacting with a dcc.Location component, you can only access it inside a callback. With my side question, I was aiming a bit more towards what Dash Pages provides you. However, I don’t think that is possible.

In any case, thanks for the feedback. Using the Div component to hold the whole layout indeed is the most straightforward and simple way to implement it.

P.S. For persisting outside of the browser session you need persistence="local". From the dash docs:

persistence_type (a value equal to: ‘local’, ‘session’ or ‘memory’; default ‘local’): Where persisted user changes will be stored: memory: only kept in memory, reset on page refresh. local: window.localStorage, data is kept after the browser quit. session: window.sessionStorage, data is cleared once the browser quit.

1 Like

Hello @Tobs,

I cater different flavors of the same website depending upon the user’s session, levels, company, etc. I also store the users navigation setting (open, closed) in the same db. This is something that I think you are after.

I use dash pages, but I understand why you wouldn’t since your app is large.

As far as reloading after a user selects a different language that previously wasn’t available. You can control supervisor from inside the app.

Make alterations to your files, run the new install process, then restart your supervisor for the web app. In the best case, you’re probably talking about very little down time.

As long as you have a load balancer in front, you shouldn’t experience any issues with requests not being fulfilled.

Edit:

The user info I pull fresh from the server with each request where it uses it, using a function called build_users, this returns a dictionary with their userID as the key, as this is the current_user in flask. With this, I pull any of the information that I want.

1 Like

@Tobs,

Another thing to keep in mind, especially when you switch to pages.

The pages get iterated through upon spin up, so your layouts will have to make sure there is a request or current_user. If not, have a fallback layout to get past that phase.

1 Like

Thanks for the interesting insights. It seems you are truly building more complex frameworks. (I have also seen other posts of yours).

My focus with Dash is enabling others, non-programmers, to easily create their own Dashboards. Where I specifically focus on taking away as much complexity from the tool as I can. For this specific issue, I liked the solution with the page-content div a lot, because it was actually quite simple to implement. In the near future I will move to Dash Pages, and will start to experiment with its features. Also, I can then build a login page, to enforce that a userID is always present etc.

Still, I always like seeing other approaches, so your input is much appreciated. For example, the supervisor setup is new to me. Do you implement that on top of the Dash Renderer?

Nope, supervisor is actually just something that Linux has to offer.

Since you are looking at dynamic loading content. Not sure if you’d be interested in an approach like this:

Basically, it’s something where the user has the ability to create their own dashboard.

1 Like