You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
8.5 KiB
187 lines
8.5 KiB
import multiprocessing |
|
import os |
|
import socket |
|
import sys |
|
from pathlib import Path |
|
from typing import Any, List, Literal, Optional, Tuple, Union |
|
|
|
import __main__ |
|
import uvicorn |
|
from starlette.routing import Route |
|
from uvicorn.main import STARTUP_FAILURE |
|
from uvicorn.supervisors import ChangeReload, Multiprocess |
|
|
|
from . import globals, helpers # pylint: disable=redefined-builtin |
|
from . import native as native_module |
|
from . import native_mode # pylint: disable=redefined-builtin |
|
from .air import Air |
|
from .language import Language |
|
|
|
APP_IMPORT_STRING = 'nicegui:app' |
|
|
|
|
|
class CustomServerConfig(uvicorn.Config): |
|
storage_secret: Optional[str] = None |
|
method_queue: Optional[multiprocessing.Queue] = None |
|
response_queue: Optional[multiprocessing.Queue] = None |
|
|
|
|
|
class Server(uvicorn.Server): |
|
|
|
def run(self, sockets: Optional[List[socket.socket]] = None) -> None: |
|
globals.server = self |
|
assert isinstance(self.config, CustomServerConfig) |
|
if self.config.method_queue is not None and self.config.response_queue is not None: |
|
native_module.method_queue = self.config.method_queue |
|
native_module.response_queue = self.config.response_queue |
|
globals.app.native.main_window = native_module.WindowProxy() |
|
|
|
helpers.set_storage_secret(self.config.storage_secret) |
|
super().run(sockets=sockets) |
|
|
|
|
|
def run(*, |
|
host: Optional[str] = None, |
|
port: int = 8080, |
|
title: str = 'NiceGUI', |
|
viewport: str = 'width=device-width, initial-scale=1', |
|
favicon: Optional[Union[str, Path]] = None, |
|
dark: Optional[bool] = False, |
|
language: Language = 'en-US', |
|
binding_refresh_interval: float = 0.1, |
|
show: bool = True, |
|
on_air: Optional[Union[str, Literal[True]]] = None, |
|
native: bool = False, |
|
window_size: Optional[Tuple[int, int]] = None, |
|
fullscreen: bool = False, |
|
frameless: bool = False, |
|
reload: bool = True, |
|
uvicorn_logging_level: str = 'warning', |
|
uvicorn_reload_dirs: str = '.', |
|
uvicorn_reload_includes: str = '*.py', |
|
uvicorn_reload_excludes: str = '.*, .py[cod], .sw.*, ~*', |
|
tailwind: bool = True, |
|
prod_js: bool = True, |
|
endpoint_documentation: Literal['none', 'internal', 'page', 'all'] = 'none', |
|
storage_secret: Optional[str] = None, |
|
**kwargs: Any, |
|
) -> None: |
|
'''ui.run |
|
|
|
You can call `ui.run()` with optional arguments: |
|
|
|
:param host: start server with this host (defaults to `'127.0.0.1` in native mode, otherwise `'0.0.0.0'`) |
|
:param port: use this port (default: `8080`) |
|
:param title: page title (default: `'NiceGUI'`, can be overwritten per page) |
|
:param viewport: page meta viewport content (default: `'width=device-width, initial-scale=1'`, can be overwritten per page) |
|
:param favicon: relative filepath, absolute URL to a favicon (default: `None`, NiceGUI icon will be used) or emoji (e.g. `'🚀'`, works for most browsers) |
|
:param dark: whether to use Quasar's dark mode (default: `False`, use `None` for "auto" mode) |
|
:param language: language for Quasar elements (default: `'en-US'`) |
|
:param binding_refresh_interval: time between binding updates (default: `0.1` seconds, bigger is more CPU friendly) |
|
:param show: automatically open the UI in a browser tab (default: `True`) |
|
:param on_air: tech preview: `allows temporary remote access <https://nicegui.io/documentation#nicegui_on_air>`_ if set to `True` (default: disabled) |
|
:param native: open the UI in a native window of size 800x600 (default: `False`, deactivates `show`, automatically finds an open port) |
|
:param window_size: open the UI in a native window with the provided size (e.g. `(1024, 786)`, default: `None`, also activates `native`) |
|
:param fullscreen: open the UI in a fullscreen window (default: `False`, also activates `native`) |
|
:param frameless: open the UI in a frameless window (default: `False`, also activates `native`) |
|
:param reload: automatically reload the UI on file changes (default: `True`) |
|
:param uvicorn_logging_level: logging level for uvicorn server (default: `'warning'`) |
|
:param uvicorn_reload_dirs: string with comma-separated list for directories to be monitored (default is current working directory only) |
|
:param uvicorn_reload_includes: string with comma-separated list of glob-patterns which trigger reload on modification (default: `'.py'`) |
|
:param uvicorn_reload_excludes: string with comma-separated list of glob-patterns which should be ignored for reload (default: `'.*, .py[cod], .sw.*, ~*'`) |
|
:param tailwind: whether to use Tailwind (experimental, default: `True`) |
|
:param prod_js: whether to use the production version of Vue and Quasar dependencies (default: `True`) |
|
:param endpoint_documentation: control what endpoints appear in the autogenerated OpenAPI docs (default: 'none', options: 'none', 'internal', 'page', 'all') |
|
:param storage_secret: secret key for browser-based storage (default: `None`, a value is required to enable ui.storage.individual and ui.storage.browser) |
|
:param kwargs: additional keyword arguments are passed to `uvicorn.run` |
|
''' |
|
globals.ui_run_has_been_called = True |
|
globals.reload = reload |
|
globals.title = title |
|
globals.viewport = viewport |
|
globals.favicon = favicon |
|
globals.dark = dark |
|
globals.language = language |
|
globals.binding_refresh_interval = binding_refresh_interval |
|
globals.tailwind = tailwind |
|
globals.prod_js = prod_js |
|
globals.endpoint_documentation = endpoint_documentation |
|
|
|
for route in globals.app.routes: |
|
if not isinstance(route, Route): |
|
continue |
|
if route.path.startswith('/_nicegui') and hasattr(route, 'methods'): |
|
route.include_in_schema = endpoint_documentation in {'internal', 'all'} |
|
if route.path == '/' or route.path in globals.page_routes.values(): |
|
route.include_in_schema = endpoint_documentation in {'page', 'all'} |
|
|
|
if on_air: |
|
globals.air = Air('' if on_air is True else on_air) |
|
|
|
if multiprocessing.current_process().name != 'MainProcess': |
|
return |
|
|
|
if reload and not hasattr(__main__, '__file__'): |
|
globals.log.warning('auto-reloading is only supported when running from a file') |
|
globals.reload = reload = False |
|
|
|
if fullscreen: |
|
native = True |
|
if frameless: |
|
native = True |
|
if window_size: |
|
native = True |
|
if native: |
|
show = False |
|
host = host or '127.0.0.1' |
|
port = native_mode.find_open_port() |
|
width, height = window_size or (800, 600) |
|
native_mode.activate(host, port, title, width, height, fullscreen, frameless) |
|
else: |
|
host = host or '0.0.0.0' |
|
|
|
# NOTE: We save host and port in environment variables so the subprocess started in reload mode can access them. |
|
os.environ['NICEGUI_HOST'] = host |
|
os.environ['NICEGUI_PORT'] = str(port) |
|
|
|
if show: |
|
helpers.schedule_browser(host, port) |
|
|
|
def split_args(args: str) -> List[str]: |
|
return [a.strip() for a in args.split(',')] |
|
|
|
# NOTE: The following lines are basically a copy of `uvicorn.run`, but keep a reference to the `server`. |
|
|
|
config = CustomServerConfig( |
|
APP_IMPORT_STRING if reload else globals.app, |
|
host=host, |
|
port=port, |
|
reload=reload, |
|
reload_includes=split_args(uvicorn_reload_includes) if reload else None, |
|
reload_excludes=split_args(uvicorn_reload_excludes) if reload else None, |
|
reload_dirs=split_args(uvicorn_reload_dirs) if reload else None, |
|
log_level=uvicorn_logging_level, |
|
**kwargs, |
|
) |
|
config.storage_secret = storage_secret |
|
config.method_queue = native_module.method_queue if native else None |
|
config.response_queue = native_module.response_queue if native else None |
|
globals.server = Server(config=config) |
|
|
|
if (reload or config.workers > 1) and not isinstance(config.app, str): |
|
globals.log.warning('You must pass the application as an import string to enable "reload" or "workers".') |
|
sys.exit(1) |
|
|
|
if config.should_reload: |
|
sock = config.bind_socket() |
|
ChangeReload(config, target=globals.server.run, sockets=[sock]).run() |
|
elif config.workers > 1: |
|
sock = config.bind_socket() |
|
Multiprocess(config, target=globals.server.run, sockets=[sock]).run() |
|
else: |
|
globals.server.run() |
|
if config.uds: |
|
os.remove(config.uds) # pragma: py-win32 |
|
|
|
if not globals.server.started and not config.should_reload and config.workers == 1: |
|
sys.exit(STARTUP_FAILURE)
|
|
|