Falko Schindler 1 vuosi sitten
vanhempi
säilyke
15c3321297
8 muutettua tiedostoa jossa 25 lisäystä ja 19 poistoa
  1. 1 3
      main.py
  2. 3 3
      nicegui/air.py
  3. 1 0
      nicegui/globals.py
  4. 8 6
      nicegui/nicegui.py
  5. 4 5
      nicegui/page.py
  6. 3 0
      nicegui/run.py
  7. 2 0
      nicegui/run_with.py
  8. 3 2
      tests/test_page.py

+ 1 - 3
main.py

@@ -38,8 +38,6 @@ if True:  # HACK: prevent the page from scrolling when closing a dialog (#1404)
         on_value_change(sender, value)
         on_value_change(sender, value)
     ui.dialog.on_value_change = on_dialog_value_change
     ui.dialog.on_value_change = on_dialog_value_change
 
 
-ui.page.RECONNECT_TIMEOUT = 3
-
 
 
 @app.get('/logo.png')
 @app.get('/logo.png')
 def logo() -> FileResponse:
 def logo() -> FileResponse:
@@ -435,4 +433,4 @@ async def documentation_page_more(name: str, client: Client) -> None:
     await client.connected()
     await client.connected()
     await ui.run_javascript(f'document.title = "{name} • NiceGUI";', respond=False)
     await ui.run_javascript(f'document.title = "{name} • NiceGUI";', respond=False)
 
 
-ui.run(uvicorn_reload_includes='*.py, *.css, *.html')
+ui.run(uvicorn_reload_includes='*.py, *.css, *.html', reconnect_timeout=3.0)

+ 3 - 3
nicegui/air.py

@@ -7,8 +7,8 @@ import httpx
 import socketio
 import socketio
 from socketio import AsyncClient
 from socketio import AsyncClient
 
 
-from . import globals  # pylint: disable=redefined-builtin
-from .nicegui import background_tasks, handle_disconnect, handle_event, handle_handshake, handle_javascript_response
+from . import background_tasks, globals  # pylint: disable=redefined-builtin
+from .nicegui import handle_disconnect, handle_event, handle_handshake, handle_javascript_response
 
 
 RELAY_HOST = 'https://on-air.nicegui.io/'
 RELAY_HOST = 'https://on-air.nicegui.io/'
 
 
@@ -77,7 +77,7 @@ class Air:
             if client_id not in globals.clients:
             if client_id not in globals.clients:
                 return
                 return
             client = globals.clients[client_id]
             client = globals.clients[client_id]
-            client.disconnect_task = background_tasks.create(handle_disconnect(client, client.page.reconnect_timeout))
+            client.disconnect_task = background_tasks.create(handle_disconnect(client))
 
 
         @self.relay.on('event')
         @self.relay.on('event')
         def on_event(data: Dict[str, Any]) -> None:
         def on_event(data: Dict[str, Any]) -> None:

+ 1 - 0
nicegui/globals.py

@@ -45,6 +45,7 @@ favicon: Optional[Union[str, Path]]
 dark: Optional[bool]
 dark: Optional[bool]
 language: Language
 language: Language
 binding_refresh_interval: float
 binding_refresh_interval: float
+reconnect_timeout: float
 tailwind: bool
 tailwind: bool
 prod_js: bool
 prod_js: bool
 endpoint_documentation: Literal['none', 'internal', 'page', 'all'] = 'none'
 endpoint_documentation: Literal['none', 'internal', 'page', 'all'] = 'none'

+ 8 - 6
nicegui/nicegui.py

@@ -3,7 +3,7 @@ import mimetypes
 import time
 import time
 import urllib.parse
 import urllib.parse
 from pathlib import Path
 from pathlib import Path
-from typing import Dict, Optional
+from typing import Dict
 
 
 from fastapi import HTTPException, Request
 from fastapi import HTTPException, Request
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.middleware.gzip import GZipMiddleware
@@ -146,6 +146,7 @@ def on_handshake(sid: str, client_id: str) -> bool:
 def handle_handshake(client: Client) -> None:
 def handle_handshake(client: Client) -> None:
     if client.disconnect_task:
     if client.disconnect_task:
         client.disconnect_task.cancel()
         client.disconnect_task.cancel()
+        client.disconnect_task = None
     for t in client.connect_handlers:
     for t in client.connect_handlers:
         safe_invoke(t, client)
         safe_invoke(t, client)
     for t in globals.connect_handlers:
     for t in globals.connect_handlers:
@@ -159,11 +160,12 @@ def on_disconnect(sid: str) -> None:
     client_id = query['client_id'][0]
     client_id = query['client_id'][0]
     client = globals.clients.get(client_id)
     client = globals.clients.get(client_id)
     if client:
     if client:
-        client.disconnect_task = background_tasks.create(handle_disconnect(client, client.page.reconnect_timeout))
+        client.disconnect_task = background_tasks.create(handle_disconnect(client))
 
 
 
 
-async def handle_disconnect(client: Client, reconnection_delay: int) -> None:
-    await asyncio.sleep(reconnection_delay)
+async def handle_disconnect(client: Client) -> None:
+    delay = client.page.reconnect_timeout if client.page.reconnect_timeout is not None else globals.reconnect_timeout
+    await asyncio.sleep(delay)
     if not client.shared:
     if not client.shared:
         delete_client(client.id)
         delete_client(client.id)
     for t in client.disconnect_handlers:
     for t in client.disconnect_handlers:
@@ -174,7 +176,7 @@ async def handle_disconnect(client: Client, reconnection_delay: int) -> None:
 
 
 @sio.on('event')
 @sio.on('event')
 def on_event(_: str, msg: Dict) -> None:
 def on_event(_: str, msg: Dict) -> None:
-    client = globals.clients[msg['client_id']]
+    client = globals.clients.get(msg['client_id'])
     if not client or not client.has_socket_connection:
     if not client or not client.has_socket_connection:
         return
         return
     handle_event(client, msg)
     handle_event(client, msg)
@@ -192,7 +194,7 @@ def handle_event(client: Client, msg: Dict) -> None:
 
 
 @sio.on('javascript_response')
 @sio.on('javascript_response')
 def on_javascript_response(_: str, msg: Dict) -> None:
 def on_javascript_response(_: str, msg: Dict) -> None:
-    client = globals.clients[msg['client_id']]
+    client = globals.clients.get(msg['client_id'])
     if not client:
     if not client:
         return
         return
     handle_javascript_response(client, msg)
     handle_javascript_response(client, msg)

+ 4 - 5
nicegui/page.py

@@ -18,7 +18,6 @@ if TYPE_CHECKING:
 
 
 
 
 class page:
 class page:
-    RECONNECT_TIMEOUT: float = 0.0  # TODO: change to 3.0 in 1.4 release
 
 
     def __init__(self,
     def __init__(self,
                  path: str, *,
                  path: str, *,
@@ -28,8 +27,8 @@ class page:
                  dark: Optional[bool] = ...,  # type: ignore
                  dark: Optional[bool] = ...,  # type: ignore
                  language: Language = ...,  # type: ignore
                  language: Language = ...,  # type: ignore
                  response_timeout: float = 3.0,
                  response_timeout: float = 3.0,
-                 api_router: Optional[APIRouter] = None,
                  reconnect_timeout: Optional[float] = None,
                  reconnect_timeout: Optional[float] = None,
+                 api_router: Optional[APIRouter] = None,
                  **kwargs: Any,
                  **kwargs: Any,
                  ) -> None:
                  ) -> None:
         """Page
         """Page
@@ -45,9 +44,9 @@ class page:
         :param favicon: optional relative filepath or absolute URL to a favicon (default: `None`, NiceGUI icon will be used)
         :param favicon: optional relative filepath or absolute URL to a favicon (default: `None`, NiceGUI icon will be used)
         :param dark: whether to use Quasar's dark mode (defaults to `dark` argument of `run` command)
         :param dark: whether to use Quasar's dark mode (defaults to `dark` argument of `run` command)
         :param language: language of the page (defaults to `language` argument of `run` command)
         :param language: language of the page (defaults to `language` argument of `run` command)
-        :param response_timeout: maximum time for the decorated function to build the page (default: 3.0)
+        :param response_timeout: maximum time for the decorated function to build the page (default: 3.0 seconds)
+        :param reconnect_timeout: maximum time the server waits for the browser to reconnect (default: 0.0 seconds)
         :param api_router: APIRouter instance to use, can be left `None` to use the default
         :param api_router: APIRouter instance to use, can be left `None` to use the default
-        :param reconnect_timeout: maximum time we wait for the browser to reconnect after a disconnect (default: 0.0)
         :param kwargs: additional keyword arguments passed to FastAPI's @app.get method
         :param kwargs: additional keyword arguments passed to FastAPI's @app.get method
         """
         """
         self._path = path
         self._path = path
@@ -59,7 +58,7 @@ class page:
         self.response_timeout = response_timeout
         self.response_timeout = response_timeout
         self.kwargs = kwargs
         self.kwargs = kwargs
         self.api_router = api_router or globals.app.router
         self.api_router = api_router or globals.app.router
-        self.reconnect_timeout = reconnect_timeout or self.RECONNECT_TIMEOUT
+        self.reconnect_timeout = reconnect_timeout
 
 
         create_favicon_route(self.path, favicon)
         create_favicon_route(self.path, favicon)
 
 

+ 3 - 0
nicegui/run.py

@@ -49,6 +49,7 @@ def run(*,
         dark: Optional[bool] = False,
         dark: Optional[bool] = False,
         language: Language = 'en-US',
         language: Language = 'en-US',
         binding_refresh_interval: float = 0.1,
         binding_refresh_interval: float = 0.1,
+        reconnect_timeout: float = 0.0,  # NOTE: the value of 0.0 is DEPRECATED, change to 3.0 in 1.4 release
         show: bool = True,
         show: bool = True,
         on_air: Optional[Union[str, Literal[True]]] = None,
         on_air: Optional[Union[str, Literal[True]]] = None,
         native: bool = False,
         native: bool = False,
@@ -78,6 +79,7 @@ def run(*,
     :param dark: whether to use Quasar's dark mode (default: `False`, use `None` for "auto" mode)
     :param dark: whether to use Quasar's dark mode (default: `False`, use `None` for "auto" mode)
     :param language: language for Quasar elements (default: `'en-US'`)
     :param language: language for Quasar elements (default: `'en-US'`)
     :param binding_refresh_interval: time between binding updates (default: `0.1` seconds, bigger is more CPU friendly)
     :param binding_refresh_interval: time between binding updates (default: `0.1` seconds, bigger is more CPU friendly)
+    :param reconnect_timeout: maximum time the server waits for the browser to reconnect (default: 0.0 seconds, i.e. no reconnect)
     :param show: automatically open the UI in a browser tab (default: `True`)
     :param show: automatically open the UI in a browser tab (default: `True`)
     :param on_air: tech preview: `allows temporary remote access <https://nicegui.io/documentation#nicegui_on_air>`_ if set to `True` (default: disabled)
     :param on_air: tech preview: `allows temporary remote access <https://nicegui.io/documentation#nicegui_on_air>`_ if set to `True` (default: disabled)
     :param native: open the UI in a native window of size 800x600 (default: `False`, deactivates `show`, automatically finds an open port)
     :param native: open the UI in a native window of size 800x600 (default: `False`, deactivates `show`, automatically finds an open port)
@@ -103,6 +105,7 @@ def run(*,
     globals.dark = dark
     globals.dark = dark
     globals.language = language
     globals.language = language
     globals.binding_refresh_interval = binding_refresh_interval
     globals.binding_refresh_interval = binding_refresh_interval
+    globals.reconnect_timeout = reconnect_timeout
     globals.tailwind = tailwind
     globals.tailwind = tailwind
     globals.prod_js = prod_js
     globals.prod_js = prod_js
     globals.endpoint_documentation = endpoint_documentation
     globals.endpoint_documentation = endpoint_documentation

+ 2 - 0
nicegui/run_with.py

@@ -17,6 +17,7 @@ def run_with(
     dark: Optional[bool] = False,
     dark: Optional[bool] = False,
     language: Language = 'en-US',
     language: Language = 'en-US',
     binding_refresh_interval: float = 0.1,
     binding_refresh_interval: float = 0.1,
+    reconnect_timeout: float = 0.0,  # NOTE: the value of 0.0 is DEPRECATED, change to 3.0 in 1.4 release
     mount_path: str = '/',
     mount_path: str = '/',
     tailwind: bool = True,
     tailwind: bool = True,
     prod_js: bool = True,
     prod_js: bool = True,
@@ -29,6 +30,7 @@ def run_with(
     globals.dark = dark
     globals.dark = dark
     globals.language = language
     globals.language = language
     globals.binding_refresh_interval = binding_refresh_interval
     globals.binding_refresh_interval = binding_refresh_interval
+    globals.reconnect_timeout = reconnect_timeout
     globals.tailwind = tailwind
     globals.tailwind = tailwind
     globals.prod_js = prod_js
     globals.prod_js = prod_js
 
 

+ 3 - 2
tests/test_page.py

@@ -294,6 +294,7 @@ def test_reconnecting_without_page_reload(screen: Screen):
 
 
     screen.open('/')
     screen.open('/')
     screen.type('hello')
     screen.type('hello')
-    element = screen.selenium.find_element(By.XPATH, '//*[@aria-label="Input"]')
     screen.click('drop connection')
     screen.click('drop connection')
-    assert element.get_attribute('value') == 'hello', 'input should be preserved after reconnect (eg. no page reload)'
+    screen.wait(2.0)
+    element = screen.selenium.find_element(By.XPATH, '//*[@aria-label="Input"]')
+    assert element.get_attribute('value') == 'hello', 'input should be preserved after reconnect (i.e. no page reload)'