upload.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from typing import Dict, List, Optional, cast
  2. from fastapi import Request
  3. from starlette.datastructures import UploadFile
  4. from typing_extensions import Self
  5. from ..events import Handler, MultiUploadEventArguments, UiEventArguments, UploadEventArguments, handle_event
  6. from ..nicegui import app
  7. from .mixins.disableable_element import DisableableElement
  8. class Upload(DisableableElement, component='upload.js'):
  9. def __init__(self, *,
  10. multiple: bool = False,
  11. max_file_size: Optional[int] = None,
  12. max_total_size: Optional[int] = None,
  13. max_files: Optional[int] = None,
  14. on_upload: Optional[Handler[UploadEventArguments]] = None,
  15. on_multi_upload: Optional[Handler[MultiUploadEventArguments]] = None,
  16. on_rejected: Optional[Handler[UiEventArguments]] = None,
  17. label: str = '',
  18. auto_upload: bool = False,
  19. ) -> None:
  20. """File Upload
  21. Based on Quasar's `QUploader <https://quasar.dev/vue-components/uploader>`_ component.
  22. :param multiple: allow uploading multiple files at once (default: `False`)
  23. :param max_file_size: maximum file size in bytes (default: `0`)
  24. :param max_total_size: maximum total size of all files in bytes (default: `0`)
  25. :param max_files: maximum number of files (default: `0`)
  26. :param on_upload: callback to execute for each uploaded file
  27. :param on_multi_upload: callback to execute after multiple files have been uploaded
  28. :param on_rejected: callback to execute for each rejected file
  29. :param label: label for the uploader (default: `''`)
  30. :param auto_upload: automatically upload files when they are selected (default: `False`)
  31. """
  32. super().__init__()
  33. self._props['multiple'] = multiple
  34. self._props['label'] = label
  35. self._props['auto-upload'] = auto_upload
  36. self._props['url'] = f'/_nicegui/client/{self.client.id}/upload/{self.id}'
  37. if max_file_size is not None:
  38. self._props['max-file-size'] = max_file_size
  39. if max_total_size is not None:
  40. self._props['max-total-size'] = max_total_size
  41. if max_files is not None:
  42. self._props['max-files'] = max_files
  43. if multiple and on_multi_upload:
  44. self._props['batch'] = True
  45. self._upload_handlers = [on_upload] if on_upload else []
  46. self._multi_upload_handlers = [on_multi_upload] if on_multi_upload else []
  47. @app.post(self._props['url'])
  48. async def upload_route(request: Request) -> Dict[str, str]:
  49. form = await request.form()
  50. uploads = [cast(UploadFile, data) for data in form.values()]
  51. self.handle_uploads(uploads)
  52. return {'upload': 'success'}
  53. if on_rejected:
  54. self.on_rejected(on_rejected)
  55. def handle_uploads(self, uploads: List[UploadFile]) -> None:
  56. """Handle the uploaded files.
  57. This method is primarily intended for internal use and for simulating file uploads in tests.
  58. """
  59. for upload in uploads:
  60. for upload_handler in self._upload_handlers:
  61. handle_event(upload_handler, UploadEventArguments(
  62. sender=self,
  63. client=self.client,
  64. content=upload.file,
  65. name=upload.filename or '',
  66. type=upload.content_type or '',
  67. ))
  68. multi_upload_args = MultiUploadEventArguments(
  69. sender=self,
  70. client=self.client,
  71. contents=[upload.file for upload in uploads],
  72. names=[upload.filename or '' for upload in uploads],
  73. types=[upload.content_type or '' for upload in uploads],
  74. )
  75. for multi_upload_handler in self._multi_upload_handlers:
  76. handle_event(multi_upload_handler, multi_upload_args)
  77. def on_upload(self, callback: Handler[UploadEventArguments]) -> Self:
  78. """Add a callback to be invoked when a file is uploaded."""
  79. self._upload_handlers.append(callback)
  80. return self
  81. def on_multi_upload(self, callback: Handler[MultiUploadEventArguments]) -> Self:
  82. """Add a callback to be invoked when multiple files have been uploaded."""
  83. self._multi_upload_handlers.append(callback)
  84. return self
  85. def on_rejected(self, callback: Handler[UiEventArguments]) -> Self:
  86. """Add a callback to be invoked when a file is rejected."""
  87. self.on('rejected', lambda: handle_event(callback, UiEventArguments(sender=self, client=self.client)), args=[])
  88. return self
  89. def reset(self) -> None:
  90. """Clear the upload queue."""
  91. self.run_method('reset')
  92. def _handle_delete(self) -> None:
  93. app.remove_route(self._props['url'])
  94. super()._handle_delete()