run.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import logging
  2. import multiprocessing
  3. import os
  4. import sys
  5. from typing import Any, List, Optional, Tuple
  6. import __main__
  7. import uvicorn
  8. from uvicorn.main import STARTUP_FAILURE
  9. from uvicorn.supervisors import ChangeReload, Multiprocess
  10. from . import globals, helpers
  11. from . import native as app_native
  12. from . import native_mode
  13. from .language import Language
  14. class Server(uvicorn.Server):
  15. def __init__(self, config):
  16. super().__init__(config)
  17. def run(self, sockets=None):
  18. globals.server = self
  19. app_native.method_queue = self.config.method_queue
  20. app_native.response_queue = self.config.response_queue
  21. if app_native.method_queue is not None:
  22. globals.app.native.main_window = app_native.WindowProxy()
  23. super().run(sockets=sockets)
  24. def run(*,
  25. host: Optional[str] = None,
  26. port: int = 8080,
  27. title: str = 'NiceGUI',
  28. viewport: str = 'width=device-width, initial-scale=1',
  29. favicon: Optional[str] = None,
  30. dark: Optional[bool] = False,
  31. language: Language = 'en-US',
  32. binding_refresh_interval: float = 0.1,
  33. show: bool = True,
  34. native: bool = False,
  35. window_size: Optional[Tuple[int, int]] = None,
  36. fullscreen: bool = False,
  37. reload: bool = True,
  38. uvicorn_logging_level: str = 'warning',
  39. uvicorn_reload_dirs: str = '.',
  40. uvicorn_reload_includes: str = '*.py',
  41. uvicorn_reload_excludes: str = '.*, .py[cod], .sw.*, ~*',
  42. exclude: str = '',
  43. tailwind: bool = True,
  44. **kwargs: Any,
  45. ) -> None:
  46. '''ui.run
  47. You can call `ui.run()` with optional arguments:
  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`)
  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 show: automatically open the UI in a browser tab (default: `True`)
  57. :param native: open the UI in a native window of size 800x600 (default: `False`, deactivates `show`, automatically finds an open port)
  58. :param window_size: open the UI in a native window with the provided size (e.g. `(1024, 786)`, default: `None`, also activates `native`)
  59. :param fullscreen: open the UI in a fullscreen window (default: `False`, also activates `native`)
  60. :param reload: automatically reload the UI on file changes (default: `True`)
  61. :param uvicorn_logging_level: logging level for uvicorn server (default: `'warning'`)
  62. :param uvicorn_reload_dirs: string with comma-separated list for directories to be monitored (default is current working directory only)
  63. :param uvicorn_reload_includes: string with comma-separated list of glob-patterns which trigger reload on modification (default: `'.py'`)
  64. :param uvicorn_reload_excludes: string with comma-separated list of glob-patterns which should be ignored for reload (default: `'.*, .py[cod], .sw.*, ~*'`)
  65. :param exclude: comma-separated string to exclude elements (with corresponding JavaScript libraries) to save bandwidth
  66. (possible entries: aggrid, audio, chart, colors, interactive_image, joystick, keyboard, log, markdown, mermaid, plotly, scene, video)
  67. :param tailwind: whether to use Tailwind (experimental, default: `True`)
  68. :param kwargs: additional keyword arguments are passed to `uvicorn.run`
  69. '''
  70. globals.ui_run_has_been_called = True
  71. globals.reload = reload
  72. globals.title = title
  73. globals.viewport = viewport
  74. globals.favicon = favicon
  75. globals.dark = dark
  76. globals.language = language
  77. globals.binding_refresh_interval = binding_refresh_interval
  78. globals.excludes = [e.strip() for e in exclude.split(',')]
  79. globals.tailwind = tailwind
  80. if multiprocessing.current_process().name != 'MainProcess':
  81. return
  82. if reload and not hasattr(__main__, '__file__'):
  83. logging.warning('auto-reloading is only supported when running from a file')
  84. globals.reload = reload = False
  85. if fullscreen:
  86. native = True
  87. if window_size:
  88. native = True
  89. if native:
  90. show = False
  91. host = host or '127.0.0.1'
  92. port = native_mode.find_open_port()
  93. width, height = window_size or (800, 600)
  94. native_mode.activate(host, port, title, width, height, fullscreen)
  95. else:
  96. host = host or '0.0.0.0'
  97. # NOTE: We save host and port in environment variables so the subprocess started in reload mode can access them.
  98. os.environ['NICEGUI_HOST'] = host
  99. os.environ['NICEGUI_PORT'] = str(port)
  100. if show:
  101. helpers.schedule_browser(host, port)
  102. def split_args(args: str) -> List[str]:
  103. return [a.strip() for a in args.split(',')]
  104. # NOTE: The following lines are basically a copy of `uvicorn.run`, but keep a reference to the `server`.
  105. config = uvicorn.Config(
  106. 'nicegui:app' if reload else globals.app,
  107. host=host,
  108. port=port,
  109. reload=reload,
  110. reload_includes=split_args(uvicorn_reload_includes) if reload else None,
  111. reload_excludes=split_args(uvicorn_reload_excludes) if reload else None,
  112. reload_dirs=split_args(uvicorn_reload_dirs) if reload else None,
  113. log_level=uvicorn_logging_level,
  114. **kwargs,
  115. )
  116. config.method_queue = app_native.method_queue if native else None
  117. config.response_queue = app_native.response_queue if native else None
  118. globals.server = Server(config=config)
  119. if (reload or config.workers > 1) and not isinstance(config.app, str):
  120. logging.warning('You must pass the application as an import string to enable "reload" or "workers".')
  121. sys.exit(1)
  122. if config.should_reload:
  123. sock = config.bind_socket()
  124. ChangeReload(config, target=globals.server.run, sockets=[sock]).run()
  125. elif config.workers > 1:
  126. sock = config.bind_socket()
  127. Multiprocess(config, target=globals.server.run, sockets=[sock]).run()
  128. else:
  129. globals.server.run()
  130. if config.uds:
  131. os.remove(config.uds) # pragma: py-win32
  132. if not globals.server.started and not config.should_reload and config.workers == 1:
  133. sys.exit(STARTUP_FAILURE)