소스 검색

move lifecycle functions and add_static_files into new app class

Falko Schindler 2 년 전
부모
커밋
66393ec00f

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

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 #!/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:
 with ui.scene(width=1024, height=800) as scene:
     scene.spot_light(distance=100, intensity=0.1).move(-10, 0, 10)
     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
 #!/usr/bin/env python3
 from pathlib import Path
 from pathlib import Path
 
 
-from nicegui import ui
+from nicegui import app, ui
 from nicegui.events import KeyEventArguments
 from nicegui.events import KeyEventArguments
 
 
 folder = Path(__file__).resolve().parent / 'slides'  # image source: https://pixabay.com/
 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]}')
         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
 slide = ui.image(f'slides/{files[index]}')  # show the first image
 ui.keyboard(on_key=handle_key)  # handle keyboard events
 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)
 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
 # 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]
 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 . import globals
 from .async_updater import AsyncUpdater
 from .async_updater import AsyncUpdater
 from .client import Client
 from .client import Client
-from .functions.lifecycle import on_startup
 from .helpers import is_coroutine
 from .helpers import is_coroutine
 from .task_logger import create_task
 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():
             if globals.loop and globals.loop.is_running():
                 create_task(wait_for_result(), name=str(handler))
                 create_task(wait_for_result(), name=str(handler))
             else:
             else:
-                on_startup(wait_for_result())
+                globals.app.on_startup(wait_for_result())
     except Exception:
     except Exception:
         traceback.print_exc()
         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 ..binding import BindableProperty
 from ..helpers import is_coroutine
 from ..helpers import is_coroutine
 from ..task_logger import create_task
 from ..task_logger import create_task
-from .lifecycle import on_startup
 
 
 
 
 class Timer:
 class Timer:
@@ -36,7 +35,7 @@ class Timer:
         if globals.state == globals.State.STARTED:
         if globals.state == globals.State.STARTED:
             globals.tasks.append(create_task(coroutine(), name=str(callback)))
             globals.tasks.append(create_task(coroutine(), name=str(callback)))
         else:
         else:
-            on_startup(coroutine)
+            globals.app.on_startup(coroutine)
 
 
     async def _run_once(self) -> None:
     async def _run_once(self) -> None:
         with self.slot:
         with self.slot:

+ 3 - 2
nicegui/globals.py

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

+ 3 - 2
nicegui/nicegui.py

@@ -4,13 +4,14 @@ import urllib.parse
 from pathlib import Path
 from pathlib import Path
 from typing import Dict, Optional
 from typing import Dict, Optional
 
 
-from fastapi import FastAPI, HTTPException, Request
+from fastapi import HTTPException, Request
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.responses import FileResponse, Response
 from fastapi.responses import FileResponse, Response
 from fastapi.staticfiles import StaticFiles
 from fastapi.staticfiles import StaticFiles
 from fastapi_socketio import SocketManager
 from fastapi_socketio import SocketManager
 
 
 from . import binding, globals
 from . import binding, globals
+from .app import App
 from .client import Client
 from .client import Client
 from .dependencies import js_components, js_dependencies
 from .dependencies import js_components, js_dependencies
 from .element import Element
 from .element import Element
@@ -19,7 +20,7 @@ from .helpers import safe_invoke
 from .page import page
 from .page import page
 from .task_logger import create_task
 from .task_logger import create_task
 
 
-globals.app = app = FastAPI()
+globals.app = app = App()
 globals.sio = sio = SocketManager(app=app)._sio
 globals.sio = sio = SocketManager(app=app)._sio
 
 
 app.add_middleware(GZipMiddleware)
 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.tree import Tree as tree
 from .elements.upload import Upload as upload
 from .elements.upload import Upload as upload
 from .elements.video import Video as video
 from .elements.video import Video as video
-from .functions import lifecycle
 from .functions.html import add_body_html, add_head_html
 from .functions.html import add_body_html, add_head_html
 from .functions.javascript import run_javascript
 from .functions.javascript import run_javascript
 from .functions.notify import notify
 from .functions.notify import notify
 from .functions.open import open
 from .functions.open import open
-from .functions.static_files import add_static_files
 from .functions.timer import Timer as timer
 from .functions.timer import Timer as timer
 from .functions.update import update
 from .functions.update import update
 from .page import page
 from .page import page

+ 5 - 5
tests/test_lifecycle.py

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

+ 19 - 5
website/reference.py

@@ -1,7 +1,7 @@
 import uuid
 import uuid
 from typing import Dict
 from typing import Dict
 
 
-from nicegui import ui
+from nicegui import app, ui
 
 
 from .example import example
 from .example import example
 
 
@@ -480,15 +480,27 @@ All three functions also provide `remove` and `replace` parameters in case the p
 
 
     h3('Action')
     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():
     def lifecycle_example():
+        from nicegui import app
+
         def handle_connect():
         def handle_connect():
             if watch.value:
             if watch.value:
                 count.set_text(str(int(count.text or 0) + 1))
                 count.set_text(str(int(count.text or 0) + 1))
 
 
         watch = ui.checkbox('count new connections')
         watch = ui.checkbox('count new connections')
         count = ui.label().classes('mt-8 self-center text-5xl')
         count = ui.label().classes('mt-8 self-center text-5xl')
-        ui.lifecycle.on_connect(handle_connect)
+        app.on_connect(handle_connect)
 
 
     @example(ui.timer)
     @example(ui.timer)
     def timer_example():
     def timer_example():
@@ -753,9 +765,11 @@ You can also set `respond=False` to send a command without waiting for a respons
 
 
     h3('Routes')
     h3('Routes')
 
 
-    @example(ui.add_static_files)
+    @example(app.add_static_files)
     def add_static_files_example():
     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.label('Some NiceGUI Examples').classes('text-h5')
         ui.link('AI interface', '/examples/ai_interface/main.py')
         ui.link('AI interface', '/examples/ai_interface/main.py')
         ui.link('Custom FastAPI app', '/examples/fastapi/main.py')
         ui.link('Custom FastAPI app', '/examples/fastapi/main.py')