Bläddra i källkod

Merge pull request #18 from zauberzeug/feature/sessions

Provides callback to handle session specific details
Falko Schindler 3 år sedan
förälder
incheckning
4f09477b97
5 ändrade filer med 51 tillägg och 7 borttagningar
  1. 1 0
      README.md
  2. 24 1
      main.py
  3. 5 3
      nicegui/config.py
  4. 19 2
      nicegui/elements/page.py
  5. 2 1
      nicegui/run.py

+ 1 - 0
README.md

@@ -69,6 +69,7 @@ You can call `ui.run()` with optional arguments for some high-level configuratio
 - `dark`: whether to use Quasar's dark mode (default: `False`, use `None` for "auto" mode)
 - `dark`: whether to use Quasar's dark mode (default: `False`, use `None` for "auto" mode)
 - `reload`: automatically reload the ui on file changes (default: `True`)
 - `reload`: automatically reload the ui on file changes (default: `True`)
 - `show`: automatically open the ui in a browser tab (default: `True`)
 - `show`: automatically open the ui in a browser tab (default: `True`)
+- `on_connect`: default function or coroutine which is called for each new client connection; the optional `request` argument provides session infos
 - `uvicorn_logging_level`: logging level for uvicorn server (default: `'warning'`)
 - `uvicorn_logging_level`: logging level for uvicorn server (default: `'warning'`)
 - `main_page_classes`: configure Quasar classes of main page (default: `q-ma-md column items-start`)
 - `main_page_classes`: configure Quasar classes of main page (default: `q-ma-md column items-start`)
 - `interactive`: used internally when run in interactive Python shell (default: `False`)
 - `interactive`: used internally when run in interactive Python shell (default: `False`)

+ 24 - 1
main.py

@@ -9,7 +9,6 @@ import re
 import asyncio
 import asyncio
 from nicegui.elements.html import Html
 from nicegui.elements.html import Html
 from nicegui.elements.markdown import Markdown
 from nicegui.elements.markdown import Markdown
-from nicegui.events import KeyEventArguments
 from nicegui.globals import page_stack
 from nicegui.globals import page_stack
 
 
 # add docutils css to webpage
 # add docutils css to webpage
@@ -443,6 +442,30 @@ with example(ui.open):
 
 
     ui.button('REDIRECT', on_click=lambda e: ui.open('/yet_another_page', e.socket))
     ui.button('REDIRECT', on_click=lambda e: ui.open('/yet_another_page', e.socket))
 
 
+sessions = """### Sessions
+
+`ui.page` provides an optional `on_connect` argument to register a callback.
+It is invoked for each new connection to the page.
+
+The optional `request` argument provides insights about the clients url parameters etc. (see [the JustPy docs](https://justpy.io/tutorial/request_object/) for more details).
+It also enables you to identify sessions over [longer time spans by configuring cookies](https://justpy.io/tutorial/sessions/).
+"""
+with example(sessions):
+    from collections import Counter
+    from datetime import datetime
+    from starlette.requests import Request
+
+    id_counter = Counter()
+    creation = datetime.now().strftime('%H:%M, %d %B %Y')
+
+    def handle_connection(request: Request):
+        id_counter[request.session_id] += 1
+        visits.set_text(f'{len(id_counter)} unique views ({sum(id_counter.values())} overall) since {creation}')
+
+    with ui.page('/session_demo', on_connect=handle_connection) as page:
+        visits = ui.label()
+
+    ui.link('Visit session demo', page)
 
 
 add_route = """### Route
 add_route = """### Route
 
 

+ 5 - 3
nicegui/config.py

@@ -1,11 +1,12 @@
-from pydantic import BaseModel
-from typing import Optional
+from dataclasses import dataclass
+from typing import Awaitable, Callable, Optional, Union
 import inspect
 import inspect
 import ast
 import ast
 import os
 import os
 from . import globals
 from . import globals
 
 
-class Config(BaseModel):
+@dataclass
+class Config():
     # NOTE: should be in sync with ui.run arguments
     # NOTE: should be in sync with ui.run arguments
     host: str = '0.0.0.0'
     host: str = '0.0.0.0'
     port: int = 8080
     port: int = 8080
@@ -14,6 +15,7 @@ class Config(BaseModel):
     dark: Optional[bool] = False
     dark: Optional[bool] = False
     reload: bool = True
     reload: bool = True
     show: bool = True
     show: bool = True
+    on_connect: Optional[Union[Callable, Awaitable]] = None
     uvicorn_logging_level: str = 'warning'
     uvicorn_logging_level: str = 'warning'
     main_page_classes: str = 'q-ma-md column items-start'
     main_page_classes: str = 'q-ma-md column items-start'
     interactive: bool = False
     interactive: bool = False

+ 19 - 2
nicegui/elements/page.py

@@ -1,6 +1,8 @@
+import inspect
 import justpy as jp
 import justpy as jp
-from typing import Optional
+from typing import Awaitable, Callable, Optional, Union
 from pygments.formatters import HtmlFormatter
 from pygments.formatters import HtmlFormatter
+from starlette.requests import Request
 from ..globals import config, page_stack, view_stack
 from ..globals import config, page_stack, view_stack
 
 
 class Page(jp.QuasarPage):
 class Page(jp.QuasarPage):
@@ -12,6 +14,7 @@ class Page(jp.QuasarPage):
                  dark: Optional[bool] = ...,
                  dark: Optional[bool] = ...,
                  classes: str = 'q-ma-md column items-start',
                  classes: str = 'q-ma-md column items-start',
                  css: str = HtmlFormatter().get_style_defs('.codehilite'),
                  css: str = HtmlFormatter().get_style_defs('.codehilite'),
+                 on_connect: Optional[Union[Awaitable, Callable]] = None,
                  ):
                  ):
         """Page
         """Page
 
 
@@ -23,6 +26,7 @@ class Page(jp.QuasarPage):
         :param dark: whether to use Quasar's dark mode (defaults to `dark` argument of `run` command)
         :param dark: whether to use Quasar's dark mode (defaults to `dark` argument of `run` command)
         :param classes: tailwind classes for the container div (default: `'q-ma-md column items-start'`)
         :param classes: tailwind classes for the container div (default: `'q-ma-md column items-start'`)
         :param css: CSS definitions
         :param css: CSS definitions
+        :param on_connect: optional function or coroutine which is called for each new client connection
         """
         """
         super().__init__()
         super().__init__()
 
 
@@ -32,6 +36,7 @@ class Page(jp.QuasarPage):
         self.dark = dark if dark is not ... else config.dark
         self.dark = dark if dark is not ... else config.dark
         self.tailwind = True  # use Tailwind classes instead of Quasars
         self.tailwind = True  # use Tailwind classes instead of Quasars
         self.css = css
         self.css = css
+        self.on_connect = on_connect or config.on_connect
         self.head_html += '''
         self.head_html += '''
             <script>
             <script>
                 confirm = () => { setTimeout(location.reload.bind(location), 100); return false; };
                 confirm = () => { setTimeout(location.reload.bind(location), 100); return false; };
@@ -42,7 +47,19 @@ class Page(jp.QuasarPage):
         self.view.add_page(self)
         self.view.add_page(self)
 
 
         self.route = route
         self.route = route
-        jp.Route(route, lambda: self)
+        jp.Route(route, self._route_function)
+
+    async def _route_function(self, request: Request):
+        if self.on_connect:
+            arg_count = len(inspect.signature(self.on_connect).parameters)
+            is_coro = inspect.iscoroutinefunction(self.on_connect)
+            if arg_count == 1:
+                await self.on_connect(request) if is_coro else self.on_connect(request)
+            elif arg_count == 0:
+                await self.on_connect() if is_coro else self.on_connect()
+            else:
+                raise ValueError(f'invalid number of arguments (0 or 1 allowed, got {arg_count})')
+        return self
 
 
     def __enter__(self):
     def __enter__(self):
         page_stack.append(self)
         page_stack.append(self)

+ 2 - 1
nicegui/run.py

@@ -1,4 +1,4 @@
-from typing import Optional
+from typing import Awaitable, Callable, Optional, Union
 import inspect
 import inspect
 import sys
 import sys
 import webbrowser
 import webbrowser
@@ -21,6 +21,7 @@ def run(self, *,
         dark: Optional[bool] = False,
         dark: Optional[bool] = False,
         reload: bool = True,
         reload: bool = True,
         show: bool = True,
         show: bool = True,
+        on_connect: Optional[Union[Callable, Awaitable]] = None,
         uvicorn_logging_level: str = 'warning',
         uvicorn_logging_level: str = 'warning',
         main_page_classes: str = 'q-ma-md column items-start',
         main_page_classes: str = 'q-ma-md column items-start',
         ):
         ):