Skip to content
Merged
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
1 change: 1 addition & 0 deletions news/6633.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`State.get_var_value()` no longer silently returns a wrong value when passed a Var operation — an arithmetic/concatenation expression such as `State.a + State.b`, or an indexed/item access such as `State.items[0]`. Previously it resolved the state and field of the operation's *first* operand and returned that field's value instead of the operation's result. It now raises `UnretrievableVarValueError`, consistent with how it already handled vars not associated with any state. Plain field and computed-var references continue to resolve as before.
15 changes: 13 additions & 2 deletions reflex/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
ComputedVar,
DynamicRouteVar,
EvenMoreBasicBaseState,
ToOperation,
Var,
computed_var,
dispatch,
Expand Down Expand Up @@ -1747,8 +1748,18 @@ async def get_var_value(self, var: Var[VAR_TYPE]) -> VAR_TYPE:
) is not unset and not isinstance(var_value, Var):
return var_value # pyright: ignore [reportReturnType]

var_data = var._get_all_var_data()
if var_data is None or not var_data.state:
# Unwrap any cast wrappers and resolve via the underlying var's *own*
# var data, not the recursive _get_all_var_data(). For an operation or
# derived var (e.g. State.a + State.b or State.items[0]), the recursive
# merge back-fills state/field_name from the first operand, which would
# make us silently return that operand's value instead of the operation's
# result. Only a plain field or computed var reference carries
# state + field_name on its own var data.
inner_var = var
while isinstance(inner_var, ToOperation):
inner_var = inner_var._original
var_data = inner_var._var_data
if var_data is None or not var_data.state or not var_data.field_name:
msg = f"Unable to retrieve value for {var._js_expr}: not associated with any state."
raise UnretrievableVarValueError(msg)
# Fastish case: this var belongs to this state
Expand Down
15 changes: 15 additions & 0 deletions tests/units/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -4575,6 +4575,21 @@ async def test_get_var_value(
"b": [4, 5, 6],
}

# Regression for https://github.com/reflex-dev/reflex/issues/6629: a Var
# operation / derived var (arithmetic, indexed or item access) must not
# silently return the value of its first constituent field. Such vars have
# no retrievable value, so raise instead of returning a plausible-but-wrong one.
with pytest.raises(UnretrievableVarValueError):
await state.get_var_value(TestState.num1 + TestState.num2)
with pytest.raises(UnretrievableVarValueError):
# array[0] is a Var operation at runtime, though statically typed as the element.
await state.get_var_value(TestState.array[0]) # pyright: ignore[reportArgumentType]
with pytest.raises(UnretrievableVarValueError):
await state.get_var_value(TestState.mapping["a"])

# Computed vars are derived but state-bound, so they remain resolvable.
assert await state.get_var_value(TestState.sum) == pytest.approx(42 + 3.15)


@pytest.mark.asyncio
async def test_get_var_value_async_computed_var(
Expand Down
Loading