Răsfoiți Sursa

move lifecycle functions and add_static_files into new app class

Falko Schindler 2 ani în urmă
părinte
comite
66393ec00f

+ 2 - 2
examples/3d_scene/main.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
-from nicegui import ui
+from nicegui import app, ui
 
-ui.add_static_files('/stl', 'static')
+app.add_static_files('/stl', 'static')
 
 with ui.scene(width=1024, height=800) as scene:
     scene.spot_light(distance=100, intensity=0.1).move(-10, 0, 10)

+ 2 - 2
examples/slideshow/main.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 from pathlib import Path
 
-from nicegui import ui
+from nicegui import app, ui
 from nicegui.events import KeyEventArguments
 
 folder = Path(__file__).resolve().parent / 'slides'  # image source: https://pixabay.com/
@@ -20,7 +20,7 @@ def handle_key(event: KeyEventArguments) -> None:
         slide.set_source(f'slides/{files[index]}')
 
 
-ui.add_static_files('/slides', folder)  # serve all files in this folder
+app.add_static_files('/slides', folder)  # serve all files in this folder
 slide = ui.image(f'slides/{files[index]}')  # show the first image
 ui.keyboard(on_key=handle_key)  # handle keyboard events
 

+ 2 - 2
main.py

@@ -21,8 +21,8 @@ from website.style import example_link, features, heading, link_target, section_
 
 prometheus.start_monitor(app)
 
-ui.add_static_files('/favicon', str(Path(__file__).parent / 'website' / 'favicon'))
-ui.add_static_files('/fonts', str(Path(__file__).parent / 'website' / 'fonts'))
+app.add_static_files('/favicon', str(Path(__file__).parent / 'website' / 'favicon'))
+app.add_static_files('/fonts', str(Path(__file__).parent / 'website' / 'fonts'))
 
 # NOTE in our global fly.io deployment we need to make sure that the websocket connects back to the same instance
 fly_instance_id = os.environ.get('FLY_ALLOC_ID', '').split('-')[0]

+ 61 - 0
nicegui/app.py

@@ -0,0 +1,61 @@
+from typing import Awaitable, Callable, Union
+
+from fastapi import FastAPI
+from fastapi.staticfiles import StaticFiles
+
+from . import globals
+
+
+class App(FastAPI):
+
+    def on_connect(self, handler: Union[Callable, Awaitable]) -> None:
+        """Called every time a new client connects to NiceGUI.
+
+        The callback has an optional parameter of `nicegui.Client`.
+        """
+        globals.connect_handlers.append(handler)
+
+    def on_disconnect(self, handler: Union[Callable, Awaitable]) -> None:
+        """Called every time a new client disconnects from NiceGUI.
+
+        The callback has an optional parameter of `nicegui.Client`.
+        """
+        globals.disconnect_handlers.append(handler)
+
+    def on_startup(self, handler: Union[Callable, Awaitable]) -> None:
+        """Called when NiceGUI is started or restarted.
+
+        Needs to be called before `ui.run()`.
+        """
+        if globals.state == globals.State.STARTED:
+            raise RuntimeError('Unable to register another startup handler. NiceGUI has already been started.')
+        globals.startup_handlers.append(handler)
+
+    def on_shutdown(self, handler: Union[Callable, Awaitable]) -> None:
+        """Called when NiceGUI is shut down or restarted.
+
+        When NiceGUI is shut down or restarted, all tasks still in execution will be automatically canceled.
+        """
+        globals.shutdown_handlers.append(handler)
+
+    async def shutdown(self) -> None:
+        """Programmatically shut down NiceGUI.
+
+        Only possible when auto-reload is disabled.
+        """
+        if globals.reload:
+            raise Exception('calling shutdown() is not supported when auto-reload is enabled')
+        globals.server.should_exit = True
+
+    def add_static_files(self, path: str, directory: str) -> None:
+        """Add static files.
+
+        `add_static_files()` makes a local directory available at the specified endpoint, e.g. `'/static'`.
+        This is useful for providing local data like images to the frontend.
+        Otherwise the browser would not be able to access the files.
+        Do only put non-security-critical files in there, as they are accessible to everyone.
+
+        :param path: string that starts with a slash "/"
+        :param directory: folder with static files to serve under the given path
+        """
+        globals.app.mount(path, StaticFiles(directory=directory))

+ 1 - 2
nicegui/events.py

@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Any, BinaryIO, Callable, List, Optional
 from . import globals
 from .async_updater import AsyncUpdater
 from .client import Client
-from .functions.lifecycle import on_startup
 from .helpers import is_coroutine
 from .task_logger import create_task
 
@@ -276,6 +275,6 @@ def handle_event(handler: Optional[Callable], arguments: EventArguments) -> None
             if globals.loop and globals.loop.is_running():
                 create_task(wait_for_result(), name=str(handler))
             else:
-                on_startup(wait_for_result())
+                globals.app.on_startup(wait_for_result())
     except Exception:
         traceback.print_exc()

+ 0 - 55
nicegui/functions/lifecycle.py

@@ -1,55 +0,0 @@
-'''Lifecycle functions
-
-You can register coroutines or functions to be called for the following events:
-
-- `ui.lifecycle.on_startup`: called when NiceGUI is started or restarted
-- `ui.lifecycle.on_shutdown`: called when NiceGUI is shut down or restarted
-- `ui.lifecycle.on_connect`: called for each client which connects (optional argument: nicegui.Client)
-- `ui.lifecycle.on_disconnect`: called for each client which disconnects (optional argument: nicegui.Client)
-
-When NiceGUI is shut down or restarted, all tasks still in execution will be automatically canceled.
-'''
-from typing import Awaitable, Callable, Union
-
-from .. import globals
-
-
-def on_connect(handler: Union[Callable, Awaitable]) -> None:
-    '''Called every time a new client connects to NiceGUI.
-
-    The callback has an optional parameter of `nicegui.Client`.
-    '''
-    globals.connect_handlers.append(handler)
-
-
-def on_disconnect(handler: Union[Callable, Awaitable]) -> None:
-    '''Called every time a new client disconnects from NiceGUI.
-
-    The callback has an optional parameter of `nicegui.Client`.
-    '''
-    globals.disconnect_handlers.append(handler)
-
-
-def on_startup(handler: Union[Callable, Awaitable]) -> None:
-    '''Called when NiceGUI is started or restarted.
-
-    Needs to be called before `ui.run()`.
-    '''
-    if globals.state == globals.State.STARTED:
-        raise RuntimeError('Unable to register another startup handler. NiceGUI has already been started.')
-    globals.startup_handlers.append(handler)
-
-
-def on_shutdown(handler: Union[Callable, Awaitable]) -> None:
-    '''Called when NiceGUI is shut down or restarted.'''
-    globals.shutdown_handlers.append(handler)
-
-
-async def shutdown() -> None:
-    '''Programmatically shut down NiceGUI.
-
-    Only possible when auto-reload is disabled.
-    '''
-    if globals.reload:
-        raise Exception('calling shutdown() is not supported when auto-reload is enabled')
-    globals.server.should_exit = True

+ 0 - 17
nicegui/functions/static_files.py

@@ -1,17 +0,0 @@
-from fastapi.staticfiles import StaticFiles
-
-from .. import globals
-
-
-def add_static_files(path: str, directory: str) -> None:
-    """Static Files
-
-    `ui.add_static_files` makes a local directory available at the specified endpoint, e.g. `'/static'`.
-    This is useful for providing local data like images to the frontend.
-    Otherwise the browser would not be able to access the files.
-    Do only put non-security-critical files in there, as they are accessible to everyone.
-
-    :param path: string that starts with a slash "/"
-    :param directory: folder with static files to serve under the given path
-    """
-    globals.app.mount(path, StaticFiles(directory=directory))

+ 1 - 2
nicegui/functions/timer.py

@@ -8,7 +8,6 @@ from ..async_updater import AsyncUpdater
 from ..binding import BindableProperty
 from ..helpers import is_coroutine
 from ..task_logger import create_task
-from .lifecycle import on_startup
 
 
 class Timer:
@@ -36,7 +35,7 @@ class Timer:
         if globals.state == globals.State.STARTED:
             globals.tasks.append(create_task(coroutine(), name=str(callback)))
         else:
-            on_startup(coroutine)
+            globals.app.on_startup(coroutine)
 
     async def _run_once(self) -> None:
         with self.slot:

+ 3 - 2
nicegui/globals.py

@@ -4,10 +4,11 @@ from contextlib import contextmanager
 from enum import Enum
 from typing import TYPE_CHECKING, Awaitable, Callable, Dict, List, Optional, Union
 
-from fastapi import FastAPI
 from socketio import AsyncServer
 from uvicorn import Server
 
+from .app import App
+
 if TYPE_CHECKING:
     from .client import Client
     from .slot import Slot
@@ -20,7 +21,7 @@ class State(Enum):
     STOPPING = 3
 
 
-app: FastAPI
+app: App
 sio: AsyncServer
 server: Server
 loop: Optional[asyncio.AbstractEventLoop] = None

+ 3 - 2
nicegui/nicegui.py

@@ -4,13 +4,14 @@ import urllib.parse
 from pathlib import Path
 from typing import Dict, Optional
 
-from fastapi import FastAPI, HTTPException, Request
+from fastapi import HTTPException, Request
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.responses import FileResponse, Response
 from fastapi.staticfiles import StaticFiles
 from fastapi_socketio import SocketManager
 
 from . import binding, globals
+from .app import App
 from .client import Client
 from .dependencies import js_components, js_dependencies
 from .element import Element
@@ -19,7 +20,7 @@ from .helpers import safe_invoke
 from .page import page
 from .task_logger import create_task
 
-globals.app = app = FastAPI()
+globals.app = app = App()
 globals.sio = sio = SocketManager(app=app)._sio
 
 app.add_middleware(GZipMiddleware)

+ 0 - 2
nicegui/ui.py

@@ -44,12 +44,10 @@ from .elements.tooltip import Tooltip as tooltip
 from .elements.tree import Tree as tree
 from .elements.upload import Upload as upload
 from .elements.video import Video as video
-from .functions import lifecycle
 from .functions.html import add_body_html, add_head_html
 from .functions.javascript import run_javascript
 from .functions.notify import notify
 from .functions.open import open
-from .functions.static_files import add_static_files
 from .functions.timer import Timer as timer
 from .functions.update import update
 from .page import page

+ 5 - 5
tests/test_lifecycle.py

@@ -1,6 +1,6 @@
 from typing import List
 
-from nicegui import Client, ui
+from nicegui import Client, app, ui
 
 from .screen import Screen
 
@@ -8,7 +8,7 @@ from .screen import Screen
 def test_adding_elements_during_onconnect_on_auto_index_page(screen: Screen):
     connections = []
     ui.label('Adding labels on_connect')
-    ui.lifecycle.on_connect(lambda _: connections.append(ui.label(f'new connection {len(connections)}')))
+    app.on_connect(lambda _: connections.append(ui.label(f'new connection {len(connections)}')))
 
     screen.open('/')
     screen.should_contain('new connection 0')
@@ -25,7 +25,7 @@ def test_async_connect_handler(screen: Screen):
     async def run_js():
         result.text = await ui.run_javascript('41 + 1')
     result = ui.label()
-    ui.lifecycle.on_connect(run_js)
+    app.on_connect(run_js)
 
     screen.open('/')
     screen.should_contain('42')
@@ -37,8 +37,8 @@ def test_connect_disconnect_is_called_for_each_client(screen: Screen):
     @ui.page('/')
     def page(client: Client):
         ui.label(f'client id: {client.id}')
-    ui.lifecycle.on_connect(lambda: events.append('connect'))
-    ui.lifecycle.on_disconnect(lambda: events.append('disconnect'))
+    app.on_connect(lambda: events.append('connect'))
+    app.on_disconnect(lambda: events.append('disconnect'))
 
     screen.open('/')
     screen.wait(0.5)

+ 19 - 5
website/reference.py

@@ -1,7 +1,7 @@
 import uuid
 from typing import Dict
 
-from nicegui import ui
+from nicegui import app, ui
 
 from .example import example
 
@@ -480,15 +480,27 @@ All three functions also provide `remove` and `replace` parameters in case the p
 
     h3('Action')
 
-    @example(ui.lifecycle)
+    @example('''#### Lifecycle functions
+
+You can register coroutines or functions to be called for the following events:
+
+- `app.on_startup`: called when NiceGUI is started or restarted
+- `app.on_shutdown`: called when NiceGUI is shut down or restarted
+- `app.on_connect`: called for each client which connects (optional argument: nicegui.Client)
+- `app.on_disconnect`: called for each client which disconnects (optional argument: nicegui.Client)
+
+When NiceGUI is shut down or restarted, all tasks still in execution will be automatically canceled.
+''')
     def lifecycle_example():
+        from nicegui import app
+
         def handle_connect():
             if watch.value:
                 count.set_text(str(int(count.text or 0) + 1))
 
         watch = ui.checkbox('count new connections')
         count = ui.label().classes('mt-8 self-center text-5xl')
-        ui.lifecycle.on_connect(handle_connect)
+        app.on_connect(handle_connect)
 
     @example(ui.timer)
     def timer_example():
@@ -753,9 +765,11 @@ You can also set `respond=False` to send a command without waiting for a respons
 
     h3('Routes')
 
-    @example(ui.add_static_files)
+    @example(app.add_static_files)
     def add_static_files_example():
-        ui.add_static_files('/examples', 'examples')
+        from nicegui import app
+
+        app.add_static_files('/examples', 'examples')
         ui.label('Some NiceGUI Examples').classes('text-h5')
         ui.link('AI interface', '/examples/ai_interface/main.py')
         ui.link('Custom FastAPI app', '/examples/fastapi/main.py')