vars: unbox EventHandler and functools.partial for dep analysis (#1305)
When calculating the variable dependencies of a cached_var, reach into objects with .func or .fn attributes and perform analysis on those. Fix #1303
This commit is contained in:
parent
e9592553c4
commit
20e2a25c9a
@ -869,10 +869,18 @@ class ComputedVar(Var, property):
|
|||||||
obj = cast(FunctionType, self.fget)
|
obj = cast(FunctionType, self.fget)
|
||||||
else:
|
else:
|
||||||
return set()
|
return set()
|
||||||
if not obj.__code__.co_varnames:
|
with contextlib.suppress(AttributeError):
|
||||||
|
# unbox functools.partial
|
||||||
|
obj = cast(FunctionType, obj.func) # type: ignore
|
||||||
|
with contextlib.suppress(AttributeError):
|
||||||
|
# unbox EventHandler
|
||||||
|
obj = cast(FunctionType, obj.fn) # type: ignore
|
||||||
|
|
||||||
|
try:
|
||||||
|
self_name = obj.__code__.co_varnames[0]
|
||||||
|
except (AttributeError, IndexError):
|
||||||
# cannot reference self if method takes no args
|
# cannot reference self if method takes no args
|
||||||
return set()
|
return set()
|
||||||
self_name = obj.__code__.co_varnames[0]
|
|
||||||
self_is_top_of_stack = False
|
self_is_top_of_stack = False
|
||||||
for instruction in dis.get_instructions(obj):
|
for instruction in dis.get_instructions(obj):
|
||||||
if instruction.opname == "LOAD_FAST" and instruction.argval == self_name:
|
if instruction.opname == "LOAD_FAST" and instruction.argval == self_name:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import functools
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -1089,6 +1090,43 @@ def test_computed_var_depends_on_parent_non_cached():
|
|||||||
assert counter == 6
|
assert counter == 6
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("use_partial", [True, False])
|
||||||
|
def test_cached_var_depends_on_event_handler(use_partial: bool):
|
||||||
|
"""A cached_var that calls an event handler calculates deps correctly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
use_partial: if true, replace the EventHandler with functools.partial
|
||||||
|
"""
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
class HandlerState(State):
|
||||||
|
x: int = 42
|
||||||
|
|
||||||
|
def handler(self):
|
||||||
|
self.x = self.x + 1
|
||||||
|
|
||||||
|
@rx.cached_var
|
||||||
|
def cached_x_side_effect(self) -> int:
|
||||||
|
self.handler()
|
||||||
|
nonlocal counter
|
||||||
|
counter += 1
|
||||||
|
return counter
|
||||||
|
|
||||||
|
if use_partial:
|
||||||
|
HandlerState.handler = functools.partial(HandlerState.handler.fn)
|
||||||
|
assert isinstance(HandlerState.handler, functools.partial)
|
||||||
|
else:
|
||||||
|
assert isinstance(HandlerState.handler, EventHandler)
|
||||||
|
|
||||||
|
s = HandlerState()
|
||||||
|
assert "cached_x_side_effect" in s.computed_var_dependencies["x"]
|
||||||
|
assert s.cached_x_side_effect == 1
|
||||||
|
assert s.x == 43
|
||||||
|
s.handler()
|
||||||
|
assert s.cached_x_side_effect == 2
|
||||||
|
assert s.x == 45
|
||||||
|
|
||||||
|
|
||||||
def test_backend_method():
|
def test_backend_method():
|
||||||
"""A method with leading underscore should be callable from event handler."""
|
"""A method with leading underscore should be callable from event handler."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user