瀏覽代碼

introduce task-dependent client stacks

Falko Schindler 2 年之前
父節點
當前提交
e711e18de3

+ 4 - 4
nicegui/client.py

@@ -33,11 +33,11 @@ class Client:
         self.is_waiting_for_handshake: bool = False
         self.is_waiting_for_handshake: bool = False
         self.environ: Optional[Dict[str, Any]] = None
         self.environ: Optional[Dict[str, Any]] = None
 
 
-        globals.client_stack.append(self)
+        globals.get_client_stack().append(self)
         with Element('q-layout').props('view="HHH LpR FFF"') as self.layout:
         with Element('q-layout').props('view="HHH LpR FFF"') as self.layout:
             with Element('q-page-container'):
             with Element('q-page-container'):
                 self.content = Element('div').classes('q-pa-md column items-start gap-4')
                 self.content = Element('div').classes('q-pa-md column items-start gap-4')
-        globals.client_stack.pop()
+        globals.get_client_stack().pop()
 
 
         self.waiting_javascript_commands: Dict[str, str] = {}
         self.waiting_javascript_commands: Dict[str, str] = {}
 
 
@@ -51,13 +51,13 @@ class Client:
         return self.environ.get('REMOTE_ADDR') if self.environ else None
         return self.environ.get('REMOTE_ADDR') if self.environ else None
 
 
     def __enter__(self):
     def __enter__(self):
-        globals.client_stack.append(self)
+        globals.get_client_stack().append(self)
         self.content.__enter__()
         self.content.__enter__()
         return self
         return self
 
 
     def __exit__(self, *_):
     def __exit__(self, *_):
         self.content.__exit__()
         self.content.__exit__()
-        globals.client_stack.pop()
+        globals.get_client_stack().pop()
 
 
     def watch_asyncs(self, coro: Coroutine) -> AsyncUpdater:
     def watch_asyncs(self, coro: Coroutine) -> AsyncUpdater:
         return AsyncUpdater(coro, self)
         return AsyncUpdater(coro, self)

+ 1 - 1
nicegui/element.py

@@ -16,7 +16,7 @@ class Element(ABC, Visibility):
 
 
     def __init__(self, tag: str) -> None:
     def __init__(self, tag: str) -> None:
         super().__init__()
         super().__init__()
-        self.client = globals.client_stack[-1]
+        self.client = globals.get_client()
         self.id = self.client.next_element_id
         self.id = self.client.next_element_id
         self.client.next_element_id += 1
         self.client.next_element_id += 1
         self.tag = tag
         self.tag = tag

+ 1 - 1
nicegui/elements/scene_object3d.py

@@ -15,7 +15,7 @@ class Object3D:
         self.type = type
         self.type = type
         self.id = str(uuid.uuid4())
         self.id = str(uuid.uuid4())
         self.name: Optional[str] = None
         self.name: Optional[str] = None
-        self.scene: 'Scene' = globals.client_stack[-1].slot_stack[-1].parent
+        self.scene: 'Scene' = globals.get_client().slot_stack[-1].parent
         self.scene.objects[self.id] = self
         self.scene.objects[self.id] = self
         self.parent: Object3D = self.scene.stack[-1]
         self.parent: Object3D = self.scene.stack[-1]
         self.args: List = list(args)
         self.args: List = list(args)

+ 2 - 2
nicegui/functions/html.py

@@ -2,8 +2,8 @@ from .. import globals
 
 
 
 
 def add_body_html(code: str) -> None:
 def add_body_html(code: str) -> None:
-    globals.client_stack[-1].body_html += code + '\n'
+    globals.get_client().body_html += code + '\n'
 
 
 
 
 def add_head_html(code: str) -> None:
 def add_head_html(code: str) -> None:
-    globals.client_stack[-1].head_html += code + '\n'
+    globals.get_client().head_html += code + '\n'

+ 1 - 1
nicegui/functions/javascript.py

@@ -5,5 +5,5 @@ from .. import globals
 
 
 async def run_javascript(code: str, *,
 async def run_javascript(code: str, *,
                          respond: bool = True, timeout: float = 1.0, check_interval: float = 0.01) -> Optional[str]:
                          respond: bool = True, timeout: float = 1.0, check_interval: float = 0.01) -> Optional[str]:
-    client = globals.client_stack[-1]
+    client = globals.get_client()
     return await client.run_javascript(code, respond=respond, timeout=timeout, check_interval=check_interval)
     return await client.run_javascript(code, respond=respond, timeout=timeout, check_interval=check_interval)

+ 1 - 1
nicegui/functions/notify.py

@@ -24,4 +24,4 @@ def notify(message: str, *,
     Note: You can pass additional keyword arguments according to [Quasar's Notify API](https://quasar.dev/quasar-plugins/notify#notify-api).
     Note: You can pass additional keyword arguments according to [Quasar's Notify API](https://quasar.dev/quasar-plugins/notify#notify-api).
     """
     """
     options = {key: value for key, value in locals().items() if not key.startswith('_') and value is not None}
     options = {key: value for key, value in locals().items() if not key.startswith('_') and value is not None}
-    create_task(globals.sio.emit('notify', options, room=str(globals.client_stack[-1].id)))
+    create_task(globals.sio.emit('notify', options, room=str(globals.get_client().id)))

+ 1 - 1
nicegui/functions/open.py

@@ -15,4 +15,4 @@ def open(target: Union[Callable, str]) -> None:
     :param socket: optional WebSocket defining the target client
     :param socket: optional WebSocket defining the target client
     """
     """
     path = target if isinstance(target, str) else globals.page_routes[target]
     path = target if isinstance(target, str) else globals.page_routes[target]
-    globals.client_stack[-1].open(path)
+    globals.get_client().open(path)

+ 7 - 5
nicegui/functions/timer.py

@@ -29,7 +29,8 @@ class Timer:
         self.interval = interval
         self.interval = interval
         self.callback = callback
         self.callback = callback
         self.active = active
         self.active = active
-        self.client = globals.client_stack[-1]
+        self.client = globals.get_client()
+        self.slot = self.client.slot_stack[-1]
 
 
         coroutine = self._run_once if once else self._run_in_loop
         coroutine = self._run_once if once else self._run_in_loop
         if globals.state == globals.State.STARTED:
         if globals.state == globals.State.STARTED:
@@ -59,9 +60,10 @@ class Timer:
 
 
     async def _invoke_callback(self) -> None:
     async def _invoke_callback(self) -> None:
         try:
         try:
-            with self.client:
-                result = self.callback()
-                if is_coroutine(self.callback):
-                    await result
+            with self.client as client:
+                with self.slot:
+                    result = self.callback()
+                    if is_coroutine(self.callback):
+                        await client.watch_asyncs(result)
         except Exception:
         except Exception:
             traceback.print_exc()
             traceback.print_exc()

+ 16 - 1
nicegui/globals.py

@@ -34,7 +34,7 @@ dark: Optional[bool]
 binding_refresh_interval: float
 binding_refresh_interval: float
 excludes: List[str]
 excludes: List[str]
 
 
-client_stack: List['Client'] = []
+client_stacks: Dict[int, List['Client']] = {}
 clients: Dict[int, 'Client'] = {}
 clients: Dict[int, 'Client'] = {}
 next_client_id: int = 0
 next_client_id: int = 0
 
 
@@ -46,3 +46,18 @@ connect_handlers: List[Union[Callable, Awaitable]] = []
 disconnect_handlers: List[Union[Callable, Awaitable]] = []
 disconnect_handlers: List[Union[Callable, Awaitable]] = []
 startup_handlers: List[Union[Callable, Awaitable]] = []
 startup_handlers: List[Union[Callable, Awaitable]] = []
 shutdown_handlers: List[Union[Callable, Awaitable]] = []
 shutdown_handlers: List[Union[Callable, Awaitable]] = []
+
+
+def get_task_id() -> int:
+    return id(asyncio.current_task()) if loop and loop.is_running() else 0
+
+
+def get_client_stack() -> List['Client']:
+    task_id = get_task_id()
+    if task_id not in client_stacks:
+        client_stacks[task_id] = []
+    return client_stacks[task_id]
+
+
+def get_client() -> 'Client':
+    return get_client_stack()[-1]

+ 1 - 1
nicegui/nicegui.py

@@ -28,7 +28,7 @@ error_client = ErrorClient(page(''))
 
 
 @app.get('/')
 @app.get('/')
 def index():
 def index():
-    return globals.client_stack[-1].build_response()
+    return index_client.build_response()
 
 
 
 
 @app.get('/_vue/dependencies/{path:path}')
 @app.get('/_vue/dependencies/{path:path}')

+ 2 - 4
nicegui/slot.py

@@ -1,7 +1,5 @@
 from typing import TYPE_CHECKING, List
 from typing import TYPE_CHECKING, List
 
 
-from . import globals
-
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from .element import Element
     from .element import Element
 
 
@@ -16,11 +14,11 @@ class Slot:
 
 
     def __enter__(self):
     def __enter__(self):
         self.child_count = len(self.children)
         self.child_count = len(self.children)
-        globals.client_stack[-1].slot_stack.append(self)
+        self.parent.client.slot_stack.append(self)
         return self
         return self
 
 
     def __exit__(self, *_):
     def __exit__(self, *_):
-        globals.client_stack[-1].slot_stack.pop()
+        self.parent.client.slot_stack.pop()
         self.lazy_update()
         self.lazy_update()
 
 
     def lazy_update(self) -> None:
     def lazy_update(self) -> None:

+ 3 - 2
tests/test_auto_context.py

@@ -39,8 +39,9 @@ def test_adding_elements_with_async_await(screen: Screen):
     with ui.card():
     with ui.card():
         ui.timer(1.1, add_b, once=True)
         ui.timer(1.1, add_b, once=True)
 
 
-    # TODO
-    # screen.open('/')
+    screen.open('/')
+    screen.should_contain('A')
+    screen.should_contain('B')
     # for _ in range(100):
     # for _ in range(100):
     #     if '    card\n      A\n    card\n      B' in screen.render_content():
     #     if '    card\n      A\n    card\n      B' in screen.render_content():
     #         return
     #         return