Dash app - Dynamically Create a Layout for Multi-Page App Validation [Error: A nonexistent object was used in an `State` of a Dash callback.]

Dash app - Dynamically Create a Layout for Multi-Page App Validation [Error: A nonexistent object was used in an `State` of a Dash callback.]

我有一个 Dash 应用程序,我正在尝试加载一些额外的输入单元格,这些输入单元格取决于早期单元格的输入

if input (query-input-1-state) = MOVE --> additional set0 of inputs load (input-X-state,input-Y-state)
if input (query-input-1-state) = PARABOLIC --> additional set1 of inputs load for further submission (input-XX-state,input-YY-state)

代码是这样的 - 布局文件(我遵循了破折号多页应用程序模板)文件包含主布局和附加输入的子布局(layout_query_parabolic 和 layout_query_move)。

layouts.py
layout_menu = html.Div([
    dcc.Link('Run Query', href='/apps/query'),html.Br(),
    dcc.Link('Optimise', href='/apps/optimise'),html.Br(),
])

#Set 0 of input boxes
layout_query_move = html.Div([
                        dcc.Input(id='input-X-state', type='number', value=2),html.Br(),
                        dcc.Input(id='input-Y-state', type='number', value=3),html.Br(),
                        ])

#Set 1 of input boxes
layout_query_parabolic = html.Div([
                        dcc.Input(id='input-XX-state', type='number', value=6),html.Br(),
                        dcc.Input(id='input-YY-state', type='number', value=7),html.Br(),
                        dcc.Input(id='input-Z-state', type='number', value=8),html.Br(),
                        ])

layout_query_menu = html.Div([
    dcc.Link('Go to Main', href='/apps/'),html.Br(),
    html.H3('Enter settings for Move'),
    dbc.Label("Ticker:        ", size="md"),dcc.Input(id='query-input-0-state', type='text', value='QQQ'),
    dbc.Label("Event:         ", size="md"),dcc.Input(id='query-input-1-state', type='text', value='MOVE'),
    html.Div(id='full-input-boxes'),
    html.Button(id='submit-button-state2', n_clicks=0, children='Show all inputs'),
    html.Button(id='submit-button-state', n_clicks=0, children='Go!'),
    dcc.Graph(id='graph-with-slider'),
])

回调文件声明了 2 个回调 - 一个用于主页 - 另一个用于显示附加输入,这取决于对 query-input-1-state 的输入。

callbacks.py

#Validation layout to 'declare' all the input values
app.validation_layout = html.Div([ 
    layout_query_move,
    layout_query_parabolic,
    layout_menu,
    layout_query_menu,
    layout_optimise,
    dcc.Input(id='input-X-state', type='number', value=2), #set 0 of inputs
    dcc.Input(id='input-Y-state', type='number', value=3), #set 0 of inputs

    dcc.Input(id='input-XX-state', type='number', value=6), #set 1 of inputs
    dcc.Input(id='input-YY-state', type='number', value=7), #set 1 of inputs
    dcc.Input(id='input-Z-state',  type='number', value=8), #set 1 of inputs
])

flask.has_request_context() == False

@app.callback(
    Output('graph-with-slider', 'figure'),
    Input('submit-button-state', 'n_clicks'),
    State('query-input-0-state', 'value'),
    State('query-input-1-state', 'value'),
    State('input-X-state', 'value'),
    State('input-Y-state', 'value'),
    State('input-XX-state', 'value'),
    State('input-YY-state', 'value'),
    State('input-Z-state', 'value'),
             )
def display_value0(n_clicks,v0,v1, v2,v3,v4,v5,v6):
    d = {'x': [v2, v2], 'y': [v2, v2]}
    df = pd.DataFrame(data=d)
    filtered_df = df
    fig = px.scatter(filtered_df, x="x", y="y")
    fig.update_layout(transition_duration=500)
    return fig


@app.callback(
    Output('full-input-boxes', 'children'),
    Input('submit-button-state2', 'n_clicks'),
    State('query-input-1-state', 'value'),
)
def ask_for_more_inputs(n_clicks,event_id): #,asset_str,event_str
    if not n_clicks: raise dash.exceptions.PreventUpdate
    if event_id == 'MOVE': return layout_query_move
    if event_id == 'PARABOLIC': return layout_query_parabolic

应用入口页面是这样的(声明应用布局):

index.py #App Entry page

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content'),
])

@app.callback(
        Output('page-content', 'children'),
        Input('url', 'pathname')
             )
def display_page(pathname):
    if pathname == '/apps/':
         return layout_menu
    elif pathname == '/apps/query':
         return layout_query_menu
    elif pathname == '/apps/optimise':
         return layout_optimise
    elif pathname == '/apps/move':
         return layout_query_move

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

但是我得到一个不存在的对象错误:

A nonexistent object was used in an `State` of a Dash callback. The id of this object is 
`input-X-state` and the property is `value`. 

我已经声明了验证布局并尝试通过直接进入引擎盖下的烧瓶来关闭错误,但是这两种措施似乎没有帮助。我也尝试过移动验证声明,但这没有任何区别。采用 input-X-state 的回调无法识别两组输入 (X,Y) 和 (XX,YY,Z),并且图表永远不会更新。

我该如何解决这个问题?

好的,所以我假设您指的是 URL Routing and Multiple Apps 的以下 Dash 文档;并且(我个人也会)决定使用他们推荐的文件结构布局的第二个选项,他们称之为 'flat',看起来像这样:

.
|-- assets
|   `-- custom.css
|-- app.py
|-- callbacks.py
|-- index.py
`-- layout.py

1 directory, 5 files

index.py 文件是应用程序文件的实际“入口”(即动态控制 URL 的文件)因此,通过使用专门为此目的构建的 dcc.Location 对象的回调函数(多 URL Dash 应用程序)).

老实说,我并不完全清楚你到底想做什么,但是,这可能没有必要。它看起来很漂亮complicated/involved/esoteric。但是,我所做的是重新安排您的文件,使它们 运行 没有错误,希望这能帮助您走上正确的轨道。

您收到该错误的原因是您加载的布局未声明上述 Inputs

Side note: I am not positive what you even want here (/need) is a multi-app/URL Dash app, or, that this is the best approach (again because I'm not 100% on what your overall program is intended to solve) but, there is more than one way to skin a cat, as they say. This approach could work out perfectly, for your given needs; especially if you understand what it is I've changed and how this Dash paradigm with altering the page based on the URL works.

如有任何问题,请随时 lmk,但是..

文件内容如下:

  1. app.py

import dash

app = dash.Dash(__name__, suppress_callback_exceptions=True)
server = app.server
  1. layouts.py

from dash import dcc
from dash import html

import dash_bootstrap_components as dbc

layout_menu = html.Div(
    [
        dcc.Link("Run Query", href="/apps/query"),
        html.Br(),
        dcc.Link("Optimise", href="/apps/optimise"),
        html.Br(),
    ]
)

# Set 0 of input boxes
layout_query_move = html.Div(
    [
        dcc.Input(id="input-X-state", type="number", value=2),
        html.Br(),
        dcc.Input(id="input-Y-state", type="number", value=3),
        html.Br(),
    ]
)

# Set 1 of input boxes
layout_query_parabolic = html.Div(
    [
        dcc.Input(id="input-XX-state", type="number", value=6),
        html.Br(),
        dcc.Input(id="input-YY-state", type="number", value=7),
        html.Br(),
        dcc.Input(id="input-Z-state", type="number", value=8),
        html.Br(),
    ]
)

layout_query_menu = html.Div(
    [
        dcc.Link("Go to Main", href="/apps/"),
        html.Br(),
        html.H3("Enter settings for Move"),
        dbc.Label("Ticker:        ", size="md"),
        dcc.Input(id="query-input-0-state", type="text", value="QQQ"),
        dbc.Label("Event:         ", size="md"),
        dcc.Input(id="query-input-1-state", type="text", value="MOVE"),
        html.Div(id="full-input-boxes"),
        html.Button(
            id="submit-button-state2", n_clicks=0, children="Show all inputs"
        ),
        html.Button(id="submit-button-state", n_clicks=0, children="Go!"),
        dcc.Graph(id="graph-with-slider"),
        dcc.Input(
            id="input-X-state", type="number", value=2
        ),  # set 0 of inputs
        dcc.Input(
            id="input-Y-state", type="number", value=3
        ),  # set 0 of inputs
        dcc.Input(
            id="input-XX-state", type="number", value=6
        ),  # set 1 of inputs
        dcc.Input(
            id="input-YY-state", type="number", value=7
        ),  # set 1 of inputs
        dcc.Input(
            id="input-Z-state", type="number", value=8
        ),  # set 1 of inputs
    ]
)

# Validation layout to 'declare' all the input values
layout_optimise = html.Div(
    [
        dcc.Input(
            id="input-X-state", type="number", value=2
        ),  # set 0 of inputs
        dcc.Input(
            id="input-Y-state", type="number", value=3
        ),  # set 0 of inputs
        dcc.Input(
            id="input-XX-state", type="number", value=6
        ),  # set 1 of inputs
        dcc.Input(
            id="input-YY-state", type="number", value=7
        ),  # set 1 of inputs
        dcc.Input(
            id="input-Z-state", type="number", value=8
        ),  # set 1 of inputs
    ]
)

  1. callbacks.py

import dash
import layouts
import pandas as pd
import plotly.express as px

from app import app
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
from dash.dependencies import State


@app.callback(
    Output("graph-with-slider", "figure"),
    Input("submit-button-state", "n_clicks"),
    State("query-input-0-state", "value"),
    State("query-input-1-state", "value"),
    State("input-X-state", "value"),
    State("input-Y-state", "value"),
    State("input-XX-state", "value"),
    State("input-YY-state", "value"),
    State("input-Z-state", "value"),
)
def display_value0(n_clicks, v0, v1, v2, v3, v4, v5, v6):
    d = {"x": [v2, v2], "y": [v2, v2]}
    df = pd.DataFrame(data=d)
    filtered_df = df
    fig = px.scatter(filtered_df, x="x", y="y")
    fig.update_layout(transition_duration=500)
    return fig


@app.callback(
    Output("full-input-boxes", "children"),
    Input("submit-button-state2", "n_clicks"),
    State("query-input-1-state", "value"),
)
def ask_for_more_inputs(n_clicks, event_id):  # ,asset_str,event_str
    if not n_clicks:
        raise dash.exceptions.PreventUpdate
    if event_id == "MOVE":
        return layouts.layout_query_move
    if event_id == "PARABOLIC":
        return layouts.layout_query_parabolic

  1. index.py

from dash import dcc
from dash import html
from dash.dependencies import Input, Output

from app import app
from layouts import (
    layout_menu,
    layout_query_menu,
    layout_optimise,
    layout_query_move,
)
import callbacks

app.layout = html.Div(
    [dcc.Location(id="url", refresh=False), html.Div(id="page-content")]
)


@app.callback(Output("page-content", "children"), Input("url", "pathname"))
def display_page(pathname):
    if pathname == "/apps/":
        return layout_menu
    elif pathname == "/apps/query":
        return layout_query_menu
    elif pathname == "/apps/optimise":
        return layout_menu
    elif pathname == "/apps/move":
        return layout_query_move
    else:
        return "404"


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

很明显,您需要 finish/fix 他们才能完全按照您的意愿行事。需要注意的一件事,例如:如果您要在回调文件中引用布局,则需要导入它(即 import layouts → 然后将其引用为 layouts.__name_of_layout__,或者,导入它直接 from layouts import _____)。 并且对于可能触发的任何单个回调,if/when它被触发时,它的所有输入(包括状态)都必须存在于当前服务的任何“布局”中is! 希望这是有道理的。

编辑:唉,由于新的 dash.Dash() 应用程序属性 "validation_layout",Dash 的新版本不再受到粗体和斜体要求的阻碍,它允许您基本上分配所有pages/layouts 到“validation_layout”(因此包含您可能使用的所有组件 ID)并且不必担心回调被绑定到特定布局

我最终让它与这个一起工作:

layouts.py

layout_query_menu = html.Div([
    html.Div(id='full-input-boxes'),
    html.Button(id='submit-button-choose-event', n_clicks=0, children='Show all inputs'),
    html.Button(id='submit-button-state-go-run-query', n_clicks=0, children='Go!'),
])

诀窍是 1) 预先声明验证布局,以及 2) 我不是将每个按钮单独声明为回调输入,而是在 1 中声明它们 State("full-input-boxes", "children") 然后在回调中检查此 State 是否存在 - 然后根据其存在处理输入。

callbacks.py

app.validation_layout = html.Div([
    layout_query_move,
    layout_query_parabolic,
    layout_menu,
    layout_query_menu,
    layout_optimise,
])

@app.callback(
    Output('graph0', 'figure'),
    Output('graph1', 'figure'),
    Output('graph2', 'figure'),
    Output('graph3', 'figure'),
    Output('graph4', 'figure'),
    Output('graph5', 'figure'),
    Input('submit-button-state-go-run-query', 'n_clicks'),
    State('EVENT_ID_state', 'value'), #
    State('MASTER_TICKER_STR_state', 'value'),
    State("full-input-boxes", "children"),
             )
def display_value0(n_clicks,event_id,master_ticker_str, children):
    d = {'x': [0, 0], 'y': [0, 0]}
    df = pd.DataFrame(data=d)
    fig = px.scatter(df, x="x", y="y")

    if children:
        #process them
        ...