diff --git a/pynecone/state.py b/pynecone/state.py index e022c764b..b7f2cc766 100644 --- a/pynecone/state.py +++ b/pynecone/state.py @@ -484,7 +484,10 @@ class State(Base, ABC, extra=pydantic.Extra.allow): func = arglist_factory(param) else: continue - cls.computed_vars[param] = func.set_state(cls) # type: ignore + # link dynamically created ComputedVar to this state class for dep determination + func.__objclass__ = cls + func.fget.__name__ = param + cls.vars[param] = cls.computed_vars[param] = func.set_state(cls) # type: ignore setattr(cls, param, func) def __getattribute__(self, name: str) -> Any: @@ -537,7 +540,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow): super().__setattr__(name, value) # Add the var to the dirty list. - if name in self.vars: + if name in self.vars or name in self.computed_var_dependencies: self.dirty_vars.add(name) self.mark_dirty() diff --git a/pynecone/var.py b/pynecone/var.py index 1cb7f8d84..4e1ee6682 100644 --- a/pynecone/var.py +++ b/pynecone/var.py @@ -857,6 +857,10 @@ class ComputedVar(Var, property): Returns: A set of variable names accessed by the given obj. + + Raises: + RuntimeError: if this ComputedVar does not have a reference to the class + it is attached to. (Assign var.__objclass__ manually to workaround.) """ d = set() if obj is None: @@ -876,6 +880,10 @@ class ComputedVar(Var, property): if self_is_top_of_stack and instruction.opname == "LOAD_ATTR": d.add(instruction.argval) elif self_is_top_of_stack and instruction.opname == "LOAD_METHOD": + if not hasattr(self, "__objclass__"): + raise RuntimeError( + f"ComputedVar {self.name!r} is not bound to a State subclass.", + ) d.update(self.deps(obj=getattr(self.__objclass__, instruction.argval))) self_is_top_of_stack = False return d diff --git a/tests/test_app.py b/tests/test_app.py index 6b97bbd66..e90cf4c95 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -105,6 +105,25 @@ def test_add_page_set_route(app: App, index_page, windows_platform: bool): assert set(app.pages.keys()) == {"test"} +def test_add_page_set_route_dynamic(app: App, index_page, windows_platform: bool): + """Test adding a page with dynamic route variable to an app. + + Args: + app: The app to test. + index_page: The index page. + windows_platform: Whether the system is windows. + """ + route = "/test/[dynamic]" + if windows_platform: + route.lstrip("/").replace("/", "\\") + assert app.pages == {} + app.add_page(index_page, route=route) + assert set(app.pages.keys()) == {"test/[dynamic]"} + assert "dynamic" in app.state.computed_vars + assert app.state.computed_vars["dynamic"].deps() == {"router_data"} + assert "router_data" in app.state().computed_var_dependencies + + def test_add_page_set_route_nested(app: App, index_page, windows_platform: bool): """Test adding a page to an app.