Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Note: Safari 15, Chrome 56, Firefox 51 and later are now required for map traces
- Update `hex_to_rgb` function to raise error for invalid-length hex codes, and emit warning for hex codes containing alpha [[#5729](https://github.com/plotly/plotly.py/pull/5729)], with thanks to @dylanpulver for the contribution!

### Fixed
- Fix `FigureWidget` state synchronization bug where frontend modifications to array properties (like `layout.shapes`) failed to properly update the Python property cache and occasionally surfaced `Undefined` objects [[#5689](https://github.com/plotly/plotly.py/issues/5689)]

## [7.0.0] - 2026-08-25

### Fixed
Expand Down
10 changes: 10 additions & 0 deletions plotly/basedatatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5461,6 +5461,16 @@ def _dispatch_change_callbacks(self, changed_paths):
-------
None
"""
# Invalidate cache for changed compound array properties so that they are
# reconstructed from the underlying properties dictionary on the next access.
# We only pop from _compound_array_props because compound arrays are immutable
# tuples that must be rebuilt to reflect added or removed elements.
for path in changed_paths:
if len(path) > 0:
prop = path[0]
if prop in self._compound_array_props:
self._compound_array_props.pop(prop, None)
Comment on lines +5464 to +5472

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dict isn't only a cache, it's also used for lookups by reference (as in here). Popping an entry here will break downstream:

fig = go.Figure()
fig.update_layout(shapes=[dict(type="rect", x0=0, x1=1, y0=0, y1=1)])
roi = fig.layout.shapes[0]

fig.plotly_relayout({"shapes[0].x0": 42})  # some update, e.g. from the frontend

roi.x0  # ValueError raised! roi is the object the dict was holding — and since you popped it out, roi can no longer find itself in the figure when asked for .x0

Another note: PR #5691 was recently merged to make concurrent reads of this dict safe. Modifying the dict here conflicts with that goal. And in any case, I'm not sure this is the right place to invalidate a cache as it's about notifying callbacks.


# Loop over registered callbacks
# ------------------------------
for prop_path_tuples, callbacks in self._change_callbacks.items():
Expand Down