Skip to content

Fix args_grouping to handle "id" in the id #2087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## Unreleased

### Fixed

- [#2087](https://github.com/plotly/dash/pull/2087) Fix bug [#2086](https://github.com/plotly/dash/issues/2086) in which using id as a key within a component's id breaks the new callback context's `args_grouping` function.
- [#2084](https://github.com/plotly/dash/pull/2084) In dash 2.5.0, a default viewport meta tag was added as recommended for mobile-optimized sites by [mdn](https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag)
This feature can be disabled by providing an empty viewport meta tag. e.g. `app = Dash(meta_tags=[{"name": "viewport"}])`

### Removed

- [#2087](https://github.com/plotly/dash/pull/2087) Removed the undocumented callback context `args_grouping_values` property which was incompatible with pattern-matching callbacks.

## [2.5.0] - 2022-06-07

### Added
Expand Down
57 changes: 2 additions & 55 deletions dash/_callback_context.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import functools
import warnings
import json
from copy import deepcopy
import flask

from . import exceptions
from ._utils import stringify_id, AttributeDict
from ._utils import AttributeDict


def has_context(func):
Expand Down Expand Up @@ -147,59 +146,7 @@ def display(btn1, btn2):
return "No clicks yet"

"""
triggered = getattr(flask.g, "triggered_inputs", [])
triggered = [item["prop_id"] for item in triggered]
grouping = getattr(flask.g, "args_grouping", {})

def update_args_grouping(g):
if isinstance(g, dict) and "id" in g:
str_id = stringify_id(g["id"])
prop_id = f"{str_id}.{g['property']}"

new_values = {
"value": g.get("value"),
"str_id": str_id,
"triggered": prop_id in triggered,
"id": AttributeDict(g["id"])
if isinstance(g["id"], dict)
else g["id"],
}
g.update(new_values)

def recursive_update(g):
if isinstance(g, (tuple, list)):
for i in g:
update_args_grouping(i)
recursive_update(i)
if isinstance(g, dict):
for i in g.values():
update_args_grouping(i)
recursive_update(i)

recursive_update(grouping)

return grouping

# todo not sure whether we need this, but it removes a level of nesting so
# you don't need to use `.value` to get the value.
@property
@has_context
def args_grouping_values(self):
grouping = getattr(flask.g, "args_grouping", {})
grouping = deepcopy(grouping)

def recursive_update(g):
if isinstance(g, (tuple, list)):
for i in g:
recursive_update(i)
if isinstance(g, dict):
for k, v in g.items():
if isinstance(v, dict) and "id" in v:
g[k] = v["value"]
recursive_update(v)

recursive_update(grouping)
return grouping
return getattr(flask.g, "args_grouping", [])

@property
@has_context
Expand Down
16 changes: 15 additions & 1 deletion dash/_grouping.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""
from dash.exceptions import InvalidCallbackReturnValue
from ._utils import AttributeDict
from ._utils import AttributeDict, stringify_id


def flatten_grouping(grouping, schema=None):
Expand Down Expand Up @@ -222,3 +222,17 @@ def validate_grouping(grouping, schema, full_schema=None, path=()):
)
else:
pass


def update_args_group(g, triggered):
if isinstance(g, dict):
str_id = stringify_id(g["id"])
prop_id = f"{str_id}.{g['property']}"

new_values = {
"value": g.get("value"),
"str_id": str_id,
"triggered": prop_id in triggered,
"id": AttributeDict(g["id"]) if isinstance(g["id"], dict) else g["id"],
}
g.update(new_values)
14 changes: 9 additions & 5 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,7 @@
from . import _watch
from . import _get_app

from ._grouping import (
flatten_grouping,
map_grouping,
grouping_len,
)
from ._grouping import flatten_grouping, map_grouping, grouping_len, update_args_group

from . import _pages
from ._pages import (
Expand Down Expand Up @@ -1431,6 +1427,14 @@ def dispatch(self):
inputs_state = inputs + state
inputs_state = convert_to_AttributeDict(inputs_state)

# update args_grouping attributes
for g in inputs_state:
# check for pattern matching: list of inputs or state
if isinstance(g, list):
for pattern_match_g in g:
update_args_group(pattern_match_g, changed_props)
update_args_group(g, changed_props)

args_grouping = map_grouping(
lambda ind: inputs_state[ind], inputs_state_indices
)
Expand Down
6 changes: 3 additions & 3 deletions tests/assets/grouping_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def grouping_app():
),
dict(
items=dict(
all=State({"item": ALL}, "children"),
all=State({"id": ALL}, "children"),
new=State("new-item", "value"),
),
triggers=[Input("add", "n_clicks"), Input("new-item", "n_submit")],
Expand All @@ -64,11 +64,11 @@ def edit_list(items, triggers):
html.Div(
[
dcc.Checklist(
id={"item": i, "action": "done"},
id={"id": i, "property": "done"},
options=[{"label": "", "value": "done"}],
style={"display": "inline"},
),
html.Div(text, id={"item": i}, style=style_todo),
html.Div(text, id={"id": i}, style=style_todo),
],
style={"clear": "both"},
)
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/callbacks/test_wildcards.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,10 @@ def assert_callback_context(items_text):
items=dict(
all=[
{
"id": {"item": i},
"id": {"id": i},
"property": "children",
"value": text,
"str_id": stringify_id({"item": i}),
"str_id": stringify_id({"id": i}),
"triggered": False,
}
for i, text in enumerate(items_text[:-1])
Expand Down