ui_run.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import multiprocessing
  2. import os
  3. import sys
  4. from pathlib import Path
  5. from typing import Any, List, Literal, Optional, Tuple, Union
  6. import __main__
  7. from starlette.routing import Route
  8. from uvicorn.main import STARTUP_FAILURE
  9. from uvicorn.supervisors import ChangeReload, Multiprocess
  10. from . import air, core, helpers
  11. from . import native as native_module
  12. from .client import Client
  13. from .language import Language
  14. from .logging import log
  15. from .server import CustomServerConfig, Server
  16. APP_IMPORT_STRING = 'nicegui:app'
  17. def run(*,
  18. host: Optional[str] = None,
  19. port: Optional[int] = None,
  20. title: str = 'NiceGUI',
  21. viewport: str = 'width=device-width, initial-scale=1',
  22. favicon: Optional[Union[str, Path]] = None,
  23. dark: Optional[bool] = False,
  24. language: Language = 'en-US',
  25. binding_refresh_interval: float = 0.1,
  26. reconnect_timeout: float = 3.0,
  27. show: bool = True,
  28. on_air: Optional[Union[str, Literal[True]]] = None,
  29. native: bool = False,
  30. window_size: Optional[Tuple[int, int]] = None,
  31. fullscreen: bool = False,
  32. frameless: bool = False,
  33. reload: bool = True,
  34. uvicorn_logging_level: str = 'warning',
  35. uvicorn_reload_dirs: str = '.',
  36. uvicorn_reload_includes: str = '*.py',
  37. uvicorn_reload_excludes: str = '.*, .py[cod], .sw.*, ~*',
  38. tailwind: bool = True,
  39. prod_js: bool = True,
  40. endpoint_documentation: Literal['none', 'internal', 'page', 'all'] = 'none',
  41. storage_secret: Optional[str] = None,
  42. show_welcome_message: bool = True,
  43. **kwargs: Any,
  44. ) -> None:
  45. """ui.run
  46. You can call `ui.run()` with optional arguments.
  47. Most of them only apply after stopping and fully restarting the app and do not apply with auto-reloading.
  48. :param host: start server with this host (defaults to `'127.0.0.1` in native mode, otherwise `'0.0.0.0'`)
  49. :param port: use this port (default: 8080 in normal mode, and an automatically determined open port in native mode)
  50. :param title: page title (default: `'NiceGUI'`, can be overwritten per page)
  51. :param viewport: page meta viewport content (default: `'width=device-width, initial-scale=1'`, can be overwritten per page)
  52. :param favicon: relative filepath, absolute URL to a favicon (default: `None`, NiceGUI icon will be used) or emoji (e.g. `'🚀'`, works for most browsers)
  53. :param dark: whether to use Quasar's dark mode (default: `False`, use `None` for "auto" mode)
  54. :param language: language for Quasar elements (default: `'en-US'`)
  55. :param binding_refresh_interval: time between binding updates (default: `0.1` seconds, bigger is more CPU friendly)
  56. :param reconnect_timeout: maximum time the server waits for the browser to reconnect (default: 3.0 seconds)
  57. :param show: automatically open the UI in a browser tab (default: `True`)
  58. :param on_air: tech preview: `allows temporary remote access <https://nicegui.io/documentation#nicegui_on_air>`_ if set to `True` (default: disabled)
  59. :param native: open the UI in a native window of size 800x600 (default: `False`, deactivates `show`, automatically finds an open port)
  60. :param window_size: open the UI in a native window with the provided size (e.g. `(1024, 786)`, default: `None`, also activates `native`)
  61. :param fullscreen: open the UI in a fullscreen window (default: `False`, also activates `native`)
  62. :param frameless: open the UI in a frameless window (default: `False`, also activates `native`)
  63. :param reload: automatically reload the UI on file changes (default: `True`)
  64. :param uvicorn_logging_level: logging level for uvicorn server (default: `'warning'`)
  65. :param uvicorn_reload_dirs: string with comma-separated list for directories to be monitored (default is current working directory only)
  66. :param uvicorn_reload_includes: string with comma-separated list of glob-patterns which trigger reload on modification (default: `'*.py'`)
  67. :param uvicorn_reload_excludes: string with comma-separated list of glob-patterns which should be ignored for reload (default: `'.*, .py[cod], .sw.*, ~*'`)
  68. :param tailwind: whether to use Tailwind (experimental, default: `True`)
  69. :param prod_js: whether to use the production version of Vue and Quasar dependencies (default: `True`)
  70. :param endpoint_documentation: control what endpoints appear in the autogenerated OpenAPI docs (default: 'none', options: 'none', 'internal', 'page', 'all')
  71. :param storage_secret: secret key for browser-based storage (default: `None`, a value is required to enable ui.storage.individual and ui.storage.browser)
  72. :param show_welcome_message: whether to show the welcome message (default: `True`)
  73. :param kwargs: additional keyword arguments are passed to `uvicorn.run`
  74. """
  75. core.app.config.add_run_config(
  76. reload=reload,
  77. title=title,
  78. viewport=viewport,
  79. favicon=favicon,
  80. dark=dark,
  81. language=language,
  82. binding_refresh_interval=binding_refresh_interval,
  83. reconnect_timeout=reconnect_timeout,
  84. tailwind=tailwind,
  85. prod_js=prod_js,
  86. show_welcome_message=show_welcome_message,
  87. )
  88. core.app.config.endpoint_documentation = endpoint_documentation
  89. for route in core.app.routes:
  90. if not isinstance(route, Route):
  91. continue
  92. if route.path.startswith('/_nicegui') and hasattr(route, 'methods'):
  93. route.include_in_schema = endpoint_documentation in {'internal', 'all'}
  94. if route.path == '/' or route.path in Client.page_routes.values():
  95. route.include_in_schema = endpoint_documentation in {'page', 'all'}
  96. if on_air:
  97. air.instance = air.Air('' if on_air is True else on_air)
  98. if multiprocessing.current_process().name != 'MainProcess':
  99. return
  100. if reload and not hasattr(__main__, '__file__'):
  101. log.warning('auto-reloading is only supported when running from a file')
  102. core.app.config.reload = reload = False
  103. if fullscreen:
  104. native = True
  105. if frameless:
  106. native = True
  107. if window_size:
  108. native = True
  109. if native:
  110. show = False
  111. host = host or '127.0.0.1'
  112. port = port or native_module.find_open_port()
  113. width, height = window_size or (800, 600)
  114. native_module.activate(host, port, title, width, height, fullscreen, frameless)
  115. else:
  116. port = port or 8080
  117. host = host or '0.0.0.0'
  118. assert host is not None
  119. assert port is not None
  120. # NOTE: We save host and port in environment variables so the subprocess started in reload mode can access them.
  121. os.environ['NICEGUI_HOST'] = host
  122. os.environ['NICEGUI_PORT'] = str(port)
  123. if show:
  124. helpers.schedule_browser(host, port)
  125. def split_args(args: str) -> List[str]:
  126. return [a.strip() for a in args.split(',')]
  127. if kwargs.get('workers', 1) > 1:
  128. raise ValueError('NiceGUI does not support multiple workers yet.')
  129. # NOTE: The following lines are basically a copy of `uvicorn.run`, but keep a reference to the `server`.
  130. config = CustomServerConfig(
  131. APP_IMPORT_STRING if reload else core.app,
  132. host=host,
  133. port=port,
  134. reload=reload,
  135. reload_includes=split_args(uvicorn_reload_includes) if reload else None,
  136. reload_excludes=split_args(uvicorn_reload_excludes) if reload else None,
  137. reload_dirs=split_args(uvicorn_reload_dirs) if reload else None,
  138. log_level=uvicorn_logging_level,
  139. **kwargs,
  140. )
  141. config.storage_secret = storage_secret
  142. config.method_queue = native_module.method_queue if native else None
  143. config.response_queue = native_module.response_queue if native else None
  144. Server.create_singleton(config)
  145. if (reload or config.workers > 1) and not isinstance(config.app, str):
  146. log.warning('You must pass the application as an import string to enable "reload" or "workers".')
  147. sys.exit(1)
  148. if config.should_reload:
  149. sock = config.bind_socket()
  150. ChangeReload(config, target=Server.instance.run, sockets=[sock]).run()
  151. elif config.workers > 1:
  152. sock = config.bind_socket()
  153. Multiprocess(config, target=Server.instance.run, sockets=[sock]).run()
  154. else:
  155. Server.instance.run()
  156. if config.uds:
  157. os.remove(config.uds) # pragma: py-win32
  158. if not Server.instance.started and not config.should_reload and config.workers == 1:
  159. sys.exit(STARTUP_FAILURE)