Falko Schindler 2 роки тому
батько
коміт
8dbca9f385
4 змінених файлів з 60 додано та 68 видалено
  1. 26 27
      nicegui/native.py
  2. 23 29
      nicegui/native_mode.py
  3. 9 10
      nicegui/run.py
  4. 2 2
      website/documentation.py

+ 26 - 27
nicegui/native.py

@@ -4,7 +4,7 @@ import logging
 from dataclasses import dataclass, field
 from functools import partial
 from multiprocessing import Queue
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Callable, Dict, Optional, Tuple
 
 import webview
 from webview.window import FixPoint
@@ -16,24 +16,25 @@ response_queue = Queue()
 
 
 class WindowProxy(webview.Window):
-    def __init__(self):
+
+    def __init__(self) -> None:
         pass  # NOTE we don't call super().__init__ here because this is just a proxy to the actual window
 
-    async def is_always_on_top(self) -> bool:
-        """whether the window is always on top"""
-        return await self._send_async()
+    async def get_always_on_top(self) -> bool:
+        """Get whether the window is always on top."""
+        return await self._request()
 
     def set_always_on_top(self, on_top: bool) -> None:
-        """set whether the window is always on top"""
+        """Set whether the window is always on top."""
         self._send(on_top)
 
     async def get_size(self) -> Tuple[int, int]:
-        """get the window size as tuple (width, height)"""
-        return await self._send_async()
+        """Get the window size as tuple (width, height)."""
+        return await self._request()
 
     async def get_position(self) -> Tuple[int, int]:
-        """get the window position as tuple (x, y)"""
-        return await self._send_async()
+        """Get the window position as tuple (x, y)."""
+        return await self._request()
 
     def load_url(self, url: str) -> None:
         self._send(url)
@@ -45,13 +46,13 @@ class WindowProxy(webview.Window):
         self._send(stylesheet)
 
     def set_title(self, title: str) -> None:
-        self.send(title)
+        self._send(title)
 
     async def get_cookies(self) -> Any:
-        return await self._send_async()
+        return await self._request()
 
     async def get_current_url(self) -> str:
-        return await self._send_async()
+        return await self._request()
 
     def destroy(self) -> None:
         self._send()
@@ -81,10 +82,10 @@ class WindowProxy(webview.Window):
         self._send(x, y)
 
     async def evaluate_js(self, script: str) -> str:
-        return await self._send_async(script)
+        return await self._request(script)
 
     async def create_confirmation_dialog(self, title: str, message: str) -> bool:
-        return await self._send_async(title, message)
+        return await self._request(title, message)
 
     async def create_file_dialog(
         self,
@@ -92,33 +93,31 @@ class WindowProxy(webview.Window):
         directory: str = '',
         allow_multiple: bool = False,
         save_filename: str = '',
-        file_types: Tuple[str, ...] = ()
+        file_types: Tuple[str, ...] = (),
     ) -> Tuple[str, ...]:
-        return await self._send_async(
+        return await self._request(
             dialog_type=dialog_type,
             directory=directory,
             allow_multiple=allow_multiple,
             save_filename=save_filename,
-            file_types=file_types
+            file_types=file_types,
         )
 
-    def expose(self, function) -> None:
+    def expose(self, function: Callable) -> None:
         raise NotImplementedError(f'exposing "{function}" is not supported')
 
-    def _send(self, *args, **kwargs) -> Any:
-        name = inspect.currentframe().f_back.f_code.co_name
-        return method_queue.put((name, args, kwargs))
-
-    async def _send_async(self, *args, **kwargs) -> Any:
+    def _send(self, *args: Any, **kwargs: Any) -> None:
         name = inspect.currentframe().f_back.f_code.co_name
+        method_queue.put((name, args, kwargs))
 
-        def wrapper(*args, **kwargs):
+    async def _request(self, *args: Any, **kwargs: Any) -> Any:
+        def wrapper(*args: Any, **kwargs: Any) -> Any:
             try:
-                method_queue.put((name, args, kwargs))  # NOTE args[1:] to skip self
+                method_queue.put((name, args, kwargs))
                 return response_queue.get()  # wait for the method to be called and writing its result to the queue
             except Exception:
                 logging.exception(f'error in {name}')
-
+        name = inspect.currentframe().f_back.f_code.co_name
         return await asyncio.get_event_loop().run_in_executor(None, partial(wrapper, *args, **kwargs))
 
     def signal_server_shutdown(self) -> None:

+ 23 - 29
nicegui/native_mode.py

@@ -8,6 +8,7 @@ import tempfile
 import time
 import warnings
 from threading import Event, Thread
+from typing import Any, Callable, Dict, List, Tuple
 
 from . import globals, helpers, native
 
@@ -33,7 +34,7 @@ def open_window(
     try:
         window = webview.create_window(**window_kwargs)
         closing = Event()
-        window.events.closing += lambda: closing.set()
+        window.events.closing += closing.set
         start_window_method_executor(window, method_queue, response_queue, closing)
         webview.start(storage_path=tempfile.mkdtemp(), **globals.app.native.start_args)
     except NameError:
@@ -44,47 +45,43 @@ def open_window(
 def start_window_method_executor(
         window: webview.Window, method_queue: mp.Queue, response_queue: mp.Queue, closing: Event
 ) -> None:
-    def execute(attr, args, kwargs):
+    def execute(method: Callable, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> None:
         try:
-            response = attr(*args, **kwargs)
-            if response is not None or 'dialog' in attr.__name__:
+            response = method(*args, **kwargs)
+            if response is not None or 'dialog' in method.__name__:
                 response_queue.put(response)
         except Exception:
-            logging.exception(f'error in window.{attr.__name__}')
+            logging.exception(f'error in window.{method.__name__}')
 
-    def window_method_executor():
-        pending_executions = []
+    def window_method_executor() -> None:
+        pending_executions: List[Thread] = []
         while not closing.is_set():
             try:
-                method, args, kwargs = method_queue.get(block=False)
-                if method == 'signal_server_shutdown':
+                method_name, args, kwargs = method_queue.get(block=False)
+                if method_name == 'signal_server_shutdown':
                     if pending_executions:
                         logging.warning('shutdown is possibly blocked by opened dialogs like a file picker')
                         while pending_executions:
                             pending_executions.pop().join()
-                    continue
-                if method == 'is_always_on_top':
+                elif method_name == 'get_always_on_top':
                     response_queue.put(window.on_top)
-                    continue
-                if method == 'set_always_on_top':
+                elif method_name == 'set_always_on_top':
                     window.on_top = args[0]
-                    continue
-                if method == 'get_position':
+                elif method_name == 'get_position':
                     response_queue.put((int(window.x), int(window.y)))
-                    continue
-                if method == 'get_size':
+                elif method_name == 'get_size':
                     response_queue.put((int(window.width), int(window.height)))
-                    continue
-                attr = getattr(window, method)
-                if callable(attr):
-                    pending_executions.append(Thread(target=execute, args=(attr, args, kwargs)))
-                    pending_executions[-1].start()
                 else:
-                    logging.error(f'window.{method} is not callable')
+                    method = getattr(window, method_name)
+                    if callable(method):
+                        pending_executions.append(Thread(target=execute, args=(method, args, kwargs)))
+                        pending_executions[-1].start()
+                    else:
+                        logging.error(f'window.{method_name} is not callable')
             except queue.Empty:
                 time.sleep(0.01)
             except Exception:
-                logging.exception(f'error in window.{method}')
+                logging.exception(f'error in window.{method_name}')
 
     Thread(target=window_method_executor).start()
 
@@ -99,11 +96,8 @@ def activate(host: str, port: int, title: str, width: int, height: int, fullscre
         _thread.interrupt_main()
 
     mp.freeze_support()
-    process = mp.Process(
-        target=open_window,
-        args=(host, port, title, width, height, fullscreen, native.method_queue, native.response_queue),
-        daemon=False
-    )
+    args = host, port, title, width, height, fullscreen, native.method_queue, native.response_queue
+    process = mp.Process(target=open_window, args=args, daemon=False)
     process.start()
 
     Thread(target=check_shutdown, daemon=True).start()

+ 9 - 10
nicegui/run.py

@@ -1,6 +1,7 @@
 import logging
 import multiprocessing
 import os
+import socket
 import sys
 from typing import Any, List, Optional, Tuple
 
@@ -10,21 +11,19 @@ from uvicorn.main import STARTUP_FAILURE
 from uvicorn.supervisors import ChangeReload, Multiprocess
 
 from . import globals, helpers
-from . import native as app_native
+from . import native as native_module
 from . import native_mode
 from .language import Language
 
 
 class Server(uvicorn.Server):
-    def __init__(self, config):
-        super().__init__(config)
 
-    def run(self, sockets=None):
+    def run(self, sockets: Optional[List[socket.socket]] = None) -> None:
         globals.server = self
-        app_native.method_queue = self.config.method_queue
-        app_native.response_queue = self.config.response_queue
-        if app_native.method_queue is not None:
-            globals.app.native.main_window = app_native.WindowProxy()
+        native_module.method_queue = self.config.method_queue
+        native_module.response_queue = self.config.response_queue
+        if native_module.method_queue is not None:
+            globals.app.native.main_window = native_module.WindowProxy()
         super().run(sockets=sockets)
 
 
@@ -130,8 +129,8 @@ def run(*,
         log_level=uvicorn_logging_level,
         **kwargs,
     )
-    config.method_queue = app_native.method_queue if native else None
-    config.response_queue = app_native.response_queue if native else None
+    config.method_queue = native_module.method_queue if native else None
+    config.response_queue = native_module.response_queue if native else None
     globals.server = Server(config=config)
     if (reload or config.workers > 1) and not isinstance(config.app, str):
         logging.warning('You must pass the application as an import string to enable "reload" or "workers".')

+ 2 - 2
website/documentation.py

@@ -586,8 +586,8 @@ def create_full() -> None:
         To customize the initial window size and display mode, use the `window_size` and `fullscreen` parameters respectively.
         Additionally, you can provide extra keyword arguments via `app.native.window_args` and `app.native.start_args`.
         Pick any parameter as it is defined by the internally used [pywebview module](https://pywebview.flowrl.com/guide/api.html)
-        for the `webview.create_window` and `webview.start` functions. 
-        Note that these keyword arguments will take precedence over the parameters defined in ui.run.
+        for the `webview.create_window` and `webview.start` functions.
+        Note that these keyword arguments will take precedence over the parameters defined in `ui.run`.
 
         In native mode the `app.native.main_window` object allows you to access the underlying window.
         It is an async version of [`Window` from pywebview](https://pywebview.flowrl.com/guide/api.html#window-object).