1
0

section_configuration_deployment.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. from nicegui import ui
  2. from ..windows import bash_window, python_window
  3. from . import doc, run_documentation
  4. doc.title('Configuration & Deployment')
  5. @doc.demo('URLs', '''
  6. You can access the list of all URLs on which the NiceGUI app is available via `app.urls`.
  7. The URLs are not available in `app.on_startup` because the server is not yet running.
  8. Instead, you can access them in a page function or register a callback with `app.urls.on_change`.
  9. ''')
  10. def urls_demo():
  11. from nicegui import app
  12. # @ui.page('/')
  13. # def index():
  14. # for url in app.urls:
  15. # ui.link(url, target=url)
  16. # END OF DEMO
  17. ui.link('https://nicegui.io', target='https://nicegui.io')
  18. doc.intro(run_documentation)
  19. @doc.demo('Native Mode', '''
  20. You can enable native mode for NiceGUI by specifying `native=True` in the `ui.run` function.
  21. To customize the initial window size and display mode, use the `window_size` and `fullscreen` parameters respectively.
  22. Additionally, you can provide extra keyword arguments via `app.native.window_args` and `app.native.start_args`.
  23. Pick any parameter as it is defined by the internally used [pywebview module](https://pywebview.flowrl.com/guide/api.html)
  24. for the `webview.create_window` and `webview.start` functions.
  25. Note that these keyword arguments will take precedence over the parameters defined in `ui.run`.
  26. Additionally, you can change `webview.settings` via `app.native.settings`.
  27. In native mode the `app.native.main_window` object allows you to access the underlying window.
  28. It is an async version of [`Window` from pywebview](https://pywebview.flowrl.com/guide/api.html#window-object).
  29. ''', tab=lambda: ui.label('NiceGUI'))
  30. def native_mode_demo():
  31. from nicegui import app
  32. app.native.window_args['resizable'] = False
  33. app.native.start_args['debug'] = True
  34. app.native.settings['ALLOW_DOWNLOADS'] = True
  35. ui.label('app running in native mode')
  36. # ui.button('enlarge', on_click=lambda: app.native.main_window.resize(1000, 700))
  37. #
  38. # ui.run(native=True, window_size=(400, 300), fullscreen=False)
  39. # END OF DEMO
  40. ui.button('enlarge', on_click=lambda: ui.notify('window will be set to 1000x700 in native mode'))
  41. # Currently, options passed via app.native are not used if they are set behind a main guard
  42. # See discussion at: https://github.com/zauberzeug/nicegui/pull/4627
  43. doc.text('', '''
  44. Note that the native app is run in a separate
  45. [process](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process).
  46. Therefore any configuration changes from code run under a
  47. [main guard](https://docs.python.org/3/library/__main__.html#idiomatic-usage) is ignored by the native app.
  48. The following examples show the difference between a working and a non-working configuration.
  49. ''')
  50. @doc.ui
  51. def native_main_guard():
  52. with ui.row().classes('w-full items-stretch'):
  53. with python_window('good_example.py', classes='max-w-lg w-full'):
  54. ui.markdown('''
  55. ```python
  56. from nicegui import app, ui
  57. app.native.window_args['resizable'] = False # works
  58. if __name__ == '__main__':
  59. ui.run(native=True, reload=False)
  60. ```
  61. ''')
  62. with python_window('bad_example.py', classes='max-w-lg w-full'):
  63. ui.markdown('''
  64. ```python
  65. from nicegui import app, ui
  66. if __name__ == '__main__':
  67. app.native.window_args['resizable'] = False # ignored
  68. ui.run(native=True, reload=False)
  69. ```
  70. ''')
  71. # Show a helpful workaround until issue is fixed upstream.
  72. # For more info see: https://github.com/r0x0r/pywebview/issues/1078
  73. doc.text('', '''
  74. If webview has trouble finding required libraries, you may get an error relating to "WebView2Loader.dll".
  75. To work around this issue, try moving the DLL file up a directory, e.g.:
  76. * from `.venv/Lib/site-packages/webview/lib/x64/WebView2Loader.dll`
  77. * to `.venv/Lib/site-packages/webview/lib/WebView2Loader.dll`
  78. ''')
  79. @doc.demo('Environment Variables', '''
  80. You can set the following environment variables to configure NiceGUI:
  81. - `MATPLOTLIB` (default: true) can be set to `false` to avoid the potentially costly import of Matplotlib.
  82. This will make `ui.pyplot` and `ui.line_plot` unavailable.
  83. - `NICEGUI_STORAGE_PATH` (default: local ".nicegui") can be set to change the location of the storage files.
  84. - `MARKDOWN_CONTENT_CACHE_SIZE` (default: 1000): The maximum number of Markdown content snippets that are cached in memory.
  85. - `RST_CONTENT_CACHE_SIZE` (default: 1000): The maximum number of ReStructuredText content snippets that are cached in memory.
  86. - `NICEGUI_REDIS_URL` (default: None, means local file storage): The URL of the Redis server to use for shared persistent storage.
  87. - `NICEGUI_REDIS_KEY_PREFIX` (default: "nicegui:"): The prefix for Redis keys.
  88. ''')
  89. def env_var_demo():
  90. from nicegui.elements import markdown
  91. ui.label(f'Markdown content cache size is {markdown.prepare_content.cache_info().maxsize}')
  92. @doc.demo('Background Tasks', '''
  93. `background_tasks.create()` allows you to run an async function in the background and return a task object.
  94. By default the task will be automatically cancelled during shutdown.
  95. You can prevent this by using the `@background_tasks.await_on_shutdown` decorator.
  96. This is useful for tasks that need to be completed even when the app is shutting down.
  97. ''')
  98. def background_tasks_demo():
  99. from nicegui import background_tasks
  100. import asyncio
  101. import aiofiles
  102. results = {'answer': '?'}
  103. async def compute() -> None:
  104. await asyncio.sleep(1)
  105. results['answer'] = 42
  106. @background_tasks.await_on_shutdown
  107. async def backup() -> None:
  108. await asyncio.sleep(1)
  109. # async with aiofiles.open('backup.json', 'w') as f:
  110. # await f.write(f'{results["answer"]}')
  111. # print('backup.json written', flush=True)
  112. ui.label().bind_text_from(results, 'answer', lambda x: f'answer: {x}')
  113. ui.button('Compute', on_click=lambda: background_tasks.create(compute()))
  114. ui.button('Backup', on_click=lambda: background_tasks.create(backup()))
  115. doc.text('Custom Vue Components', '''
  116. You can create custom components by subclassing `ui.element` and implementing a corresponding Vue component.
  117. The ["Custom Vue components" example](https://github.com/zauberzeug/nicegui/tree/main/examples/custom_vue_component)
  118. demonstrates how to create a custom counter component which emits events and receives updates from the server.
  119. The ["Signature pad" example](https://github.com/zauberzeug/nicegui/blob/main/examples/signature_pad)
  120. shows how to define dependencies for a custom component using a `package.json` file.
  121. This allows you to use third-party libraries via NPM in your component.
  122. Last but not least, the ["Node module integration" example](https://github.com/zauberzeug/nicegui/blob/main/examples/node_module_integration)
  123. demonstrates how to create a package.json file and a webpack.config.js file to bundle a custom Vue component with its dependencies.
  124. ''')
  125. doc.text('Server Hosting', '''
  126. To deploy your NiceGUI app on a server, you will need to execute your `main.py` (or whichever file contains your `ui.run(...)`) on your cloud infrastructure.
  127. You can, for example, just install the [NiceGUI python package via pip](https://pypi.org/project/nicegui/) and use systemd or similar service to start the main script.
  128. In most cases, you will set the port to 80 (or 443 if you want to use HTTPS) with the `ui.run` command to make it easily accessible from the outside.
  129. A convenient alternative is the use of our [pre-built multi-arch Docker image](https://hub.docker.com/r/zauberzeug/nicegui) which contains all necessary dependencies.
  130. With this command you can launch the script `main.py` in the current directory on the public port 80:
  131. ''')
  132. @doc.ui
  133. def docker_run():
  134. with bash_window(classes='max-w-lg w-full h-44'):
  135. ui.markdown('''
  136. ```bash
  137. docker run -it --restart always \\
  138. -p 80:8080 \\
  139. -e PUID=$(id -u) \\
  140. -e PGID=$(id -g) \\
  141. -v $(pwd)/:/app/ \\
  142. zauberzeug/nicegui:latest
  143. ```
  144. ''')
  145. doc.text('', '''
  146. The demo assumes `main.py` uses the port 8080 in the `ui.run` command (which is the default).
  147. The `-d` tells docker to run in background and `--restart always` makes sure the container is restarted if the app crashes or the server reboots.
  148. Of course this can also be written in a Docker compose file:
  149. ''')
  150. @doc.ui
  151. def docker_compose():
  152. with python_window('docker-compose.yml', classes='max-w-lg w-full h-60'):
  153. ui.markdown('''
  154. ```yaml
  155. app:
  156. image: zauberzeug/nicegui:latest
  157. restart: always
  158. ports:
  159. - 80:8080
  160. environment:
  161. - PUID=1000 # change this to your user id
  162. - PGID=1000 # change this to your group id
  163. volumes:
  164. - ./:/app/
  165. ```
  166. ''')
  167. doc.text('', '''
  168. There are other handy features in the Docker image like non-root user execution and signal pass-through.
  169. For more details we recommend to have a look at our [Docker example](https://github.com/zauberzeug/nicegui/tree/main/examples/docker_image).
  170. To serve your application with [HTTPS](https://fastapi.tiangolo.com/deployment/https/) encryption, you can provide SSL certificates in multiple ways.
  171. For instance, you can directly provide your certificates to [Uvicorn](https://www.uvicorn.org/), which NiceGUI is based on, by passing the
  172. relevant [options](https://www.uvicorn.org/#command-line-options) to `ui.run()`:
  173. ''')
  174. @doc.ui
  175. def uvicorn_ssl():
  176. with python_window('main.py', classes='max-w-lg w-full'):
  177. ui.markdown('''
  178. ```python
  179. from nicegui import ui
  180. ui.run(
  181. port=443,
  182. ssl_certfile="<path_to_certfile>",
  183. ssl_keyfile="<path_to_keyfile>",
  184. )
  185. ```
  186. ''')
  187. doc.text('', '''
  188. In production we also like using reverse proxies like [Traefik](https://doc.traefik.io/traefik/) or [NGINX](https://www.nginx.com/) to handle these details for us.
  189. See our development [docker-compose.yml](https://github.com/zauberzeug/nicegui/blob/main/docker-compose.yml) as an example based on traefik or
  190. [this example nginx.conf file](https://github.com/zauberzeug/nicegui/blob/main/examples/nginx_https/nginx.conf) showing how NGINX can be used to handle the SSL certificates and
  191. reverse proxy to your NiceGUI app.
  192. You may also have a look at [our demo for using a custom FastAPI app](https://github.com/zauberzeug/nicegui/tree/main/examples/fastapi).
  193. This will allow you to do very flexible deployments as described in the [FastAPI documentation](https://fastapi.tiangolo.com/deployment/).
  194. Note that there are additional steps required to allow multiple workers.
  195. ''')
  196. doc.text('Package for Installation', '''
  197. NiceGUI apps can also be bundled into an executable with `nicegui-pack` which is based on [PyInstaller](https://www.pyinstaller.org/).
  198. This allows you to distribute your app as a single file that can be executed on any computer.
  199. Just make sure to call `ui.run` with `reload=False` in your main script to disable the auto-reload feature.
  200. Running the `nicegui-pack` command below will create an executable `myapp` in the `dist` folder:
  201. ''')
  202. @doc.ui
  203. def pyinstaller():
  204. with ui.row().classes('w-full items-stretch'):
  205. with python_window(classes='max-w-lg w-full'):
  206. ui.markdown('''
  207. ```python
  208. from nicegui import native, ui
  209. ui.label('Hello from PyInstaller')
  210. ui.run(reload=False, port=native.find_open_port())
  211. ```
  212. ''')
  213. with bash_window(classes='max-w-lg w-full'):
  214. ui.markdown('''
  215. ```bash
  216. nicegui-pack --onefile --name "myapp" main.py
  217. ```
  218. ''')
  219. doc.text('', '''
  220. **Packaging Tips:**
  221. - When building a PyInstaller app, your main script can use a native window (rather than a browser window) by
  222. using `ui.run(reload=False, native=True)`.
  223. The `native` parameter can be `True` or `False` depending on whether you want a native window or to launch a
  224. page in the user's browser - either will work in the PyInstaller generated app.
  225. - Specifying `--windowed` to `nicegui-pack` will prevent a terminal console from appearing.
  226. However you should only use this option if you have also specified `native=True` in your `ui.run` command.
  227. Without a terminal console the user won't be able to exit the app by pressing Ctrl-C.
  228. With the `native=True` option, the app will automatically close when the window is closed, as expected.
  229. - Specifying `--windowed` to `nicegui-pack` will create an `.app` file on Mac which may be more convenient to distribute.
  230. When you double-click the app to run it, it will not show any console output.
  231. You can also run the app from the command line with `./myapp.app/Contents/MacOS/myapp` to see the console output.
  232. - Specifying `--onefile` to `nicegui-pack` will create a single executable file.
  233. Whilst convenient for distribution, it will be slower to start up.
  234. This is not NiceGUI's fault but just the way Pyinstaller zips things into a single file, then unzips everything
  235. into a temporary directory before running.
  236. You can mitigate this by removing `--onefile` from the `nicegui-pack` command,
  237. and zip up the generated `dist` directory yourself, distribute it,
  238. and your end users can unzip once and be good to go,
  239. without the constant expansion of files due to the `--onefile` flag.
  240. - Summary of user experience for different options:
  241. | `nicegui-pack` | `ui.run(...)` | Explanation |
  242. | :--- | :--- | :--- |
  243. | `onefile` | `native=False` | Single executable generated in `dist/`, runs in browser |
  244. | `onefile` | `native=True` | Single executable generated in `dist/`, runs in popup window |
  245. | `onefile` and `windowed` | `native=True` | Single executable generated in `dist/` (on Mac a proper `dist/myapp.app` generated incl. icon), runs in popup window, no console appears |
  246. | `onefile` and `windowed` | `native=False` | Avoid (no way to exit the app) |
  247. | Specify neither | | A `dist/myapp` directory created which can be zipped manually and distributed; run with `dist/myapp/myapp` |
  248. - If you are using a Python virtual environment, ensure you `pip install pyinstaller` within your virtual environment
  249. so that the correct PyInstaller is used, or you may get broken apps due to the wrong version of PyInstaller being picked up.
  250. That is why the `nicegui-pack` invokes PyInstaller using `python -m PyInstaller` rather than just `pyinstaller`.
  251. ''')
  252. @doc.ui
  253. def install_pyinstaller():
  254. with bash_window(classes='max-w-lg w-full h-42 self-center'):
  255. ui.markdown('''
  256. ```bash
  257. python -m venv venv
  258. source venv/bin/activate
  259. pip install nicegui
  260. pip install pyinstaller
  261. ```
  262. ''')
  263. doc.text('', '''
  264. Note:
  265. If you're getting an error "TypeError: a bytes-like object is required, not 'str'", try adding the following lines to the top of your `main.py` file:
  266. ```py
  267. import sys
  268. sys.stdout = open('logs.txt', 'w')
  269. ```
  270. See <https://github.com/zauberzeug/nicegui/issues/681> for more information.
  271. ''')
  272. doc.text('', '''
  273. **macOS Packaging**
  274. Add the following snippet before anything else in your main app's file, to prevent new processes from being spawned in an endless loop:
  275. ```python
  276. # macOS packaging support
  277. from multiprocessing import freeze_support # noqa
  278. freeze_support() # noqa
  279. # all your other imports and code
  280. ```
  281. The `# noqa` comment instructs Pylance or autopep8 to not apply any PEP rule on those two lines, guaranteeing they remain on top of anything else.
  282. This is key to prevent process spawning.
  283. ''')
  284. doc.text('NiceGUI On Air', '''
  285. By using `ui.run(on_air=True)` you can share your local app with others over the internet 🧞.
  286. When accessing the on-air URL, all libraries (like Vue, Quasar, ...) are loaded from our CDN.
  287. Thereby only the raw content and events need to be transmitted by your local app.
  288. This makes it blazing fast even if your app only has a poor internet connection (e.g. a mobile robot in the field).
  289. By setting `on_air=True` you will get a random URL which is valid for 1 hour.
  290. If you sign-up at <https://on-air.nicegui.io>, you can setup an organization and device name to get a fixed URL:
  291. `https://on-air.nicegui.io/<my-org>/<my_device_name>`.
  292. The device is then identified by a unique, private token which you can use instead of a boolean flag: `ui.run(on_air='<your token>')`.
  293. If you [sponsor us](https://github.com/sponsors/zauberzeug),
  294. we will enable multi-device management and provide built-in passphrase protection for each device.
  295. Currently On Air is available as a tech preview and can be used free of charge.
  296. We will gradually improve stability and extend the service with usage statistics, remote terminal access and more.
  297. Please let us know your feedback on [GitHub](https://github.com/zauberzeug/nicegui/discussions),
  298. [Reddit](https://www.reddit.com/r/nicegui/), or [Discord](https://discord.gg/TEpFeAaF4f).
  299. **Data Privacy:**
  300. We take your privacy very serious.
  301. NiceGUI On Air does not log or store any content of the relayed data.
  302. ''')