Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dash callbacks raising Flask 'RuntimeError: Working outside of request context.' #966

Closed
Mudyla opened this issue Oct 15, 2019 · 5 comments

Comments

@Mudyla
Copy link

Mudyla commented Oct 15, 2019

Hi,

Not sure if it is a bug or me not knowing how to do this properly but I am trying to make a Dash app with Flask login and home page. User would be redirected to Dash app when link clicked on home page. My app structure is as below with Dash app in app1.py:

example/
  dashapp/
    static/
      custom-css.css
    templates/
      base.html
      home.html
      login.html
    __init__.py
    app1.py
    forms.py
    models.py
    routes.py
  application.py
  config.py
  users.db

I wanted to pass logged user name into Dash app but kept getting 'RuntimeError: Working outside of request context.' error. After multiple searches I found a solution which involved putting app layout into function. It works fine unless any callback is added. Is there a solution for this? I tried adding callbacks into another function but it didn't do anything.

Code for app1.py:

import dash
import dash_html_components as html
from dash.dependencies import Input, Output
from werkzeug.urls import url_parse
from dashapp import application, routes
from flask_login import login_required, current_user
from flask import session, current_app

app1 = dash.Dash(__name__, server = application, routes_pathname_prefix = '/app1/', assets_folder = 'static', assets_url_path = '/static')
app1.scripts.config.serve_locally = True
app1.css.config.serve_locally = True

def serve_layout():
	return html.Div(
		children = [
			html.Div(
				id='div1',
				children = 'Test1'
			),
	
			html.Div(
				id='div2',
				children = '{}'.format(session.get('username', None))
			),

			html.Div(
				id='div3',
				children = 'Test3'
			),

		],
	)

#def app_callback(app):
#	@app.callback(
#		Output('div1', 'children'),
#		[Input('div3', 'children')])
#	def update_intervalCurrentTime(children):
#		return children

app1.layout = serve_layout		
#app_callback(app1)

for view_func in app1.server.view_functions:
	if view_func.startswith('/app1/'):
		app1.server.view_functions[view_func] = login_required(app1.server.view_functions[view_func])

Code for routes.py:

from flask import render_template, flash, redirect, url_for, request, session
from flask_login import login_user, logout_user, current_user, login_required
from werkzeug.urls import url_parse
from dashapp import application, db
from dashapp.forms import LoginForm
from dashapp.models import User
from dashapp import app1

@application.route('/')
@application.route('/home')
@login_required
def home():
	return render_template('home.html')

@application.route('/login', methods=['GET', 'POST'])
def login():
	if current_user.is_authenticated:
		session['username'] = current_user.username
		return redirect(url_for('home'))
	form = LoginForm()
	if form.validate_on_submit():
		session['username'] = form.username.data
		user = User.query.filter_by(username=form.username.data).first()
		if user is None or not user.check_password(form.password.data):
			flash('Invalid username or password')
			return redirect(url_for('login'))
		login_user(user, remember=form.remember_me.data)
		next_page = request.args.get('next')
		if not next_page or url_parse(next_page).netloc != '':
			next_page = url_for('home')
		return redirect(next_page)
	return render_template('login.html', form=form)

@application.route('/logout')
def logout():
	logout_user()
	return redirect(url_for('login'))

Other files are pretty much standard. I can paste those later if needed.
Traceback:

Traceback (most recent call last):
  File "application.py", line 1, in <module>
    from dashapp import application, db
  File "C:\Users\Mudyla\Desktop\Example\dashapp\__init__.py", line 16, in <module>
    from dashapp import routes, models
  File "C:\Users\Mudyla\Desktop\Example\dashapp\routes.py", line 7, in <module>
    from dashapp import app1
  File "C:\Users\Mudyla\Desktop\Example\dashapp\app1.py", line 42, in <module>
    app_callback(app1)
  File "C:\Users\Mudyla\Desktop\Example\dashapp\app1.py", line 37, in app_callback
    [Input('div3', 'children')])
  File "C:\Users\Mudyla\AppData\Roaming\Python\Python37\site-packages\dash\dash.py", line 1153, in callback
    self._validate_callback(output, inputs, state)
  File "C:\Users\Mudyla\AppData\Roaming\Python\Python37\site-packages\dash\dash.py", line 807, in _validate_callback
    layout = self._cached_layout or self._layout_value()
  File "C:\Users\Mudyla\AppData\Roaming\Python\Python37\site-packages\dash\dash.py", line 430, in _layout_value
    self._cached_layout = self._layout()
  File "C:\Users\Mudyla\Desktop\Example\dashapp\app1.py", line 23, in serve_layout
    children = '{}'.format(session.get('username', None))
  File "C:\Users\Mudyla\AppData\Roaming\Python\Python37\site-packages\werkzeug\local.py", line 348, in __getattr__
    return getattr(self._get_current_object(), name)
  File "C:\Users\Mudyla\AppData\Roaming\Python\Python37\site-packages\werkzeug\local.py", line 307, in _get_current_object
    return self.__local()
  File "C:\Users\Mudyla\AppData\Roaming\Python\Python37\site-packages\flask\globals.py", line 37, in _lookup_req_object
    raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.

Let me know if any other details are needed. Thanks!

@alexcjohnson
Copy link
Collaborator

This kind of question is a better fit for https://community.plot.ly/c/dash, but I'll take a quick stab at it here: I'm a bit surprised it works with the layout in a function, as we tend to call that outside of requests as well for validation purposes. That kind of usage wouldn't care about the username, so you could always wrap the session.get in a try/except. But the best practice would be to put div2.children as a callback output, with the input being irrelevant at least for now - a callback needs to have some input, but it could be something that never changes, it'll get called on page load regardless. You could add a dcc.Location component and have its pathname be the input.

If you want to discuss further, please open a community thread - folks there likely have more experience with Flask login and may have other ideas to share.

@Mudyla
Copy link
Author

Mudyla commented Oct 15, 2019

Thanks for the suggestion - it worked! It is very simple but don't think I found such answer when was searching today. This callback seems to solve my problem:

@app.callback(
	Output('div2', 'children'),
	[Input('div3', 'children')])
def update_user(children):
	try:
		return 'User: {}'.format(session.get('username', None))
	except:
		return ''

Also there is no need to put app layout in the function now.

@alexcjohnson
Copy link
Collaborator

Nice! I wouldn't think you'd need the try/except inside a callback though, do you? A callback should always have a request context.

@Mudyla
Copy link
Author

Mudyla commented Oct 15, 2019

Yes - you are right. Changed to:

@app.callback(
	Output('div2', 'children'),
	[Input('div3', 'children')])
def update_user(children):
	return 'User: {}'.format(session.get('username', None))

Thanks again!

@alexmojaki
Copy link

I just spent a bit of time being confused about this and I think the same thing might have been what threw off @Mudyla, so for future readers:

You can always use the request context inside a callback. You can sometimes use it in a layout function. But you can't use it in a layout function if you define at least one callback. When the callback is initially registered, it is validated against the layout: see _validate_callback and _layout_value in the traceback. This happens when the server is starting up, well before any requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants