From 68cc4099f3cfe95a35657623a82834cf1fe903ae Mon Sep 17 00:00:00 2001 From: alice grosch <a.grosch@fz-juelich.de> Date: Tue, 14 Jan 2020 12:10:52 +0100 Subject: [PATCH] Rename extension --- jsfileupload/__init__.py | 10 +++ jsfileupload/_frontend.py | 12 ++++ jsfileupload/_version.py | 8 +++ jsfileupload/nbextension/__init__.py | 13 ++++ jsfileupload/nbextension/static/extension.js | 17 +++++ jsfileupload/tests/__init__.py | 0 jsfileupload/tests/conftest.py | 54 ++++++++++++++ jsfileupload/tests/test_example.py | 14 ++++ jsfileupload/tests/test_nbextension_path.py | 15 ++++ jsfileupload/upload_widget.py | 74 ++++++++++++++++++++ 10 files changed, 217 insertions(+) create mode 100644 jsfileupload/__init__.py create mode 100644 jsfileupload/_frontend.py create mode 100644 jsfileupload/_version.py create mode 100644 jsfileupload/nbextension/__init__.py create mode 100644 jsfileupload/nbextension/static/extension.js create mode 100644 jsfileupload/tests/__init__.py create mode 100644 jsfileupload/tests/conftest.py create mode 100644 jsfileupload/tests/test_example.py create mode 100644 jsfileupload/tests/test_nbextension_path.py create mode 100644 jsfileupload/upload_widget.py diff --git a/jsfileupload/__init__.py b/jsfileupload/__init__.py new file mode 100644 index 0000000..bd7a852 --- /dev/null +++ b/jsfileupload/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC). +# Distributed under the terms of the Modified BSD License. + +from .upload_widget import FileUpload +from ._version import __version__, version_info + +from .nbextension import _jupyter_nbextension_paths diff --git a/jsfileupload/_frontend.py b/jsfileupload/_frontend.py new file mode 100644 index 0000000..a5bc604 --- /dev/null +++ b/jsfileupload/_frontend.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC). +# Distributed under the terms of the Modified BSD License. + +""" +Information about the frontend package of the widgets. +""" + +module_name = "jsfileupload" +module_version = "^0.1.0" diff --git a/jsfileupload/_version.py b/jsfileupload/_version.py new file mode 100644 index 0000000..fdb1be3 --- /dev/null +++ b/jsfileupload/_version.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC). +# Distributed under the terms of the Modified BSD License. + +version_info = (0, 1, 0, 'dev') +__version__ = ".".join(map(str, version_info)) diff --git a/jsfileupload/nbextension/__init__.py b/jsfileupload/nbextension/__init__.py new file mode 100644 index 0000000..b525f3a --- /dev/null +++ b/jsfileupload/nbextension/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC) +# Distributed under the terms of the Modified BSD License. + +def _jupyter_nbextension_paths(): + return [{ + 'section': 'notebook', + 'src': 'nbextension/static', + 'dest': 'jsfileupload', + 'require': 'jsfileupload/extension' + }] diff --git a/jsfileupload/nbextension/static/extension.js b/jsfileupload/nbextension/static/extension.js new file mode 100644 index 0000000..fed306e --- /dev/null +++ b/jsfileupload/nbextension/static/extension.js @@ -0,0 +1,17 @@ +// Entry point for the notebook bundle containing custom model definitions. +// +define(function() { + "use strict"; + + window['requirejs'].config({ + map: { + '*': { + 'jsfileupload': 'nbextensions/jsfileupload/index', + }, + } + }); + // Export the required load_ipython_extension function + return { + load_ipython_extension : function() {} + }; +}); diff --git a/jsfileupload/tests/__init__.py b/jsfileupload/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jsfileupload/tests/conftest.py b/jsfileupload/tests/conftest.py new file mode 100644 index 0000000..f4ecae6 --- /dev/null +++ b/jsfileupload/tests/conftest.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC). +# Distributed under the terms of the Modified BSD License. + +import pytest + +from ipykernel.comm import Comm +from ipywidgets import Widget + +class MockComm(Comm): + """A mock Comm object. + + Can be used to inspect calls to Comm's open/send/close methods. + """ + comm_id = 'a-b-c-d' + kernel = 'Truthy' + + def __init__(self, *args, **kwargs): + self.log_open = [] + self.log_send = [] + self.log_close = [] + super(MockComm, self).__init__(*args, **kwargs) + + def open(self, *args, **kwargs): + self.log_open.append((args, kwargs)) + + def send(self, *args, **kwargs): + self.log_send.append((args, kwargs)) + + def close(self, *args, **kwargs): + self.log_close.append((args, kwargs)) + +_widget_attrs = {} +undefined = object() + + +@pytest.fixture +def mock_comm(): + _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined) + Widget._comm_default = lambda self: MockComm() + _widget_attrs['_ipython_display_'] = Widget._ipython_display_ + def raise_not_implemented(*args, **kwargs): + raise NotImplementedError() + Widget._ipython_display_ = raise_not_implemented + + yield MockComm() + + for attr, value in _widget_attrs.items(): + if value is undefined: + delattr(Widget, attr) + else: + setattr(Widget, attr, value) diff --git a/jsfileupload/tests/test_example.py b/jsfileupload/tests/test_example.py new file mode 100644 index 0000000..112621d --- /dev/null +++ b/jsfileupload/tests/test_example.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC). +# Distributed under the terms of the Modified BSD License. + +import pytest + +from ..example import ExampleWidget + + +def test_example_creation_blank(): + w = ExampleWidget() + assert w.value == 'Hello World' diff --git a/jsfileupload/tests/test_nbextension_path.py b/jsfileupload/tests/test_nbextension_path.py new file mode 100644 index 0000000..9193605 --- /dev/null +++ b/jsfileupload/tests/test_nbextension_path.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC). +# Distributed under the terms of the Modified BSD License. + + +def test_nbextension_path(): + # Check that magic function can be imported from package root: + from jsfileupload import _jupyter_nbextension_paths + # Ensure that it can be called without incident: + path = _jupyter_nbextension_paths() + # Some sanity checks: + assert len(path) == 1 + assert isinstance(path[0], dict) diff --git a/jsfileupload/upload_widget.py b/jsfileupload/upload_widget.py new file mode 100644 index 0000000..5bbc0b5 --- /dev/null +++ b/jsfileupload/upload_widget.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juelich Supercomputing Centre (JSC). +# Distributed under the terms of the Modified BSD License. + +""" +FileUpload Widget using the Jupyter Notebook Server RestAPI. +Can handle large files by uploading files in chunks. +""" +from ipywidgets import ValueWidget +from ipywidgets import register, widget_serialization +from ipywidgets.widgets.trait_types import InstanceDict +from ipywidgets.widgets.widget_button import ButtonStyle +from ipywidgets.widgets.widget_description import DescriptionWidget + +from traitlets import ( + default, Unicode, Dict, List, Int, Bool, Bytes, CaselessStrEnum +) +from ._frontend import module_name, module_version + + +class FileUpload(DescriptionWidget, ValueWidget): + """FileUpload Widget. Uploads via the Jupyter Notebook Server RestAPI. + + The widget is able to upload large files by uploading them in chunks. + The file contents will however not be directly available over the widget, + but will have to be read into the notebook seperately. + """ + _model_name = Unicode('FileUploadModel').tag(sync=True) + _model_module = Unicode(module_name).tag(sync=True) + _model_module_version = Unicode(module_version).tag(sync=True) + _view_name = Unicode('FileUploadView').tag(sync=True) + _view_module = Unicode(module_name).tag(sync=True) + _view_module_version = Unicode(module_version).tag(sync=True) + + accept = Unicode(help='File types to accept, empty string for all').tag(sync=True) + multiple = Bool(help='If True, allow for multiple files upload').tag(sync=True) + disabled = Bool(help='Enable or disable button').tag(sync=True) + icon = Unicode('folder', help="Font-awesome icon name, without the 'fa-' prefix.").tag(sync=True) + button_style = CaselessStrEnum( + values=['primary', 'success', 'info', 'warning', 'danger', ''], default_value='', + help="""Use a predefined styling for the button.""").tag(sync=True) + style = InstanceDict(ButtonStyle).tag(sync=True, **widget_serialization) + metadata = List(Dict(), help='List of file metadata').tag(sync=True) + + # Needed for uploading using the Notebook Server RestAPI. + token = Unicode(help='Jupyter API token').tag(sync=True) + upload_url = Unicode('http://localhost:8888/api/contents/', + help='http(s)://<notebook_url>/api/contents/<path>').tag(sync=True) + + # Variables set on the JavaScript side. + files = List().tag(sync=True) + responses = List().tag(sync=True) + finished = Bool(False).tag(sync=True) + _upload = Bool(False).tag(sync=True) + + + def __init__(self, upload_url='http://localhost:8888/api/contents/', token='', *args, **kwargs): + """Args: + upload_url (str): Jupyter notebook URL appended by api/contents/<path>/. Directories on <path> must already exist. + token (str): Jupyter notebook authentication token. + """ + super(FileUpload, self).__init__(*args, **kwargs) + self.upload_url = upload_url + self.token = token + + @default('description') + def _default_description(self): + return 'Browse' + + def upload(self): + """Uploads file(s) via the JS fetch API.""" + self._upload = True \ No newline at end of file -- GitLab