From e5e6f405fd6c48b4c33b2d43f7ad57ce757fefff Mon Sep 17 00:00:00 2001 From: Huseyin Kaya Date: Sun, 24 Mar 2024 18:24:05 +0300 Subject: [PATCH] refactor: added _FileDrop to prevent repetitions --- solara/components/file_drop.py | 125 +++++++++++++++------------------ 1 file changed, 56 insertions(+), 69 deletions(-) diff --git a/solara/components/file_drop.py b/solara/components/file_drop.py index 45103f5a5..1625000c8 100644 --- a/solara/components/file_drop.py +++ b/solara/components/file_drop.py @@ -1,8 +1,7 @@ import threading import typing -from typing import Callable, List, Optional, cast +from typing import Callable, List, Optional, Union, cast -import ipyvuetify import traitlets from ipyvue import Template from ipyvuetify.extra import FileInput @@ -30,40 +29,22 @@ class FileDropZone(FileInput): @solara.component -def FileDrop( - label="Drop file here", +def _FileDrop( + label="Drop file(s) here", on_total_progress: Optional[Callable[[float], None]] = None, - on_file: Optional[Callable[[FileInfo], None]] = None, + on_file: Optional[Callable[[Union[FileInfo, List[FileInfo]]], None]] = None, lazy: bool = True, -) -> ipyvuetify.extra.FileInput: - """Region a user can drop a file into for file uploading. - - If lazy=True, no file content will be loaded into memory, - nor will any data be transferred by default. - If lazy=False, file content will be loaded into memory and passed to the `on_file` callback via the `FileInfo.data` attribute. - - - A file object is of the following argument type: - ```python - class FileInfo(typing.TypedDict): - name: str # file name - size: int # file size in bytes - file_obj: typing.BinaryIO - data: Optional[bytes]: bytes # only present if lazy=False - ``` - - - ## Arguments - * `on_total_progress`: Will be called with the progress in % of the file upload. - * `on_file`: Will be called with a `FileInfo` object, which contains the file `.name`, `.length` and a `.file_obj` object. - * `lazy`: Whether to load the file contents into memory or not. If `False`, - the file contents will be loaded into memory via the `.data` attribute of file object(s). + multiple: bool = False, +): + """Generic implementation used by FileDrop and FileDropMultiple. + If multiple=True, multiple files can be uploaded. """ + file_info, set_file_info = solara.use_state(None) wired_files, set_wired_files = solara.use_state(cast(Optional[typing.List[FileInfo]], None)) - file_drop = FileDropZone.element(label=label, on_total_progress=on_total_progress, on_file_info=set_file_info, multiple=False) # type: ignore + file_drop = FileDropZone.element(label=label, on_total_progress=on_total_progress, on_file_info=set_file_info, multiple=multiple) # type: ignore def wire_files(): if not file_info: @@ -83,11 +64,15 @@ def handle_file(cancel: threading.Event): if not wired_files: return if on_file: - if not lazy: - wired_files[0]["data"] = wired_files[0]["file_obj"].read() + for i in range(len(wired_files)): + if not lazy: + wired_files[i]["data"] = wired_files[i]["file_obj"].read() + else: + wired_files[i]["data"] = None + if multiple: + on_file(wired_files) else: - wired_files[0]["data"] = None - on_file(wired_files[0]) + on_file(wired_files[0]) result: solara.Result = hooks.use_thread(handle_file, [wired_files]) if result.error: @@ -96,16 +81,51 @@ def handle_file(cancel: threading.Event): return file_drop +@solara.component +def FileDrop( + label="Drop file here", + on_total_progress: Optional[Callable[[float], None]] = None, + on_file: Optional[Callable[[FileInfo], None]] = None, + lazy: bool = True, +): + """Region a user can drop a file into for file uploading. + + If lazy=True, no file content will be loaded into memory, + nor will any data be transferred by default. + If lazy=False, file content will be loaded into memory and passed to the `on_file` callback via the `FileInfo.data` attribute. + + + A file object is of the following argument type: + ```python + class FileInfo(typing.TypedDict): + name: str # file name + size: int # file size in bytes + file_obj: typing.BinaryIO + data: Optional[bytes]: bytes # only present if lazy=False + ``` + + + ## Arguments + * `on_total_progress`: Will be called with the progress in % of the file upload. + * `on_file`: Will be called with a `FileInfo` object, which contains the file `.name`, `.length` and a `.file_obj` object. + * `lazy`: Whether to load the file contents into memory or not. If `False`, + the file contents will be loaded into memory via the `.data` attribute of file object(s). + + """ + + return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=False) + + @solara.component def FileDropMultiple( label="Drop files here", on_total_progress: Optional[Callable[[float], None]] = None, on_file: Optional[Callable[[List[FileInfo]], None]] = None, lazy: bool = True, -) -> ipyvuetify.extra.FileInput: +): """Region a user can drop multiple files into for file uploading. - Almost identical to `FileDrop` except that `on_file` is called + Almost identical to `FileDrop` except that multiple files can be dropped and `on_file` is called with a list of `FileInfo` objects. ## Arguments @@ -115,38 +135,5 @@ def FileDropMultiple( * `lazy`: Whether to load the file contents into memory or not. """ - file_info, set_file_info = solara.use_state(None) - wired_files, set_wired_files = solara.use_state(cast(Optional[typing.List[FileInfo]], None)) - - file_drop = FileDropZone.element(label=label, on_total_progress=on_total_progress, on_file_info=set_file_info, multiple=True) # type: ignore - - def wire_files(): - if not file_info: - return - - real = cast(FileDropZone, solara.get_widget(file_drop)) - - # workaround for @observe being cleared - real.version += 1 - real.reset_stats() - - set_wired_files(cast(typing.List[FileInfo], real.get_files())) - - solara.use_side_effect(wire_files, [file_info]) - - def handle_file(cancel: threading.Event): - if not wired_files: - return - if on_file: - for i in range(len(wired_files)): - if not lazy: - wired_files[i]["data"] = wired_files[i]["file_obj"].read() - else: - wired_files[i]["data"] = None - on_file(wired_files) - - result: solara.Result = hooks.use_thread(handle_file, [wired_files]) - if result.error: - raise result.error - return file_drop + return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=True)