Skip to content

Commit

Permalink
Merge pull request #1248 from plotly/1245-persist-patterns
Browse files Browse the repository at this point in the history
support persistence with dict ids
  • Loading branch information
alexcjohnson authored May 15, 2020
2 parents 79c6f30 + 20cf65f commit 308dece
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Changed
- [#1237](https://github.com/plotly/dash/pull/1237) Closes [#920](https://github.com/plotly/dash/issues/920): Converts hot reload fetch failures into a server status indicator showing whether the latest fetch succeeded or failed. Callback fetch failures still appear as errors but have a clearer message.

### Fixed
- [#1248](https://github.com/plotly/dash/pull/1248) Fixes [#1245](https://github.com/plotly/dash/issues/1245), so you can use prop persistence with components that have dict IDs, ie for pattern-matching callbacks.

## [1.12.0] - 2020-05-05
### Added
- [#1228](https://github.com/plotly/dash/pull/1228) Adds control over firing callbacks on page (or layout chunk) load. Individual callbacks can have their initial calls disabled in their definition `@app.callback(..., prevent_initial_call=True)` and similar for `app.clientside_callback`. The app-wide default can also be changed with `app=Dash(prevent_initial_callbacks=True)`, then individual callbacks may disable this behavior.
Expand Down
3 changes: 2 additions & 1 deletion dash-renderer/src/persistence.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
import {createAction} from 'redux-actions';

import Registry from './registry';
import {stringifyId} from './actions/dependencies';

export const storePrefix = '_dash_persistence.';

Expand Down Expand Up @@ -270,7 +271,7 @@ const getTransform = (element, propName, propPart) =>
: noopTransform;

const getValsKey = (id, persistedProp, persistence) =>
`${id}.${persistedProp}.${JSON.stringify(persistence)}`;
`${stringifyId(id)}.${persistedProp}.${JSON.stringify(persistence)}`;

const getProps = layout => {
const {props, type, namespace} = layout;
Expand Down
8 changes: 4 additions & 4 deletions tests/integration/devtools/test_hot_reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

def test_dvhr001_hot_reload(dash_duo):
app = dash.Dash(__name__, assets_folder="hr_assets")
app.layout = html.Div([
html.H3("Hot reload", id="text"),
html.Button("Click", id="btn")
], id="hot-reload-content")
app.layout = html.Div(
[html.H3("Hot reload", id="text"), html.Button("Click", id="btn")],
id="hot-reload-content",
)

@app.callback(Output("text", "children"), [Input("btn", "n_clicks")])
def new_text(n):
Expand Down
77 changes: 76 additions & 1 deletion tests/integration/renderer/test_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from selenium.webdriver.common.keys import Keys

import dash
from dash.dependencies import Input, Output
from dash.dependencies import Input, Output, State, MATCH

import dash_core_components as dcc
import dash_html_components as html
Expand Down Expand Up @@ -451,3 +451,78 @@ def set_out(val):
dash_duo.find_element("#persistence-val").send_keys("2")
assert not dash_duo.get_logs()
dash_duo.wait_for_text_to_equal("#out", "artichoke")


def test_rdps012_pattern_matching(dash_duo):
# copy of rdps010 but with dict IDs,
# plus a button to change the dict ID so the persistence should reset
def make_input(persistence, n):
return dcc.Input(
id={"i": n, "id": "persisted"},
className="persisted",
value="a",
persistence=persistence,
)

app = dash.Dash(__name__)
app.layout = html.Div(
[html.Button("click", id="btn", n_clicks=0), html.Div(id="content")]
)

@app.callback(Output("content", "children"), [Input("btn", "n_clicks")])
def content(n):
return [
dcc.Input(
id={"i": n, "id": "persistence-val"},
value="",
className="persistence-val",
),
html.Div(make_input("", n), id={"i": n, "id": "persisted-container"}),
html.Div(id={"i": n, "id": "out"}, className="out"),
]

@app.callback(
Output({"i": MATCH, "id": "persisted-container"}, "children"),
[Input({"i": MATCH, "id": "persistence-val"}, "value")],
[State("btn", "n_clicks")],
)
def set_persistence(val, n):
return make_input(val, n)

@app.callback(
Output({"i": MATCH, "id": "out"}, "children"),
[Input({"i": MATCH, "id": "persisted"}, "value")],
)
def set_out(val):
return val

dash_duo.start_server(app)

for _ in range(3):
dash_duo.wait_for_text_to_equal(".out", "a")
dash_duo.find_element(".persisted").send_keys("lpaca")
dash_duo.wait_for_text_to_equal(".out", "alpaca")

dash_duo.find_element(".persistence-val").send_keys("s")
dash_duo.wait_for_text_to_equal(".out", "a")
dash_duo.find_element(".persisted").send_keys("nchovies")
dash_duo.wait_for_text_to_equal(".out", "anchovies")

dash_duo.find_element(".persistence-val").send_keys("2")
dash_duo.wait_for_text_to_equal(".out", "a")
dash_duo.find_element(".persisted").send_keys(
Keys.BACK_SPACE
) # persist falsy value
dash_duo.wait_for_text_to_equal(".out", "")

# alpaca not saved with falsy persistence
dash_duo.clear_input(".persistence-val")
dash_duo.wait_for_text_to_equal(".out", "a")

# anchovies and aardvark saved
dash_duo.find_element(".persistence-val").send_keys("s")
dash_duo.wait_for_text_to_equal(".out", "anchovies")
dash_duo.find_element(".persistence-val").send_keys("2")
dash_duo.wait_for_text_to_equal(".out", "")

dash_duo.find_element("#btn").click()

0 comments on commit 308dece

Please # to comment.