我正在尝试构建一个具有长回调的多页面 Dash 应用程序。当我使用单页应用程序运行应用程序时,它工作正常。工作代码如下
单页.py
import json
import dash
from dash import html, dcc
from dash.long_callback import DiskcacheLongCallbackManager
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import datetime
import pandas as pd
import plotly.express as px
## Diskcache
import diskcache
from query_4_entity_explr import get_ORG_info, get_daterange_entity_dataframe
cache = diskcache.Cache("./cache")
long_callback_manager = DiskcacheLongCallbackManager(cache)
app = dash.Dash("topx_org",
long_callback_manager=long_callback_manager,
external_stylesheets=[dbc.themes.BOOTSTRAP],
meta_tags=[{'name': 'viewport', 'content': 'initial-scale=1'}])
today = datetime.datetime.today().date()
date_30_day_back = (datetime.datetime.today() - datetime.timedelta(days=1)).date()
app.layout = dbc.Container([
dbc.Row([
dbc.Col(html.H3("Explore Organizations mentioned in media", className='text-center text-primary py-5'), width=12)
]),
dbc.Row([
dbc.Col(dbc.Label(['Select daterange']), width={'size': 2, 'offset': 0}),
dbc.Col(dcc.DatePickerRange(id='input_date_picker_range', display_format='YYYY-MM-DD', start_date=date_30_day_back, end_date=today), width={'size':10, 'offset':0}),
], justify='around', className='mb-1'),
dbc.Row([
dbc.Col(dbc.Label(['Choose Companies with mention count']), width=2),
dbc.Col(dcc.Dropdown(id = "input_item_dropdown", options = [], value=[], multi=True), width=10),
], justify='around', className='mb-1'),
# dbc.Row(dbc.Button(id="cancel_button_id", children="Cancel Running Job!")),
dbc.Row([
dbc.Col(html.Div(id="output_text", children=[]), width={'size':10, 'offset':2}),
], justify='around', className='py-3'),
dbc.Row([
html.Progress(id="progress_bar"),
]),
dbc.Row([
dbc.Col(dcc.Graph(id='output_graph', figure={}), width=12),
], justify='around', className='py-5'),
dcc.Store(id='stored-dataframe')
])
@app.long_callback(
[Output(component_id='output_text', component_property='children'),
Output(component_id="input_item_dropdown", component_property="options"),
Output(component_id='stored-dataframe', component_property='data'),
],
[Input(component_id="input_date_picker_range", component_property="start_date"),
Input(component_id="input_date_picker_range", component_property="end_date"),
],
manager=long_callback_manager,
running=[
(Output("output_text", "disabled"), False, True),
(
Output("output_graph", "style"),
{"visibility": "hidden"},
{"visibility": "visible"},
),
(
Output("progress_bar", "style"),
{"visibility": "visible"},
{"visibility": "hidden"},
),
],
)
def update_figure(start_date, end_date):
print(start_date)
print(end_date)
# Data Acquisition
TOP_N = 20
unique_org_list, top_companys, org_gp_pdf = get_ORG_info(get_daterange_entity_dataframe(start_date,end_date), TOP_N)
# For the div
# _text = str(f"Between {start_date} to {end_date} for all the organizations : { ', '.join (item_list_value) }")
_text = str(f"Between {start_date} to {end_date} there are {len(unique_org_list)} organizations mentioned in media.")
# Store the data in browser storage.
datasets = {
'so_1': org_gp_pdf.to_json(orient='split', date_format='iso'),
'so_2': top_companys,
}
return [_text], [{'label': str(c[0]) + f" ({c[1]})", 'value': str(c[0])} for c in unique_org_list[0:]], json.dumps(datasets)
@app.callback(
[Output(component_id='output_graph', component_property='figure')],
[Input(component_id="input_item_dropdown", component_property="value"),
Input(component_id='stored-dataframe', component_property='data')
],
progress=[Output("progress_bar", "value"), Output("progress_bar", "max")],
)
def update_graph_2(item_list_value, jsonified_cleaned_data):
# Load the stored DF and List from Browser storage
datasets = json.loads(jsonified_cleaned_data)
df_2 = pd.read_json(datasets['so_1'], orient='split')
top_companys = datasets['so_2']
if len(item_list_value) == 0:
fig = px.bar( df_2[df_2.ORG.isin(top_companys)].sort_values(['NEWS_DATE'], ascending=True), title=f"Showing top {len(top_companys)} organizations", x="NEWS_DATE", y="COUNT", color="ORG", barmode="group") # .query("ORG=='Mentice' | ORG=='Isofol Medical AB'")
else:
queryString = " | ".join([f"ORG=='{item}'" for item in item_list_value])
fig = px.bar(df_2.query(queryString).sort_values(['NEWS_DATE'], ascending=True) ,title=f"Showing some selected organizations", x="NEWS_DATE", y="COUNT", color="ORG", barmode="group") # .query("ORG=='Mentice' | ORG=='Isofol Medical AB'")
# align title
fig.update_layout(title_x=0.5, xaxis=dict(title_text='NEWS PUBLISH DATE'), yaxis=dict(title_text='DAILY ORG MENTION COUNT '))
return [fig]
if _name_ == "__main__":
app.run_server(port=8050, debug=False)
但是当我尝试在多页面结构中运行相同的应用程序时,它向我显示 @long_callback 不可调用。 在多页结构中,我的主应用程序文件如下所示 应用程序.py
from pages import topx_org, topx_per
import dash
from dash import html, dcc
from dash.long_callback import DiskcacheLongCallbackManager
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
## Diskcache
import diskcache
cache = diskcache.Cache("./cache")
long_callback_manager = DiskcacheLongCallbackManager(cache)
app = dash.Dash(__name__,
suppress_callback_exceptions=True,
long_callback_manager=long_callback_manager,
external_stylesheets=[dbc.themes.BOOTSTRAP],
meta_tags=[{'name': 'viewport', 'content': 'initial-scale=1'}])
server = app.server
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 == '/topx_org':
return topx_org.layout
elif pathname == '/topx_per':
return topx_per.layout
else:
return '404'
if _name_ == '__main__':
app.run_server(debug=True)
下面给出了 topx_org.py
from dash import callback, long_callback
import json
from dash import html, dcc
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import datetime
import pandas as pd
import plotly.express as px
from query_4_entity_explr import get_ORG_info, get_daterange_entity_dataframe
today = datetime.datetime.today().date()
date_30_day_back = (datetime.datetime.today() - datetime.timedelta(days=1)).date()
layout = dbc.Container([
dbc.Row([
dbc.Col(html.H3("Explore Organizations mentioned in media", className='text-center text-primary py-5'), width=12)
]),
dbc.Row([
dbc.Col(dbc.Label(['Select daterange' ]), width={'size':2, 'offset':0}),
dbc.Col(dcc.DatePickerRange(id='input_date_picker_range', display_format='YYYY-MM-DD',start_date=date_30_day_back, end_date=today), width={'size':10, 'offset':0}),
], justify='around', className='mb-1'),
dbc.Row([
dbc.Col(dbc.Label(['Choose Companies with mention count']), width=2),
dbc.Col(dcc.Dropdown(id = "input_item_dropdown", options = [], value=[], multi=True), width=10),
], justify='around', className='mb-1'),
dbc.Row([
dbc.Col(html.Div(id="output_text", children=[]), width={'size':10, 'offset':2}),
], justify='around', className='py-3'),
dbc.Row([
dbc.Col(dcc.Graph(id='output_graph', figure={}), width=12),
], justify='around', className='py-5'),
dcc.Store(id='stored-dataframe')
])
# Output(component_id='output_graph', component_property='figure'),
@callback(
[Output(component_id='output_text', component_property='children'),
Output(component_id="input_item_dropdown", component_property="options"),
Output(component_id='stored-dataframe', component_property='data'),
],
[Input(component_id="input_date_picker_range", component_property="start_date"),
Input(component_id="input_date_picker_range", component_property="end_date"),
],
progress=[Output("progress_bar", "value"), Output("progress_bar", "max")],
)
def update_figure(start_date, end_date):
print(start_date)
print(end_date)
# print(item_list_value)
# Data Acquisition
TOP_N = 20
unique_org_list, top_companys, org_gp_pdf = get_ORG_info(get_daterange_entity_dataframe(start_date,end_date), TOP_N)
# For the div
# _text = str(f"Between {start_date} to {end_date} for all the organizations : { ', '.join (item_list_value) }")
_text = str(f"Between {start_date} to {end_date} there are {len(unique_org_list)} organizations mentioned in media.")
# Store the data in browser storage.
datasets = {
'so_1': org_gp_pdf.to_json(orient='split', date_format='iso'),
'so_2': top_companys,
}
return [_text], [{'label': str(c[0]) + f" ({c[1]})", 'value': str(c[0])} for c in unique_org_list[0:]], json.dumps(datasets)
@long_callback(
[Output(component_id='output_graph', component_property='figure')],
[Input(component_id="input_item_dropdown", component_property="value"),
Input(component_id='stored-dataframe', component_property='data')
],
running=[
(Output("output_text", "disabled"), False, True),
(
Output("output_graph", "style"),
{"visibility": "hidden"},
{"visibility": "visible"},
),
(
Output("progress_bar", "style"),
{"visibility": "visible"},
{"visibility": "hidden"},
),
],
)
def update_graph_2(item_list_value, jsonified_cleaned_data):
# Load the stored DF and List from Browser storage
datasets = json.loads(jsonified_cleaned_data)
df_2 = pd.read_json(datasets['so_1'], orient='split')
top_companys = datasets['so_2']
if len(item_list_value) == 0:
fig = px.bar( df_2[df_2.ORG.isin(top_companys)].sort_values(['NEWS_DATE'], ascending=True), title=f"Showing top {len(top_companys)} organizations", x="NEWS_DATE", y="COUNT", color="ORG", barmode="group") # .query("ORG=='Mentice' | ORG=='Isofol Medical AB'")
else:
queryString = " | ".join([f"ORG=='{item}'" for item in item_list_value])
fig = px.bar(df_2.query(queryString).sort_values(['NEWS_DATE'], ascending=True) ,title=f"Showing some selected organizations", x="NEWS_DATE", y="COUNT", color="ORG", barmode="group") # .query("ORG=='Mentice' | ORG=='Isofol Medical AB'")
# align title
fig.update_layout(title_x=0.5, xaxis=dict(title_text='NEWS PUBLISH DATE'), yaxis=dict(title_text='DAILY ORG MENTION COUNT '))
return [fig]
我遇到以下错误
@long_callback(
TypeError: 'module' object is not callable
现在可以在最新的 dash 版本中使用函数
app = dash.get_app()
,然后调用 @app.long_callback
。
(已更新)根据this post,这还不可能,因为 long_callback 需要应用程序对象(@app.long_callback)。如果您尝试在页面文件中导入应用程序对象,您将创建循环导入,这将导致错误。
帖子中提到的唯一可能性是将所有长回调放在 app.py 文件中。 “正常”回调和布局可以保留在页面文件中。
在 Dash 的当前版本中,您可以为多页面应用程序启用后台回调,如下所示:
myapp.py
from celery import Celery
import dash
import os
_app = None
_background_callback_manager = None
_celery_app = None
def _get_app():
global _app
if _app is None:
_app = dash.Dash(
__name__,
background_callback_manager=_get_background_callback_manager(),
)
return _app
def _get_background_callback_manager():
global _background_callback_manager
if _background_callback_manager is None:
_background_callback_manager = dash.CeleryManager(_get_celery_app())
return _background_callback_manager
def _get_celery_app():
global _celery_app
if _celery_app is None:
_celery_app = Celery(
__name__,
broker=os.getenv("REDIS_URL"),
backend=os.getenv("REDIS_URL"),
)
default_config = "celeryconfig"
_celery_app.config_from_object(default_config)
return _celery_app
app = _get_app()
celery_app = _get_celery_app()
芹菜配置,看起来像这样,需要导入任何有 app.callback() 且背景 = True 的模块:
celeryconfig.py
## See https://docs.celeryq.dev/en/stable/userguide/configuration.html
# List of modules to import when the Celery worker starts
# i.e. anywhere an @app.callback has background=True
imports = ("module1", "module2")
在模块中,回调如下所示:
模块1.py
from myapp import app
from dash.dependencies import Input, Output
@app.callback(
Output("my-output", "children"),
Input("my-input", "value"),
background=True,
)
def my_callback(_):
return long_running_query()
此外,Celery 工作线程需要从以下内容开始:
celery --app=myapp.celery_app worker --loglevel=INFO
我建议使用较新的后台回调
@dash.callback
和选项background=True
。如您所见,不再需要 app
实例。