Solución:
Los comentarios, sugerencias y correcciones son bienvenidos.
Me inspiré en Jupyter Notebook (4.x) en sí mismo del NotebookList.prototype.handleFilesUpload
función del archivo notebooklist.js. Después de leer algo sobre la sintaxis de JavaScript, se me ocurrió lo siguiente:
(Tenga en cuenta que los archivos se cargan en modo texto sin verificación).
import base64 # You need it if you define binary uploads
from __future__ import print_function # py 2.7 compat.
import ipywidgets as widgets # Widget definitions.
from traitlets import List, Unicode # Traitlets needed to add synced attributes to the widget.
class FileWidget(widgets.DOMWidget):
_view_name = Unicode('FilePickerView').tag(sync=True)
_view_module = Unicode('filepicker').tag(sync=True)
filenames = List([], sync=True)
# values = List(trait=Unicode, sync=True)
def __init__(self, **kwargs):
"""Constructor"""
super().__init__(**kwargs)
# Allow the user to register error callbacks with the following signatures:
# callback()
# callback(sender)
self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])
# Listen for custom msgs
self.on_msg(self._handle_custom_msg)
def _handle_custom_msg(self, content):
"""Handle a msg from the front-end.
Parameters
----------
content: dict
Content of the msg."""
if 'event' in content and content['event'] == 'error':
self.errors()
self.errors(self)
%%javascript
requirejs.undef('filepicker');
define('filepicker', ["jupyter-js-widgets"], function(widgets) {
var FilePickerView = widgets.DOMWidgetView.extend({
render: function(){
// Render the view using HTML5 multiple file input support.
this.setElement($('<input multiple="multiple" name="datafile" />')
.attr('type', 'file'));
},
events: {
// List of events and their handlers.
'change': 'handle_file_change',
},
handle_file_change: function(evt) {
// Handle when the user has changed the file.
// Save context (or namespace or whatever this is)
var that = this;
// Retrieve the FileList object
var files = evt.originalEvent.target.files;
var filenames = [];
var file_readers = [];
console.log("Reading files:");
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.log("Filename: " + file.name);
console.log("Type: " + file.type);
console.log("Size: " + file.size + " bytes");
filenames.push(file.name);
// Read the file's textual content and set value_i to those contents.
file_readers.push(new FileReader());
file_readers[i].onload = (function(file, i) {
return function(e) {
that.model.set('value_' + i, e.target.result);
that.touch();
console.log("file_" + i + " loaded: " + file.name);
};
})(file, i);
file_readers[i].readAsText(file);
}
// Set the filenames of the files.
this.model.set('filenames', filenames);
this.touch();
},
});
// Register the FilePickerView with the widget manager.
return {
FilePickerView: FilePickerView
};
});
file_widget = FileWidget()
def file_loaded(change):
'''Register an event to save contents when a file has been uploaded.'''
print(change['new'])
i = int(change['name'].split('_')[1])
fname = file_widget.filenames[i]
print('file_loaded: {}'.format(fname))
def file_loading(change):
'''Update self.model when user requests a list of files to be uploaded'''
print(change['new'])
num = len(change['new'])
traits = [('value_{}'.format(i), Unicode(sync=True)) for i in range(num)]
file_widget.add_traits(**dict(traits))
for i in range(num):
file_widget.observe(file_loaded, 'value_{}'.format(i))
file_widget.observe(file_loading, names="filenames")
def file_failed():
print("Could not load some file contents.")
file_widget.errors.register_callback(file_failed)
file_widget
Un botón con el texto Browse...
debería aparecer indicando cuántos archivos están seleccionados. Ya que print
declaraciones se incluyen en el file_loading
y file_loaded
funciones, debería ver los nombres de archivo y el contenido de archivo en la salida. Los nombres y tipos de archivos también se muestran en el registro de la consola.
Este problema https://github.com/ipython/ipython/issues/8383 responde a su pregunta parcialmente. Ya hay un botón de carga disponible en jupyter 4.0 en la pantalla del tablero. Este botón de carga admite la selección de varios archivos.
Tenga en cuenta que los enlaces actualizados se encuentran aquí para el widget de carga:
https://github.com/ipython/ipywidgets/blob/master/docs/source/examples/File%20Upload%20Widget.ipynb
También hay una extensión disponible para descargar e instalar rápidamente en sus computadoras portátiles:
https://github.com/peteut/ipython-file-upload
pip install fileupload
o
pip install git+https://github.com/peteut/ipython-file-upload
Tenga en cuenta que la extensión está confirmada para funcionar en Linux solo según el autor.
Hay un enfoque aún más nuevo que fileupload, que usé en el pasado y funciona bastante bien (publicado por @denfromufa) con un widget de carga de archivos compatible de forma nativa.
import io
import ipywidgets as widgets
widgets.FileUpload(
accept=".txt", # Accepted file extension e.g. '.txt', '.pdf', 'image/*', 'image/*,.pdf'
multiple=True # True to accept multiple files upload else False
)
Un par de consejos:
- Si su computadora portátil tiene un código ‘debajo’ del cargador, continuará. Utilizo el bloqueo de ipython para ‘pausar’ la ejecución del resto del cuaderno hasta que el archivo se cargue correctamente … Luego tengo un segundo botón que básicamente reinicia la ejecución del código después de que el usuario haya subido el (los) archivo (s) correcto (s) y esté listo para proceder.
- El cargador de ipywidget difiere de [fileupload] (pip install fileupload) ya que extrae el archivo en Python para que pueda trabajar con él. Si desea colocarlo en un directorio, debe usar algún otro método para escribir los datos del valor en un archivo:
Para un widget de carga de archivos ‘myupload’, puede escribir una función dirigida por eventos que incluya algo como lo siguiente cuando se carga un archivo:
# Get the original filename which is the first key in the widget's value dict:
uploaded_filename = next(iter(myupload.value))
content = myupload.value[uploaded_filename]['content']
with open('myfile', 'wb') as f: f.write(content)