Skip to content

Commit 8c26eeb

Browse files
authored
Merge pull request #2094 from plotly/master-2.5.1
Version 2.5.1
2 parents 34f678a + de77d00 commit 8c26eeb

File tree

9 files changed

+74
-75
lines changed

9 files changed

+74
-75
lines changed

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## [2.5.1] - 2022-06-13
6+
7+
### Fixed
8+
9+
- [#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.
10+
- [#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)
11+
This feature can be disabled by providing an empty viewport meta tag. e.g. `app = Dash(meta_tags=[{"name": "viewport"}])`
12+
- [#2090](https://github.com/plotly/dash/pull/2090), [#2092](https://github.com/plotly/dash/pull/2092). Fixed bug where the `path` to the `pages_folder` was incorrect on Windows.
13+
14+
### Removed
15+
16+
- [#2087](https://github.com/plotly/dash/pull/2087) Removed the undocumented callback context `args_grouping_values` property which was incompatible with pattern-matching callbacks.
17+
518
## [2.5.0] - 2022-06-07
619

720
### Added
821

922
- [#1947](https://github.com/plotly/dash/pull/1947) Added `pages` - a better way to build multi-page apps. For more information see the [forum post.](https://community.plotly.com/t/introducing-dash-pages-a-dash-2-x-feature-preview/57775)
1023
- [#1965](https://github.com/plotly/dash/pull/1965) Add component as props.
11-
- [#2049](https://github.com/plotly/dash/pull/2043) Added `wait_for_class_to_equal` and `wait_for_contains_class` methods to `dash.testing`
24+
- [#2049](https://github.com/plotly/dash/pull/2049) Added `wait_for_class_to_equal` and `wait_for_contains_class` methods to `dash.testing`
1225

1326
### Changed
1427

dash/_callback_context.py

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import functools
22
import warnings
33
import json
4-
from copy import deepcopy
54
import flask
65

76
from . import exceptions
8-
from ._utils import stringify_id, AttributeDict
7+
from ._utils import AttributeDict
98

109

1110
def has_context(func):
@@ -147,59 +146,7 @@ def display(btn1, btn2):
147146
return "No clicks yet"
148147
149148
"""
150-
triggered = getattr(flask.g, "triggered_inputs", [])
151-
triggered = [item["prop_id"] for item in triggered]
152-
grouping = getattr(flask.g, "args_grouping", {})
153-
154-
def update_args_grouping(g):
155-
if isinstance(g, dict) and "id" in g:
156-
str_id = stringify_id(g["id"])
157-
prop_id = f"{str_id}.{g['property']}"
158-
159-
new_values = {
160-
"value": g.get("value"),
161-
"str_id": str_id,
162-
"triggered": prop_id in triggered,
163-
"id": AttributeDict(g["id"])
164-
if isinstance(g["id"], dict)
165-
else g["id"],
166-
}
167-
g.update(new_values)
168-
169-
def recursive_update(g):
170-
if isinstance(g, (tuple, list)):
171-
for i in g:
172-
update_args_grouping(i)
173-
recursive_update(i)
174-
if isinstance(g, dict):
175-
for i in g.values():
176-
update_args_grouping(i)
177-
recursive_update(i)
178-
179-
recursive_update(grouping)
180-
181-
return grouping
182-
183-
# todo not sure whether we need this, but it removes a level of nesting so
184-
# you don't need to use `.value` to get the value.
185-
@property
186-
@has_context
187-
def args_grouping_values(self):
188-
grouping = getattr(flask.g, "args_grouping", {})
189-
grouping = deepcopy(grouping)
190-
191-
def recursive_update(g):
192-
if isinstance(g, (tuple, list)):
193-
for i in g:
194-
recursive_update(i)
195-
if isinstance(g, dict):
196-
for k, v in g.items():
197-
if isinstance(v, dict) and "id" in v:
198-
g[k] = v["value"]
199-
recursive_update(v)
200-
201-
recursive_update(grouping)
202-
return grouping
149+
return getattr(flask.g, "args_grouping", [])
203150

204151
@property
205152
@has_context

dash/_grouping.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
1515
"""
1616
from dash.exceptions import InvalidCallbackReturnValue
17-
from ._utils import AttributeDict
17+
from ._utils import AttributeDict, stringify_id
1818

1919

2020
def flatten_grouping(grouping, schema=None):
@@ -222,3 +222,17 @@ def validate_grouping(grouping, schema, full_schema=None, path=()):
222222
)
223223
else:
224224
pass
225+
226+
227+
def update_args_group(g, triggered):
228+
if isinstance(g, dict):
229+
str_id = stringify_id(g["id"])
230+
prop_id = f"{str_id}.{g['property']}"
231+
232+
new_values = {
233+
"value": g.get("value"),
234+
"str_id": str_id,
235+
"triggered": prop_id in triggered,
236+
"id": AttributeDict(g["id"]) if isinstance(g["id"], dict) else g["id"],
237+
}
238+
g.update(new_values)

dash/_pages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def _module_name_to_page_name(filename):
6161
def _infer_path(filename, template):
6262
if template is None:
6363
if CONFIG.pages_folder:
64-
pages_folder = CONFIG.pages_folder.split("/")[-1]
64+
pages_folder = CONFIG.pages_folder.replace("\\", "/").split("/")[-1]
6565
path = (
6666
filename.replace("_", "-")
6767
.replace(".", "/")

dash/dash.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,7 @@
6161
from . import _watch
6262
from . import _get_app
6363

64-
from ._grouping import (
65-
flatten_grouping,
66-
map_grouping,
67-
grouping_len,
68-
)
64+
from ._grouping import flatten_grouping, map_grouping, grouping_len, update_args_group
6965

7066
from . import _pages
7167
from ._pages import (
@@ -843,7 +839,7 @@ def _generate_meta_html(self):
843839
x.get("http-equiv", "") == "X-UA-Compatible" for x in meta_tags
844840
)
845841
has_charset = any("charset" in x for x in meta_tags)
846-
has_viewport = any("viewport" in x for x in meta_tags)
842+
has_viewport = any(x.get("name") == "viewport" for x in meta_tags)
847843

848844
tags = []
849845
if not has_ie_compat:
@@ -1431,6 +1427,14 @@ def dispatch(self):
14311427
inputs_state = inputs + state
14321428
inputs_state = convert_to_AttributeDict(inputs_state)
14331429

1430+
# update args_grouping attributes
1431+
for g in inputs_state:
1432+
# check for pattern matching: list of inputs or state
1433+
if isinstance(g, list):
1434+
for pattern_match_g in g:
1435+
update_args_group(pattern_match_g, changed_props)
1436+
update_args_group(g, changed_props)
1437+
14341438
args_grouping = map_grouping(
14351439
lambda ind: inputs_state[ind], inputs_state_indices
14361440
)
@@ -2171,7 +2175,9 @@ def _import_layouts_from_pages(self):
21712175
continue
21722176

21732177
page_filename = os.path.join(root, file).replace("\\", "/")
2174-
_, _, page_filename = page_filename.partition(walk_dir + "/")
2178+
_, _, page_filename = page_filename.partition(
2179+
walk_dir.replace("\\", "/") + "/"
2180+
)
21752181
page_filename = page_filename.replace(".py", "").replace("/", ".")
21762182

21772183
pages_folder = (

dash/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.5.0"
1+
__version__ = "2.5.1"

tests/assets/grouping_app.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def grouping_app():
3939
),
4040
dict(
4141
items=dict(
42-
all=State({"item": ALL}, "children"),
42+
all=State({"id": ALL}, "children"),
4343
new=State("new-item", "value"),
4444
),
4545
triggers=[Input("add", "n_clicks"), Input("new-item", "n_submit")],
@@ -64,11 +64,11 @@ def edit_list(items, triggers):
6464
html.Div(
6565
[
6666
dcc.Checklist(
67-
id={"item": i, "action": "done"},
67+
id={"id": i, "property": "done"},
6868
options=[{"label": "", "value": "done"}],
6969
style={"display": "inline"},
7070
),
71-
html.Div(text, id={"item": i}, style=style_todo),
71+
html.Div(text, id={"id": i}, style=style_todo),
7272
],
7373
style={"clear": "both"},
7474
)

tests/integration/callbacks/test_wildcards.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,10 @@ def assert_callback_context(items_text):
421421
items=dict(
422422
all=[
423423
{
424-
"id": {"item": i},
424+
"id": {"id": i},
425425
"property": "children",
426426
"value": text,
427-
"str_id": stringify_id({"item": i}),
427+
"str_id": stringify_id({"id": i}),
428428
"triggered": False,
429429
}
430430
for i, text in enumerate(items_text[:-1])

tests/integration/test_integration.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from dash.testing.wait import until
1717

1818

19-
def test_inin004_wildcard_data_attributes(dash_duo):
19+
def test_inin003_wildcard_data_attributes(dash_duo):
2020
app = Dash()
2121
test_time = datetime.datetime(2012, 1, 10, 2, 3)
2222
test_date = datetime.date(test_time.year, test_time.month, test_time.day)
@@ -48,7 +48,7 @@ def test_inin004_wildcard_data_attributes(dash_duo):
4848
assert dash_duo.get_logs() == []
4949

5050

51-
def test_inin005_no_props_component(dash_duo):
51+
def test_inin004_no_props_component(dash_duo):
5252
app = Dash()
5353
app.layout = html.Div(
5454
[
@@ -71,7 +71,7 @@ def test_inin005_no_props_component(dash_duo):
7171
assert re.sub("\\s+", " ", inner) == expected
7272

7373

74-
def test_inin006_flow_component(dash_duo):
74+
def test_inin005_flow_component(dash_duo):
7575
app = Dash()
7676

7777
app.layout = html.Div(
@@ -109,7 +109,7 @@ def display_output(react_value, flow_value):
109109
dash_duo.percy_snapshot(name="flowtype")
110110

111111

112-
def test_inin007_meta_tags(dash_duo):
112+
def test_inin006_meta_tags(dash_duo):
113113
metas = [
114114
{"name": "description", "content": "my dash app"},
115115
{"name": "custom", "content": "customized"},
@@ -133,6 +133,25 @@ def test_inin007_meta_tags(dash_duo):
133133
assert meta_tag.get_attribute("content") == meta_info["content"]
134134

135135

136+
def test_inin007_change_viewport_meta_tag(dash_duo):
137+
"""
138+
As of dash 2.5 the default viewport meta tag is:
139+
[{"name": "viewport", "content": "width=device-width, initial-scale=1"}]
140+
Test verifies that this feature can be disabled by using an empty viewport tag.
141+
"""
142+
143+
app = Dash(meta_tags=[{"name": "viewport"}])
144+
145+
app.layout = html.Div(id="content")
146+
147+
dash_duo.start_server(app)
148+
149+
viewport_meta = dash_duo.find_elements('meta[name="viewport"]')
150+
151+
assert len(viewport_meta) == 1, "Should have 1 viewport meta tags"
152+
assert viewport_meta[0].get_attribute("content") == ""
153+
154+
136155
def test_inin008_index_customization(dash_duo):
137156
app = Dash()
138157

0 commit comments

Comments
 (0)