瀏覽代碼

Cache ComputedVar fixup for dynamic route vars (#952)

Masen Furer 2 年之前
父節點
當前提交
b4e534cc97
共有 3 個文件被更改,包括 32 次插入2 次删除
  1. 5 2
      pynecone/state.py
  2. 8 0
      pynecone/var.py
  3. 19 0
      tests/test_app.py

+ 5 - 2
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()
 

+ 8 - 0
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

+ 19 - 0
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.