nicegui.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import asyncio
  2. import builtins
  3. import urllib.parse
  4. from pathlib import Path
  5. from typing import Dict, Optional
  6. from fastapi import FastAPI, HTTPException, Request
  7. from fastapi.middleware.gzip import GZipMiddleware
  8. from fastapi.responses import FileResponse
  9. from fastapi.staticfiles import StaticFiles
  10. from fastapi_socketio import SocketManager
  11. from . import binding, globals, vue
  12. from .client import Client
  13. from .error import error_content
  14. from .favicon import create_favicon_routes
  15. from .helpers import safe_invoke
  16. from .page import page
  17. from .task_logger import create_task
  18. globals.app = app = builtins.nicegui_APP if hasattr(builtins, 'nicegui_APP') else FastAPI()
  19. globals.sio = sio = SocketManager(app=app)._sio
  20. app.add_middleware(GZipMiddleware)
  21. app.mount('/static', StaticFiles(directory=Path(__file__).parent / 'static'), name='static')
  22. globals.index_client = Client(page('/'), shared=True).__enter__()
  23. @app.get('/')
  24. def index(request: Request) -> str:
  25. return globals.index_client.build_response(request)
  26. @app.get('/_vue/dependencies/{path:path}')
  27. def vue_dependencies(path: str):
  28. if Path(path).exists() and vue.is_js_dependency(Path(path)):
  29. return FileResponse(path, media_type='text/javascript')
  30. raise HTTPException(status_code=404, detail=f'{path} not found')
  31. @app.get('/_vue/components/{name}')
  32. def vue_dependencies(name: str):
  33. return FileResponse(vue.js_components[name], media_type='text/javascript')
  34. @app.on_event('startup')
  35. def on_startup() -> None:
  36. globals.state = globals.State.STARTING
  37. globals.loop = asyncio.get_running_loop()
  38. create_favicon_routes()
  39. [safe_invoke(t) for t in globals.startup_handlers]
  40. create_task(binding.loop())
  41. globals.state = globals.State.STARTED
  42. print(f'NiceGUI ready to go on http://{globals.host}:{globals.port}')
  43. @app.on_event('shutdown')
  44. def shutdown() -> None:
  45. globals.state = globals.State.STOPPING
  46. [safe_invoke(t) for t in globals.shutdown_handlers]
  47. [t.cancel() for t in globals.tasks]
  48. globals.state = globals.State.STOPPED
  49. @app.exception_handler(404)
  50. async def exception_handler(request: Request, exception: Exception):
  51. globals.log.warning(f'{request.url} not found')
  52. with Client(page('')) as client:
  53. error_content(404, exception)
  54. return client.build_response(request, 404)
  55. @app.exception_handler(Exception)
  56. async def exception_handler(request: Request, exception: Exception):
  57. with Client(page('')) as client:
  58. error_content(500, exception)
  59. return client.build_response(request, 500)
  60. @sio.on('connect')
  61. async def handle_connect(sid: str, _) -> None:
  62. client = get_client(sid)
  63. if not client:
  64. return
  65. client.environ = sio.get_environ(sid)
  66. sio.enter_room(sid, str(client.id))
  67. with client:
  68. [safe_invoke(t) for t in client.connect_handlers]
  69. @sio.on('disconnect')
  70. async def handle_disconnect(sid: str) -> None:
  71. client = get_client(sid)
  72. if not client:
  73. return
  74. if not client.shared:
  75. del globals.clients[client.id]
  76. with client:
  77. [safe_invoke(t) for t in client.disconnect_handlers]
  78. @sio.on('event')
  79. def handle_event(sid: str, msg: Dict) -> None:
  80. client = get_client(sid)
  81. if not client:
  82. return
  83. with client:
  84. sender = client.elements.get(msg['id'])
  85. if sender:
  86. sender.handle_event(msg)
  87. @sio.on('javascript_response')
  88. def handle_event(sid: str, msg: Dict) -> None:
  89. client = get_client(sid)
  90. if not client:
  91. return
  92. client.waiting_javascript_commands[msg['request_id']] = msg['result']
  93. def get_client(sid: str) -> Optional[Client]:
  94. query_bytes: bytearray = sio.get_environ(sid)['asgi.scope']['query_string']
  95. query = urllib.parse.parse_qs(query_bytes.decode())
  96. client_id = int(query['client_id'][0])
  97. return globals.clients.get(client_id)