Data-bound chart components 4/7: serve composite plan tokens - #464
Data-bound chart components 4/7: serve composite plan tokens#464FarhanAliRaza wants to merge 1 commit into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Greptile SummaryThe PR makes composite plan/data tokens servable as figures while preserving embedded-session affinity and adding typed plan failures.
Confidence Score: 5/5The PR appears safe to merge, with no blocking failure remaining. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| python/reflex_xy/namespace.py | Adds composite-token identity handling, plan dependency registration, typed rebuild failures, and room-wide error broadcasts. |
| python/reflex_xy/state_bridge.py | Extends state-backed recovery to data methods and composite plan binding while keeping bare data tokens non-servable. |
| python/reflex_xy/app.py | Wires registry-side asynchronous failures to namespace room broadcasts. |
| spec/design/reflex-integration.md | Documents composite-token recovery, session affinity, and republish fan-out semantics. |
| tests/reflex_adapter/test_socket_data_plane.py | Adds protocol-level integration coverage for composite serving, rebuilding, fan-out, affinity, and failure envelopes. |
Reviews (2): Last reviewed commit: "feat(reflex): serve composite plan token..." | Re-trigger Greptile
|
|
||
|
|
||
| class PlaneSchema(TypedDict): | ||
| x: np.ndarray |
There was a problem hiding this comment.
Browser rendering evidence is missing
The added tests begin at the Socket.IO client and stop at payload metadata and interaction replies, so they do not exercise the composite-token flow through a browser-mounted Reflex component. Please add the required scratch Reflex example and screenshot evidence to demonstrate that the resulting chart paints correctly.
Rule Used: You need to make an example svg/png, html, jupyter... (source)
Knowledge Base Used: Testing and Benchmarks
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
2 issues found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="python/reflex_xy/state_bridge.py">
<violation number="1" location="python/reflex_xy/state_bridge.py:114">
P3: The bind-failure `source` label differs between the two composite bind paths, so the same underlying mismatch produces two different `err` frames. Here `rebuild_plan_figure` truncates the state full name to its last dotted component (e.g. `PlaneData.table`) before passing it to `plan.bind(columns, source=...)`, whereas `registry._rebuild_dependent` (the republish/fan-out path) passes the full `state_full_name` (e.g. `tests...PlaneData.table`). Since this PR's whole point is typed, spec-consistent error frames that "name both sides," aligning the two sources would keep the subscribe-time and republish-time `err` messages consistent. Prefer using the full `state_full_name` here, matching the republish path.</violation>
</file>
<file name="python/reflex_xy/namespace.py">
<violation number="1" location="python/reflex_xy/namespace.py:255">
P2: Each subscribe registers a `(data_token, digest)` binding in the registry's `_digests_by_data_token` index (this new line), but nothing removes that binding on unsubscribe or disconnect. The index is only pruned lazily inside `registry._rebuild_dependent` on the *next republish of that same data token*. Because a data token embeds the session's `client_token`, a session that mounts a data-bound plan and then disconnects will never republish its columns again — so its binding is never reclaimed. In a long-lived worker with many short-lived sessions the index grows with cumulative sessions (not with mounted plans), directly contradicting the spec change's claim that the index "stays bounded by mounted plans." Each stale binding also causes a wasted `_rebuild_dependent` lookup + discard on any future republish of that token. Consider pruning `_digests_by_data_token` from `unsubscribe`/`disconnect` (e.g. discard a binding when neither the composite entry nor `_rebuildable_subscribers[composite]` remains).</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| # Index the mounted plan before serving, so a column | ||
| # republish racing this subscribe rebuilds it (the | ||
| # re-read below then serves that fresher generation). | ||
| self.registry.bind_plan(*identity.plan_bound) |
There was a problem hiding this comment.
P2: Each subscribe registers a (data_token, digest) binding in the registry's _digests_by_data_token index (this new line), but nothing removes that binding on unsubscribe or disconnect. The index is only pruned lazily inside registry._rebuild_dependent on the next republish of that same data token. Because a data token embeds the session's client_token, a session that mounts a data-bound plan and then disconnects will never republish its columns again — so its binding is never reclaimed. In a long-lived worker with many short-lived sessions the index grows with cumulative sessions (not with mounted plans), directly contradicting the spec change's claim that the index "stays bounded by mounted plans." Each stale binding also causes a wasted _rebuild_dependent lookup + discard on any future republish of that token. Consider pruning _digests_by_data_token from unsubscribe/disconnect (e.g. discard a binding when neither the composite entry nor _rebuildable_subscribers[composite] remains).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/namespace.py, line 255:
<comment>Each subscribe registers a `(data_token, digest)` binding in the registry's `_digests_by_data_token` index (this new line), but nothing removes that binding on unsubscribe or disconnect. The index is only pruned lazily inside `registry._rebuild_dependent` on the *next republish of that same data token*. Because a data token embeds the session's `client_token`, a session that mounts a data-bound plan and then disconnects will never republish its columns again — so its binding is never reclaimed. In a long-lived worker with many short-lived sessions the index grows with cumulative sessions (not with mounted plans), directly contradicting the spec change's claim that the index "stays bounded by mounted plans." Each stale binding also causes a wasted `_rebuild_dependent` lookup + discard on any future republish of that token. Consider pruning `_digests_by_data_token` from `unsubscribe`/`disconnect` (e.g. discard a binding when neither the composite entry nor `_rebuildable_subscribers[composite]` remains).</comment>
<file context>
@@ -208,7 +246,13 @@ async def on_sub(self, sid: str, data: Any) -> None:
+ # Index the mounted plan before serving, so a column
+ # republish racing this subscribe rebuilds it (the
+ # re-read below then serves that fresher generation).
+ self.registry.bind_plan(*identity.plan_bound)
# A normal state publish can replace a just-rebuilt entry
# while its room-wide broadcast is still completing, before
</file context>
| # Future binds (other plans over the same data var) hit the cache; | ||
| # bind_plan below is the subscribe path's job, not the rebuild's. | ||
| registry.publish_columns(composite.data_token, columns) | ||
| source = f"{composite.data.state_full_name.rsplit('.', 1)[-1]}.{composite.data.var_name}" |
There was a problem hiding this comment.
P3: The bind-failure source label differs between the two composite bind paths, so the same underlying mismatch produces two different err frames. Here rebuild_plan_figure truncates the state full name to its last dotted component (e.g. PlaneData.table) before passing it to plan.bind(columns, source=...), whereas registry._rebuild_dependent (the republish/fan-out path) passes the full state_full_name (e.g. tests...PlaneData.table). Since this PR's whole point is typed, spec-consistent error frames that "name both sides," aligning the two sources would keep the subscribe-time and republish-time err messages consistent. Prefer using the full state_full_name here, matching the republish path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/state_bridge.py, line 114:
<comment>The bind-failure `source` label differs between the two composite bind paths, so the same underlying mismatch produces two different `err` frames. Here `rebuild_plan_figure` truncates the state full name to its last dotted component (e.g. `PlaneData.table`) before passing it to `plan.bind(columns, source=...)`, whereas `registry._rebuild_dependent` (the republish/fan-out path) passes the full `state_full_name` (e.g. `tests...PlaneData.table`). Since this PR's whole point is typed, spec-consistent error frames that "name both sides," aligning the two sources would keep the subscribe-time and republish-time `err` messages consistent. Prefer using the full `state_full_name` here, matching the republish path.</comment>
<file context>
@@ -40,40 +50,81 @@ def _resolve_state_cls(state_full_name: str) -> Any:
+ # Future binds (other plans over the same data var) hit the cache;
+ # bind_plan below is the subscribe path's job, not the rebuild's.
+ registry.publish_columns(composite.data_token, columns)
+ source = f"{composite.data.state_full_name.rsplit('.', 1)[-1]}.{composite.data.var_name}"
+ return plan.bind(columns, source=source).figure()
+
</file context>
| source = f"{composite.data.state_full_name.rsplit('.', 1)[-1]}.{composite.data.var_name}" | |
| source = f"{composite.data.state_full_name}.{composite.data.var_name}" |
Makes xyp1|<digest>|<data token> a servable figure identity. The namespace
learns one concept — what a token reveals about affinity and
rebuildability — instead of branching on prefixes at each call site: a
composite enforces affinity through its embedded data token and rebuilds
like any state token; a bare xyd1 token names columns, never a figure, so
it keeps affinity but is never served or rebuilt as one.
Serving a composite is plan lookup + columns (registry hit, else the data
method re-run against session state) + bind into a fresh Chart. Both halves
are independently recoverable on any worker, so §3.2's reconnect promise
holds for this tier without a central store.
Failures are typed rather than flattened to "unknown figure token": a plan
miss (hot-reload digest drift) answers err {resync} naming the digest, and
a bind mismatch answers the reason, naming both sides. The registry's
error seam is wired to a room-wide err broadcast here — a column republish
whose bind fails has no request to answer, and without it subscribers would
sit on stale pixels with nothing in the log.
The wire envelope grew no fields: rooms, versions, mid addressing, and the
attachment cap treat a composite as an ordinary fig string.
Spec: reflex-integration.md §3.6 (composite tokens, republish fan-out),
file map.
e35456f to
25a9fdc
Compare
Stacked on #463. Base is
stack/3-plans-and-data-vars.Makes
xyp1|<digest>|<data token>a servable figure identity.Change
The namespace learns one concept — what a token reveals about affinity and rebuildability — instead of branching on prefixes at each call site:
xyd1token names columns, never a figure, so it keeps affinity but is never served or rebuilt as one.Serving a composite is plan lookup + columns (registry hit, else the data method re-run against session state) + bind into a fresh
Chart. Both halves are independently recoverable on any worker, so §3.2's reconnect promise holds for this tier without a central store.Failures are typed rather than flattened to "unknown figure token": a plan miss (hot-reload digest drift) answers
err {resync}naming the digest, and a bind mismatch answers the reason naming both sides.The registry's error seam is wired to a room-wide
errbroadcast here — a column republish whose bind fails has no request to answer, and without this subscribers would sit on stale pixels with nothing in the log.The wire envelope grew no fields. Rooms, versions,
midaddressing, and the attachment cap treat a composite as an ordinaryfigstring.Spec
reflex-integration.md§3.6 (composite tokens, republish fan-out), file map.Test plan
uv run pytest tests/reflex_adapter tests/test_validation_timing.py— 208 passed, including composite fan-out, plan-miss resync, and bind-error frames against a real uvicorn + socket.io clientpre-commit run --all-files,ruff check,ruff format --check,ty check— clean