Browse Source

forbid yield on shared pages

Falko Schindler 2 years ago
parent
commit
1ba3a11f3b
4 changed files with 40 additions and 16 deletions
  1. 5 5
      api_docs_and_examples.py
  2. 21 9
      nicegui/page.py
  3. 2 1
      tests/screen.py
  4. 12 1
      tests/test_pages.py

+ 5 - 5
api_docs_and_examples.py

@@ -581,24 +581,24 @@ To make it "private" or to change other attributes like title, favicon etc. you
         ui.link('private page', private_page)
         ui.link('shared page', shared_page)
 
-    yield_page_ready = '''#### Yield for Page Ready
+    yield_page_ready = '''#### Yielding for Page-Ready
 
-This is an handy alternative to using the `on_page_ready` callback (either as parameter of `@ui.page` or via `ui.on_page_ready` function).
+This is a handy alternative to the `on_page_ready` callback (either as parameter of `@ui.page` or via `ui.on_page_ready` function).
 
 If a `yield` statement is provided in a page builder function, all code below that statement is executed after the page is ready.
 This allows you to execute JavaScript; which is only possible after the page has been loaded (see [#112](https://github.com/zauberzeug/nicegui/issues/112)).
-Also it's possible to do async stuff while the user alreay sees the content added before the yield statement.
+Also it is possible to do async stuff while the user already sees the content added before the yield statement.
     '''
     with example(yield_page_ready):
         @ui.page('/yield_page_ready')
         async def yield_page_ready():
             ui.label('This text is displayed immediately.')
             yield
-            ui.run_javascript('document.title = "JavaScript Controlled Title")')
+            ui.run_javascript('document.title = "JavaScript-Controlled Title")')
             await asyncio.sleep(3)
             ui.label('This text is displayed 3 seconds after the page has been fully loaded.')
 
-        ui.link('show page ready code after yield', '/yield_page_ready')
+        ui.link('show page-ready code after yield', '/yield_page_ready')
 
     with example(ui.open):
         @ui.page('/yet_another_page')

+ 21 - 9
nicegui/page.py

@@ -6,7 +6,7 @@ import time
 import types
 import uuid
 from functools import wraps
-from typing import Callable, Dict, Optional
+from typing import Callable, Dict, Generator, Optional
 
 import justpy as jp
 from addict import Dict as AdDict
@@ -46,7 +46,7 @@ class Page(jp.QuasarPage):
         self.css = css
         self.connect_handler = on_connect
         self.page_ready_handler = on_page_ready
-        self.page_ready_generator: types.GeneratorType = None
+        self.page_ready_generator: Optional[Generator[None, None, None]] = None
         self.disconnect_handler = on_disconnect
         self.delete_flag = not shared
 
@@ -199,15 +199,21 @@ class page:
             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()
+                        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)
                 if isinstance(result, types.GeneratorType):
+                    if self.shared:
+                        globals.log.error('Yielding for page_ready is not supported on shared pages')
+                        return error(501)
                     next(result)
                 if isinstance(result, types.AsyncGeneratorType):
+                    if self.shared:
+                        globals.log.error('Yielding for page_ready is not supported on shared pages')
+                        return error(501)
                     await result.__anext__()
                 self.page.page_ready_generator = result
                 await self.footer()
@@ -239,16 +245,22 @@ def find_parent_view() -> jp.HTMLBaseComponent:
     return view_stack[-1]
 
 
-def error404() -> jp.QuasarPage:
-    title = globals.config.title if globals.config else '404'
+def error(status_code: int) -> jp.QuasarPage:
+    title = globals.config.title if globals.config else f'Error {status_code}'
     favicon = globals.config.favicon if globals.config else None
     dark = globals.config.dark if globals.config else False
     wp = jp.QuasarPage(title=title, favicon=favicon, dark=dark, tailwind=True)
     div = jp.Div(a=wp, classes='py-20 text-center')
     jp.Div(a=div, classes='text-8xl py-5', text='☹',
            style='font-family: "Arial Unicode MS", "Times New Roman", Times, serif;')
-    jp.Div(a=div, classes='text-6xl py-5', text='404')
-    jp.Div(a=div, classes='text-xl py-5', text='This page doesn\'t exist.')
+    jp.Div(a=div, classes='text-6xl py-5', text=status_code)
+    if 400 <= status_code <= 499:
+        message = "This page doesn't exist"
+    elif 500 <= status_code <= 599:
+        message = 'Server error'
+    else:
+        message = 'Unknown error'
+    jp.Div(a=div, classes='text-xl py-5', text=message)
     return wp
 
 
@@ -265,6 +277,6 @@ def init_auto_index_page() -> None:
 
 
 def create_page_routes() -> None:
-    jp.Route("/{path:path}", error404, last=True)
+    jp.Route("/{path:path}", lambda: error(404), last=True)
     for route, page_builder in globals.page_builders.items():
         page_builder.create_route(route)

+ 2 - 1
tests/screen.py

@@ -20,6 +20,7 @@ IGNORED_CLASSES = ['row', 'column', 'q-card', 'q-field', 'q-field__label', 'q-in
 
 class Screen:
     SCREENSHOT_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'screenshots')
+    UI_RUN_KWARGS = {'port': PORT, 'show': False, 'reload': False}
 
     def __init__(self, selenium: webdriver.Chrome) -> None:
         self.selenium = selenium
@@ -27,7 +28,7 @@ class Screen:
 
     def start_server(self) -> None:
         '''Start the webserver in a separate thread. This is the equivalent of `ui.run()` in a normal script.'''
-        self.server_thread = threading.Thread(target=ui.run, kwargs={'port': PORT, 'show': False, 'reload': False})
+        self.server_thread = threading.Thread(target=ui.run, kwargs=self.UI_RUN_KWARGS)
         self.server_thread.start()
 
     def stop_server(self) -> None:

+ 12 - 1
tests/test_pages.py

@@ -176,7 +176,8 @@ def test_shared_page_with_request_parameter_raises_exception(screen: Screen):
         ui.label('Hello, world!')
 
     screen.open('/')
-    screen.should_contain("This page doesn't exist")
+    screen.should_contain('501')
+    screen.should_contain('Server error')
 
 
 def test_adding_elements_in_on_page_ready_event(screen: Screen):
@@ -213,3 +214,13 @@ def test_pageready_after_yield_on_non_async_page(screen: Screen):
     screen.open('/')
     timing = screen.find('loading page took')
     assert 0 < float(timing.text.split()[-2]) < 1
+
+
+def test_pageready_after_yield_on_shared_page_raises_exception(screen: Screen):
+    @ui.page('/', shared=True)
+    def page():
+        yield
+
+    screen.open('/')
+    screen.should_contain('501')
+    screen.should_contain('Server error')