Forráskód Böngészése

fix some mypy errors

Falko Schindler 2 éve
szülő
commit
a362e60c58

+ 2 - 2
main.py

@@ -21,8 +21,8 @@ from website.style import example_link, features, heading, link_target, section_
 
 prometheus.start_monitor(app)
 
-ui.add_static_files('/favicon', Path(__file__).parent / 'website' / 'favicon')
-ui.add_static_files('/fonts', Path(__file__).parent / 'website' / 'fonts')
+ui.add_static_files('/favicon', str(Path(__file__).parent / 'website' / 'favicon'))
+ui.add_static_files('/fonts', str(Path(__file__).parent / 'website' / 'fonts'))
 
 # NOTE in our global fly.io deployment we need to make sure that the websocket connects back to the same instance
 fly_instance_id = os.environ.get('FLY_ALLOC_ID', '').split('-')[0]

+ 5 - 5
nicegui/binding.py

@@ -2,13 +2,13 @@ import asyncio
 import logging
 import time
 from collections import defaultdict
-from typing import Any, Callable, List, Optional, Set, Tuple, Type
+from typing import Any, Callable, DefaultDict, Dict, List, Optional, Set, Tuple, Type
 
 from . import globals
 
-bindings = defaultdict(list)
-bindable_properties = dict()
-active_links = []
+bindings: DefaultDict[Tuple[int, str], List] = defaultdict(list)
+bindable_properties: Dict[Tuple[int, str], Any] = dict()
+active_links: List[Tuple[Any, str, Any, str, Callable]] = []
 
 
 async def loop():
@@ -26,7 +26,7 @@ async def loop():
         await asyncio.sleep(globals.binding_refresh_interval)
 
 
-def propagate(source_obj: Any, source_name: str, visited: 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:
         visited = set()
     visited.add((id(source_obj), source_name))

+ 4 - 4
nicegui/client.py

@@ -6,7 +6,7 @@ from pathlib import Path
 from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union
 
 from fastapi import Request
-from fastapi.responses import HTMLResponse
+from fastapi.responses import Response
 from fastapi.templating import Jinja2Templates
 
 from . import globals, vue
@@ -27,7 +27,7 @@ class Client:
         self.created = time.time()
         globals.clients[self.id] = self
 
-        self.elements: Dict[str, Element] = {}
+        self.elements: Dict[int, Element] = {}
         self.next_element_id: int = 0
         self.is_waiting_for_handshake: bool = False
         self.environ: Optional[Dict[str, Any]] = None
@@ -62,7 +62,7 @@ class Client:
     def __exit__(self, *_):
         self.content.__exit__()
 
-    def build_response(self, request: Request, status_code: int = 200) -> HTMLResponse:
+    def build_response(self, request: Request, status_code: int = 200) -> Response:
         prefix = request.headers.get('X-Forwarded-Prefix', '')
         vue_html, vue_styles, vue_scripts = vue.generate_vue_content()
         elements = json.dumps({id: element.to_dict() for id, element in self.elements.items()})
@@ -99,7 +99,7 @@ class Client:
         }
         create_task(globals.sio.emit('run_javascript', command, room=self.id))
         if not respond:
-            return
+            return None
         deadline = time.time() + timeout
         while request_id not in self.waiting_javascript_commands:
             if time.time() > deadline:

+ 11 - 6
nicegui/element.py

@@ -3,7 +3,7 @@ from __future__ import annotations
 import shlex
 from abc import ABC
 from copy import deepcopy
-from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union
 
 from . import binding, globals
 from .elements.mixins.visibility import Visibility
@@ -25,7 +25,7 @@ class Element(ABC, Visibility):
         self.tag = tag
         self._classes: List[str] = []
         self._style: Dict[str, str] = {}
-        self._props: Dict[str, str] = {}
+        self._props: Dict[str, Any] = {}
         self._event_listeners: List[EventListener] = []
         self._text: str = ''
         self.slots: Dict[str, Slot] = {}
@@ -87,7 +87,7 @@ class Element(ABC, Visibility):
 
     @staticmethod
     def _parse_style(text: Optional[str]) -> Dict[str, str]:
-        return dict((word.strip() for word in part.split(':')) for part in text.strip('; ').split(';')) if text else {}
+        return dict(_split(part, ':') for part in text.strip('; ').split(';')) if text else {}
 
     def style(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None):
         '''CSS style sheet definitions to modify the look of the element.
@@ -107,13 +107,13 @@ class Element(ABC, Visibility):
         return self
 
     @staticmethod
-    def _parse_props(text: Optional[str]) -> Dict[str, str]:
+    def _parse_props(text: Optional[str]) -> Dict[str, Any]:
         if not text:
             return {}
         lexer = shlex.shlex(text, posix=True)
         lexer.whitespace = ' '
         lexer.wordchars += '=-.%:/'
-        return dict(word.split('=', 1) if '=' in word else (word, True) for word in lexer)
+        return dict(_split(word, '=') if '=' in word else (word, True) for word in lexer)
 
     def props(self, add: Optional[str] = None, *, remove: Optional[str] = None):
         '''Quasar props https://quasar.dev/vue-components/button#design to modify the look of the element.
@@ -171,7 +171,7 @@ class Element(ABC, Visibility):
         create_task(globals.sio.emit('update', {'elements': elements}, room=self.client.id))
 
     def run_method(self, name: str, *args: Any) -> None:
-        if globals.loop is None:
+        if not globals.loop:
             return
         data = {'id': self.id, 'name': name, 'args': args}
         create_task(globals.sio.emit('run_method', data, room=self.client.id))
@@ -194,3 +194,8 @@ class Element(ABC, Visibility):
         for slot in self.slots.values():
             slot.children[:] = [e for e in slot.children if e.id != element.id]
         self.update()
+
+
+def _split(text: str, separator: str) -> Tuple[str, str]:
+    words = text.split(separator, 1)
+    return words[0].strip(), words[1].strip()

+ 2 - 2
nicegui/elements/color_input.py

@@ -8,8 +8,8 @@ from .mixins.value_element import ValueElement
 
 class ColorInput(ValueElement):
 
-    def __init__(self, label: str = None, *,
-                 placeholder: str = None, value: str = '', on_change: Optional[Callable] = None) -> None:
+    def __init__(self, label: Optional[str] = None, *,
+                 placeholder: Optional[str] = None, value: str = '', on_change: Optional[Callable] = None) -> None:
         """Color Input
 
         :param label: displayed label for the color input

+ 2 - 2
nicegui/elements/input.py

@@ -5,8 +5,8 @@ from .mixins.value_element import ValueElement
 
 class Input(ValueElement):
 
-    def __init__(self, label: str = None, *,
-                 placeholder: str = None, value: str = '', on_change: Optional[Callable] = None) -> None:
+    def __init__(self, label: Optional[str] = None, *,
+                 placeholder: Optional[str] = None, value: str = '', on_change: Optional[Callable] = None) -> None:
         """Text Input
 
         :param label: displayed label for the text input

+ 2 - 2
nicegui/elements/line_plot.py

@@ -18,8 +18,8 @@ class LinePlot(Plot):
         """
         super().__init__(close=close, **kwargs)
 
-        self.x = []
-        self.Y = [[] for _ in range(n)]
+        self.x: List[float] = []
+        self.Y: List[List[float]] = [[] for _ in range(n)]
         self.lines = [self.fig.gca().plot([], [])[0] for _ in range(n)]
         self.slice = slice(0 if limit is None else -limit, None)
         self.update_every = update_every

+ 3 - 1
nicegui/elements/log.py

@@ -1,3 +1,5 @@
+from typing import Optional
+
 from ..element import Element
 from ..vue import register_component
 
@@ -6,7 +8,7 @@ register_component('log', __file__, 'log.js')
 
 class Log(Element):
 
-    def __init__(self, max_lines: int = None) -> None:
+    def __init__(self, max_lines: Optional[int] = None) -> None:
         """Log view
 
         Create a log view that allows to add new lines without re-transmitting the whole history to the client.

+ 2 - 1
nicegui/elements/menu.py

@@ -37,11 +37,12 @@ class MenuItem(TextElement):
         :param auto_close: whether the menu should be closed after a click event (default: `True`)
         """
         super().__init__(tag='q-item', text=text)
-        self.menu: Menu = globals.get_slot().parent
+        self.menu = globals.get_slot().parent
         self._props['clickable'] = True
 
         def handle_click(_) -> None:
             handle_event(on_click, ClickEventArguments(sender=self, client=self.client))
             if auto_close:
+                assert isinstance(self.menu, Menu)
                 self.menu.close()
         self.on('click', handle_click)

+ 4 - 4
nicegui/elements/number.py

@@ -6,10 +6,10 @@ from .mixins.value_element import ValueElement
 class Number(ValueElement):
 
     def __init__(self,
-                 label: str = None, *,
-                 placeholder: str = None,
-                 value: float = None,
-                 format: str = None,
+                 label: Optional[str] = None, *,
+                 placeholder: Optional[str] = None,
+                 value: Optional[float] = None,
+                 format: Optional[str] = None,
                  on_change: Optional[Callable] = None) -> None:
         """Number Input
 

+ 1 - 1
nicegui/elements/slider.py

@@ -9,7 +9,7 @@ class Slider(ValueElement):
                  min: float,
                  max: float,
                  step: float = 1.0,
-                 value: float = None,
+                 value: Optional[float] = None,
                  on_change: Optional[Callable] = None) -> None:
         """Slider
 

+ 1 - 1
nicegui/error.py

@@ -21,6 +21,6 @@ def error_content(status_code: int, exception: Union[str, Exception] = '') -> No
 
     with ui.column().classes('w-full py-20 items-center gap-0'):
         ui.html((Path(__file__).parent / 'static' / 'sad_face.svg').read_text()).classes('w-32 py-5')
-        ui.label(status_code).classes('text-6xl py-5')
+        ui.label(str(status_code)).classes('text-6xl py-5')
         ui.label(title).classes('text-xl py-5')
         ui.label(message).classes('text-lg text-gray-500')

+ 1 - 1
nicegui/event_listener.py

@@ -4,7 +4,7 @@ from typing import Callable, List
 
 @dataclass
 class EventListener:
-    element_id: str
+    element_id: int
     type: str
     args: List[str]
     handler: Callable

+ 9 - 3
nicegui/events.py

@@ -104,8 +104,13 @@ class KeyboardKey:
     code: str
     location: int
 
-    def __eq__(self, other: str) -> bool:
-        return self.name == other or self.code == other
+    def __eq__(self, other: object) -> bool:
+        if isinstance(other, str):
+            return self.name == other or self.code == other
+        elif isinstance(other, KeyboardKey):
+            return self == other
+        else:
+            return False
 
     def __repr__(self):
         return str(self.name)
@@ -268,6 +273,7 @@ def handle_event(handler: Optional[Callable], arguments: EventArguments) -> None
         if handler is None:
             return
         no_arguments = not signature(handler).parameters
+        assert arguments.sender.parent_slot is not None
         with arguments.sender.parent_slot:
             result = handler() if no_arguments else handler(arguments)
         if is_coroutine(handler):
@@ -277,6 +283,6 @@ def handle_event(handler: Optional[Callable], arguments: EventArguments) -> None
             if globals.loop and globals.loop.is_running():
                 create_task(wait_for_result(), name=str(handler))
             else:
-                on_startup(None, wait_for_result())
+                on_startup(wait_for_result())
     except Exception:
         traceback.print_exc()

+ 4 - 4
nicegui/favicon.py

@@ -1,5 +1,5 @@
 from pathlib import Path
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Optional
 
 from fastapi.responses import FileResponse
 
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
 def create_favicon_routes() -> None:
     fallback = Path(__file__).parent / 'static' / 'favicon.ico'
     for path, favicon in globals.favicons.items():
-        if is_remote_url(favicon):
+        if favicon and is_remote_url(favicon):
             continue
         globals.app.add_route(f'{"" if path == "/" else path}/favicon.ico',
                               lambda _, favicon=favicon or globals.favicon or fallback: FileResponse(favicon))
@@ -20,7 +20,7 @@ def create_favicon_routes() -> None:
 
 def get_favicon_url(page: 'page', prefix: str) -> str:
     favicon = page.favicon or globals.favicon
-    if is_remote_url(favicon):
+    if favicon and is_remote_url(favicon):
         return favicon
     elif not favicon:
         return f'{prefix}/_nicegui/static/favicon.ico'
@@ -31,4 +31,4 @@ def get_favicon_url(page: 'page', prefix: str) -> str:
 
 
 def is_remote_url(favicon: str) -> bool:
-    return favicon and (favicon.startswith('http://') or favicon.startswith('https://'))
+    return favicon.startswith('http://') or favicon.startswith('https://')

+ 2 - 2
nicegui/globals.py

@@ -21,7 +21,7 @@ class State(Enum):
 
 app: FastAPI
 sio: AsyncServer
-server: Optional[Server] = None
+server: Server
 loop: Optional[asyncio.AbstractEventLoop] = None
 log: logging.Logger = logging.getLogger('nicegui')
 state: State = State.STOPPED
@@ -38,7 +38,7 @@ socket_io_js_extra_headers: Dict = {}
 
 slot_stacks: Dict[int, List['Slot']] = {}
 clients: Dict[str, 'Client'] = {}
-index_client: 'Client' = ...
+index_client: 'Client'
 
 page_routes: Dict[Callable, str] = {}
 favicons: Dict[str, Optional[str]] = {}

+ 0 - 14
nicegui/helpers.py

@@ -1,25 +1,11 @@
 import asyncio
 import functools
-import inspect
-import time
 from typing import Any, Awaitable, Callable, Union
 
 from . import globals
 from .task_logger import create_task
 
 
-def measure(*, reset: bool = False, ms: bool = False) -> None:
-    global t
-    if 't' in globals() and not reset:
-        dt = time.time() - t
-        line = inspect.stack()[1][0].f_lineno
-        output = f'{dt * 1000:7.3f} ms' if ms else f'{dt:7.3f} s'
-        print(f'{inspect.stack()[1].filename}:{line}', output, flush=True)
-    if reset:
-        print('------------', flush=True)
-    t = time.time()
-
-
 def is_coroutine(object: Any) -> bool:
     while isinstance(object, functools.partial):
         object = object.func

+ 17 - 12
nicegui/nicegui.py

@@ -6,7 +6,7 @@ from typing import Dict, Optional
 
 from fastapi import FastAPI, HTTPException, Request
 from fastapi.middleware.gzip import GZipMiddleware
-from fastapi.responses import FileResponse
+from fastapi.responses import FileResponse, Response
 from fastapi.staticfiles import StaticFiles
 from fastapi_socketio import SocketManager
 
@@ -29,19 +29,19 @@ globals.index_client = Client(page('/'), shared=True).__enter__()
 
 
 @app.get('/')
-def index(request: Request) -> str:
+def index(request: Request) -> Response:
     return globals.index_client.build_response(request)
 
 
 @app.get('/_nicegui/dependencies/{id}/{name}')
-def vue_dependencies(id: int, name: str):
+def get_dependencies(id: int, name: str):
     if id in vue.js_dependencies and vue.js_dependencies[id].path.exists():
         return FileResponse(vue.js_dependencies[id].path, media_type='text/javascript')
     raise HTTPException(status_code=404, detail=f'dependency "{name}" with ID {id} not found')
 
 
 @app.get('/_nicegui/components/{name}')
-def vue_dependencies(name: str):
+def get_components(name: str):
     return FileResponse(vue.js_components[name].path, media_type='text/javascript')
 
 
@@ -50,7 +50,8 @@ def handle_startup(with_welcome_message: bool = True) -> None:
     globals.state = globals.State.STARTING
     globals.loop = asyncio.get_running_loop()
     create_favicon_routes()
-    [safe_invoke(t) for t in globals.startup_handlers]
+    for t in globals.startup_handlers:
+        safe_invoke(t)
     create_task(binding.loop())
     create_task(prune_clients())
     globals.state = globals.State.STARTED
@@ -61,13 +62,15 @@ def handle_startup(with_welcome_message: bool = True) -> None:
 @app.on_event('shutdown')
 def handle_shutdown() -> None:
     globals.state = globals.State.STOPPING
-    [safe_invoke(t) for t in globals.shutdown_handlers]
-    [t.cancel() for t in globals.tasks]
+    for t in globals.shutdown_handlers:
+        safe_invoke(t)
+    for t in globals.tasks:
+        t.cancel()
     globals.state = globals.State.STOPPED
 
 
 @app.exception_handler(404)
-async def exception_handler(request: Request, exception: Exception):
+async def exception_handler_404(request: Request, exception: Exception) -> Response:
     globals.log.warning(f'{request.url} not found')
     with Client(page('')) as client:
         error_content(404, exception)
@@ -75,7 +78,7 @@ async def exception_handler(request: Request, exception: Exception):
 
 
 @app.exception_handler(Exception)
-async def exception_handler(request: Request, exception: Exception):
+async def exception_handler_500(request: Request, exception: Exception) -> Response:
     globals.log.exception(exception)
     with Client(page('')) as client:
         error_content(500, exception)
@@ -90,7 +93,8 @@ async def handle_handshake(sid: str) -> bool:
     client.environ = sio.get_environ(sid)
     sio.enter_room(sid, client.id)
     with client:
-        [safe_invoke(t) for t in client.connect_handlers]
+        for t in client.connect_handlers:
+            safe_invoke(t)
     return True
 
 
@@ -102,7 +106,8 @@ async def handle_disconnect(sid: str) -> None:
     if not client.shared:
         delete_client(client.id)
     with client:
-        [safe_invoke(t) for t in client.disconnect_handlers]
+        for t in client.disconnect_handlers:
+            safe_invoke(t)
 
 
 @sio.on('event')
@@ -117,7 +122,7 @@ def handle_event(sid: str, msg: Dict) -> None:
 
 
 @sio.on('javascript_response')
-def handle_event(sid: str, msg: Dict) -> None:
+def handle_javascript_response(sid: str, msg: Dict) -> None:
     client = get_client(sid)
     if not client:
         return

+ 3 - 3
nicegui/page.py

@@ -42,11 +42,11 @@ class page:
         return self.title if self.title is not None else globals.title
 
     def resolve_dark(self) -> Optional[bool]:
-        return str(self.dark if self.dark is not ... else globals.dark)
+        return self.dark if self.dark is not ... else globals.dark
 
     def __call__(self, func: Callable) -> Callable:
         # NOTE we need to remove existing routes for this path to make sure only the latest definition is used
-        globals.app.routes[:] = [r for r in globals.app.routes if r.path != self.path]
+        globals.app.routes[:] = [r for r in globals.app.routes if getattr(r, 'path', None) != self.path]
         parameters_of_decorated_func = list(inspect.signature(func).parameters.keys())
 
         async def decorated(*dec_args, **dec_kwargs) -> Response:
@@ -58,7 +58,7 @@ class page:
                     dec_kwargs['client'] = client
                 result = func(*dec_args, **dec_kwargs)
             if inspect.isawaitable(result):
-                async def wait_for_result() -> Response:
+                async def wait_for_result() -> None:
                     with client:
                         await AsyncUpdater(result)
                 task = create_task(wait_for_result())

+ 3 - 3
nicegui/task_logger.py

@@ -23,16 +23,16 @@ def create_task(
     using the provided ``logger``, with additional context provided by ``message`` and optionally
     ``message_args``.
     '''
-
     logger = logging.getLogger(__name__)
     message = 'Task raised an exception'
     message_args = ()
     if loop is None:
         loop = globals.loop
+        assert loop is not None
     if sys.version_info[1] < 8:
-        task = loop.create_task(coroutine)  # name parameter is only supported from 3.8 onward
+        task: asyncio.Task[T] = loop.create_task(coroutine)  # name parameter is only supported from 3.8 onward
     else:
-        task = loop.create_task(coroutine, name=name)
+        task: asyncio.Task[T] = loop.create_task(coroutine, name=name)
     task.add_done_callback(
         functools.partial(_handle_task_result, logger=logger, message=message, message_args=message_args)
     )

+ 1 - 1
nicegui/vue.py

@@ -46,7 +46,7 @@ def register_component(name: str, py_filepath: str, component_filepath: str, dep
         js_dependencies[id].dependents.add(name)
 
 
-def generate_vue_content() -> Tuple[str]:
+def generate_vue_content() -> Tuple[str, str, str]:
     builds = [
         vbuild.VBuild(name, component.path.read_text())
         for name, component in vue_components.items()