浏览代码

use fastapi exception_handler

Falko Schindler 2 年之前
父节点
当前提交
e9a18bcd49
共有 4 个文件被更改,包括 55 次插入62 次删除
  1. 25 1
      nicegui/client.py
  2. 9 3
      nicegui/nicegui.py
  3. 19 51
      nicegui/page.py
  4. 2 7
      tests/test_page.py

+ 25 - 1
nicegui/client.py

@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
 
 
 from fastapi.responses import HTMLResponse
 from fastapi.responses import HTMLResponse
 
 
-from . import globals, vue
+from . import globals, ui, vue
 from .element import Element
 from .element import Element
 from .favicon import get_favicon_url
 from .favicon import get_favicon_url
 from .slot import Slot
 from .slot import Slot
@@ -104,3 +104,27 @@ class Client:
     def open(self, target: Union[Callable, str]) -> None:
     def open(self, target: Union[Callable, str]) -> None:
         path = target if isinstance(target, str) else globals.page_routes[target]
         path = target if isinstance(target, str) else globals.page_routes[target]
         create_task(globals.sio.emit('open', path, room=str(self.id)))
         create_task(globals.sio.emit('open', path, room=str(self.id)))
+
+
+class ErrorClient(Client):
+
+    def __init__(self, page: 'page') -> None:
+        super().__init__(page)
+        with self:
+            with ui.column().classes('w-full py-20 items-center gap-0'):
+                ui.icon('☹').classes('text-8xl py-5') \
+                    .style('font-family: "Arial Unicode MS", "Times New Roman", Times, serif;')
+                self.status_code = ui.label().classes('text-6xl py-5')
+                self.title = ui.label().classes('text-xl py-5')
+                self.message = ui.label().classes('text-lg py-2 text-gray-500')
+
+    def build_response(self, status_code: int, message: str = '') -> HTMLResponse:
+        self.status_code.text = status_code
+        if 400 <= status_code <= 499:
+            self.title.text = "This page doesn't exist"
+        elif 500 <= status_code <= 599:
+            self.title.text = 'Server error'
+        else:
+            self.title.text = 'Unknown error'
+        self.message.text = message
+        return super().build_response()

+ 9 - 3
nicegui/nicegui.py

@@ -3,14 +3,14 @@ import urllib.parse
 from pathlib import Path
 from pathlib import Path
 from typing import Dict
 from typing import Dict
 
 
-from fastapi import FastAPI
+from fastapi import FastAPI, Request
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.responses import FileResponse
 from fastapi.responses import FileResponse
 from fastapi.staticfiles import StaticFiles
 from fastapi.staticfiles import StaticFiles
 from fastapi_socketio import SocketManager
 from fastapi_socketio import SocketManager
 
 
 from . import binding, globals, vue
 from . import binding, globals, vue
-from .client import Client
+from .client import Client, ErrorClient
 from .favicon import create_favicon_routes
 from .favicon import create_favicon_routes
 from .helpers import safe_invoke
 from .helpers import safe_invoke
 from .page import page
 from .page import page
@@ -22,7 +22,8 @@ globals.sio = sio = SocketManager(app=app)._sio
 app.add_middleware(GZipMiddleware)
 app.add_middleware(GZipMiddleware)
 app.mount("/static", StaticFiles(directory=Path(__file__).parent / 'static'), name='static')
 app.mount("/static", StaticFiles(directory=Path(__file__).parent / 'static'), name='static')
 
 
-Client(page('/')).__enter__()
+index_client = Client(page('/')).__enter__()
+error_client = ErrorClient(page(''))
 
 
 
 
 @app.get('/')
 @app.get('/')
@@ -59,6 +60,11 @@ def shutdown() -> None:
     globals.state = globals.State.STOPPED
     globals.state = globals.State.STOPPED
 
 
 
 
+@app.exception_handler(Exception)
+async def exception_handler(_: Request, exc: Exception):
+    return error_client.build_response(500, str(exc))
+
+
 @sio.on('connect')
 @sio.on('connect')
 async def handle_connect(sid: str, _) -> None:
 async def handle_connect(sid: str, _) -> None:
     client = get_client(sid)
     client = get_client(sid)

+ 19 - 51
nicegui/page.py

@@ -4,9 +4,8 @@ import time
 from typing import Callable, Optional
 from typing import Callable, Optional
 
 
 from fastapi import Response
 from fastapi import Response
-from fastapi.responses import HTMLResponse
 
 
-from . import globals, ui
+from . import globals
 from .client import Client
 from .client import Client
 from .task_logger import create_task
 from .task_logger import create_task
 
 
@@ -47,28 +46,24 @@ class page:
         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 r.path != self.path]
 
 
         async def decorated(*dec_args, **dec_kwargs) -> Response:
         async def decorated(*dec_args, **dec_kwargs) -> Response:
-            try:
-                with Client(self) as client:
-                    if any(p.name == 'client' for p in inspect.signature(func).parameters.values()):
-                        dec_kwargs['client'] = client
-                    result = func(*dec_args, **dec_kwargs)
-                if inspect.isawaitable(result):
-                    async def wait_for_result() -> Response:
-                        with client:
-                            await result
-                    task = create_task(wait_for_result())
-                    deadline = time.time() + self.response_timeout
-                    while task and not client.is_waiting_for_handshake and not task.done():
-                        if time.time() > deadline:
-                            raise TimeoutError(f'Response not ready after {self.response_timeout} seconds')
-                        await asyncio.sleep(0.1)
-                    result = task.result() if task.done() else None
-                if isinstance(result, Response):  # NOTE if setup returns a response, we don't need to render the page
-                    return result
-                return client.build_response()
-            except Exception as e:
-                globals.log.exception(e)
-                return error_client.build_response(500, str(e))
+            with Client(self) as client:
+                if any(p.name == 'client' for p in inspect.signature(func).parameters.values()):
+                    dec_kwargs['client'] = client
+                result = func(*dec_args, **dec_kwargs)
+            if inspect.isawaitable(result):
+                async def wait_for_result() -> Response:
+                    with client:
+                        await result
+                task = create_task(wait_for_result())
+                deadline = time.time() + self.response_timeout
+                while task and not client.is_waiting_for_handshake and not task.done():
+                    if time.time() > deadline:
+                        raise TimeoutError(f'Response not ready after {self.response_timeout} seconds')
+                    await asyncio.sleep(0.1)
+                result = task.result() if task.done() else None
+            if isinstance(result, Response):  # NOTE if setup returns a response, we don't need to render the page
+                return result
+            return client.build_response()
 
 
         parameters = [p for p in inspect.signature(func).parameters.values() if p.name != 'client']
         parameters = [p for p in inspect.signature(func).parameters.values() if p.name != 'client']
         decorated.__signature__ = inspect.Signature(parameters)
         decorated.__signature__ = inspect.Signature(parameters)
@@ -76,30 +71,3 @@ class page:
         globals.page_routes[decorated] = self.path
         globals.page_routes[decorated] = self.path
 
 
         return globals.app.get(self.path)(decorated)
         return globals.app.get(self.path)(decorated)
-
-
-class ErrorClient(Client):
-
-    def __init__(self) -> None:
-        super().__init__(page(''))
-        with self:
-            with ui.column().classes('w-full py-20 items-center gap-0'):
-                ui.icon('☹').classes('text-8xl py-5') \
-                    .style('font-family: "Arial Unicode MS", "Times New Roman", Times, serif;')
-                self.status_code = ui.label().classes('text-6xl py-5')
-                self.title = ui.label().classes('text-xl py-5')
-                self.message = ui.label().classes('text-lg py-2 text-gray-500')
-
-    def build_response(self, status_code: int, message: str = '') -> HTMLResponse:
-        self.status_code.text = status_code
-        if 400 <= status_code <= 499:
-            self.title.text = "This page doesn't exist"
-        elif 500 <= status_code <= 599:
-            self.title.text = 'Server error'
-        else:
-            self.title.text = 'Unknown error'
-        self.message.text = message
-        return super().build_response()
-
-
-error_client = ErrorClient()

+ 2 - 7
tests/test_page.py

@@ -1,11 +1,6 @@
 import asyncio
 import asyncio
-from time import time
-from typing import Generator
 from uuid import uuid4
 from uuid import uuid4
 
 
-import justpy.htmlcomponents
-from starlette.requests import Request
-
 from nicegui import Client, task_logger, ui
 from nicegui import Client, task_logger, ui
 
 
 from .screen import Screen
 from .screen import Screen
@@ -144,7 +139,7 @@ def test_exception(screen: Screen):
     screen.open('/')
     screen.open('/')
     screen.should_contain('500')
     screen.should_contain('500')
     screen.should_contain('Server error')
     screen.should_contain('Server error')
-    screen.assert_py_logger('ERROR', 'some exception')
+    screen.assert_py_logger('ERROR', 'Exception in ASGI application\n')
 
 
 
 
 def test_exception_after_handshake(screen: Screen):
 def test_exception_after_handshake(screen: Screen):
@@ -156,7 +151,7 @@ def test_exception_after_handshake(screen: Screen):
 
 
     screen.open('/')
     screen.open('/')
     screen.should_contain('this is shown')
     screen.should_contain('this is shown')
-    screen.assert_py_logger('ERROR', 'Failed to execute page-ready')
+    screen.assert_py_logger('ERROR', 'Task raised an exception')
 
 
 
 
 def test_page_with_args(screen: Screen):
 def test_page_with_args(screen: Screen):