浏览代码

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

Falko Schindler 2 年之前
父节点
当前提交
b710c4e7ea
共有 3 个文件被更改,包括 57 次插入18 次删除
  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 addict import Dict as AdDict
 from pygments.formatters import HtmlFormatter
 from pygments.formatters import HtmlFormatter
 from starlette.requests import Request
 from starlette.requests import Request
+from starlette.routing import compile_path
 
 
 from . import globals
 from . import globals
 from .helpers import is_coroutine
 from .helpers import is_coroutine
 from .page_builder import PageBuilder
 from .page_builder import PageBuilder
+from .routes import convert_arguments
 
 
 
 
 class Page(jp.QuasarPage):
 class Page(jp.QuasarPage):
@@ -181,8 +183,9 @@ class page:
         self.on_disconnect = on_disconnect
         self.on_disconnect = on_disconnect
         self.shared = shared
         self.shared = shared
         self.page: Optional[Page] = None
         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)
         @wraps(func)
         async def decorated(request: Optional[Request] = None) -> Page:
         async def decorated(request: Optional[Request] = None) -> Page:
             self.page = Page(
             self.page = Page(
@@ -201,10 +204,10 @@ class page:
                     if self.shared:
                     if self.shared:
                         globals.log.error('Cannot use `request` argument in shared page')
                         globals.log.error('Cannot use `request` argument in shared page')
                         return error(501)
                         return error(501)
-                    kwargs['request'] = request
                 await self.connected(request)
                 await self.connected(request)
                 await self.header()
                 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 isinstance(result, types.GeneratorType):
                     if self.shared:
                     if self.shared:
                         globals.log.error('Yielding for page_ready is not supported on shared pages')
                         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 inspect
 import os.path
 import os.path
 from functools import wraps
 from functools import wraps
-from typing import List
+from typing import Any, Callable, Dict, List
 
 
 import justpy as jp
 import justpy as jp
 from starlette.requests import Request
 from starlette.requests import Request
 from starlette.responses import FileResponse, PlainTextResponse
 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 starlette.staticfiles import StaticFiles
 
 
 from . import globals
 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)))
     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):
 def get(self, path: str):
     """GET Decorator
     """GET Decorator
 
 
@@ -54,19 +71,7 @@ def get(self, path: str):
     def decorator(func):
     def decorator(func):
         @wraps(func)
         @wraps(func)
         async def decorated(request: Request):
         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)
             return await func(**args) if is_coroutine(func) else func(**args)
         self.add_route(Route(path, decorated))
         self.add_route(Route(path, decorated))
         return 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')