Skip to content

fix: raise BackendVarFormatError for a backend var in a LiteralVar - #7651

Open
darshjoshi wants to merge 3 commits into
reflex-dev:mainfrom
darshjoshi:fix/7459-backend-field-literal-var
Open

darshjoshi wants to merge 3 commits into
reflex-dev:mainfrom
darshjoshi:fix/7459-backend-field-literal-var

Conversation

@darshjoshi

@darshjoshi darshjoshi commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Description

Class access to a field with no frontend Var (a backend var such as State._secret, an is_var=False field, or any field of a mixin state) returns the raw Field. When that Field is passed where a Var gets built from a Python value, it reaches the end of LiteralVar.create (_create_literal_var), or of its var-data twin _get_all_var_data_without_creating_var_dispatch. Neither recognises a Field, so both fall through to the generic TypeError: Unsupported type ... for LiteralVar. That covers rx.Var.create([F]), rx.Var.create({"a": F}), rx.toast(F), rx.console_log(F), rx.image(src=F) and rx.data_table(data=F) from the issue. Formatting the same field with f"{F}" already raises a clear BackendVarFormatError from Field.__format__.

This PR follows the proposal in the issue and gives the LiteralVar path the same wording:

  • Field.__format__ now builds its message in a small helper, Field._no_frontend_var_error(). The text is unchanged.
  • Both LiteralVar fallthroughs check isinstance(value, Field) right before the generic TypeError and raise that error. The check runs only on the path that was about to fail, so supported values pay nothing. BackendVarFormatError is a VarTypeError, which is a TypeError, so except TypeError code still catches it.

test_backend_field_literal_var_reports_repr asserted the old generic message, so it is replaced by tests that expect BackendVarFormatError.

Before / After (F = S._secret, S a state with _secret: str = "x"):

# before (upstream/main d28eb2e0)
rx.Var.create([F]) -> builtins.TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar. Tried to create a LiteralVar from Field(default='x', is_var=True, annotated_type=<class 'str'>).
rx.Var.create({"a": F}) -> builtins.TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar. ...
rx.toast(F) rendered -> builtins.TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar. ...
rx.console_log(F) rendered -> builtins.TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar. ...
rx.image(src=F) rendered -> builtins.TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar. ...
rx.data_table(data=F) rendered -> builtins.TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar. ...
f"{F}" (control) -> reflex_base.utils.exceptions.BackendVarFormatError: Backend var 'S._secret' exists only on the server ...

# after
rx.Var.create([F]) -> reflex_base.utils.exceptions.BackendVarFormatError: Backend var 'S._secret' exists only on the server and has no frontend value, so it cannot be used in the UI. Use S._secret.default_value() for its default value, declare it as ClassVar[...] for a constant shared by all sessions, or use a regular state var for a value the UI should show and update.
rx.Var.create({"a": F}) -> reflex_base.utils.exceptions.BackendVarFormatError: Backend var 'S._secret' exists only on the server ...
rx.toast(F) rendered -> reflex_base.utils.exceptions.BackendVarFormatError: Backend var 'S._secret' exists only on the server ...
rx.console_log(F) rendered -> reflex_base.utils.exceptions.BackendVarFormatError: Backend var 'S._secret' exists only on the server ...
rx.image(src=F) rendered -> reflex_base.utils.exceptions.BackendVarFormatError: Backend var 'S._secret' exists only on the server ...
rx.data_table(data=F) rendered -> reflex_base.utils.exceptions.BackendVarFormatError: Backend var 'S._secret' exists only on the server ...

The error still comes up where the Var is built (stringified or rendered), not at the rx.toast(...) / rx.image(...) call. That is the same place as before, but now with the explanation. Not covered here, and better left as follow-ups:

  • rx.upload_files(on_upload_progress=F) and app.add_page(title=F, ...) never reach LiteralVar, so they keep their current errors.
  • The two cases in the issue comment (str(F) + "px" silently embedding the repr, and rx.box(id=F)) are also on other code paths.
  • The rx.get_upload_url(F) / reflex/experimental/hooks.py test suggestion.

Tests:

  • tests/units/reflex_base/vars/test_base.py: LiteralVar.create(F), str(Var.create([F])), str(Var.create({"a": F})), and _get_all_var_data() on the list and dict literals (this reaches the var-data twin). Plus a mixin field case for the mixin wording.
  • tests/units/test_event.py: rx.toast(F) and rx.console_log(F).
  • tests/units/components/test_component.py: rx.image(src=F) and rx.data_table(data=F) rendered.

With vars/base.py reverted to main, all 10 new cases fail with the generic TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar (8 raised at base.py:1932, 2 at :2011). With the fix they pass:

$ uv run pytest tests/units/reflex_base/vars/test_base.py tests/units/test_event.py tests/units/components/test_component.py -k format_error
14 passed, 472 deselected, 1 warning in 0.06s

$ uv run pytest tests/units --cov --no-cov-on-fail --cov-report=
Required test coverage of 72.0% reached. Total coverage: 80.37%
12243 passed, 37 skipped, 22 warnings in 134.52s (0:02:14)

ruff format, ruff check, codespell, update-pyi-files, hash-examples, unasync-reflex-build-sdk, ty, biome format and make_pyi.py --check pass. pyright --pythonplatform Linux reports 0 errors. The reflex-release changelog-check and check-headings checks pass with the news fragment packages/reflex-base/news/+backend-var-literal-var.bugfix.md.

Closes #7459

View guided diff

Field.__format__ now raises an error built by Field._no_frontend_var_error, so other code paths that reject a Field can reuse the same message.
A backend var (or a mixin field) passed where a Var is built from a Python value, such as rx.Var.create([State._secret]), rx.toast(State._secret) or rx.image(src=State._secret), reached the generic fallthrough of LiteralVar.create and _get_all_var_data_without_creating_var_dispatch and failed with TypeError: Unsupported type ... for LiteralVar. Both now raise the same BackendVarFormatError that formatting the field raises.
@darshjoshi
darshjoshi requested a review from a team as a code owner October 23, 2026 18:31
@greptile-apps

greptile-apps Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge.

Summary

The PR gives unsupported Field values the same clear error in LiteralVar conversion and formatting.

  • Backend fields inside literal values now show the field-specific UI error.

darshjoshi explicitly deferred paths outside LiteralVar: rx.upload_files(on_upload_progress=F), app.add_page(title=F, ...), str(F) + "px", rx.box(id=F), and the rx.get_upload_url(F) / reflex/experimental/hooks.py test suggestion.

Reviews (1) · Last reviewed commit: "docs: add news fragment for backend vars..." · Reviewed by Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/reflex-base/news/+backend-var-literal-var.bugfix.md">

<violation number="1" location="packages/reflex-base/news/+backend-var-literal-var.bugfix.md:1">
P3: This release-note example claims `rx.Var.create([State._secret])` itself raises, but array creation defers nested-field traversal until rendering or var-data collection. Qualify the examples with the operation that triggers rendering/collection.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

@@ -0,0 +1 @@
Passing a backend var where a Var is built from a Python value, e.g. `rx.Var.create([State._secret])`, `rx.toast(State._secret)` or `rx.image(src=State._secret)`, now raises `BackendVarFormatError` with the same explanation as `f"{State._secret}"`, instead of a generic `TypeError: Unsupported type ... for LiteralVar`.

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.

P3: This release-note example claims rx.Var.create([State._secret]) itself raises, but array creation defers nested-field traversal until rendering or var-data collection. Qualify the examples with the operation that triggers rendering/collection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/reflex-base/news/+backend-var-literal-var.bugfix.md, line 1:

<comment>This release-note example claims `rx.Var.create([State._secret])` itself raises, but array creation defers nested-field traversal until rendering or var-data collection. Qualify the examples with the operation that triggers rendering/collection.</comment>

<file context>
@@ -0,0 +1 @@
+Passing a backend var where a Var is built from a Python value, e.g. `rx.Var.create([State._secret])`, `rx.toast(State._secret)` or `rx.image(src=State._secret)`, now raises `BackendVarFormatError` with the same explanation as `f"{State._secret}"`, instead of a generic `TypeError: Unsupported type ... for LiteralVar`.
</file context>
Suggested change
Passing a backend var where a Var is built from a Python value, e.g. `rx.Var.create([State._secret])`, `rx.toast(State._secret)` or `rx.image(src=State._secret)`, now raises `BackendVarFormatError` with the same explanation as `f"{State._secret}"`, instead of a generic `TypeError: Unsupported type ... for LiteralVar`.
Passing a backend var into a Var-building path now raises `BackendVarFormatError` when the resulting Var is rendered or its var data is collected, e.g. `str(rx.Var.create([State._secret]))`, rendering `rx.toast(State._secret)` or `rx.image(src=State._secret)`, with the same explanation as `f"{State._secret}"` instead of a generic `TypeError: Unsupported type ... for LiteralVar`.

@codspeed

codspeed Bot commented Oct 23, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 5%

2 warnings

⚠️ 9 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 147 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_route_argument_extraction 120.5 µs 114.7 µs +5%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing darshjoshi:fix/7459-backend-field-literal-var (046bcaa) with main (d28eb2e)

Open in CodSpeed

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Backend var Fields are accepted by several APIs and fail later at render/compile time

1 participant