Skip to content

Commit fbaed89

Browse files
authored
Merge pull request #2859 from plotly/fix/patch-ops
Fix patch operators on the base object.
2 parents 746d648 + b74047b commit fbaed89

File tree

3 files changed

+107
-4
lines changed

3 files changed

+107
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
77
## Fixed
88

99
- [#2854](https://github.com/plotly/dash/pull/2854) Fix dcc.Dropdown resetting empty values to null and triggering callbacks. Fixes [#2850](https://github.com/plotly/dash/issues/2850)
10+
- [#2859](https://github.com/plotly/dash/pull/2859) Fix base patch operators. fixes [#2855](https://github.com/plotly/dash/issues/2855)
1011

1112
## [2.17.0] - 2024-05-03
1213

dash/_patch.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,18 @@ def __getstate__(self):
3636
def __setstate__(self, state):
3737
vars(self).update(state)
3838

39-
def __getitem__(self, item):
39+
def __getitem__(self, item) -> "Patch":
4040
validate_slice(item)
4141
return Patch(location=self._location + [item], parent=self)
4242

43-
def __getattr__(self, item):
43+
def __getattr__(self, item) -> "Patch":
4444
if item == "tolist":
4545
# to_json fix
4646
raise AttributeError
4747
if item == "_location":
48-
return self._location
48+
return self._location # type: ignore
4949
if item == "_operations":
50-
return self._operations
50+
return self._operations # type: ignore
5151
return self.__getitem__(item)
5252

5353
def __setattr__(self, key, value):
@@ -81,22 +81,32 @@ def __iadd__(self, other):
8181
self.extend(other)
8282
else:
8383
self._operations.append(_operation("Add", self._location, value=other))
84+
if not self._location:
85+
return self
8486
return _noop
8587

8688
def __isub__(self, other):
8789
self._operations.append(_operation("Sub", self._location, value=other))
90+
if not self._location:
91+
return self
8892
return _noop
8993

9094
def __imul__(self, other):
9195
self._operations.append(_operation("Mul", self._location, value=other))
96+
if not self._location:
97+
return self
9298
return _noop
9399

94100
def __itruediv__(self, other):
95101
self._operations.append(_operation("Div", self._location, value=other))
102+
if not self._location:
103+
return self
96104
return _noop
97105

98106
def __ior__(self, other):
99107
self.update(E=other)
108+
if not self._location:
109+
return self
100110
return _noop
101111

102112
def __iter__(self):

tests/integration/test_patch.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,95 @@ def test_pch005_clientside_duplicate(dash_duo):
452452

453453
dash_duo.find_element("#click2").click()
454454
dash_duo.wait_for_text_to_equal("#output", "click2")
455+
456+
457+
def test_pch006_base_operators(dash_duo):
458+
app = Dash()
459+
460+
app.layout = [
461+
dcc.Store(data=2, id="store"),
462+
html.Button("add-one", id="add-one"),
463+
html.Button("remove-one", id="remove-one"),
464+
html.Button("mul-two", id="mul-two"),
465+
html.Button("div-two", id="div-two"),
466+
html.Button("merge", id="merge"),
467+
dcc.Store(data={"initial": "initial"}, id="dict-store"),
468+
html.Div(id="store-output"),
469+
html.Div(id="dict-store-output"),
470+
]
471+
472+
@app.callback(
473+
Output("store", "data"), Input("add-one", "n_clicks"), prevent_initial_call=True
474+
)
475+
def on_add(_):
476+
patched = Patch()
477+
patched += 1
478+
return patched
479+
480+
@app.callback(
481+
Output("store", "data", allow_duplicate=True),
482+
Input("remove-one", "n_clicks"),
483+
prevent_initial_call=True,
484+
)
485+
def on_remove(_):
486+
patched = Patch()
487+
patched -= 1
488+
return patched
489+
490+
@app.callback(
491+
Output("store", "data", allow_duplicate=True),
492+
Input("mul-two", "n_clicks"),
493+
prevent_initial_call=True,
494+
)
495+
def on_mul(_):
496+
patched = Patch()
497+
patched *= 2
498+
return patched
499+
500+
@app.callback(
501+
Output("store", "data", allow_duplicate=True),
502+
Input("div-two", "n_clicks"),
503+
prevent_initial_call=True,
504+
)
505+
def on_div(_):
506+
patched = Patch()
507+
patched /= 2
508+
return patched
509+
510+
@app.callback(
511+
Output("dict-store", "data", allow_duplicate=True),
512+
Input("merge", "n_clicks"),
513+
prevent_initial_call=True,
514+
)
515+
def on_merge(_):
516+
patched = Patch()
517+
patched |= {"merged": "merged"}
518+
return patched
519+
520+
app.clientside_callback(
521+
"data => data", Output("store-output", "children"), Input("store", "data")
522+
)
523+
app.clientside_callback(
524+
"data => JSON.stringify(data)",
525+
Output("dict-store-output", "children"),
526+
Input("dict-store", "data"),
527+
)
528+
529+
dash_duo.start_server(app)
530+
531+
dash_duo.find_element("#add-one").click()
532+
dash_duo.wait_for_text_to_equal("#store-output", "3")
533+
534+
dash_duo.find_element("#remove-one").click()
535+
dash_duo.wait_for_text_to_equal("#store-output", "2")
536+
537+
dash_duo.find_element("#mul-two").click()
538+
dash_duo.wait_for_text_to_equal("#store-output", "4")
539+
540+
dash_duo.find_element("#div-two").click()
541+
dash_duo.wait_for_text_to_equal("#store-output", "2")
542+
543+
dash_duo.find_element("#merge").click()
544+
dash_duo.wait_for_text_to_equal(
545+
"#dict-store-output", '{"initial":"initial","merged":"merged"}'
546+
)

0 commit comments

Comments
 (0)