Selaa lähdekoodia

allow declaring path parameters for ui.page like with ui.get

Falko Schindler 2 vuotta sitten
vanhempi
säilyke
b710c4e7ea
3 muutettua tiedostoa jossa 57 lisäystä ja 18 poistoa
  1. 6 3
      nicegui/page.py
  2. 20 15
      nicegui/routes.py
  3. 31 0
      tests/test_routes.py

+ 6 - 3
nicegui/page.py

@@ -12,10 +12,12 @@ import justpy as jp
 from addict import Dict as AdDict
 from pygments.formatters import HtmlFormatter
 from starlette.requests import Request
+from starlette.routing import compile_path
 
 from . import globals
 from .helpers import is_coroutine
 from .page_builder import PageBuilder
+from .routes import convert_arguments
 
 
 class Page(jp.QuasarPage):
@@ -181,8 +183,9 @@ class page:
         self.on_disconnect = on_disconnect
         self.shared = shared
         self.page: Optional[Page] = None
+        *_, self.converters = compile_path(route)
 
-    def __call__(self, func, *args, **kwargs) -> Callable:
+    def __call__(self, func, **args) -> Callable:
         @wraps(func)
         async def decorated(request: Optional[Request] = None) -> Page:
             self.page = Page(
@@ -201,10 +204,10 @@ class page:
                     if self.shared:
                         globals.log.error('Cannot use `request` argument in shared page')
                         return error(501)
-                    kwargs['request'] = request
                 await self.connected(request)
                 await self.header()
-                result = await func(*args, **kwargs) if is_coroutine(func) else func(*args, **kwargs)
+                args = convert_arguments(request, self.converters, func)
+                result = await func(**args) if is_coroutine(func) else func(**args)
                 if isinstance(result, types.GeneratorType):
                     if self.shared:
                         globals.log.error('Yielding for page_ready is not supported on shared pages')

+ 20 - 15
nicegui/routes.py

@@ -1,12 +1,12 @@
 import inspect
 import os.path
 from functools import wraps
-from typing import List
+from typing import Any, Callable, Dict, List
 
 import justpy as jp
 from starlette.requests import Request
 from starlette.responses import FileResponse, PlainTextResponse
-from starlette.routing import BaseRoute, Mount, Route, compile_path
+from starlette.routing import BaseRoute, Convertor, Mount, Route, compile_path
 from starlette.staticfiles import StaticFiles
 
 from . import globals
@@ -38,6 +38,23 @@ def add_static_files(self, path: str, directory: str) -> None:
     add_route(None, Mount(path, app=StaticFiles(directory=directory)))
 
 
+def convert_arguments(request: Request, converters: Dict[str, Convertor], func: Callable) -> Dict[str, Any]:
+    args = {name: converter.convert(request.path_params.get(name)) for name, converter in converters.items()}
+    parameters = inspect.signature(func).parameters
+    for key in parameters:
+        if parameters[key].annotation.__name__ == 'bool':
+            args[key] = bool(args[key])
+        if parameters[key].annotation.__name__ == 'int':
+            args[key] = int(args[key])
+        elif parameters[key].annotation.__name__ == 'float':
+            args[key] = float(args[key])
+        elif parameters[key].annotation.__name__ == 'complex':
+            args[key] = complex(args[key])
+    if 'request' in parameters and 'request' not in args:
+        args['request'] = request
+    return args
+
+
 def get(self, path: str):
     """GET Decorator
 
@@ -54,19 +71,7 @@ def get(self, path: str):
     def decorator(func):
         @wraps(func)
         async def decorated(request: Request):
-            args = {name: converter.convert(request.path_params.get(name)) for name, converter in converters.items()}
-            parameters = inspect.signature(func).parameters
-            for key in parameters:
-                if parameters[key].annotation.__name__ == 'bool':
-                    args[key] = bool(args[key])
-                if parameters[key].annotation.__name__ == 'int':
-                    args[key] = int(args[key])
-                elif parameters[key].annotation.__name__ == 'float':
-                    args[key] = float(args[key])
-                elif parameters[key].annotation.__name__ == 'complex':
-                    args[key] = complex(args[key])
-            if 'request' in parameters and 'request' not in args:
-                args['request'] = request
+            args = convert_arguments(request, converters, func)
             return await func(**args) if is_coroutine(func) else func(**args)
         self.add_route(Route(path, decorated))
         return decorated

+ 31 - 0
tests/test_routes.py

@@ -0,0 +1,31 @@
+from nicegui import ui
+from starlette import responses
+
+from .screen import Screen
+
+
+def test_get(screen: Screen):
+    @ui.get('/some/route')
+    def some_route():
+        return responses.PlainTextResponse('Ok')
+
+    screen.open('/some/route')
+    screen.should_contain('Ok')
+
+
+def test_get_with_args(screen: Screen):
+    @ui.get('/route/{id}')
+    def route(id: int):
+        return responses.PlainTextResponse(f'id={id}')
+
+    screen.open('/route/42')
+    screen.should_contain('id=42')
+
+
+def test_page_with_args(screen: Screen):
+    @ui.page('/page/{id}')
+    def page(id: int):
+        ui.label(f'Page {id}')
+
+    screen.open('/page/42')
+    screen.should_contain('Page 42')