run.py 7.9 KB

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