3
votes

Relatively new to Dash, and this is a problem that has been vexing me for months now. I am making a multi-page app that shows some basic data trends using cards, and graphs embedded within cardbody. 30% of the time, the app works well without any errors and the other 70% it throws either one of the following:

  1. ImportError: cannot import name 'ValidatorCache' from partially initialized module 'plotly.validator_cache' (most likely due to a circular import) OR
  2. ImportError: cannot import name 'Layout' from partially initialized module 'plotly.graph_objects' (most likely due to a circular import)

Both these appear quite randomly and I usually refresh the app to make them go away. But obviously I am doing something wrong. I have a set of dropdowns that trigger callbacks on graphs. I have been wracking my head about this. Any help/leads would be appreciated. The only pattern I see in the errors is they seem to emerge when the plotly express graphs are being called in the callbacks.

What am I doing wrong? I have searched all over online for help but nothing yet.

Sharing with some relevant snippets of code (this may be too long and many parts not important to the question, but to give you a general idea of what I have been working towards)

import dash
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from plotly.subplots import make_subplots
import plotly.express as px


card_content1_1 = [
    dbc.CardHeader([dbc.Row([html.H5("SALES VOLUME TREND", className = "font-weight-bold text-success"),
                   dbc.Button(
                       html.I(className="fa fa-window-maximize"),
                       color="success",
                       id="sales_maximize",
                       className="ml-auto",
                       # href="www.cogitaas.com"
                   )
                             ])]),
     dbc.CardBody(
        [dcc.Graph(
    id='sales_graph',
    figure={},
            style={'height':'30vh'}
    # className="mt-5"
            )])]


card_stacked_discount = [
    dbc.CardHeader([dbc.Row([html.H5("VOLUMES UNDER DIFFERENT DISCOUNT LEVELS", className="font-weight-bold text-info text-center"),
                   dbc.Button(
                       html.I(className="fa fa-window-maximize"),
                       color="info",
                       id="discount_maximize",
                       className="ml-auto",
                       # href="www.cogitaas.com"
                   )
                             ])]),
    dbc.CardBody(
        [dcc.Dropdown(
                id = 'stacked_discount_dropdown',
                options =stacked_discount_options,
                value=stacked_discount_options[0].get('value'),
                style={'color':'black'},
                # multi=True
            ),
            dcc.Graph(
    id='stacked_discount_graph',
    figure={},
style={'height':'30vh'}
            )])]
cards = html.Div(
        [
            dbc.Row(
                [
                    dbc.Col(dbc.Card(card_content1_1, color="success", outline=True,
                                     style={'height':'auto'}), width=8),


                ],
                className="mb-4",

            ),

            dbc.Row(
                [
                    dbc.Col(dbc.Card(card_stacked_discount, color="info", outline=True), width=8),
                    dbc.Col(dbc.Card([
                        dbc.Row([
                            dbc.Col(dbc.Card(disc_sub_title, color="info", inverse=True)),
                        ]),
                        html.Br(),
                        dbc.Row([
                            dbc.Col(dbc.Card(disc_sub_card1, color="info", outline=True)),
                                              ]),
                    ]), width=4)
                ],
                className="mb-4",
            ),
        ]
    )

tab1_content = dbc.Card(
    dbc.CardBody(
        [cards,]
    ),
    className="mt-3",
)

tabs = dbc.Tabs(dbc.Tab(tab1_content, label="Data", label_style={'color':'blue'}, tab_style={"margin-left":"auto"}),])

content = html.Div([
        html.Div([tabs]),
        ],id="page-content")

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


@app.callback(
    dash.dependencies.Output('sales_graph', 'figure'),
    [dash.dependencies.Input('platform-dropdown', 'value'),
     dash.dependencies.Input('signature-dropdown', 'value'),
     dash.dependencies.Input('franchise-dropdown', 'value'),
     dash.dependencies.Input('sales_maximize', 'n_clicks'),
     dash.dependencies.Input('time-dropdown', 'value'),
     ])
def update_sales_graph(plat, sign, fran, maximize, time_per):
    print(str(time_per)+"Test")
    time_ax=[]
    if isinstance(time_per,str):
        time_ax.append(time_per)
        time_per=time_ax
    if (time_per==None) or ('Full Period' in (time_per)):
        dff = df[(df.Platform==plat) & (df.Signature==sign) & (df.Franchise==fran)]
    elif ('YTD' in time_per):
        dff = df[(df.Platform == plat) & (df.Signature == sign) & (df.Franchise == fran) & (df.year==2020)]
    else:
        dff = df[(df.Platform==plat) & (df.Signature==sign) & (df.Franchise==fran) & (df.Qtr_yr.isin(time_per))]

    fig = px.area(dff, x='Date', y='Qty_Orig', color_discrete_sequence=px.colors.qualitative.Dark2)
    fig.add_trace(go.Scatter(x=dff['Date'], y=dff['Outliers'], mode = 'markers', name='Outliers',
                             line=dict(color='darkblue')))
    fig.add_trace(go.Scatter(x=dff['Date'], y=dff['bestfit'], name='Long Term Trend',
                             line=dict(color='darkblue')))
    fig.update_layout(font_family="Rockwell",
                      title={'text': fran + " Volume Trend",
                             'y': 0.99,
                             # 'x': 0.15,
                             # 'xanchor': 'auto',
                             'yanchor': 'top'
                             },
                      legend=dict(
                          orientation="h",
                          # y=-.15, yanchor="bottom", x=0.5, xanchor="center"
                      ),
                      yaxis_visible=False, yaxis_showticklabels=False,
                      xaxis_title=None,
                    margin=dict(l=0, r=0, t=0, b=0, pad=0),
                      plot_bgcolor='White',
                    paper_bgcolor='White',
                                          )
    fig.update_xaxes(showgrid=False, zeroline=True)
    fig.update_yaxes(showgrid=False, zeroline=True)
    changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0]
    if 'maximize' in changed_id:
        fig.show()
    return fig


@app.callback(
    dash.dependencies.Output('stacked_discount_graph', 'figure'),
    [dash.dependencies.Input('platform-dropdown', 'value'),
     dash.dependencies.Input('signature-dropdown', 'value'),
     dash.dependencies.Input('franchise-dropdown', 'value'),
     dash.dependencies.Input('discount_maximize', 'n_clicks'),
     dash.dependencies.Input('stacked_discount_dropdown', 'value'),
     dash.dependencies.Input('time-dropdown', 'value'),
     ])
def stacked_discount(plat, sign, fran, maximize, sales_days, time_per):
    time_ax=[]
    if isinstance(time_per,str):
        time_ax.append(time_per)
        time_per=time_ax
    # else:
    #     time_per=list(time_per)

    if (time_per==None) or ('Full Period' in (time_per)):
        df_promo = df_promo_vol[(df_promo_vol.Platform==plat) & (df_promo_vol.Signature==sign) & (df_promo_vol.Franchise==fran)]
    elif ('YTD' in time_per):
        df_promo = df_promo_vol[(df_promo_vol.Platform == plat) & (df_promo_vol.Signature == sign) & (df_promo_vol.Franchise == fran) & (df_promo_vol.Year==2020)]
    else:
        df_promo = df_promo_vol[(df_promo_vol.Platform==plat) & (df_promo_vol.Signature==sign) & (df_promo_vol.Franchise==fran) & (df_promo_vol.Qtr_yr.isin(time_per))]

    color_discrete_map = {
        "0 - 10": "orange",
        "10 - 15": "green",
        "15 - 20": "blue",
        "20 - 25": "goldenrod",
        "25 - 30": "magenta",
        "30 - 35": "red",
        "35 - 40": "aqua",
        "40 - 45": "violet",
        "45 - 50": "brown",
        "50 + ": "black"
    }

    category_orders = {'disc_range': ['0 - 10', '10 - 15', '15 - 20', '20 - 25', '25 - 30', '30 - 35', '35 - 40',
                                      '40 - 45', '45 - 50', '50 + ']}

    if (sales_days == None) or (sales_days == 'sales_act'):
        fig = px.bar(df_promo, x='start', y='units_shipped', color='disc_range',
                     color_discrete_map=color_discrete_map,
                     category_orders=category_orders,
                 )
    else:
        fig = px.bar(df_promo, x='start', y='Date', color="disc_range",
                    color_discrete_map=color_discrete_map,
                     category_orders=category_orders,
                     )

    fig.update_layout(font_family="Rockwell",
                      title={'text': fran + " Sales Decomposition",
                             'y': 0.99,
                             'x': 0.1,
                             # 'xanchor': 'auto',
                             'yanchor': 'top'
                             },
                      legend=dict(
                          orientation="h",
                          # y=-.15, yanchor="bottom", x=0.5, xanchor="center"
                      ),
                      # yaxis_visible=False, yaxis_showticklabels=False,
                      xaxis_title=None,
                      margin=dict(l=0, r=0, t=30, b=30, pad=0),
                      plot_bgcolor='White',
                      paper_bgcolor='White',
                      )
    fig.update_xaxes(showgrid=False, zeroline=True)
    fig.update_yaxes(showgrid=False, zeroline=True)
    changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0]
    if 'maximize' in changed_id:
        fig.show()
    return fig
2

2 Answers

1
votes

Well, it appears I may have stumbled on to an answer. I was using the pretty much the same inputs for multiple callbacks and that could have been causing some interference with the sequencing of inputs. Once I integrated the code into one callback with multiple outputs, the problem seems to have disappeared.

1
votes

Was dealing with this same issue where everything in my app worked fine, then I made an entirely separate section & callback that started throwing those circular import errors.

Was reluctant to re-arrange my (rightfully) separated callbacks to be just a single one and found you can fix the issue by just simply importing what the script says it's failing to get. In my case, plotly was trying to import the ValidatorCache and Layout so adding these to the top cleared the issue and now my app works as expected. Hope this helps someone experiencing a similar issue.

from plotly.graph_objects import Layout
from plotly.validator_cache import ValidatorCache