Browse Source

refresh binding before page delivery

Falko Schindler 1 year ago
parent
commit
f5dd41d395
4 changed files with 33 additions and 17 deletions
  1. 18 14
      nicegui/binding.py
  2. 1 1
      nicegui/nicegui.py
  3. 2 1
      nicegui/page.py
  4. 12 1
      tests/test_binding.py

+ 18 - 14
nicegui/binding.py

@@ -32,24 +32,28 @@ def set_attribute(obj: Union[object, Mapping], name: str, value: Any) -> None:
         setattr(obj, name, value)
         setattr(obj, name, value)
 
 
 
 
-async def loop() -> None:
+async def refresh_loop() -> None:
     while True:
     while True:
-        visited: Set[Tuple[int, str]] = set()
-        t = time.time()
-        for link in active_links:
-            (source_obj, source_name, target_obj, target_name, transform) = link
-            if has_attribute(source_obj, source_name):
-                value = transform(get_attribute(source_obj, source_name))
-                if not has_attribute(target_obj, target_name) or get_attribute(target_obj, target_name) != value:
-                    set_attribute(target_obj, target_name, value)
-                    propagate(target_obj, target_name, visited)
-            del link, source_obj, target_obj  # pylint: disable=modified-iterating-list
-        if time.time() - t > MAX_PROPAGATION_TIME:
-            globals.log.warning(
-                f'binding propagation for {len(active_links)} active links took {time.time() - t:.3f} s')
+        _refresh_step()
         await asyncio.sleep(globals.binding_refresh_interval)
         await asyncio.sleep(globals.binding_refresh_interval)
 
 
 
 
+def _refresh_step():
+    visited: Set[Tuple[int, str]] = set()
+    t = time.time()
+    for link in active_links:
+        (source_obj, source_name, target_obj, target_name, transform) = link
+        if has_attribute(source_obj, source_name):
+            value = transform(get_attribute(source_obj, source_name))
+            if not has_attribute(target_obj, target_name) or get_attribute(target_obj, target_name) != value:
+                set_attribute(target_obj, target_name, value)
+                propagate(target_obj, target_name, visited)
+        del link, source_obj, target_obj  # pylint: disable=modified-iterating-list
+    if time.time() - t > MAX_PROPAGATION_TIME:
+        globals.log.warning(
+            f'binding propagation for {len(active_links)} active links took {time.time() - t:.3f} s')
+
+
 def propagate(source_obj: Any, source_name: str, visited: Optional[Set[Tuple[int, str]]] = None) -> None:
 def propagate(source_obj: Any, source_name: str, visited: Optional[Set[Tuple[int, str]]] = None) -> None:
     if visited is None:
     if visited is None:
         visited = set()
         visited = set()

+ 1 - 1
nicegui/nicegui.py

@@ -91,7 +91,7 @@ def handle_startup(with_welcome_message: bool = True) -> None:
     with globals.index_client:
     with globals.index_client:
         for t in globals.startup_handlers:
         for t in globals.startup_handlers:
             safe_invoke(t)
             safe_invoke(t)
-    background_tasks.create(binding.loop(), name='refresh bindings')
+    background_tasks.create(binding.refresh_loop(), name='refresh bindings')
     background_tasks.create(outbox.loop(), name='send outbox')
     background_tasks.create(outbox.loop(), name='send outbox')
     background_tasks.create(prune_clients(), name='prune clients')
     background_tasks.create(prune_clients(), name='prune clients')
     background_tasks.create(prune_slot_stacks(), name='prune slot stacks')
     background_tasks.create(prune_slot_stacks(), name='prune slot stacks')

+ 2 - 1
nicegui/page.py

@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, Union
 
 
 from fastapi import Request, Response
 from fastapi import Request, Response
 
 
-from . import background_tasks, globals  # pylint: disable=redefined-builtin
+from . import background_tasks, binding, globals  # pylint: disable=redefined-builtin
 from .client import Client
 from .client import Client
 from .favicon import create_favicon_route
 from .favicon import create_favicon_route
 from .language import Language
 from .language import Language
@@ -100,6 +100,7 @@ class page:
                 result = task.result() if task.done() else None
                 result = task.result() if task.done() else None
             if isinstance(result, Response):  # NOTE if setup returns a response, we don't need to render the page
             if isinstance(result, Response):  # NOTE if setup returns a response, we don't need to render the page
                 return result
                 return result
+            binding._refresh_step()  # pylint: disable=protected-access
             return client.build_response(request)
             return client.build_response(request)
 
 
         parameters = [p for p in inspect.signature(func).parameters.values() if p.name != 'client']
         parameters = [p for p in inspect.signature(func).parameters.values() if p.name != 'client']

+ 12 - 1
tests/test_binding.py

@@ -1,4 +1,3 @@
-
 from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.common.keys import Keys
 
 
 from nicegui import ui
 from nicegui import ui
@@ -83,3 +82,15 @@ def test_binding_to_input(screen: Screen):
     element.value = 'five'
     element.value = 'five'
     screen.should_contain_input('five')
     screen.should_contain_input('five')
     assert data.text == 'five'
     assert data.text == 'five'
+
+
+def test_binding_refresh_before_page_delivery(screen: Screen):
+    state = {'count': 0}
+
+    @ui.page('/')
+    def main_page() -> None:
+        ui.label().bind_text_from(state, 'count')
+        state['count'] += 1
+
+    screen.open('/')
+    screen.should_contain('1')