Skip to content

Commit

Permalink
Merge branch 'dev' into fix-2467
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
#	tests/integration/test_patch.py
  • Loading branch information
T4rk1n committed Mar 28, 2023
2 parents 09efbed + fde5033 commit 944eda2
Show file tree
Hide file tree
Showing 11 changed files with 561 additions and 405 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).

## Fixed

- [#2471](https://github.com/plotly/dash/pull/2471) Fix allow_duplicate output with clientside callback, fix [#2467](https://github.com/plotly/dash/issues/2467)
- [#2479](https://github.com/plotly/dash/pull/2479) Fix `KeyError` "Callback function not found for output [...], , perhaps you forgot to prepend the '@'?" issue when using duplicate callbacks targeting the same output. This issue would occur when the app is restarted or when running with multiple `gunicorn` workers.
- [#2471](https://github.com/plotly/dash/pull/2471) Fix `allow_duplicate` output with clientside callback, fix [#2467](https://github.com/plotly/dash/issues/2467)

## [2.9.1] - 2023-03-17

Expand Down
2 changes: 1 addition & 1 deletion components/dash-core-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,6 @@
"react-dom": ">=16"
},
"browserslist": [
"last 7 years and not dead"
"last 8 years and not dead"
]
}
871 changes: 485 additions & 386 deletions components/dash-html-components/package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions components/dash-html-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.19.6",
"@babel/preset-env": "^7.19.4",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.3",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^8.2.5",
"babel-loader": "^9.1.2",
"cheerio": "^0.22.0",
"cross-env": "^7.0.3",
"es-check": "^7.0.1",
Expand All @@ -48,7 +48,7 @@
"react-docgen": "^5.4.3",
"request": "^2.88.2",
"string": "^3.3.3",
"webpack": "^5.76.2",
"webpack": "^5.76.3",
"webpack-cli": "^4.10.0"
},
"files": [
Expand All @@ -59,6 +59,6 @@
"react-dom": ">=17"
},
"browserslist": [
"last 7 years and not dead"
"last 8 years and not dead"
]
}
2 changes: 1 addition & 1 deletion components/dash-table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,6 @@
"npm": ">=6.1.0"
},
"browserslist": [
"last 7 years and not dead"
"last 8 years and not dead"
]
}
2 changes: 1 addition & 1 deletion dash/_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def insert_callback(
output, prevent_initial_call, config_prevent_initial_callbacks
)

callback_id = create_callback_id(output)
callback_id = create_callback_id(output, inputs)
callback_spec = {
"output": callback_id,
"inputs": [c.to_dict() for c in inputs],
Expand Down
11 changes: 9 additions & 2 deletions dash/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,22 @@ def first(self, *names):
return next(iter(self), {})


def create_callback_id(output):
def create_callback_id(output, inputs):
# A single dot within a dict id key or value is OK
# but in case of multiple dots together escape each dot
# with `\` so we don't mistake it for multi-outputs
hashed_inputs = None

def _concat(x):
nonlocal hashed_inputs
_id = x.component_id_str().replace(".", "\\.") + "." + x.component_property
if x.allow_duplicate:
if not hashed_inputs:
hashed_inputs = hashlib.md5(
".".join(str(x) for x in inputs).encode("utf-8")
).hexdigest()
# Actually adds on the property part.
_id += f"@{uuid.uuid4().hex}"
_id += f"@{hashed_inputs}"
return _id

if isinstance(output, (list, tuple)):
Expand Down
2 changes: 1 addition & 1 deletion dash/dash-renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@
],
"prettier": "@plotly/prettier-config-dash",
"browserslist": [
"last 7 years and not dead"
"last 8 years and not dead"
]
}
7 changes: 4 additions & 3 deletions dash/testing/composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ def __init__(self, server, **kwargs):
super().__init__(**kwargs)
self.server = server

def start_server(self, app, **kwargs):
def start_server(self, app, navigate=True, **kwargs):
"""Start the local server with app."""

# start server with app and pass Dash arguments
self.server(app, **kwargs)

# set the default server_url, it implicitly call wait_for_page
self.server_url = self.server.url
if navigate:
# set the default server_url, it implicitly call wait_for_page
self.server_url = self.server.url


class DashRComposite(Browser):
Expand Down
50 changes: 49 additions & 1 deletion tests/integration/test_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,55 @@ def on_click(_, value):
dash_duo.wait_for_text_to_equal("#initial", "init")


def test_pch004_clientside_duplicate(dash_duo):
def test_pch004_duplicate_output_restart(dash_duo_mp):
# Duplicate output ids should be the same between restarts for the same ids
def create_app():
app = Dash(__name__)
app.layout = html.Div(
[
html.Button("Click 1", id="click1"),
html.Button("Click 2", id="click2"),
html.Div(id="output"),
]
)

@app.callback(
Output("output", "children", allow_duplicate=True),
Input("click1", "n_clicks"),
prevent_initial_call=True,
)
def on_click(_):
return "click 1"

@app.callback(
Output("output", "children", allow_duplicate=True),
Input("click2", "n_clicks"),
prevent_initial_call=True,
)
def on_click(_):
return "click 2"

return app

dash_duo_mp.start_server(create_app())

dash_duo_mp.wait_for_element("#click1").click()
dash_duo_mp.wait_for_text_to_equal("#output", "click 1")

dash_duo_mp.wait_for_element("#click2").click()
dash_duo_mp.wait_for_text_to_equal("#output", "click 2")

dash_duo_mp.server.stop()

dash_duo_mp.start_server(create_app(), navigate=False)
dash_duo_mp.wait_for_element("#click1").click()
dash_duo_mp.wait_for_text_to_equal("#output", "click 1")

dash_duo_mp.wait_for_element("#click2").click()
dash_duo_mp.wait_for_text_to_equal("#output", "click 2")


def test_pch005_clientside_duplicate(dash_duo):
app = Dash(__name__)

app.layout = html.Div(
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/library/test_grouped_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def check_output_for_grouping(grouping):
mock_fn = mock.Mock()
mock_fn.return_value = grouping
if multi:
callback_id = create_callback_id(flatten_grouping(outputs))
callback_id = create_callback_id(flatten_grouping(outputs), [])
else:
callback_id = create_callback_id(outputs)
callback_id = create_callback_id(outputs, [])

app.callback(
outputs,
Expand Down

0 comments on commit 944eda2

Please sign in to comment.