Callbacks stop working when I add another callback

I’m working in Pycharm which seems to pick up errors as they should and my app won’t even run if I have syntax errors, etc.

But now it is running but my callbacks stop working as soon as I uncomment my new callback called update_nutrients_table()

It’s failing silently which makes it tough to track down the error.

# -*- coding: utf-8 -*-
"""

"""
import re
from dash import Dash
from dash.dependencies import Input, Output, State
from Dashboard.utils.Dash_fun import apply_layout_with_auth
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_table
from mongoengine import connect
#from app import db
from app.models import (
    CNFFoodName, CNFConversionFactor, CNFNutrientAmount,
        CNFYieldAmount, CNFRefuseAmount
)
from app.models.model_nutrients import (
            ElementsRDI, VitaminsRDI, ElementsUpperRDI,
            VitaminsUpperRDI, MacronutrientsDistRange
        )
from app.models.model_infantsRDI import (
    InfantsElementsRDI, InfantsVitaminsRDI, InfantsMacroRDI, InfantsElementsUpperRDI,\
        InfantsVitaminsUpperRDI
)
from app.models.model_childrenRDI import (
    ChildrenElementsRDI, ChildrenVitaminsRDI, ChildrenMacroRDI, ChildrenElementsUpperRDI,\
        ChildrenVitaminsUpperRDI
)
from app.models.model_malesRDI import (
    MalesElementsRDI, MalesVitaminsRDI, MalesMacroRDI, MalesElementsUpperRDI, MalesVitaminsUpperRDI
)
from app.models.model_femalesRDI import (
    FemalesElementsRDI, FemalesVitaminsRDI, FemalesMacroRDI, FemalesElementsUpperRDI,\
        FemalesVitaminsUpperRDI
)
from app.models.model_pregnancyRDI import (
    PregnancyElementsRDI, PregnancyVitaminsRDI, PregnancyMacroRDI, PregnancyElementsUpperRDI,\
        PregnancyVitaminsUpperRDI
)
from app.models.model_lactationRDI import (
    LactationElementsRDI, LactationVitaminsRDI, LactationMacroRDI, LactationElementsUpperRDI,\
        LactationVitaminsUpperRDI
)

cnf_modelnames_arr = ['MacronutrientsDistRange',
                      'InfantsElementsRDI', 'InfantsVitaminsRDI', 'InfantsMacroRDI', 'InfantsElementsUpperRDI', \
                      'InfantsVitaminsUpperRDI',
                    'ChildrenElementsRDI', 'ChildrenVitaminsRDI', 'ChildrenMacroRDI', 'ChildrenElementsUpperRDI',\
                            'ChildrenVitaminsUpperRDI',
                        'MalesElementsRDI', 'MalesVitaminsRDI', 'MalesMacroRDI', 'MalesElementsUpperRDI',
                      'MalesVitaminsUpperRDI',
                        'FemalesElementsRDI', 'FemalesVitaminsRDI', 'FemalesMacroRDI', 'FemalesElementsUpperRDI',\
                        'FemalesVitaminsUpperRDI',
                        'PregnancyElementsRDI', 'PregnancyVitaminsRDI', 'PregnancyMacroRDI', 'PregnancyElementsUpperRDI',\
                                'PregnancyVitaminsUpperRDI',
                    'LactationElementsRDI', 'LactationVitaminsRDI', 'LactationMacroRDI', 'LactationElementsUpperRDI',\
                            'LactationVitaminsUpperRDI'
                      ]
# import the rdi csv table names and filenames arrs
from Dashboard.utils.Dash_App_utils import (table_names_arr,csv_names_arr, make_dataframes,
                                        make_table, make_figure)
import pandas as pd
import re
connect('cnf')

url_base = '/dash/shiny/'

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

colors={
        'background': 'white',
        'text': 'black'
}
# todo: use this for cumulation
rdi_nutrients = {'water': "", "fat":"", 'fiber': "", 'linoleicAcid':"", 'alphaLinolenicAcid': "", 'carbohydrate': "", 'protein': "",
                'calcium': "", 'chromium': "", 'copper': "", 'fluoride': "", 'iodine': "", 'iron': "", 'magnesium': "",
                'manganese': "", 'molybdenum': "", 'phosphorus': "", 'selenium': "", 'zinc': "", 'potassium': "",
                'sodium': "", 'chloride': "",
                'vitaminA': "", 'vitaminC': "", 'vitaminD': "", 'vitaminE': "", 'vitaminK': "", 'thiamin': "",
                'riboflavin': "", 'niacin': "", 'vitaminB6': "", 'folate': "", 'vitaminB12': "",
                'pantothenicAcid': "",'biotin': "", 'choline': ""}


# more global variables
food_name_query_set = CNFFoodName.objects(description__exists=True)
food_names_arr = []
food_ids = []
food_groups = []
food_sources = []
food_dict = {}

for food in food_name_query_set:
    '''
    # dict with description as key, values: id, group, source
    food_dict[food['description']] = [food['id'], food['food_group'],
                                      food['food_source'],
                                      food['scientific_name']]
    '''
    food_names_arr.append(food['description'])
    food_ids.append(food['id'])

#assert len(food_names_arr) == len(food_ids)

food_to_id_dict = dict(zip(food_names_arr, food_ids))

# gets allocated in function, {ingredient: measure_name}
foodnameDict = {}

# these get values in functions for make_tables()
# conversions for selected food
#global helper fn
#helper fn

#helper fn
def get_unit_names(food_id):
    """
    class_collection: one of the CNF collections
    get the unit names and return after cleaning and keeping only unique
    """
    # get units
    conversions = CNFConversionFactor.objects.filter(food=str(food_id))
    unit_names = []
    for c in conversions:
        unit_names.append(c.measure.name)
    print(f'unit_names {unit_names}')
    # take out the numbers and whitespace
    # todo: pattern has to include all non-alpha
    pattern = '[0-9]'
    temp_arr = [re.sub(pattern, '', x) for x in unit_names]
    unit_names = [x.strip() for x in temp_arr]
    # only keep the unique measure names
    measure_set = set(unit_names)
    unit_names = list(measure_set)

    return unit_names

def make_conversions_df(food_id):
    """
    param
    """
    food = CNFFoodName.objects.get(id=str(food_id))
    conversions = CNFConversionFactor.objects.filter(food=food)
    measure_names = []
    measure_vals = []
    for c in conversions:
        measure_names.append(c.measure.name)
        measure_vals.append(c.value)

    conversions_df = pd.DataFrame(
        {'Name': measure_names,
         'Multiplier': measure_vals
         })

    return conversions_df

#global helper fn
def make_nutrients_df(food_id):
    food = CNFFoodName.objects.get(id=str(food_id))
    nutrients = CNFNutrientAmount.objects.filter(food=food, nutrient_value__gt=0)

    nutrient_names = []
    nutrient_vals = []
    nutrient_units = []

    for n in nutrients:
        nutrient_names.append(n.nutrient_name.name)
        nutrient_vals.append(n.nutrient_value)
        nutrient_units.append(n.nutrient_name.unit)

    nutrients_df = pd.DataFrame(
        {"Name": nutrient_names,
         "Value": nutrient_vals,
         "Units": nutrient_units
         })

    return nutrients_df


# make a dummy table so references in callbacks don't have issues
dummy_food = food_names_arr[0]
dummy_food_id = food_to_id_dict[dummy_food]
conversions_df = make_conversions_df(dummy_food_id)
#
nutrients_df = make_nutrients_df(dummy_food_id)

#list of lists of dicts for cum ingredients
cum_ingredients = [{'ingredient': "", "amt": "", "units": ""}]

layout=html.Div([
    html.Label(
            "Enter age and select gender or pregnant/lactating"
        ),
    html.Div([
        dcc.Input(
            id="age-input", type="number", value='30',
            autoComplete=False, debounce=True
        ),
        dcc.RadioItems(
            options=[
                {'label': 'male', 'value': 'Males'},
                {'label': 'female', 'value': 'Females'},
                {'label': 'pregnant', 'value': 'Pregnancy'},
                {'label': 'lactating', 'value': 'Lactation' }
            ],
            value='Females'
        ),
    ]),
    #todo: show if over RDI and display red if over upper RDI
    html.Div( # todo: output table here
        dash_table.DataTable(
            id='cumulative-ingredients',
            columns=[{
                'ingredient': '',
                'amount': '',
                'units': '',
                'deletable': True,
                'renamable': True
            }],
            data=[None],
            editable=True,
            row_deletable=True
        )
    ),
    html.Label(
        "1. Choose Ingredient"
    ),
    html.Div([
        dcc.Input(
            id="search-ingredient",
            list="food_names", placeholder=food_names_arr[3333],
            debounce=True,
            style={'width': '80%'}
        ),
    ],
    ),
    html.Datalist(
        id="food_names", children=[
            html.Option(value=food) for food in food_names_arr
        ]
    ),
    html.Br(),
    html.Label(
        "2. Amount Units"
    ),
    html.Div([
        dcc.Dropdown(
            id="units-dropdown",
            ),
    ]),
    html.Br(),
    html.Label(
        "3. Quantity"
    ),
    dcc.Input(
        id="numerical-amount", type="number",
        autoComplete=False, debounce=True
    ),
    html.Br(),
    html.Button(
        "Add Ingredient", id='add-ingredient', n_clicks=0
    ),
    html.Button(
        "Remove Ingredient", id="remove-ingredient", n_clicks=0
    ),
    html.Br(),
    #todo: separate layouts
    html.Div(
        dcc.RadioItems(
                    options=[
                        {'label': 'tables for ingredient', 'value': 'cnf-tables-1'},
                        {'label': 'vs RDI for ingredient', 'value': 'rdi-graphs-1'},
                        {'label': 'tables for cumulative', 'value': 'cnf-tables-cum'},
                        {'label': 'vs RDI for ingredient', 'value': 'rdi-graphs-cum'},
                    ],
                    value='cnf-tables-1'
        ),
    ),
    html.Br(),
    html.Div(
        id="chosen-food"  # shows full name in <H3>
    ),
    html.Div(
        id="table-foodgroup-source"
    ),
    html.Div(
      html.H5("Conversions Multipliers")
    ),
    html.Div(
        id="conversion-table"
    ),
    html.Div(
        html.H5("Nutrients")
    ),
    html.Div(
        id="nutrients-table"
    ),
    html.Br(),

    html.Div(
        dcc.Graph(
            id="cnf-vs-rdi"
        )
    ),
])

def Add_Dash(server):
    app = Dash(server=server, url_base_pathname=url_base,
               external_stylesheets=external_stylesheets)
    #application = app.server
    # external_stylesheets=[dbc.themes.BOOTSTRAP])
    apply_layout_with_auth(app, layout)

    @app.callback(
        Output('chosen-food', 'children'),
        Output('units-dropdown', 'options'),
        Input('search-ingredient', 'value')
    )
    def update_dropdown(ingredient):
        food_name = html.H3(f"{ingredient}")
        #get food_id
        food_id = food_to_id_dict[ingredient]
        # get units
        measure_names = get_unit_names(food_id)
        # make dict to return options for dropdown
        foodnameDict={ingredient:measure_names}

        return food_name, [{'label':unit, 'value':unit} for unit in foodnameDict[ingredient]]

    # 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('conversion-table', 'children'),
        Output('nutrients-table', 'children'),
        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_table = make_conversions_table(conversions_df)

        nutrients_df = make_nutrients_df(food_id)

        nutrients_table = make_nutrients_table(nutrients_df)

        return food_group_table, conversions_table, nutrients_table



    def make_conversions_table(conversions_df):
        conversions_table = dash_table.DataTable(
                            id='conversions_table',
                            columns=[{"name": i, "id": i} for i in conversions_df.columns],
                            data=conversions_df.to_dict('records'),
                            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'
                            },

                        )
        return conversions_table

    def make_nutrients_table(nutrients_df):
        nutrients_table = dash_table.DataTable(
                        id='nutrients_table',
                        columns=[{"name": i, "id": i} for i in nutrients_df.columns],
                        data=nutrients_df.to_dict('records'),
                        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'
                        },
                    )
        return nutrients_table

    #return app.server

    #todo:
    # also show graph of elements, vitamins and macronutrients as % of RDI
    # for graph each nutrient is shown against its own RDI as %
    # for macro need to sum total calories and divide that by intake of each macro

    @app.callback(
        Output("nutrients-table", "children"),
        Input("add-ingredient", "n_clicks"),
        State("numerical-amount", "value"),
        State('units-dropdown', 'value'),

    )
    def update_nutrients_table(num_clicks, amount, units):
        '''
        get multiplier from conversions table for correct units,
        multiply by each nutrient by correct amount
        conv_cols of Conversions: Name, Multiplier
        '''
        curr_nutrients_df = nutrients_df.copy()

        # todo: if n_clicks reset to zero check for <0 for remove ingredient
        if num_clicks > 0:
            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 = row['Multiplier']
                    break

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

            #multiply all nutrients by new_multiplier
            for index, row in curr_nutrients_df.iterrows():
                for col in curr_nutrients_df.columns:
                    curr_nutrients_df.iloc[row, col] *= new_multiplier

            updated_nutrients_table = make_nutrients_table(curr_nutrients_df)

            return updated_nutrients_table

        return make_nutrients_table(curr_nutrients_df)

    return app.server

    """
    # todo: user clicks add ingredient, show on "cumulative ingredients"
    # [name, amount, units]
    # https://dash.plotly.com/datatable/editable
    @app.callback(
        Output('cumulative-ingredients', "children"),
        Input("add-ingredient", "n_clicks"),
        [State("search-ingredient", 'value'),
         State("numerical-amount", 'value'),
         State('units-dropdown', 'value'),
         State('cumulative-ingredients', 'data'),
         State('cumulative-ingredients', 'columns')]
    )
    def update_cum_ingredients(num_clicks, ingredient, amount, units, rows, cols):
        if num_clicks > 0:
            rows.append({cols['ingredient']: ingredient, cols['amount']: amount,
                         cols['units']: units})
            return rows
        pass
    """

    #return app.server

I raised an issue, feature request: [Feature Request] Need error messages on fail · Issue #1452 · plotly/dash · GitHub

Does the Enterprise version come with error messages?
Is that a paid feature???

I thought about running the app in Jupyter to debug but apparently that’s a bad idea.
So I made a toy example and discovered, if I want to use the State of a dcc.Input, it’s probably best to not use Debounce=True.

@app.callback(
        Output("test-out", "children"),
        [Input("add-ingredient", "n_clicks")],
        [State("numerical-amount", "value"),
        State('units-dropdown', 'value')],
    )
    def test_update_table(num_clicks, amt, units):
        return f'clicks: {num_clicks}, amt: {amt}, units: {units}'

As advised, I tested my function in Jupyter and it was broken (mainly casting in dataframe to float) and now it is fixed but it still ends up breaking my other callbacks when I add it back into.

@app.callback(
        Output("nutrients-table", "children"),
        [Input("add-ingredient", "n_clicks")],
        [State("numerical-amount", "value"),
        State('units-dropdown', 'value')],

    )
    def update_nutrients_table(num_clicks, amount, units):
        '''
        get multiplier from conversions table for correct units,
        multiply by each nutrient by correct amount
        conv_cols of Conversions: Name, Multiplier
        '''
        curr_nutrients_df = nutrients_df.copy().astype(str)

        # todo: if n_clicks reset to zero check for <0 for remove ingredient
        if num_clicks > 0:
            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(amount)) / measure_num
            new_multiplier = multiplier_factor * curr_multiplier

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

            # return updated_nutrients_table
            # print(curr_nutrients_df.head)
            return make_nutrients_table(curr_nutrients_df)
        #not updated
        return make_nutrients_table(curr_nutrients_df)