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
2 changes: 1 addition & 1 deletion docs/changelog/upgrading/upgrading-to-0-10.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This page covers the 0.10 changes most likely to break an existing app, or to be

## Reading a backend var on a state class

Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component raises `ChildrenTypeError` (as a child); a prop may raise `BackendVarFormatError` when its value is built or rendered (earlier 0.10 releases may instead raise `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar`), while typed props can still reject it with `TypeError` during component creation. Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.
Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component as a child or a prop raises `BackendVarFormatError` (earlier releases may instead raise `ChildrenTypeError` for a child, `TypeError` for a typed prop, or `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar` when the value is built or rendered). Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.

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.

P2: This paragraph documents the str()/f-string raising as 0.10 behavior, but the 0.11 guide and the v0.11.0 changelog put it in 0.11, and v0.10.1 does not mention it. Move the str()/f-string sentence to the 0.11 guide only, or correct the version it names.

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 docs/changelog/upgrading/upgrading-to-0-10.md, line 12:

<comment>This paragraph documents the `str()`/f-string raising as 0.10 behavior, but the 0.11 guide and the v0.11.0 changelog put it in 0.11, and v0.10.1 does not mention it. Move the `str()`/f-string sentence to the 0.11 guide only, or correct the version it names.</comment>

<file context>
@@ -9,7 +9,7 @@ This page covers the 0.10 changes most likely to break an existing app, or to be
 ## Reading a backend var on a state class
 
-Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component raises `ChildrenTypeError` (as a child); a prop may raise `BackendVarFormatError` when its value is built or rendered (earlier 0.10 releases may instead raise `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar`), while typed props can still reject it with `TypeError` during component creation. Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.
+Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component as a child or a prop raises `BackendVarFormatError` (earlier releases may instead raise `ChildrenTypeError` for a child, `TypeError` for a typed prop, or `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar` when the value is built or rendered). Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.
 
 Replace the class read with what the code meant:
</file context>


Replace the class read with what the code meant:

Expand Down
2 changes: 1 addition & 1 deletion docs/changelog/upgrading/upgrading-to-0-11.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def serialize_point(point: Point) -> dict:

## Printing a backend var on a state class

A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component prop, `rx.toast(State._secret)`, or the `title`, `description` or `image` of `app.add_page(...)`.
A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.

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: Logging does not raise here. With %-style arguments, the stdlib logging handler catches the error and prints a --- Logging error --- report. Name print() and str() as the raising cases, and say that repr() is the way to log the field.

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 docs/changelog/upgrading/upgrading-to-0-11.md, line 244:

<comment>Logging does not raise here. With %-style arguments, the stdlib logging handler catches the error and prints a `--- Logging error ---` report. Name `print()` and `str()` as the raising cases, and say that `repr()` is the way to log the field.</comment>

<file context>
@@ -241,7 +241,7 @@ def serialize_point(point: Point) -> dict:
 ## Printing a backend var on a state class
 
-A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component prop, `rx.toast(State._secret)`, or the `title`, `description` or `image` of `app.add_page(...)`.
+A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.
 
 ```python
</file context>
Suggested change
A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.
A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, `str()` and f-strings of it raise `BackendVarFormatError` ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). Logging it with an f-string raises too, but logging with `%s` arguments does not raise to the caller. The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.


```python
class State(rx.State):
Expand Down
1 change: 1 addition & 0 deletions news/+backend-var-on-load.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`app.add_page(...)` now raises `BackendVarFormatError` when `on_load` is a backend var such as `State._handler`, instead of accepting it.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Using a backend var such as `State._secret` as a component child, a typed prop (`rx.el.div(title=State._secret)`, `rx.link(href=State._secret)`) or an event handler argument now raises `BackendVarFormatError`, which names the var and the fix. A child used to raise a `ChildrenTypeError` that named neither, and a typed prop or event argument a generic error that printed the var's default value. Comparing a backend var or a mixin's field with a value (`State._secret == "x"`) or truth-testing it now raises the same error, instead of a plain `False` or `True` that made `rx.cond` always take one branch. Fields still compare equal to themselves.
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ def _post_init(self, *args, **kwargs):
Raises:
TypeError: If an invalid prop is passed.
ValueError: If an event trigger passed is not valid.
BackendVarFormatError: If a child or typed prop is a field with no frontend var.
"""
# Set the id and children initially.
children = kwargs.get("children", [])
Expand Down Expand Up @@ -991,6 +992,8 @@ def _post_init(self, *args, **kwargs):

# Check whether the key is a component prop.
if is_var:
if isinstance(value, Field):
raise value._no_frontend_var_error()
try:
kwargs[key] = LiteralVar.create(value)

Expand Down Expand Up @@ -1286,6 +1289,9 @@ def _validate_children(cls, children: tuple | list):
if isinstance(child, (tuple, list)):
cls._validate_children(child)

if isinstance(child, Field):
raise child._no_frontend_var_error()

# Make sure the child is a valid type.
if isinstance(child, dict) or not isinstance(child, ComponentChildTypes):
error = ChildrenTypeError(component=cls.__name__, child=child)
Expand Down
16 changes: 14 additions & 2 deletions packages/reflex-base/src/reflex_base/event/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,12 @@ def __call__(self, *args: Any, **kwargs: Any) -> "EventSpec":

Raises:
EventHandlerTypeError: If the arguments are invalid.
BackendVarFormatError: If an argument is a field with no frontend var.
"""
from reflex_base.utils.exceptions import EventHandlerTypeError
from reflex_base.utils.exceptions import (
BackendVarFormatError,
EventHandlerTypeError,
)

if args and isinstance(args[0], self.state or _base_state_type()):
# A decorator stacked above @rx.event calls the handler like the
Expand Down Expand Up @@ -868,6 +872,8 @@ def __call__(self, *args: Any, **kwargs: Any) -> "EventSpec":
# Otherwise, convert to JSON.
try:
payload.append((Var(_js_expr=fn_arg), LiteralVar.create(arg)))
except BackendVarFormatError:
raise
except TypeError as e:
msg = f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg!r} of type {type(arg)}."
raise EventHandlerTypeError(msg) from e
Expand Down Expand Up @@ -1033,8 +1039,12 @@ def add_args(self, *args: Var) -> "EventSpec":

Raises:
EventHandlerTypeError: If the arguments are invalid.
BackendVarFormatError: If an argument is a field with no frontend var.
"""
from reflex_base.utils.exceptions import EventHandlerTypeError
from reflex_base.utils.exceptions import (
BackendVarFormatError,
EventHandlerTypeError,
)

n_self_args = 1 if self.handler.state is not None else 0

Expand All @@ -1048,6 +1058,8 @@ def add_args(self, *args: Var) -> "EventSpec":
try:
for arg in args:
values.append(LiteralVar.create(value=arg)) # noqa: PERF401, RUF100
except BackendVarFormatError:
raise
except TypeError as e:
msg = f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg!r} of type {type(arg)}."
raise EventHandlerTypeError(msg) from e
Expand Down
50 changes: 50 additions & 0 deletions packages/reflex-base/src/reflex_base/vars/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4447,6 +4447,56 @@ def __str__(self) -> str:
"""
raise self._no_frontend_var_error()

def _has_no_frontend_var(self) -> bool:
"""Whether class access reaches the field itself, not a Var.

Returns:
Whether the field is a backend var or a mixin's field.
"""
return self._var is None and (
self._backend or getattr(self._owner, "_mixin", False)
)

def __eq__(self, other: object) -> bool:
"""Compare by identity, refusing to compare a backend var with a value.

Class access reaches a backend var's field itself, so ``State._x == "a"``
would otherwise be a plain ``False``, and ``rx.cond`` on it would always
take the same branch. Comparing two fields still works, by identity.
``!=`` uses this method too.

Args:
other: The object to compare with.

Returns:
Whether the two are the same field.

Raises:
BackendVarFormatError: If the field has no frontend var and the
other object is not a field.
"""
if not isinstance(other, Field):
if self._has_no_frontend_var():
raise self._no_frontend_var_error()
return NotImplemented
return self is other

# Defining __eq__ would otherwise make fields unhashable.
__hash__ = object.__hash__

def __bool__(self) -> bool:
"""Refuse to truth-test a backend var, which would always be true.

Returns:
True, for a field with a frontend var.

Raises:
BackendVarFormatError: If the field has no frontend var.
"""
if self._has_no_frontend_var():
raise self._no_frontend_var_error()
return True

def _no_frontend_var_error(self) -> BackendVarFormatError:
"""Build the error for using the field where a frontend var is needed.

Expand Down
10 changes: 7 additions & 3 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,10 @@ def default_error_boundary(*children: Component, **props) -> Component:


def _reject_page_meta_fields(*values: Any) -> None:
"""Reject page metadata given as a field with no frontend var.
"""Reject page metadata or on_load events given as a field with no frontend var.

Args:
*values: The page metadata values.
*values: The page metadata values or on_load events.

Raises:
BackendVarFormatError: If a value is a field with no frontend var.
Expand Down Expand Up @@ -1436,7 +1436,7 @@ def add_page(
context: Values passed to page for custom page-specific logic.

Raises:
BackendVarFormatError: When title, description or image is a field with no frontend var.
BackendVarFormatError: When title, description, image or on_load is a field with no frontend var.
PageValueError: When the component is not set for a non-404 page.
RouteValueError: When the specified route name already exists.
ValueError: When the route syntax is invalid.
Expand Down Expand Up @@ -1485,11 +1485,15 @@ def _prepare_page(
The validated page descriptor and its dynamic route arguments.

Raises:
BackendVarFormatError: When title, description, image or on_load is a field with no frontend var.
PageValueError: When the component is not set for a non-404 page.
RouteValueError: When a route cannot be derived.
ValueError: When the route syntax is invalid.
"""
_reject_page_meta_fields(title, description, image)
_reject_page_meta_fields(
*(on_load if is_non_string_sequence(on_load) else [on_load])
)
route = self._page_route_key(component, route)
if route is None:
msg = "Route must be set if component is not a callable."
Expand Down
40 changes: 32 additions & 8 deletions tests/units/components/test_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,12 +789,15 @@ def test_component_create_unallowed_types(children, test_component):


def test_component_create_backend_var_child(test_component):
"""A backend var child reports its type rather than formatting the field."""
"""A backend var child raises the backend var format error."""

class BackendChildState(BaseState):
_secret: int = 42

with pytest.raises(ChildrenTypeError, match=r"received child of type Field\."):
with pytest.raises(
BackendVarFormatError,
match=r"Backend var 'BackendChildState\._secret' exists only",

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 prefix-only match passes even if the rest of the error, including its suggested fix, changes. Assert the complete expected message here and in the other new backend-var error checks.

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 tests/units/components/test_component.py, line 799:

<comment>This prefix-only `match` passes even if the rest of the error, including its suggested fix, changes. Assert the complete expected message here and in the other new backend-var error checks.</comment>

<file context>
@@ -789,12 +789,15 @@ def test_component_create_unallowed_types(children, test_component):
-    with pytest.raises(ChildrenTypeError, match=r"received child of type Field\."):
+    with pytest.raises(
+        BackendVarFormatError,
+        match=r"Backend var 'BackendChildState\._secret' exists only",
+    ):
         test_component.create(BackendChildState._secret)
</file context>

):
test_component.create(BackendChildState._secret)


Expand Down Expand Up @@ -844,14 +847,35 @@ def test_component_create_event_child(make_child, type_name, test_component):
]


def test_component_create_backend_var_prop():
"""A backend var passed to a typed prop reports its repr, not a format error."""
class BackendPropState(BaseState):
"""A state with a backend var, for typed prop misuse tests."""

_size: str = "3"


@pytest.mark.parametrize(
"create",
[
lambda f: rx.heading("x", size=f),
lambda f: rx.el.div(title=f),
lambda f: rx.link("x", href=f),
],
ids=["heading_size", "div_title", "link_href"],
)
def test_component_create_backend_var_prop(create: Callable[[Any], Component]):
"""A backend var passed to a typed prop raises the backend var format error.

class BackendPropState(BaseState):
_size: str = "3"
The error must not include the field's default value.

with pytest.raises(TypeError, match=r"got value Field\(default='3'"):
rx.heading("x", size=BackendPropState._size) # pyright: ignore[reportArgumentType]
Args:
create: Creates a component with the backend var as a prop.
"""
with pytest.raises(
BackendVarFormatError,
match=r"Backend var 'BackendPropState\._size' exists only",
) as exc_info:
create(BackendPropState._size)
assert "default=" not in str(exc_info.value)
Comment on lines +873 to +878

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.

P2 Error checks miss message changes. These new assertions match only the start of the error, so they still pass if the suggested fix disappears or becomes wrong. The repository requires tests to compare complete error messages and explicitly forbids partial regex matches. Compare the full expected message here and in the other new checks, including those in test_base.py and test_app.py.

Context Used: CLAUDE.md (source)

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!



class BackendSrcState(BaseState):
Expand Down
56 changes: 56 additions & 0 deletions tests/units/reflex_base/vars/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,62 @@ class Model(EvenMoreBasicBaseState):
use(Model._secret)


@pytest.mark.parametrize(
"use",
[
lambda f: f == "x",
lambda f: f != "x",
lambda f: f in ["x", "y"],
lambda f: bool(f),
],
ids=["eq", "ne", "in_list", "bool"],
)
def test_backend_field_comparison_raises_format_error(
use: typing.Callable[[Any], Any],
):
"""Comparing or truth-testing a backend var raises instead of a plain bool.

Args:
use: Compares or truth-tests the backend field.
"""

class Model(EvenMoreBasicBaseState):
_secret: str = "s"

with pytest.raises(
BackendVarFormatError, match=r"Backend var 'Model\._secret' exists only"
):
use(Model._secret)


def test_backend_field_identity_and_hash_unchanged():
"""Fields still compare and hash by identity with other fields."""

class Model(EvenMoreBasicBaseState):
_secret: str = "s"
_other: str = "o"

secret, other = Model._secret, Model._other
assert secret == secret
assert secret != other
assert {secret: 1, other: 2}[secret] == 1
assert secret in [other, secret]
assert len({secret, other, secret}) == 2


def test_mixin_field_comparison_raises_format_error():
"""Comparing or truth-testing a mixin's field says to use the including state."""

class Mixin(EvenMoreBasicBaseState, mixin=True):
count: int = 0

match = r"Var 'Mixin\.count' is declared on a mixin state"
with pytest.raises(BackendVarFormatError, match=match):
Mixin.count == 1 # noqa: B015
with pytest.raises(BackendVarFormatError, match=match):
bool(Mixin.count)


def test_mixin_field_literal_var_raises_format_error():
"""A mixin's field inside a LiteralVar says to use the including state."""

Expand Down
40 changes: 40 additions & 0 deletions tests/units/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,46 @@ def test_add_page_backend_var_meta_raises_format_error(
app.add_page(index_page, route="/meta", **{key: BackendMetaState._meta}) # pyright: ignore[reportArgumentType]


def test_add_page_backend_var_meta_content_raises_format_error(
app: App, index_page: ComponentCallable
):
"""A backend var in a meta tag's content raises when the page is compiled.

Args:
app: The app to test.
index_page: The index page.
"""
app.add_page(
index_page,
route="/meta",
meta=[{"name": "x", "content": BackendMetaState._meta}],
)
with pytest.raises(
exceptions.BackendVarFormatError,
match=r"Backend var 'BackendMetaState\._meta' exists only",

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: The new pytest.raises calls match only a fragment of the error message via a partial regex. Per AGENTS.md, assert the full error message exactly, so a changed or truncated message is caught. Please match the complete expected text in both new tests.

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 tests/units/test_app.py, line 352:

<comment>The new `pytest.raises` calls match only a fragment of the error message via a partial regex. Per `AGENTS.md`, assert the full error message exactly, so a changed or truncated message is caught. Please match the complete expected text in both new tests.</comment>

<file context>
@@ -333,6 +333,46 @@ def test_add_page_backend_var_meta_raises_format_error(
+    )
+    with pytest.raises(
+        exceptions.BackendVarFormatError,
+        match=r"Backend var 'BackendMetaState\._meta' exists only",
+    ):
+        app._compile_page("meta")
</file context>

):
app._compile_page("meta")


@pytest.mark.parametrize("as_list", [False, True])
def test_add_page_backend_var_on_load_raises_format_error(
app: App, index_page: ComponentCallable, as_list: bool
):
"""A backend var passed as on_load raises when the page is added.

Args:
app: The app to test.
index_page: The index page.
as_list: Whether on_load is given as a list.
"""
on_load = [BackendMetaState._meta] if as_list else BackendMetaState._meta
with pytest.raises(
exceptions.BackendVarFormatError,
match=r"Backend var 'BackendMetaState\._meta' exists only",
):
app.add_page(index_page, route="/meta", on_load=on_load) # pyright: ignore[reportArgumentType]


def test_add_page_set_route(app: App, index_page: ComponentCallable):
"""Test adding a page to an app.

Expand Down
18 changes: 10 additions & 8 deletions tests/units/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from reflex_base.utils.exceptions import (
BackendVarFormatError,
EventHandlerArgTypeMismatchError,
EventHandlerTypeError,
EventHandlerValueError,
)
from reflex_base.utils.form import FormData
Expand Down Expand Up @@ -266,20 +265,23 @@ def args_spec(value: Var[dict]) -> list[Var[dict]]:
assert handler.prevent_default._type_hints is handler._type_hints


def test_backend_var_event_arg_reports_repr():
"""A backend var passed as an event arg reports its repr, not a format error."""
def test_backend_var_event_arg_raises_format_error():
"""A backend var passed as an event arg raises the backend var format error."""

class S(BaseState):
class BackendEventArgState(BaseState):
_secret: int = 42

@event
def on_event(self, value: int):
pass

with pytest.raises(EventHandlerTypeError, match=r"Got Field\(default=42"):
S.on_event(S._secret)
with pytest.raises(EventHandlerTypeError, match=r"Got Field\(default=42"):
cast(EventSpec, S.on_event()).add_args(S._secret) # pyright: ignore[reportArgumentType]
match = r"Backend var 'BackendEventArgState\._secret' exists only"

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 match is a partial regex, so the test passes for any message starting with this prefix. AGENTS.md asks for the full error message to be compared exactly. Match the complete BackendVarFormatError text from _no_frontend_var_error, with the owner and field names fixed.

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 tests/units/test_event.py, line 278:

<comment>This `match` is a partial regex, so the test passes for any message starting with this prefix. AGENTS.md asks for the full error message to be compared exactly. Match the complete `BackendVarFormatError` text from `_no_frontend_var_error`, with the owner and field names fixed.</comment>

<file context>
@@ -266,20 +265,23 @@ def args_spec(value: Var[dict]) -> list[Var[dict]]:
-        S.on_event(S._secret)
-    with pytest.raises(EventHandlerTypeError, match=r"Got Field\(default=42"):
-        cast(EventSpec, S.on_event()).add_args(S._secret)  # pyright: ignore[reportArgumentType]
+    match = r"Backend var 'BackendEventArgState\._secret' exists only"
+    with pytest.raises(BackendVarFormatError, match=match):
+        BackendEventArgState.on_event(BackendEventArgState._secret)
</file context>

with pytest.raises(BackendVarFormatError, match=match):
BackendEventArgState.on_event(BackendEventArgState._secret)
with pytest.raises(BackendVarFormatError, match=match):
cast(EventSpec, BackendEventArgState.on_event()).add_args(
BackendEventArgState._secret # pyright: ignore[reportArgumentType]
)


class BackendTriggerState(BaseState):
Expand Down
Loading