فهرست منبع

replace check_interval for JavaScript requests with asyncio event (see #2482) (#2827)

Falko Schindler 1 سال پیش
والد
کامیت
ea59b943fa

+ 10 - 11
nicegui/client.py

@@ -18,6 +18,7 @@ from .awaitable_response import AwaitableResponse
 from .dependencies import generate_resources
 from .element import Element
 from .favicon import get_favicon_url
+from .javascript_request import JavaScriptRequest
 from .logging import log
 from .outbox import Outbox
 from .version import __version__
@@ -65,8 +66,6 @@ class Client:
                 with Element('q-page'):
                     self.content = Element('div').classes('nicegui-content')
 
-        self.waiting_javascript_commands: Dict[str, Any] = {}
-
         self.title: Optional[str] = None
 
         self._head_html = ''
@@ -173,7 +172,9 @@ class Client:
 
     def run_javascript(self, code: str, *,
                        respond: Optional[bool] = None,  # DEPRECATED
-                       timeout: float = 1.0, check_interval: float = 0.01) -> AwaitableResponse:
+                       timeout: float = 1.0,
+                       check_interval: float = 0.01,  # DEPRECATED
+                       ) -> AwaitableResponse:
         """Execute JavaScript on the client.
 
         The client connection must be established before this method is called.
@@ -184,7 +185,6 @@ class Client:
 
         :param code: JavaScript code to run
         :param timeout: timeout in seconds (default: `1.0`)
-        :param check_interval: interval in seconds to check for a response (default: `0.01`)
 
         :return: AwaitableResponse that can be awaited to get the result of the JavaScript code
         """
@@ -196,6 +196,10 @@ class Client:
             raise ValueError('The "respond" argument of run_javascript() has been removed. '
                              'Now the method always returns an AwaitableResponse that can be awaited. '
                              'Please remove the "respond=False" argument and call the method without awaiting.')
+        if check_interval != 0.01:
+            log.warning('The "check_interval" argument of run_javascript() and similar methods has been removed. '
+                        'Now the method automatically returns when receiving a response without checking regularly in an interval. '
+                        'Please remove the "check_interval" argument.')
 
         request_id = str(uuid.uuid4())
         target_id = self._temporary_socket_id or self.id
@@ -205,12 +209,7 @@ class Client:
 
         async def send_and_wait():
             self.outbox.enqueue_message('run_javascript', {'code': code, 'request_id': request_id}, target_id)
-            deadline = time.time() + timeout
-            while request_id not in self.waiting_javascript_commands:
-                if time.time() > deadline:
-                    raise TimeoutError(f'JavaScript did not respond within {timeout:.1f} s')
-                await asyncio.sleep(check_interval)
-            return self.waiting_javascript_commands.pop(request_id)
+            return await JavaScriptRequest(request_id, timeout=timeout)
 
         return AwaitableResponse(send_and_forget, send_and_wait)
 
@@ -269,7 +268,7 @@ class Client:
 
     def handle_javascript_response(self, msg: Dict) -> None:
         """Store the result of a JavaScript command."""
-        self.waiting_javascript_commands[msg['request_id']] = msg['result']
+        JavaScriptRequest.resolve(msg['request_id'], msg['result'])
 
     def safe_invoke(self, func: Union[Callable[..., Any], Awaitable]) -> None:
         """Invoke the potentially async function in the client context and catch any exceptions."""

+ 0 - 1
nicegui/element.py

@@ -477,7 +477,6 @@ class Element(Visibility):
         :param name: name of the method
         :param args: arguments to pass to the method
         :param timeout: maximum time to wait for a response (default: 1 second)
-        :param check_interval: time between checks for a response (default: 0.01 seconds)
         """
         if not core.loop:
             return NullResponse()

+ 1 - 4
nicegui/elements/aggrid.py

@@ -105,7 +105,6 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         :param name: name of the method
         :param args: arguments to pass to the method
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """
@@ -127,7 +126,6 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         :param name: name of the method
         :param args: arguments to pass to the method
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """
@@ -146,7 +144,6 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         :param name: name of the method
         :param args: arguments to pass to the method
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """
@@ -185,7 +182,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         This does not happen when the cell loses focus, unless ``stopEditingWhenCellsLoseFocus: True`` is set.
 
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
+
         :return: list of row data
         """
         result = await self.client.run_javascript(f'''

+ 0 - 1
nicegui/elements/echart.py

@@ -109,7 +109,6 @@ class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min
         :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
         :param args: arguments to pass to the method (Python objects or JavaScript expressions)
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """

+ 0 - 1
nicegui/elements/json_editor.py

@@ -68,7 +68,6 @@ class JsonEditor(Element, component='json_editor.js', exposed_libraries=['lib/va
         :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
         :param args: arguments to pass to the method (Python objects or JavaScript expressions)
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """

+ 0 - 2
nicegui/elements/leaflet.py

@@ -128,7 +128,6 @@ class Leaflet(Element, component='leaflet.js'):
         :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
         :param args: arguments to pass to the method
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """
@@ -144,7 +143,6 @@ class Leaflet(Element, component='leaflet.js'):
         :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
         :param args: arguments to pass to the method
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """

+ 0 - 1
nicegui/elements/leaflet_layer.py

@@ -38,7 +38,6 @@ class Layer:
         :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
         :param args: arguments to pass to the method
         :param timeout: timeout in seconds (default: 1 second)
-        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """

+ 2 - 13
nicegui/functions/javascript.py

@@ -2,11 +2,10 @@ from typing import Optional
 
 from .. import context
 from ..awaitable_response import AwaitableResponse
-from ..logging import log
 
 
 def run_javascript(code: str, *,
-                   respond: Optional[bool] = None,  # DEPRECATED
+                   respond: Optional[bool] = None,
                    timeout: float = 1.0, check_interval: float = 0.01) -> AwaitableResponse:
     """Run JavaScript
 
@@ -19,17 +18,7 @@ def run_javascript(code: str, *,
 
     :param code: JavaScript code to run
     :param timeout: timeout in seconds (default: `1.0`)
-    :param check_interval: interval in seconds to check for a response (default: `0.01`)
 
     :return: AwaitableResponse that can be awaited to get the result of the JavaScript code
     """
-    if respond is True:
-        log.warning('The "respond" argument of run_javascript() has been removed. '
-                    'Now the function always returns an AwaitableResponse that can be awaited. '
-                    'Please remove the "respond=True" argument.')
-    if respond is False:
-        raise ValueError('The "respond" argument of run_javascript() has been removed. '
-                         'Now the function always returns an AwaitableResponse that can be awaited. '
-                         'Please remove the "respond=False" argument and call the function without awaiting.')
-
-    return context.get_client().run_javascript(code, timeout=timeout, check_interval=check_interval)
+    return context.get_client().run_javascript(code, respond=respond, timeout=timeout, check_interval=check_interval)

+ 32 - 0
nicegui/javascript_request.py

@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+import asyncio
+from typing import Any, Dict
+
+
+class JavaScriptRequest:
+    _instances: Dict[str, JavaScriptRequest] = {}
+
+    def __init__(self, request_id: str, *, timeout: float) -> None:
+        self.request_id = request_id
+        self._instances[request_id] = self
+        self.timeout = timeout
+        self._event = asyncio.Event()
+        self._result: Any = None
+
+    @classmethod
+    def resolve(cls, request_id: str, result: Any) -> None:
+        """Store the result of a JavaScript request and unblock the awaiter."""
+        request = cls._instances[request_id]
+        request._result = result  # pylint: disable=protected-access
+        request._event.set()  # pylint: disable=protected-access
+
+    def __await__(self) -> Any:
+        try:
+            yield from asyncio.wait_for(self._event.wait(), self.timeout).__await__()
+        except asyncio.TimeoutError as e:
+            raise TimeoutError(f'JavaScript did not respond within {self.timeout:.1f} s') from e
+        else:
+            return self._result
+        finally:
+            self._instances.pop(self.request_id)