Data-bound chart components 5/7: the chart factories - #465
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 introduces the user-facing data-bound chart factory API and registers chart plans in backend-only workers.
Confidence Score: 5/5The PR appears safe to merge based on the eligible follow-up review findings. No blocking failure remains in the available follow-up review scope.
|
| Filename | Overview |
|---|---|
| python/reflex_xy/factories.py | Adds signature-derived factory dispatch, compile-time schema and plan validation, and live/static mounting. |
| python/reflex_xy/app.py | Adds worker-startup evaluation of page component functions so process-local chart plans are registered. |
| python/reflex_xy/assets/XYChart.jsx | Adds client-side plan/data token composition and bounds repeated resynchronization attempts. |
| python/reflex_xy/component.py | Extends the private Reflex component with typed plan-tier data props. |
| python/reflex_xy/init.py | Exposes the chart factories and a curated set of xy node constructors. |
| tests/reflex_adapter/test_factories.py | Covers factory partitioning, validation, mounting, public exports, and supported chart kinds. |
| tests/reflex_adapter/test_page_plan_registration.py | Covers backend-worker plan registration and isolation of failing page evaluations. |
| spec/design/reflex-integration.md | Documents the factory surface, static symmetry, supported kinds, and worker registration lifecycle. |
Reviews (2): Last reviewed commit: "feat(reflex): data-bound chart factories" | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
3 issues found across 8 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/__init__.py">
<violation number="1" location="python/reflex_xy/__init__.py:50">
P3: The new quickstart multi-mark example references a column (`"t"`) that is not defined in the preceding `CloudData`/`Dash.cloud` example, so users copying this snippet will hit a validation error immediately. Using an existing field (for example `"y"` or `"mag"`) keeps the example runnable and aligned with the documented data contract.</violation>
</file>
<file name="python/reflex_xy/assets/XYChart.jsx">
<violation number="1" location="python/reflex_xy/assets/XYChart.jsx:916">
P2: Live charts can get stuck unsubscribed after repeated transient `err{resync:true}` responses, because retries are permanently disabled after 5 attempts unless a payload is successfully applied. Consider resetting or decaying the retry budget (or using backoff) so temporary backend recovery can resume without requiring remount/disconnect.</violation>
</file>
<file name="python/reflex_xy/factories.py">
<violation number="1" location="python/reflex_xy/factories.py:82">
P3: `stem_chart` is implemented and registered but omitted from `__all__`, so exported API surfaces can miss this factory even though it is supported internally. Adding it to `__all__` keeps the public factory set consistent.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| if (destroyed || !data || data.fig !== liveToken) return; | ||
| console.warn(`xy: ${data.error} (fig ${data.fig})`); | ||
| if (data.resync === true && socket.connected) subscribe(); | ||
| if (data.resync === true && socket.connected && errResyncs < 5) { |
There was a problem hiding this comment.
P2: Live charts can get stuck unsubscribed after repeated transient err{resync:true} responses, because retries are permanently disabled after 5 attempts unless a payload is successfully applied. Consider resetting or decaying the retry budget (or using backoff) so temporary backend recovery can resume without requiring remount/disconnect.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/assets/XYChart.jsx, line 916:
<comment>Live charts can get stuck unsubscribed after repeated transient `err{resync:true}` responses, because retries are permanently disabled after 5 attempts unless a payload is successfully applied. Consider resetting or decaying the retry budget (or using backoff) so temporary backend recovery can resume without requiring remount/disconnect.</comment>
<file context>
@@ -899,7 +913,10 @@ export function XYChart(props) {
if (destroyed || !data || data.fig !== liveToken) return;
console.warn(`xy: ${data.error} (fig ${data.fig})`);
- if (data.resync === true && socket.connected) subscribe();
+ if (data.resync === true && socket.connected && errResyncs < 5) {
+ errResyncs += 1;
+ subscribe();
</file context>
| app = rx.App() | ||
|
|
||
| Multi-mark charts compose xy nodes around the same data var | ||
| (``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "t"), |
There was a problem hiding this comment.
P3: The new quickstart multi-mark example references a column ("t") that is not defined in the preceding CloudData/Dash.cloud example, so users copying this snippet will hit a validation error immediately. Using an existing field (for example "y" or "mag") keeps the example runnable and aligned with the documented data contract.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/__init__.py, line 50:
<comment>The new quickstart multi-mark example references a column (`"t"`) that is not defined in the preceding `CloudData`/`Dash.cloud` example, so users copying this snippet will hit a validation error immediately. Using an existing field (for example `"y"` or `"mag"`) keeps the example runnable and aligned with the documented data contract.</comment>
<file context>
@@ -3,38 +3,55 @@
app = rx.App()
+
+Multi-mark charts compose xy nodes around the same data var
+(``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "t"),
+data=Dash.cloud)``), and charts whose *structure* depends on state keep the
+escape hatch: an ``@reflex_xy.figure`` method returning an ``xy.Chart``,
</file context>
| (``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "t"), | |
| (``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "y"), |
| "line_chart", | ||
| "scatter_chart", | ||
| "segments_chart", | ||
| "step_chart", |
There was a problem hiding this comment.
P3: stem_chart is implemented and registered but omitted from __all__, so exported API surfaces can miss this factory even though it is supported internally. Adding it to __all__ keeps the public factory set consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/factories.py, line 82:
<comment>`stem_chart` is implemented and registered but omitted from `__all__`, so exported API surfaces can miss this factory even though it is supported internally. Adding it to `__all__` keeps the public factory set consistent.</comment>
<file context>
@@ -0,0 +1,489 @@
+ "line_chart",
+ "scatter_chart",
+ "segments_chart",
+ "step_chart",
+]
+
</file context>
The user-facing half of the tier: reflex_xy.scatter_chart(data=Dash.cloud, x="x", y="y", color="mag") and the composed reflex_xy.chart(*nodes, data=...) for multi-mark charts. A factory call at page evaluation builds a plan, so the chart's structure is validated at `reflex run` rather than at hydrate. What that buys, in the order a user hits it: a hallucinated factory name fails at import (the xy node re-exports are an explicit curated map, not getattr passthrough); an unknown kwarg fails with a did-you-mean; a bad colormap, enum, or axis ref fails in the zero-row probe; an unknown column name fails against the data var's TypedDict without executing the data method; and the wrong var or a raw string in data= fails on the typed prop. The kwarg partition — mark options vs chrome vs component props vs event handlers — is derived from inspect.signature at import rather than hand-listed, because a hand-listed partition silently drifts from xy's signatures and Reflex absorbs unknown non-event kwargs into `style` where a typo would vanish rather than raise (the hazard Phase 0 pinned). Collisions get generated aliases, pinned by test. Two mounts from one surface: a Var data source becomes the plan/data props the wrapper composes into a composite subscription, while a concrete mapping binds immediately and routes to the static payload-asset path — same validation, works under `reflex export`. Aggregating kinds (box, violin, hexbin, contour, heatmap, stairs, ecdf) and the data-taking composites (pie, radar, wind_rose, sankey) are excluded from the plan tier and refused by name with the two working routes: their validators need real values, and a synthetic-row probe would validate against made-up data — a silent decimation of the compile guarantee. Page-plan registration lands here too, with the factories that populate the map: backend-only workers import the app without evaluating pages, so the startup lifespan evaluates them once, making "the plan map is populated in every worker" true by construction instead of an assumption about Reflex. Spec: reflex-integration.md §3.6 (factories, static symmetry, kind coverage, page-plan registration), file map.
1a9158c to
6301617
Compare
Stacked on #464. Base is
stack/4-composite-serving. This is the headline PR — the user-facing surface.What the compile now catches
A factory call at page evaluation builds a plan, so structure is validated at
reflex runrather than at hydrate. In the order a user hits it:getattrpassthrough);data=fails on the typed prop.The kwarg partition
Derived from
inspect.signatureat import, not hand-listed. A hand-listed partition silently drifts from xy's signatures, and Reflex absorbs unknown non-event kwargs intostyle— so a typo would vanish rather than raise (the hazard PR1 pinned). Collisions get generated aliases (mark_<name>, withwidth→stroke_widthwhere the mark hasn't claimed it), pinned by test.Two mounts, one surface
A
Vardata source becomes theplan/dataprops the wrapper composes into a composite subscription. A concrete mapping binds immediately and routes to the static payload-asset path — same validation, works underreflex export.Excluded kinds (recorded decision)
Aggregating marks (box, violin, hexbin, contour, heatmap, stairs, ecdf) and the data-taking composites (pie, radar, wind_rose, sankey) are refused by name, pointing at the two routes that work. Their validators need real values, and a synthetic-row probe would validate against made-up data — a silent decimation of the compile guarantee.
Page-plan registration
_ensure_page_planslands here rather than in PR4, with the factories that populate the map: backend-only workers import the app without evaluating pages, so the startup lifespan evaluates them once, making "the plan map is populated in every worker" true by construction instead of an assumption about Reflex.Spec
reflex-integration.md§3.6 (factories, static symmetry, kind coverage, page-plan registration), file map.Test plan
uv run pytest tests/reflex_adapter tests/test_validation_timing.py— 235 passedpre-commit run --all-files,ruff check,ruff format --check,ty check— clean