UPDATE: I was able to solve part of the problem by changing the name of the last variable returned as “data.”
But I still have not resolved the big problem yet.
I am hoping to use dcc.Store to resolve a significant problem.
For some reason, my app is returning the output (final values) from a previous user session, as if it were cached in the browser somewhere unknown.
I need to figure out how to exercise greater control over where the data from each session is stored and when it is cleared. But my efforts over the last two days have failed.
By using dcc.Store, I had hoped to solve the problem by setting the ‘storage_type’ argument to ‘memory’ and changing the ‘clear_data’ argument’s value to True with a callback at the end of the program.
Thank you for reviewing my question.
This is my dcc.Store code, which resides at the tail end of the dbc.Accordion component:
# dcc.Store inside the user's current browser session
dcc.Store(id='store-data', data=[], storage_type='memory', clear_data=False) # 'local' or 'session'
]) # Later use callback to chang clear_data to True when final analysis is completed.
Here is the Traceback:
Traceback (most recent call last):
File "C:\Users\rober\ings\venv\Lib\site-packages\dash\_callback.py", line 151, in add_context
output_value = func(*func_args, **func_kwargs) # %% callback invoked %%
TypeError: store_data() takes 1 positional argument but 2 were given
I am not entirely clear on why I receive this message, except that I DO have two callbacks with data as the Output.
But one of those callbacks is the one takes the user input and stores it in the data_key in dcc.Store.
I will present you with those callbacks as the minimal, meaningful examples. Below, I pasted the code from the entire page below just in case someone wishes to review it.
Here is the first callback that uses “data” in the Output statement:
# This callback and function accepts the input and processes the recipe,
# producing an editable table of amounts, units of measurement,
# ingredients and modifiers essential to a nutritional profile.
# Important Note: The user-edited data must be resaved as valid_recipe_ents.json.
@callback(
Output('nutrients-review-datatable', 'data'),
Input('recipe-textarea-submit-button', 'n_clicks'),
State('recipe-textarea', 'value')
)
def update_output(n_clicks, value):
if n_clicks > 0:
print("Recipe submitted")
with open("temp/recipe_contents.txt", "w", encoding='utf-8') as recipe_contents:
recipe = value.split('\n')
for line in recipe:
recipe_contents.write(line)
recipe_contents.write('\n')
convert_unicode_fractions()
convert_regular_fractions()
prepare_recipe_text()
parse_recipe_ner()
validate_recipe_ents()
prepare_data_for_table()
with open("./json/recipe_ents_list.json", "r") as final_recipe_file:
data = json.loads(final_recipe_file.read())
return data
Here is the second callback that uses “data” in the Output statement:
# This callback takes the edited data from the datatable
# and stores that data in the dcc.Store item at the
# base of the accordion.
@callback(
Output('store-data', 'data'),
Input('nutrients-review-datatable-submit-button', 'n_clicks'),
State('nutrients-review-datatable', 'data'),
prevent_initial_call=True
)
def store_data(data):
# hypothetical enormous dataset with millions of rows
return dataset.to_dict('records')
Again, I appreciate your time and assistanhce.
Sincerely,
Robert
============================================================
JUST IN CASE SOMEONE WANTS TO SEE THE ENTIRE PAGE.
dash.register_page(__name__, path='/')
layout = html.Div(
[
html.Br(),
html.H5("Welcome to Ingredible's Nutrition Assistant"),
html.Br(),
dbc.Accordion(
[
dbc.AccordionItem( # Step 1: Paste your recipe here.
[
html.Div("Please paste your recipe here.", className="mb-4"),
dcc.Textarea(
id="recipe-textarea",
value="",
style={"width": "100%", "height": 400},
placeholder="Please note: All information in brackets or parantheses will be deleted.",
className="mb-4"
),
dbc.Button(
"Submit Recipe",
id="recipe-textarea-submit-button",
className="me-2",
n_clicks=0
),
dbc.Button(
"Reset",
id="recipe-textarea-reset-button",
className="me-2",
n_clicks=0
),
html.Div(id="MSG-1"),
],
title="Step 1",
item_id="acc_textarea",
),
dbc.AccordionItem( # Step 2: Edit initial results.
[
html.Div("Please edit initial results.", className="mb-4"),
dash_table.DataTable(
id="nutrients-review-datatable",
columns=[{'id': i, 'name':i} for i in ['Index', 'ID', 'Grams', 'Amounts', 'Modifiers', 'Units', 'Ings', 'Multiplier']],
style_cell={'textAlign': 'left', 'padding': '10px', 'fontSize':14, 'font-family': 'sans-serif', 'fontWeight': 'bold'},
style_header={'backgroundColor': '6eafde', 'fontWeight': 'bold', 'font-size': 14, 'border': '1px solid black', 'align': 'left', 'color': 'black'},
style_data = {'color': 'black'},
style_data_conditional=[{'if': {'row_index': 'odd'}, 'backgroundColor': 'rgb(248,248,248)'}],
data=None,
editable=True,
row_deletable=True,
#tooltip,
hidden_columns=['Index', 'ID', 'Grams', 'Total Grams', 'Final Multipliers'],
css=[{"selector": ".show-hide", "rule": "display: none"}],
sort_action='native',
page_action='native',
page_size=10,
style_table={'height': '400px', 'overflowY': 'auto'},
),
dbc.Button(
"Submit Edits",
id="nutrients-review-datatable-submit-button",
className="me-2",
n_clicks=0
),
dbc.Button(
"Reset",
id="recipe-textarea-reset-button",
className="me-2",
n_clicks=0
),
html.Div(id="MSG-2"),
],
title="Step 2",
item_id="acc_datatable",
),
dbc.AccordionItem(# Step 3: Review final report.
[
html.Div("Review final analysis.", className="mb-4"),
html.Div(id="final-nutritional-profile-output", className="mb-4"),
],
title="Step 3",
item_id="acc_report",
),
],
id="accordion",
active_item="acc_textarea",
flush=True,
),
html.Div(id="accordion-contents", className="mt-3"),
# dcc.Store inside the user's current browser session
dcc.Store(id='store-data', data=[], storage_type='memory', clear_data=False) # 'local' or 'session'
]) # Later use callback to chang clear_data to True when final analysis is completed.
# @app.callback(
# Output("accordion-contents", "children"),
# [Input("accordion", "active_item")],
# )
# def change_item(item):
# return f"Item selected: {item}"
# This callback and funtion resets the text area value
# to empty when the user clicks the reset button.
@callback(
Output("recipe-textarea", "value"),
[Input("recipe-textarea-reset-button", "n_clicks")],
prevent_initial_call=True
)
def reset_textarea(n_clicks):
if n_clicks > 0:
return ""
# This callback and function accepts the input and processes the recipe,
# producing an editable table of amounts, units of measurement,
# ingredients and modifiers essential to a nutritional profile.
# Important Note: The user-edited data must be resaved as valid_recipe_ents.json.
@callback(
Output('nutrients-review-datatable', 'data'),
Input('recipe-textarea-submit-button', 'n_clicks'),
State('recipe-textarea', 'value')
)
def update_output(n_clicks, value):
if n_clicks > 0:
print("Recipe submitted")
with open("temp/recipe_contents.txt", "w", encoding='utf-8') as recipe_contents:
recipe = value.split('\n')
for line in recipe:
recipe_contents.write(line)
recipe_contents.write('\n')
convert_unicode_fractions()
convert_regular_fractions()
prepare_recipe_text()
parse_recipe_ner()
validate_recipe_ents()
prepare_data_for_table()
with open("./json/recipe_ents_list.json", "r") as final_recipe_file:
data = json.loads(final_recipe_file.read())
return data
# This callback takes the edited data from the datatable
# and stores that data in the dcc.Store item at the
# base of the accordion.
@callback(
Output('store-data', 'data'),
Input('nutrients-review-datatable-submit-button', 'n_clicks'),
State('nutrients-review-datatable', 'data'),
prevent_initial_call=True
)
def store_data(data):
# hypothetical enormous dataset with millions of rows
return dataset.to_dict('records')
# 2. or save as string like JSON
# return dataset.to_json(orient='split')
# This function and callback accept the user's edits to the nutritional
# elements in the table and produce a nutritional profile. In the future, check boxes
# allow the user to tailor the report, tracking 50+ nutrients.
@callback(
Output('final-nutritional-profile-output', 'children'),
Input('nutrients-review-datatable-submit-button', 'n_clicks'),
State('nutrients-review-datatable', 'data'),
prevent_initial_call=True
)
def create_nutritional_profile(n_clicks, data):
if n_clicks > 0:
finalize_ing_dicts(data)
create_match_idslist()
create_nutrient_dicts()
merge_nutrient_to_ing_dicts()
results = final_conversions()
for each_dict in results:
for k,v in each_dict.items():
print(k, v)
df = pd.read_csv('final_charts_data.csv')
# round numbers to two decimal places
dff = df.round(decimals = 2)
# supress/hide certain columns
dfff = dff.drop(['Gender', 'Ages', 'Benchmarks'], axis=1)
# unique values only
data = dfff.drop_duplicates(subset=['Nutrients'])
return dbc.Table.from_dataframe(data, id='review-final-analysis-datatable',
class_name="me-2",
striped=True,
bordered=True,
hover=True,
style={"width": "1200px"} )
if __name__ == "__main__":
app.run_server(debug=True, port=2000)