Can’t debug functions if they have an error in it?

Firstly, Plotly Dash is so awesome that I’m doing everything in Python.
Cleaning, handling data was never my strength but I’m getting better at that and list comprehensions, Pandas, MongoDB so this is really really nice to get away from writing HTML then CSS then some Javascript, etc.
But…
When I’m still writing more callbacks and I have the app running, unless I comment out the incomplete function I’m writing (putting pass at the bottom of an incomplete function does not work for this), the other callbacks stop working so I can’t change any input to any of the completed callbacks just to check an output.
So basically I have to write everything and then run it and deal with errors or debug it.

and writing code in commented out sections is too terrible, especially in Pycharm where it’s just green font color

Also, when it fails, say clicking Enter after finishing input on dcc.input(debounc=True) and something is wrong so nothing gets executed, there is not even an error message. That is baffling. There should be error messages in this case.

Hi,
without any concrete example this is a bit hard to say if the issues you encounter are really on dash’s side or if you can solve them in your own code.

writing code in commented out sections is too terrible, especially in Pycharm where it’s just green font color

Hum, I would not say that any of this is dash’s fault. Maybe use a different color scheme?

As for dealing with errors here is how I proceed:

  1. For any complex callback (let us say > 40 LoC) I write it first as a dedicated function which can be tested in a friendlier dev environment (ipython, notebook, does not matter)

  2. If I get errors moving it to the callback syntax, I just use the builtin Pycharm debugger. It works just fine in a running dash app and it is life changing for any kind of Python dev.

2 Likes

@Batalex so pros actually do use jupyter and ipython to test while writing!
I started doing that and thought, “Oh, this is so amaterish” and stopped.
Here is a link to the question with code in question.
: Callbacks stop working when I add another callbackThat third callback to update_nutrients_table(), when that gets uncommented, ie. added to the program, then it’s failing silently. I can put breakpoints in there in Pycharm and it won’t tell me what about that third callback ends up breaking even the first two callbacks. It’s bizarre.

I’m sorry for ranting. I do like cashew cheese with my whine.

I tested in Jupyter as advised,posted it here: Callbacks stop working when I add another callback

Still the same issue of it breaking my other callbacks.

I looked at this answer: DatePickerRange to Update Data Table

But I think there is an error in it as there is no df.to_dict(‘rows’). It probably should be 'records' to update data in a DataTable

But I get the point that data and columns need to update separately. So I reorganized my layout and callbacks and i’m still failing silently.
Part of my layout:

DataTable(
        id="conversion-table",
        data=[],
        style_cell={'textAlign': 'left'},
        style_data_conditional=[{
            'if': {'row_index': 'odd'},
            'backgroundColor': 'rgb(248,248,248)'
        }],
        style_header={
            'backgroundColor': 'rgb(230,230,230)',
            'fontWeight': 'bold'
        },
    ),
    html.Div(
        html.H5("Nutrients")
    ),
    html.Br(),
    DataTable(
        id="nutrients-table",
        data=[],
        style_cell_conditional=[{
            'if': {'column_id': c},
            'textAlign': 'left'
        } for c in ['Name']
        ],
        style_data_conditional=[{
            'if': {'row_index': 'odd'},
            'backgroundColor': 'rgb(248,248,248)'
        }],
        style_header={
            'backgroundColor': 'rgb(230,230,230)',
            'fontWeight': 'bold'
        },
    ),

and my callbacks, not even to update but just to show the tables as they start off blank with data=[] me thinks.

@app.callback(
        Output('table-foodgroup-source', 'children'),
        Output('conversion-table', 'data'),
        Output('conversions-table', 'columns'),
        Output('nutrients-table', 'data'),
        Output('nutrients-table', 'columns'),
        Input('search-ingredient', 'value')
    )
    def show_tables(ingredient):
        '''
        return top table, conversions table, nutrients table, yield table,
        refuse table
        '''
        food_id = food_to_id_dict[ingredient]
        food = CNFFoodName.objects.get(id=str(food_id))
        food_grp = food.food_group.name
        food_src = food.food_source.description
        food_sci_name = "n/a"
        if food.scientific_name:
            food_sci_name = food.scientific_name
        # table for food group, source, scientific name
        food_group_table = html.Table([
                                html.Thead([
                                    html.Tr([html.Th("Group"), html.Th("Source"), html.Th("Scientific Name")])
                                ]),
                                html.Tbody([
                                    html.Tr([
                                        html.Td(food_grp),html.Td(food_src),html.Td(food_sci_name)
                                    ])
                                ])
                            ])

        conversions_df = make_conversions_df(food_id)
        #conversions_cols = [{"id": i, "name": conversions_df.columns[i]} for i in range(len(conversions_df.columns))]
        conversions_cols = [{"name": i, "id": i} for i in conversions_df.columns]
        conversions_data = conversions_df.to_dict('records')

        nutrients_df = make_nutrients_df(food_id)
        #nutrients_cols = [{"id": i, "name": nutrients_df.columns[i]} for i in range(len(nutrients_df.columns))]
        nutrients_cols = [{"name": i, "id": i} for i in nutrients_df.columns]
        nutrients_data = nutrients_df.to_dict('records')
        #nutrients_table = make_nutrients_table(nutrients_df)

        return food_group_table, conversions_data, conversions_cols,\
               nutrients_data, nutrients_cols

Not even that first food-group table that is a plaint html.Table is outputting. My other callback that depends on the same input is working. This is sad.

so pros actually do use jupyter and ipython to test while writing!

Whatever gets the job done :+1:

Actually it is a bit weird that you never had an error displayed according to your posts. Are you running the development server with app.run_server(debug=True)?

Moreover could you try to open your browser builtin dev tools (F12) and take a look at the console tab to see if you have errors there?

@Batalex
I have Dash apps embedded in a Flask app here: https://github.com/nyck33/cnf_dash2/blob/master/Dashboard/Shiny.py

I think I’m doing this wrong by having one callback output a table and another one trying to update it.
Here is the error in the console:

tml: "In the callback for output(s):\n  nutrients-table.data\n  nutrients-table.columns\nOutput 0 (nutrients-table.data) is already in use.\nAny given output can only have one callback that sets it.\nTo resolve this situation, try combining these into\none callback function, distinguishing the trigger\nby using `dash.callback_context` if necessary."
​
message: "Duplicate callback outputs"

So I have to learn what dash.callack_context is.
Thanks :slight_smile:

The Pycharm debugger works except for on this type of error, it just seems to fail silently.

I got it working with contexts and also using no update shown here: https://dash.plotly.com/advanced-callbacks

I combined the show and update functions into one and use contexts to see which button is pressed.

# call back to show conversions table, nutrients table and food group/src table
    # for currently selected ingredient
    @app.callback(
        [Output('table-foodgroup-source', 'children'),
        Output('conversions-table', 'data'),
        Output('conversions-table', 'columns'),
        Output('nutrients-table', 'data'),
        Output('nutrients-table', 'columns'),
        Output('err-nutrients-table', 'children'),
        Output('ctx-msg', 'children')],
        [Input('search-ingredient-btn', 'n_clicks'),
         Input('add-ingredient', 'n_clicks')],
        [State('search-ingredient', 'value'),
        State("numerical-amount", "value"),
        State('units-dropdown', 'value')],
    )
    def show_update_tables(ingredient_clicks, add_clicks, ingredient, amt, units):
        '''
        return top table, conversions table, nutrients table, yield table,
        refuse table
        '''
        #todo: change this to DataTable
        ctx = callback_context
        # get id of trigger
        trigger = ctx.triggered[0]['prop_id'].split(".")[0]
        ctx_msg = json.dumps({
            'states': ctx.states,
            'triggered': ctx.triggered,
            'inputs': ctx.inputs
        }, indent=2)
        if trigger == 'search-ingredient-btn' and ingredient_clicks > 0:
            food_to_id_dict, _, _ = make_food_to_id_dict()
            food_id = food_to_id_dict[ingredient]
            food = CNFFoodName.objects.get(id=str(food_id))
            food_grp = food.food_group.name
            food_src = food.food_source.description
            food_sci_name = "n/a"
            if food.scientific_name:
                food_sci_name = food.scientific_name
            # table for food group, source, scientific name
            food_group_table = html.Table([
                                    html.Thead([
                                        html.Tr([html.Th("Group"), html.Th("Source"), html.Th("Scientific Name")])
                                    ]),
                                    html.Tbody([
                                        html.Tr([
                                            html.Td(food_grp),html.Td(food_src),html.Td(food_sci_name)
                                        ])
                                    ])
                                ])

            conversions_df = make_conversions_df(food_id)
            conversions_cols = [{"name": i, "id": i} for i in conversions_df.columns]
            conversions_data = conversions_df.to_dict('records')

            nutrients_df = make_nutrients_df(food_id)
            nutrients_cols = [{"name": i, "id": i} for i in nutrients_df.columns]
            nutrients_data = nutrients_df.to_dict('records')

            return food_group_table, conversions_data, conversions_cols,\
                   nutrients_data, nutrients_cols, '', ctx_msg

        elif trigger == 'add-ingredient' and add_clicks >0:
            #num_clicks is there
            food_to_id_dict, _, _ = make_food_to_id_dict()
            food_id = food_to_id_dict[ingredient]
            nutrients_df = make_nutrients_df(food_id)
            conversions_df = make_conversions_df(food_id)

            measure_name = ''
            measure_num = -1
            curr_multiplier = ''
            for index, row in conversions_df.iterrows():
                if units in str(row['Name']):
                    # get number
                    measure_name = str(row['Name'])
                    measure_num = float(re.findall("\d+", measure_name)[0])
                    curr_multiplier = float(row['Multiplier'])
                    break

            # divide amount/measure_num
            multiplier_factor = (float(amt)) / measure_num
            new_multiplier = multiplier_factor * curr_multiplier

            if new_multiplier == curr_multiplier:
                return no_update, no_update, f'not updated, new mult: {new_multiplier} == {curr_multiplier}'

            # multiply all nutrients by new_multiplier
            for index, row in nutrients_df.iterrows():
                new_row_val = float(row['Value']) * new_multiplier
                row['Value'] = str(new_row_val)

            nutrients_cols = [{"name": i, "id": i} for i in nutrients_df.columns]
            nutrients_data = nutrients_df.to_dict('records')

            return no_update, no_update, no_update, nutrients_data, nutrients_cols, \
                   'only update nutrients table', ctx_msg

        return no_update, no_update, no_update, no_update, no_update, 'none updated', ctx_msg

So that is a firm rule? Each output can only be associated with one callback?

Yes it is a firm rule as of now: you can only have one callback per output component. If you try to write to callbacks to the same output item you get this error:

Screen Shot 2020-11-03 at 6.08.49 PM

Yes, the doc says so : https://dash.plotly.com/callback-gotchas.
Now that I think about it that is why you could not debug this with the debugger: the issue happen during the callback registration, not during the actual execution.
Well, now you know that you need to rework your callbacks to avoid duplicated outputs.
Good luck!