How to access Flask app's context from within embedded Dash app when using app factory pattern?

Viewed 2994

I am building a web app using Flask and Dash. The main Flask app handles user login and authentication, and protects the routes of the Dash app. The Dash app is served from within the Flask app.

The main Flask app was built using the Flask Mega Tutorial example (https://github.com/miguelgrinberg/microblog), with the app factory pattern. I stripped out everything except the login/authentication stuff and the app works fine at that stage.

I then added a simple Dash app within the Flask app following the example here: https://github.com/okomarov/dash_on_flask. The Dash app can be access at /dashboard, and the dashboard is properly protected against unauthorised access and redirects back to the main Flask login page as expected.

My challenge is now to access the Flask app's context and other things like the database session from within the Dash app. This is so the Dash app can access and display information from the main database.

I am currently stuck trying to follow examples that I have found online, and how to adapt them to the particular pattern of my app.

I have tried to follow examples linked here: https://github.com/plotly/dash/issues/214#issuecomment-391223557.

However, these examples seem to all be based on a flat app structure where the Flask and Dash apps are created in the same file.

All of my code is located here: https://github.com/danielcopelin/dacy-budget

At the moment, my main Flask app looks like:

dacybudget.py

from app import create_app, db
from app.models import User

app = create_app()


@app.shell_context_processor
def make_shell_context():
    return {'db': db, 'User': User}

create_app() looks like:

def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)

    register_dashapps(app)

    db.init_app(app)
    migrate.init_app(app, db)
    login.init_app(app)
    mail.init_app(app)
    bootstrap.init_app(app)

... and register_dashapps() looks like:

def register_dashapps(app):
    from app.dashapp1.layout import layout
    from app.dashapp1.callbacks import register_callbacks

    # Meta tags for viewport responsiveness
    meta_viewport = {
        "name": "viewport",
        "content": "width=device-width, initial-scale=1, shrink-to-fit=no",
    }
    external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
    dashapp1 = dash.Dash(
        __name__,
        server=app,
        url_base_pathname="/dashboard/",
        assets_folder=get_root_path(__name__) + "/dashboard/assets/",
        meta_tags=[meta_viewport],
        external_stylesheets=external_stylesheets,
    )

    dashapp1.title = "Dashapp 1"
    dashapp1.layout = layout
    dashapp1.url_base_pathname = "/dashboard/"  # I dont know why I had to do this
    register_callbacks(dashapp1)
    _protect_dashviews(dashapp1)

From within app.dashapp1.layout and app.dashapp1.callbacks I want to be able to access the main Flask app's session and database, etc.

If I try various imports from within app.dashapp1.layout, for example "from .. import db" and then try to do something with that db object, I get errors like:

RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.

Could anyone offer any suggestions on how to navigate this issue?

2 Answers

I managed to solve my issue. I was struggling to understand where in the Dash side of the app it was possible to access the main Flask app's context, so that I could provide Dash with access to the database. It turns out that this can be done within the register_callbacks() function and the actual callback functions defined within there like so:

def register_callbacks(app):
    from app import db
    from app.models import Transaction
    ...

    @app.callback(
        Output("main", "children"),
        [Input("transaction_table", "data_previous")],
        [
            State("transaction_table", "data"),
            State("transaction_table", "page_current"),
        ],
    )
    def update_database_and_generate_table(old_table_data, table_data, page_current):
        with app.server.app_context():
            if old_table_data is not None:
                update_changed_data(old_table_data, table_data)
            transactions = db.session.query(Transaction)
            df = pd.read_sql(transactions.statement, transactions.session.bind)

For this to work, another change has to be made to the code of my original question. In the create_app() function, you need to register the Dash app after the database has been initialised:

def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)

    db.init_app(app)
    migrate.init_app(app, db)
    login.init_app(app)
    mail.init_app(app)
    bootstrap.init_app(app)

    app = dashapp.add_dash(app)
    ...

The full working example is located here: https://github.com/danielcopelin/dacy-budget

Your answer is here Project Layout. I had similar problem and couldn't do 'normal' python imports within the flask app.

Name your app flaskr as suggested, and move app.py and templates there ... just like in the tutorial. It worked for me.

Related