Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request FileUpload widget #1542

Closed
draperjames opened this issue Jul 26, 2017 · 54 comments
Closed

Feature request FileUpload widget #1542

draperjames opened this issue Jul 26, 2017 · 54 comments
Labels
good first issue resolved-locked Closed issues are locked after 30 days inactivity. Please open a new issue for related discussion.
Milestone

Comments

@draperjames
Copy link

Objective

Create a ipywidget capable of uploading files (either a list of paths as strings and/or their binary contents)

Ideally a user would initialize this widget, display it, then on making a selection a list of file paths as strings would then be available to access through jupyter notebook.

Starting points

I've start on this already but I'm currently working out some kinks see this gist. A janky but functional tkinter based solution can be found at this gist.

I plan to submit a PR of the first method soon as I can get it to work.

Long term goals

While have been thinking narrowly about just retrieving the file paths as string @jasongrout mentioned that file contents could be read directly from the widget by utilizing the file API along with a file reader. That way files can be uploaded even if the browser and kernel are on not the same machine!

So I'm filing this issue to hold my self accountable and actually create the PR and to get the discussion started.

All feedback is welcome.

@pbugnion
Copy link
Member

A file upload widget sounds great! Should this be a core widget or a separate widget library? Either way, I think this would be a great contribution to the widget ecosystem.

@draperjames
Copy link
Author

I think that this would work well as a core widget b/c it leverages basic HTML/javascript properties without having to add any more outside javascript libraries. Also I think this widget should be easy for beginners to use. If it were maintained as a separate package then that makes it that much harder for them.

@jasongrout jasongrout added this to the Future milestone Jul 28, 2017
@cloga
Copy link

cloga commented Jul 28, 2017

I find this https://github.com/peteut/ipython-file-upload

@jasongrout
Copy link
Member

Thanks for pointing that out, @cloga.

@mlucool
Copy link
Contributor

mlucool commented Aug 30, 2018

@jasongrout are there any plans to add that or a similar widget to core jupyter widgets? I think this would be super useful as well

@jasongrout
Copy link
Member

I think someone should just submit a PR (i.e., a PR for a widget that accepts a file upload and sets the value of the widget to the contents of the file). Sounds like a great widget to have!

@jasongrout
Copy link
Member

jasongrout commented Aug 30, 2018

I'll mark it as a good (advanced) first issue - create a widget that syncs back to the kernel the contents of a selected file. I would imagine such a widget has an html file input element, and it uses the file reader api to get the contents of the file, which it then syncs as the widget value.

@mlucool
Copy link
Contributor

mlucool commented Aug 31, 2018

@jasongrout I have started working on a PR, but I was unable to use traitlets.Bytes correctly. I am not sure how to set this in JS (except to pass a string, which does not work in all cases). Ideally these should be a List as this widget accepts multiple. I want to make multiple configurable from python, but always set selected values into to a list. Any objection to using file_uploader.values of type traitlets.List over file_uploader.value of type traitlets.Bytes always? This is not inline with the general APIs for the rest of the widgets which has a single value nor will it be efficient for large files, which is why I ask.

@jasongrout
Copy link
Member

How often do you think people will need to upload multiple files to such a widget (as opposed to creating multiple file upload widgets)? To answer user-defined questions like this, it might be helpful to write down a couple of scenarios where this widget is useful.

@maartenbreddels
Copy link
Member

maartenbreddels commented Sep 1, 2018 via email

@mlucool
Copy link
Contributor

mlucool commented Sep 1, 2018

How often do you think people will need to upload multiple files to such a widget (as opposed to creating multiple file upload widgets)?

Although I agree that a single file may be the best default and the most common case, I think this still is pretty useful. E.g. uploading a folder of pictures or CSVs for processing would be painful without this. For applications that I am interested in using this in, I would use it in multi mode and let the users of the shared notebook decide if they want to upload more than one file and "do the right thing" with their input. It's a less limiting experience.

@mlucool
Copy link
Contributor

mlucool commented Sep 15, 2018

#2211 has been created as a start.

There are a few issues we should conclude on in this thread:

  1. value vs. values. What is the recommendation here at at times this widgets will have multiple values. Should I reuse value so that it can be a ValueWidget?
  2. I was unable to get the documentation to update, any tips?
  3. I was unable to use Bytes for transferring content, so it is the larger base64 strings. If someone knows how to do it, please add a commit. If not, this will be a backwards compatible enhancement later as data is transferred on a private variable.
  4. In the case that some files do not upload, what should we do? Should the python widget go to an error state? Should we add something on the UI?
  5. Should we add in state to show the files are being uploaded from the browser to the kernel? For large files this may be useful.
  6. Should we support a description? Did I miss anything else that most widgets have?

I'll also note that I had problems getting the dev-install.py to work. It turns out that it did not create a symlink so my python code did work, but the JS code did not. This took a while to debug. Another thing I wish there was from a dev perspective, was a continuous build (i.e. a JS file watcher that built every time a file is changed).

@mlucool
Copy link
Contributor

mlucool commented Sep 22, 2018

@jasongrout @maartenbreddels Any thoughts on the previous update?

@maartenbreddels
Copy link
Member

Hi Marc,

great work!
I would say, just use .value, and make it a Union, a Bytes | List(Bytes) trait, although I don't think maybe people are a fan of unions.
For non-base 64, please take a look at the Image widget:
https://github.com/jupyter-widgets/ipywidgets/blob/master/packages/controls/src/widget_image.ts

class Image(_Media):

https://github.com/jupyter-widgets/ipywidgets/blob/7c97f2d3f87ef1c3c35e2d7df65e27186a10bd6d/packages/controls/src/widget_image.ts
If something does not work, check the WebSocket frames for 'lost' error messages.
Could there be a progress trait for (float between 0 and 1) that can be used to connect to the progress widget?

@jasongrout
Copy link
Member

although I don't think maybe people are a fan of unions.

raises hand - I'm not particularly a fan of unions. I think I'd rather it was a list always, than have it be sometimes a list and sometimes a value.

@jasongrout
Copy link
Member

I'll also note that I had problems getting the dev-install.py to work. It turns out that it did not create a symlink so my phone code did work, but the JS code did not.

Is this on windows? IIRC symlinks won't work on windows.

@vidartf
Copy link
Member

vidartf commented Sep 26, 2018

I think I'd rather it was a list always

I'd rather it was a TypedTuple, as List is just confusing for widgets.

@mlucool
Copy link
Contributor

mlucool commented Sep 26, 2018

Is this on windows? IIRC symlinks won't work on windows.

This was on osx

raises hand - I'm not particularly a fan of unions. I think I'd rather it was a list always, than have it be sometimes a list and sometimes a value.

The to clearer on this, Bytes will not work as we do need to send other data along with the contents (name, contents, type, and lastModified to start). We can send parallel arrays so that we can use Bytes as the transfer medium.

Do you have a problem if .value is List of objects always?

@jasongrout
Copy link
Member

I'd rather it was a TypedTuple, as List is just confusing for widgets.

Right, I forgot that that is our new recommendation.

@jasongrout
Copy link
Member

Do you have a problem if .value is List of objects always?

I think having consistent typing is easier to reason about. I'm a little torn, though, since it would be convenient to link, say, an image source with a file upload value.

How often do you think a single file will be wanted vs multiple files?

@rskabelund
Copy link

rskabelund commented Sep 27, 2018

This is going to be great! I vote for multiple files as an option please, I created an upload button widget for this purpose, but it has issues when a user selects too many files. I suspect it is an issue with the async event timing since I didn't know how to use promises at the time. Can't wait to see what you guys come up with and would be happy to help test it.

I also had envisioned having a drag drop upload widget that could be attached to box widget somehow.

-Randy

@mlucool
Copy link
Contributor

mlucool commented Sep 27, 2018

How often do you think a single file will be wanted vs multiple files?

As a user, I would be happy knowing my value was always a list than sometimes a list. I think multi file will be too common to ignore or make hard.

I have now added a loader div, but am unable to give progress percent. It turns out that for large files the time is dominated by sending it from the browser to the kernel, which we have no way of keeping a tab on. Therefore, the loader div is enabled by the browser and disabled when the python side thinks everything is loaded. You could observe f.loading if you wanted to know if it was processing or not.

I have also gone back to using two lists, one for metadata and one for content. The front end then merges these to set f.value. I can try to move it to a list of bytes again if people think that will be much better. I am waiting until we come to consensus for making any changes like that. Given that it is now hidden from the user (via private variables) we can also do this in another commit.

See the gif below for POC (there are small files, so you won't even notice the loading indicator)

fileupload2

@jasongrout
Copy link
Member

My question is if we want:

A. two different widgets (allows for a common usecase of a single file, but you can use the multiple version for multiple files if you want), or
B. one widget that has a union, and handles multiple and single files (perhaps with a synced attribute, so you could switch between them if needed?), or
C. one that allows only multiple files (harder to deal with single files if that's all you want, e.g., upload and analyze only a single image at a time), or
D. one that does single files (your responsibility for putting multiple widgets on the page if you want multiple files)

Sounds like C and D are out - I think the single file usecase is common enough we should explicitly support it. I think I prefer A slightly if it doesn't result in too much code duplication because it's a little more straightforward and predictable. Thoughts?

@mlucool
Copy link
Contributor

mlucool commented Sep 28, 2018

The JS side can be exactly the same for A and B as the current design hides how data is transferred from the user anyway. Currently setting multi allows for multiple or not in the UI.

So it's a question of what we want to put in .value. I am very torn if we want .value to always be a List or in the single case just store a TypedTuple. I don't think its bad to just have two python classes where one is called MultiUploadWidget and the other UploadWidget. It adds to maintenance and figuring out what you want, but it does make the .value cleaner in the single case.

@jasongrout
Copy link
Member

I'm torn about whether we want .value to be a union and .multiple to be an attribute, or if we want multiple/single to be two separate widgets to keep .value typing predictable. I don't think people will often want to switch between single/multiple, so that points me to having two separate widgets with simple predictable typing.

@mlucool
Copy link
Contributor

mlucool commented Sep 29, 2018

@jasongrout I went with two widgets as suggested above: FileUpload and MultiFileUpload. The former has a simpler .value while the latter uses a TypedTuple for .value and allows for .multiple to be set (defaults to true). The JS code is unchanged and MultiFileUpload depends on FileUpload.

Please review: #2211

@mlucool
Copy link
Contributor

mlucool commented Oct 1, 2018

Brainstorming here: how about files giving a list, and value being the single file files[0] or None?

I don't love how easy it is to mess up usage of the widget in the multi upload case. This is a reach, but this would be like returning the min value in a range selector and knowing you had to use .range for access for the actual range.

@oscar6echo
Copy link
Contributor

I just published a custom ipywidgets ipyupload because they need it now in my company.
But I also think it should be a core widget.

In short, it syncs List(Bytest) for the file contents and List(Dict) for the files metadata. It can handle multiple uploads. The result is presented to the user as a dict in trait .value.

@mlucool
Copy link
Contributor

mlucool commented Oct 31, 2018

@oscar6echo I won't be able to look at this for a little while. If you want to change/update this to help move getting this widget included along that would be great!

@oscar6echo
Copy link
Contributor

oscar6echo commented Oct 31, 2018

I added a compress (optional) feature to ipyupload v0.1.2 to reduce transmitted data size from browser to kernel.

@oscar6echo
Copy link
Contributor

@mlucool why not if I have time - it would be an interesting exercise/challenge.

However I have quite a few questions:

  • Do you mean I could port my custom code to your PR ?
  • Or make a new one from scratch ?
  • Is the dev install doc enough to proceed or is there more to know ?
  • Does everybody agree with the way I implemented the file upload ? Including:
    • the button style extra freedom ? (I would love that kind of transparent CSS to many core widgets - but I understand this is controversial)
    • the button overlay (I tried to default to ipywidget core button styling)
  • If I use TypedTuple instead of List as mentionned by @vidartf what are the constraints ? any example ? why is TypedTuple not available under the same namespace as List, Dict and the other containers.

@mlucool
Copy link
Contributor

mlucool commented Nov 5, 2018

@oscar6echo I meant take what was in this commit and continue to extend it for better transport type and how things are accessible from python. i.e. incorporate some of the things you did here. Also you should bringing in any other features you think are useful

Does everybody agree with the way I implemented the file upload ? Including:

I think we should stick with the default browser implementation of an input box for now. A future should should include drag/drop though.

@oscar6echo
Copy link
Contributor

Ok, finally it was not as difficult as I feared. Thanks to the magical yarn build !

I created a new PR #2258 because the previous one #2211 is already late on versions and I thought it was simpler to just start over from scratch - git subtleties always elude me.

Basically it is an "import" of ipyupload. If you ask me, I find it quite convenient with its current options.

I use the browser default <input type="file">. Only I cover it with a <button> for design consistency with the other core widgets (which look good, I think). This technique seems recommended by CSS experts. I just simplified further with display:none.

Tested on Chrome, Firefox, Safari, Opera, and Min. I don't have Edge, and not sure IE is still relevant..?

Re docs, I added an example in the Widget_List.ipynb notebook, after the Controller widget.

@flothesof
Copy link

Hi all,
This discussion is really interesting and I'd love to use the FileUpload widget.
It seems that the PR was ready but that the build failed.
@jasongrout, do you think this could be merged?
Anyway, I think this would be a great addition to ipywidgets interactions. Thank you everyone!

@tiagofrepereira2012
Copy link

That's a great feature.
Looking forward to see this in the next release

@LukaPitamic
Copy link

@oscar6echo great work!
ipyupload widget works like a charm for Jupyter notebook, but in JupyterLab is returns Error displaying widget

Any ideas?
image

@oscar6echo
Copy link
Contributor

The JLab API has changed since I wrote this custom widget (it was working when I published it).
I don't know the current API... If I can find info, I will to update it.

@LukaPitamic
Copy link

LukaPitamic commented Feb 20, 2019

@oscar6echo awesome, can't wait :D. now I'm using this as a work around:

import os
import ipywidgets as widgets
class FileBrowser(object):
    def __init__(self):
        self.path = os.getcwd()+ "/" #Data"
        self._update_files()      
    def _update_files(self):
        self.files = list()
        self.dirs = list()
        if(os.path.isdir(self.path)):
            for f in os.listdir(self.path):
                ff = self.path + "/" + f
                if os.path.isdir(ff):
                    self.dirs.append(f)
                else:
                    self.files.append(f)
    def widget(self):
        box = widgets.VBox()
        self._update(box)
        return box
    def _update(self, box):
        def on_click(b):
            if b.description == '..':
                self.path = os.path.split(self.path)[0]
            else:
                self.path = self.path + "/" + b.description
            self._update_files()
            self._update(box)
        buttons = []
        if self.files:
            button = widgets.Button(description='..', background_color='#d0d0ff')
            button.on_click(on_click)
            buttons.append(button)
        for f in self.dirs:
            button = widgets.Button(description=f, background_color='#d0d0ff')
            button.on_click(on_click)
            buttons.append(button)
        for f in self.files:
            button = widgets.Button(description=f)
            button.on_click(on_click)
            buttons.append(button)
        box.children = tuple(buttons)
f = FileBrowser()
f.widget()

image

It looks ridiculous I know, found it somewhere, I believe on stackoveflow. It draws buttons with names of the files to choose a file.
So yeah, please save our life :D

@LukaPitamic
Copy link

LukaPitamic commented Mar 8, 2019

@oscar6echo Any good news about ETA of your wonderful ipyupload widget to work with JupyterLab?

@martinRenou
Copy link
Member

@LukaPitamic it would depend on how you installed ipyupload.
If you installed ipyupload using pip or conda, you would need to run jupyter labextension install ipyupload and that would do the trick (you need nodejs installed on your computer for this to work).

If you installed ipyupload from sources using pip install . or pip install -e . you would need to run jupyter labextension install js from the root dir of your ipyupload clone.

If it still does not work, make sure to install the ipywidgets extension as well with: jupyter labextension install @jupyter-widgets/jupyterlab-manager

@oscar6echo
Copy link
Contributor

@martinRenou thx for explaining the install steps (and the great work on ipysheet which I used as a template).

@LukaPitamic I just re-tested locally (regular and dev installs) and it still does work on JLab. I thought I needed to update something in the code but no. In fact only documenting the install steps in the case of JLab was missing. Done in the README. Sorry to have kept you waiting for so long when there was so little to do !..

@LukaPitamic
Copy link

@martinRenou & @oscar6echo Thank you so much, it works like a charm! I still can't believe this is not a part of widgets list. I guess I'm not the only one missing it in original widgets library. @oscar6echo you're da man!!!! ;)

@Liwellyen
Copy link

Liwellyen commented Mar 19, 2019

Is it possible to use this approach for a file or folder selection, and to set the name of the button and also get the path of the selected file or folder? And how are the meta data from images are read. Would be also interested in this. Would need this very much. Best regards!

@kirkanshin
Copy link

@oscar6echo The best solution I found so far, thank you! and
@Liwellyen +1, a folder selection would be an amazing feature to have.

@dfrail24
Copy link

@oscar6echo Great work with the package & PR!
@jasongrout Could PR #2258 be reviewed to close loop on this feature request?

@bollwyvl
Copy link
Contributor

Thanks for the inspiration here, lots of good insights and pointers! I, too, was having some issues with some of the existing implementations, and had a go at this:
Screenshot from 2019-04-19 23-20-31

The approach is:

  • a File widget with an initial naive but useful downloadable a
    • by setting download, the default behavior is, well, to download... middle click will still open a new tab, but of course that repo might have it do... other things...
  • a FileBox which can accept and then show Files
    • implements most of the standard attributes on a <input type="file"/>
    • uses new_widget, but does work the other way (but doesn't update input.files)

Drag-and-drop support is probably next.

I don't think folders are doable natively at present, but Ctrl+A in most native file pickers isn't that bad, unless you have folders-of-folders...

Happy to do a PR, and definitely feel like this is a big missing piece given how much better this has all gotten since the bad old days.

@Liwellyen
Copy link

Liwellyen commented May 19, 2019

@oscar6echo I really tried some things now, but is it somehwo possible to change the text of the upload button? Best regards and thanks for your effort!

And i dont now why this happenend. But now after installing ipyupload the normal ipywidgets not working anymore in jupyterlab

@oscar6echo
Copy link
Contributor

@Liwellyen it would be possible to change the button text if it becomes a parameter but what sort of flexibility do you envisage ? can you describe a bit more ?

I'm very busy on other things now, but when things settle I can try customizing a bit further if there is a real need.

@Liwellyen
Copy link

Liwellyen commented May 21, 2019

@oscar6echo Yeah I just want to change the name on the button. So its called something like: Chose File. But what is more important for my understanding and what I now realized the first time: The widget uploads the file to the browser?. Because I just need a file Browser where you can select files or folders and those a saved to a variable. After that I can progress with that paths like i want it. Is such a thing possible or maybe it exists already and I dont be aware of it.

Furthermore it seems, if I install ipyupload and use it with jupyterlab I get this error mesagge in my terminal for all ipywidgets:

[W 14:52:20.056 NotebookApp] 404 GET /nullnbextensions/ipyupload/vendors~@jupyter-widgets/controls~vega.3dd933b62461edbc58d8.js (192.168.2.30) 1.46ms referer=....

i use it on a remote server. In the normal jupyter notebook it works. If I deactivate the ipyupload jupyterlab extension all the others work again

@SylvainCorlay
Copy link
Member

Closing as fixed! Thanks @oscar6echo for the PR!

@stojan-jovic
Copy link

@Liwellyen Button text will be customizable in official version of widget, at least that's how I understood discussion on the PR.

@lock lock bot added the resolved-locked Closed issues are locked after 30 days inactivity. Please open a new issue for related discussion. label May 21, 2020
@lock lock bot locked as resolved and limited conversation to collaborators May 21, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
good first issue resolved-locked Closed issues are locked after 30 days inactivity. Please open a new issue for related discussion.
Projects
None yet
Development

Successfully merging a pull request may close this issue.