Browse Source

Passing request to page builder function.
Demonstrated with new authentication example.

Rodja Trappe 2 years ago
parent
commit
de0328b3fc

+ 37 - 0
examples/authentication/main.py

@@ -0,0 +1,37 @@
+
+import starlette
+from nicegui import ui
+
+from session_info import SessionInfo
+
+session_infos = SessionInfo()
+
+
+def build_content(username: str) -> None:
+    with ui.row().classes('flex justify-center w-full mt-20'):
+        ui.label(f'Hello {username}!').classes('text-2xl')
+
+
+def build_login_form() -> None:
+    def on_login(username: str, password: str, socket: starlette.websockets.WebSocket):
+        session_id = socket.cookies['jp_token'].split('.')[0]
+        if (username == 'user1' and password == 'pass1') or (username == 'user2' and password == 'pass2'):
+            session_infos[session_id] = {'authenticated': True, 'user': username}
+            ui.open('/', socket)
+
+    with ui.row().classes('flex justify-center w-full mt-20'):
+        with ui.card():
+            username = ui.input('User Name')
+            password = ui.input('Password').classes('w-full').props('type=password')
+            ui.button('Log in', on_click=lambda e: on_login(username.value, password.value, e.socket))
+
+
+@ui.page('/')
+def main_page(request: starlette.requests.Request):
+    if session_infos[request.session_id].get('authenticated', False):
+        build_content(session_infos[request.session_id]["user"])
+    else:
+        build_login_form()
+
+
+ui.run()

+ 13 - 0
examples/authentication/session_info.py

@@ -0,0 +1,13 @@
+from collections import UserDict
+
+
+# in reality we would load/save session info to DB
+class SessionInfo(UserDict):
+
+    def __getitem__(self, item):
+        if item not in self.data:
+            self.data[item] = {}
+        return super().__getitem__(item)
+
+    def __setitem__(self, key, value):
+        return super().__setitem__(key, value)

+ 12 - 3
nicegui/page.py

@@ -8,9 +8,9 @@ from functools import wraps
 from typing import Callable, Optional
 
 import justpy as jp
+import starlette
 from addict import Dict
 from pygments.formatters import HtmlFormatter
-from starlette.requests import Request
 
 from . import globals
 from .helpers import is_coroutine
@@ -55,7 +55,7 @@ class Page(jp.QuasarPage):
         self.view = jp.Div(a=self, classes=classes, style='row-gap: 1em', temp=False)
         self.view.add_page(self)
 
-    async def _route_function(self, request: Request):
+    async def _route_function(self, request: starlette.requests.Request) -> Page:
         for handler in globals.connect_handlers + ([self.connect_handler] if self.connect_handler else []):
             arg_count = len(inspect.signature(handler).parameters)
             is_coro = is_coroutine(handler)
@@ -171,7 +171,7 @@ class page:
     def __call__(self, func, *args, **kwargs):
 
         @wraps(func)
-        async def decorated():
+        async def decorated(request: starlette.requests.Request = None):
             self.page = Page(
                 title=self.title,
                 favicon=self.favicon,
@@ -184,6 +184,12 @@ class page:
                 shared=self.shared,
             )
             with globals.within_view(self.page.view):
+                if 'request' in inspect.signature(func).parameters:
+                    if self.shared:
+                        globals.log.error('cannot use `request` argument in shared page; providing 404 error page')
+                        return error404()
+                    kwargs['request'] = request
+                await self.connected(request)
                 await self.header()
                 await func(*args, **kwargs) if is_coroutine(func) else func(*args, **kwargs)
                 await self.footer()
@@ -194,6 +200,9 @@ class page:
         globals.page_builders[self.route] = builder
         return decorated
 
+    async def connected(self, request: starlette.requests.Request):
+        pass
+
     async def header(self):
         pass
 

+ 7 - 1
nicegui/page_builder.py

@@ -1,3 +1,4 @@
+import asyncio
 from dataclasses import dataclass
 from typing import TYPE_CHECKING, Awaitable, Callable, Optional
 
@@ -22,7 +23,12 @@ class PageBuilder:
         self._shared_page = await self.function()
 
     async def route_function(self, request: Request) -> 'Page':
-        page = self._shared_page if self.shared else await self.function()
+        if self.shared:
+            while self._shared_page is None:
+                await asyncio.sleep(0.05)
+            return self._shared_page
+        else:
+            page = await self.function(request)
         return await page._route_function(request)
 
     def create_route(self, route: str) -> None:

+ 18 - 2
tests/test_pages.py

@@ -3,9 +3,11 @@ from tkinter import N
 from uuid import uuid4
 
 import justpy.htmlcomponents
+import pytest
+import starlette
 from nicegui import task_logger, ui
 
-from .screen import Screen
+from .screen import PORT, Screen
 
 
 def test_page(screen: Screen):
@@ -142,6 +144,11 @@ def test_customised_page(screen: Screen):
             super().__init__(route, title='My Customized Page', **kwargs)
             trace.append('init')
 
+        async def connected(self, request):
+            await super().connected(request)
+            assert isinstance(request, starlette.requests.Request)
+            trace.append('connected')
+
         async def header(self) -> None:
             assert isinstance(self.page.view, justpy.htmlcomponents.Div), \
                 'we should be able to access the underlying justpy view'
@@ -161,4 +168,13 @@ def test_customised_page(screen: Screen):
     screen.should_contain('Hello, world!')
     screen.should_contain('My Customized Page')
     assert 'body--dark' in screen.get_tags('body')[0].get_attribute('class')
-    assert trace == ['init', 'header', 'content', 'footer']
+    assert trace == ['init', 'connected', 'header', 'content', 'footer']
+
+
+def test_shared_page_with_request_parameter_raises_exception(screen: Screen):
+    @ui.page('/', shared=True)
+    def page(request):
+        ui.label('Hello, world!')
+
+    screen.open('/')
+    screen.should_contain("This page doesn't exist")