Hello community,
here is the log from the commit of package python-jupyter_jupyterlab_launcher for openSUSE:Factory checked in at 2018-08-03 12:39:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jupyter_jupyterlab_launcher (Old)
and /work/SRC/openSUSE:Factory/.python-jupyter_jupyterlab_launcher.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jupyter_jupyterlab_launcher"
Fri Aug 3 12:39:45 2018 rev:2 rq:627154 version:0.11.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-jupyter_jupyterlab_launcher/python-jupyter_jupyterlab_launcher.changes 2018-05-13 16:03:52.432939315 +0200
+++ /work/SRC/openSUSE:Factory/.python-jupyter_jupyterlab_launcher.new/python-jupyter_jupyterlab_launcher.changes 2018-08-03 12:39:46.399790118 +0200
@@ -1,0 +2,14 @@
+Thu Aug 2 18:34:03 UTC 2018 - toddrme2178@gmail.com
+
+- Update to 0.11.2
+ * Update test utils to remove redundancy.
+ * Support the launcher specific endpoints in the barebones app.
+ * Implement workspaces listing.
+ * Implement individual workspace deletion.
+ * Handle requests to /lab/api/workspaces and /lab/api/workspaces/
+ * Improve use of process quiet flag
+ * move process utilities from jupyterlab to here
+ * make workspaces directory if it does not exist.
+ * Include plugin/schema name in error messages.
+
+-------------------------------------------------------------------
Old:
----
jupyterlab_launcher-0.10.5.tar.gz
New:
----
jupyterlab_launcher-0.11.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jupyter_jupyterlab_launcher.spec ++++++
--- /var/tmp/diff_new_pack.uiS9aU/_old 2018-08-03 12:39:46.955790947 +0200
+++ /var/tmp/diff_new_pack.uiS9aU/_new 2018-08-03 12:39:46.955790947 +0200
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%bcond_without test
Name: python-jupyter_jupyterlab_launcher
-Version: 0.10.5
+Version: 0.11.2
Release: 0
License: BSD-3-Clause
Summary: Jupyter Launcher
@@ -68,6 +68,7 @@
%files %{python_files}
%defattr(-,root,root,-)
%doc LICENSE README.md
-%{python_sitelib}/*
+%{python_sitelib}/jupyterlab_launcher-%{version}-py*.egg-info
+%{python_sitelib}/jupyterlab_launcher/
%changelog
++++++ jupyterlab_launcher-0.10.5.tar.gz -> jupyterlab_launcher-0.11.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/PKG-INFO new/jupyterlab_launcher-0.11.2/PKG-INFO
--- old/jupyterlab_launcher-0.10.5/PKG-INFO 2018-02-20 19:04:12.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/PKG-INFO 2018-07-20 13:01:43.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: jupyterlab_launcher
-Version: 0.10.5
+Version: 0.11.2
Summary: Jupyter Launcher
Home-page: http://jupyter.org
Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/_version.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/_version.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/_version.py 2018-02-20 19:03:57.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/_version.py 2018-07-20 13:01:33.000000000 +0200
@@ -1,2 +1,2 @@
-version_info = (0, 10, 5)
+version_info = (0, 11, 2)
__version__ = ".".join(map(str, version_info))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/handlers.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/handlers.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/handlers.py 2018-01-25 14:49:44.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/handlers.py 2018-07-20 12:50:50.000000000 +0200
@@ -222,11 +222,20 @@
# Handle API requests for workspaces.
config.workspaces_api_url = ujoin(base_url, default_workspaces_api_url)
- workspaces_api_path = config.workspaces_api_url + '(?P.+)'
+
+ # Handle requests for the list of workspaces. Make slash optional.
+ workspaces_api_path = config.workspaces_api_url + '?'
handlers.append((workspaces_api_path, WorkspacesHandler, {
'workspaces_url': config.workspaces_url,
'path': config.workspaces_dir
}))
+
+ # Handle requests for an individually named workspace.
+ workspace_api_path = config.workspaces_api_url + '(?P.+)'
+ handlers.append((workspace_api_path, WorkspacesHandler, {
+ 'workspaces_url': config.workspaces_url,
+ 'path': config.workspaces_dir
+ }))
# Handle local themes.
if config.themes_dir:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/process.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/process.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/process.py 1970-01-01 01:00:00.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/process.py 2018-07-20 13:01:25.000000000 +0200
@@ -0,0 +1,291 @@
+# coding: utf-8
+"""JupyterLab Launcher process handler"""
+
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+from __future__ import print_function
+
+import atexit
+import logging
+import os
+import re
+import signal
+import sys
+import threading
+import time
+import weakref
+
+from tornado import gen
+
+from ipython_genutils.py3compat import which as _which
+
+try:
+ import subprocess32 as subprocess
+except ImportError:
+ import subprocess
+
+try:
+ import pty
+except ImportError:
+ pty = False
+
+if sys.platform == 'win32':
+ list2cmdline = subprocess.list2cmdline
+else:
+ def list2cmdline(cmd_list):
+ import pipes
+ return ' '.join(map(pipes.quote, cmd_list))
+
+logging.basicConfig(format='%(message)s', level=logging.INFO)
+
+
+def which(command, env=None):
+ """Get the full path to a command.
+
+ Parameters
+ ----------
+ command: str
+ The command name or path.
+ env: dict, optional
+ The environment variables, defaults to `os.environ`.
+ """
+ env = env or os.environ
+ path = env.get('PATH') or os.defpath
+ command_with_path = _which(command, path=path)
+
+ # Allow nodejs as an alias to node.
+ if command == 'node' and not command_with_path:
+ command = 'nodejs'
+ command_with_path = _which('nodejs', path=path)
+
+ if not command_with_path:
+ if command in ['nodejs', 'node', 'npm']:
+ msg = 'Please install nodejs 5+ and npm before continuing installation. nodejs may be installed using conda or directly from the nodejs website.'
+ raise ValueError(msg)
+ raise ValueError('The command was not found or was not ' +
+ 'executable: %s.' % command)
+ return command_with_path
+
+
+class Process(object):
+ """A wrapper for a child process.
+ """
+ _procs = weakref.WeakSet()
+ _pool = None
+
+ def __init__(self, cmd, logger=None, cwd=None, kill_event=None,
+ env=None, quiet=False):
+ """Start a subprocess that can be run asynchronously.
+
+ Parameters
+ ----------
+ cmd: list
+ The command to run.
+ logger: :class:`~logger.Logger`, optional
+ The logger instance.
+ cwd: string, optional
+ The cwd of the process.
+ env: dict, optional
+ The environment for the process.
+ kill_event: :class:`~threading.Event`, optional
+ An event used to kill the process operation.
+ quiet: bool, optional
+ Whether to suppress output.
+ """
+ if not isinstance(cmd, (list, tuple)):
+ raise ValueError('Command must be given as a list')
+
+ if kill_event and kill_event.is_set():
+ raise ValueError('Process aborted')
+
+ self.logger = logger = logger or logging.getLogger('jupyterlab')
+ self._last_line = ''
+ if not quiet:
+ self.logger.info('> ' + list2cmdline(cmd))
+ self.cmd = cmd
+
+ kwargs = {}
+ if quiet:
+ kwargs['stdout'] = subprocess.DEVNULL
+
+ self.proc = self._create_process(cwd=cwd, env=env, **kwargs)
+ self._kill_event = kill_event or threading.Event()
+
+ Process._procs.add(self)
+
+ def terminate(self):
+ """Terminate the process and return the exit code.
+ """
+ proc = self.proc
+
+ # Kill the process.
+ if proc.poll() is None:
+ os.kill(proc.pid, signal.SIGTERM)
+
+ # Wait for the process to close.
+ try:
+ proc.wait()
+ finally:
+ Process._procs.remove(self)
+
+ return proc.returncode
+
+ def wait(self):
+ """Wait for the process to finish.
+
+ Returns
+ -------
+ The process exit code.
+ """
+ proc = self.proc
+ kill_event = self._kill_event
+ while proc.poll() is None:
+ if kill_event.is_set():
+ self.terminate()
+ raise ValueError('Process was aborted')
+ time.sleep(1.)
+ return self.terminate()
+
+ @gen.coroutine
+ def wait_async(self):
+ """Asynchronously wait for the process to finish.
+ """
+ proc = self.proc
+ kill_event = self._kill_event
+ while proc.poll() is None:
+ if kill_event.is_set():
+ self.terminate()
+ raise ValueError('Process was aborted')
+ yield gen.sleep(1.)
+
+ raise gen.Return(self.terminate())
+
+ def _create_process(self, **kwargs):
+ """Create the process.
+ """
+ cmd = self.cmd
+ kwargs.setdefault('stderr', subprocess.STDOUT)
+
+ cmd[0] = which(cmd[0], kwargs.get('env'))
+
+ if os.name == 'nt':
+ kwargs['shell'] = True
+
+ proc = subprocess.Popen(cmd, **kwargs)
+ return proc
+
+ @classmethod
+ def _cleanup(cls):
+ """Clean up the started subprocesses at exit.
+ """
+ for proc in list(cls._procs):
+ proc.terminate()
+
+
+class WatchHelper(Process):
+ """A process helper for a watch process.
+ """
+
+ def __init__(self, cmd, startup_regex, logger=None, cwd=None,
+ kill_event=None, env=None):
+ """Initialize the process helper.
+
+ Parameters
+ ----------
+ cmd: list
+ The command to run.
+ startup_regex: string
+ The regex to wait for at startup.
+ logger: :class:`~logger.Logger`, optional
+ The logger instance.
+ cwd: string, optional
+ The cwd of the process.
+ env: dict, optional
+ The environment for the process.
+ kill_event: callable, optional
+ A function to call to check if we should abort.
+ """
+ super(WatchHelper, self).__init__(cmd, logger=logger,
+ cwd=cwd, kill_event=kill_event, env=env)
+
+ if not pty:
+ self._stdout = self.proc.stdout
+
+ while 1:
+ line = self._stdout.readline().decode('utf-8')
+ if not line:
+ raise RuntimeError('Process ended improperly')
+ print(line.rstrip())
+ if re.match(startup_regex, line):
+ break
+
+ self._read_thread = threading.Thread(target=self._read_incoming)
+ self._read_thread.setDaemon(True)
+ self._read_thread.start()
+
+ def terminate(self):
+ """Terminate the process.
+ """
+ proc = self.proc
+
+ if proc.poll() is None:
+ if os.name != 'nt':
+ # Kill the process group if we started a new session.
+ os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
+ else:
+ os.kill(proc.pid, signal.SIGTERM)
+
+ # Close stdout.
+ try:
+ self._stdout.close()
+ except Exception as e:
+ pass
+
+ # Wait for the process to close.
+ try:
+ proc.wait()
+ finally:
+ Process._procs.remove(self)
+
+ return proc.returncode
+
+ def _read_incoming(self):
+ """Run in a thread to read stdout and print"""
+ fileno = self._stdout.fileno()
+ while 1:
+ try:
+ buf = os.read(fileno, 1024)
+ except OSError as e:
+ self.logger.debug('Read incoming error %s', e)
+ return
+
+ if not buf:
+ return
+
+ print(buf.decode('utf-8'), end='')
+
+ def _create_process(self, **kwargs):
+ """Create the watcher helper process.
+ """
+ kwargs['bufsize'] = 0
+
+ if pty:
+ master, slave = pty.openpty()
+ kwargs['stderr'] = kwargs['stdout'] = slave
+ kwargs['start_new_session'] = True
+ self._stdout = os.fdopen(master, 'rb')
+ else:
+ kwargs['stdout'] = subprocess.PIPE
+
+ if os.name == 'nt':
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ kwargs['startupinfo'] = startupinfo
+ kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
+ kwargs['shell'] = True
+
+ return super(WatchHelper, self)._create_process(**kwargs)
+
+
+# Register the cleanup handler.
+atexit.register(Process._cleanup)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/process_app.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/process_app.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/process_app.py 1970-01-01 01:00:00.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/process_app.py 2018-07-20 12:50:50.000000000 +0200
@@ -0,0 +1,48 @@
+# coding: utf-8
+"""A lab app that runs a sub process for a demo or a test."""
+
+from __future__ import print_function, absolute_import
+
+import sys
+
+from notebook.notebookapp import NotebookApp
+from tornado.ioloop import IOLoop
+from traitlets import Bool
+
+from .handlers import add_handlers, LabConfig
+from .process import Process
+
+
+class ProcessApp(NotebookApp):
+ """A notebook app that runs a separate process and exits on completion."""
+
+ open_browser = Bool(False)
+
+ lab_config = LabConfig()
+
+ def get_command(self):
+ """Get the command and kwargs to run with `Process`.
+ This is intended to be overridden.
+ """
+ return ['python', '--version'], dict()
+
+ def start(self):
+ """Start the application.
+ """
+ add_handlers(self.web_app, self.lab_config)
+ IOLoop.current().add_callback(self._run_command)
+ NotebookApp.start(self)
+
+ def _run_command(self):
+ command, kwargs = self.get_command()
+ kwargs.setdefault('logger', self.log)
+ future = Process(command, **kwargs).wait_async()
+ IOLoop.current().add_future(future, self._process_finished)
+
+ def _process_finished(self, future):
+ try:
+ IOLoop.current().stop()
+ sys.exit(future.result())
+ except Exception as e:
+ self.log.error(str(e))
+ sys.exit(1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/settings_handler.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/settings_handler.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/settings_handler.py 2018-01-05 04:17:45.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/settings_handler.py 2018-07-20 12:50:50.000000000 +0200
@@ -27,13 +27,11 @@
try:
self.overrides = json.load(fid)
except Exception as e:
- self.log.warn(str(e))
+ self.log.warn('Failed loading overrides {}'.format(str(e)))
@json_errors
@web.authenticated
def get(self, section_name):
- self.set_header('Content-Type', 'application/json')
-
schema = _get_schema(self.schemas_dir, section_name, self.overrides)
path = _path(self.settings_dir, section_name, _file_extension)
raw = '{}'
@@ -46,7 +44,8 @@
raw = fid.read() or raw
settings = json.loads(json_minify(raw))
except Exception as e:
- self.log.warn(str(e))
+ message = 'Failed loading settings ({}): {}'
+ self.log.warn(message.format(section_name, str(e)))
# Validate the parsed data against the schema.
if len(settings):
@@ -54,7 +53,8 @@
try:
validator.validate(settings)
except ValidationError as e:
- self.log.warn(str(e))
+ message = 'Failed validating settings ({}): {}'
+ self.log.warn(message.format(section_name, str(e)))
raw = '{}'
# Send back the raw data to the client.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/test_settings_api.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/test_settings_api.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/test_settings_api.py 2018-01-05 04:17:45.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/test_settings_api.py 2018-07-20 12:50:50.000000000 +0200
@@ -1,5 +1,7 @@
"""Test the kernels service API."""
import json
+import os
+import shutil
from jupyterlab_launcher.tests.utils import LabTestBase, APITester
from notebook.tests.launchnotebook import assert_http_error
@@ -21,11 +23,19 @@
"""Test the settings web service API"""
def setUp(self):
+ src = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)),
+ 'schemas',
+ '@jupyterlab')
+ dst = os.path.join(self.lab_config.schemas_dir, '@jupyterlab')
+ if not os.path.exists(dst):
+ shutil.copytree(src, dst)
self.settings_api = SettingsAPI(self.request)
def test_get(self):
id = '@jupyterlab/apputils-extension:themes'
data = self.settings_api.get(id).json()
+
assert data['id'] == id
assert len(data['schema'])
assert 'raw' in data
@@ -36,8 +46,8 @@
def test_patch(self):
id = '@jupyterlab/shortcuts-extension:plugin'
- resp = self.settings_api.put(id, dict())
- assert resp.status_code == 204
+
+ assert self.settings_api.put(id, dict()).status_code == 204
def test_patch_wrong_id(self):
with assert_http_error(404):
@@ -45,6 +55,6 @@
def test_patch_bad_data(self):
id = '@jupyterlab/codemirror-extension:commands'
- data = dict(keyMap=10)
+
with assert_http_error(400):
- self.settings_api.put(id, data)
+ self.settings_api.put(id, dict(keyMap=10))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/test_workspaces_api.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/test_workspaces_api.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/test_workspaces_api.py 2018-01-05 04:17:45.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/test_workspaces_api.py 2018-07-20 12:50:50.000000000 +0200
@@ -1,5 +1,7 @@
"""Test the kernels service API."""
import json
+import os
+import shutil
from jupyterlab_launcher.tests.utils import LabTestBase, APITester
@@ -9,7 +11,10 @@
url = 'lab/api/workspaces'
- def get(self, section_name):
+ def delete(self, section_name):
+ return self._req('DELETE', section_name)
+
+ def get(self, section_name=''):
return self._req('GET', section_name)
def put(self, section_name, body):
@@ -20,15 +25,37 @@
"""Test the workspaces web service API"""
def setUp(self):
+ data = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)),
+ 'workspaces')
+ for item in os.listdir(data):
+ src = os.path.join(data, item)
+ dst = os.path.join(self.lab_config.workspaces_dir, item)
+ if not os.path.exists(dst):
+ shutil.copy(src, self.lab_config.workspaces_dir)
self.workspaces_api = WorkspacesAPI(self.request)
+ def test_delete(self):
+ orig = 'foo'
+ copy = 'baz'
+ data = self.workspaces_api.get(orig).json()
+ data['metadata']['id'] = copy
+
+ assert self.workspaces_api.put(copy, data).status_code == 204
+ assert self.workspaces_api.delete(copy).status_code == 204
+
def test_get(self):
id = 'foo'
- data = self.workspaces_api.get(id).json()
- assert data['metadata']['id'] == id
+
+ assert self.workspaces_api.get(id).json()['metadata']['id'] == id
+
+ def test_listing(self):
+ listing = set(['foo', 'bar'])
+
+ assert set(self.workspaces_api.get().json()['workspaces']) == listing
def test_put(self):
id = 'foo'
data = self.workspaces_api.get(id).json()
- resp = self.workspaces_api.put(id, data)
- assert resp.status_code == 204
+
+ assert self.workspaces_api.put(id, data).status_code == 204
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/utils.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/utils.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/utils.py 2018-01-05 04:17:45.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/utils.py 2018-07-20 12:50:50.000000000 +0200
@@ -23,6 +23,8 @@
class LabTestBase(NotebookTestBase):
+ Application = LabLauncherApp
+ """The application being tested. Sub-classes should change this."""
@classmethod
def setup_class(cls):
@@ -38,20 +40,30 @@
return path
cls.home_dir = tmp('home')
- data_dir = cls.data_dir = tmp('data')
- config_dir = cls.config_dir = tmp('config')
- runtime_dir = cls.runtime_dir = tmp('runtime')
- cls.notebook_dir = tmp('notebooks')
+ cls.data_dir = tmp('data')
+ cls.config_dir = tmp('config')
+ cls.runtime_dir = tmp('runtime')
+ cls.lab_dir = tmp('lab')
+ cls.lab_schemas = tmp('labschemas')
+ cls.lab_settings = tmp('labsettings')
+ cls.lab_workspaces = tmp('labworkspaces')
cls.env_patch = patch.dict('os.environ', {
'HOME': cls.home_dir,
'PYTHONPATH': os.pathsep.join(sys.path),
'IPYTHONDIR': pjoin(cls.home_dir, '.ipython'),
'JUPYTER_NO_CONFIG': '1', # needed in the future
- 'JUPYTER_CONFIG_DIR': config_dir,
- 'JUPYTER_DATA_DIR': data_dir,
- 'JUPYTER_RUNTIME_DIR': runtime_dir,
+ 'JUPYTER_CONFIG_DIR': cls.config_dir,
+ 'JUPYTER_DATA_DIR': cls.data_dir,
+ 'JUPYTER_RUNTIME_DIR': cls.runtime_dir,
+ 'JUPYTERLAB_DIR': cls.lab_dir,
+ 'JUPYTERLAB_SETTINGS_DIR': cls.lab_settings
})
cls.env_patch.start()
+ cls.lab_config = LabConfig(
+ schemas_dir=cls.lab_schemas,
+ user_settings_dir=cls.lab_settings,
+ workspaces_dir=cls.lab_workspaces)
+ cls.notebook_dir = tmp('notebooks')
cls.path_patch = patch.multiple(
jupyter_core.paths,
SYSTEM_JUPYTER_PATH=[tmp('share', 'jupyter')],
@@ -61,19 +73,19 @@
)
cls.path_patch.start()
- config = cls.config or Config()
- config.NotebookNotary.db_file = ':memory:'
+ cls.config = cls.config or Config()
+ cls.config.NotebookNotary.db_file = ':memory:'
cls.token = hexlify(os.urandom(4)).decode('ascii')
started = Event()
- lab_config = LabConfig(schemas_dir=pjoin(here, 'schemas'),
- user_settings_dir=tmp('user_settings'),
- workspaces_dir=pjoin(here, 'workspaces'))
-
def start_thread():
- app = cls.notebook = LabLauncherApp(
+ if 'asyncio' in sys.modules:
+ import asyncio
+ asyncio.set_event_loop(asyncio.new_event_loop())
+ app = cls.notebook = cls.Application(
+ app_dir=cls.lab_dir,
port=cls.port,
port_retries=0,
open_browser=False,
@@ -82,10 +94,10 @@
runtime_dir=cls.runtime_dir,
notebook_dir=cls.notebook_dir,
base_url=cls.url_prefix,
- config=config,
+ config=cls.config,
allow_root=True,
token=cls.token,
- lab_config=lab_config
+ lab_config=cls.lab_config
)
# don't register signal handler during tests
app.init_signal = lambda: None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/workspaces/bar.jupyterlab-workspace new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/workspaces/bar.jupyterlab-workspace
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/workspaces/bar.jupyterlab-workspace 1970-01-01 01:00:00.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/workspaces/bar.jupyterlab-workspace 2018-07-20 12:50:50.000000000 +0200
@@ -0,0 +1 @@
+{"data": {}, "metadata": {"id": "bar"}}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/workspaces/foo.jupyterlab-workspace new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/workspaces/foo.jupyterlab-workspace
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/tests/workspaces/foo.jupyterlab-workspace 2018-01-05 04:17:45.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/tests/workspaces/foo.jupyterlab-workspace 2018-07-20 12:50:50.000000000 +0200
@@ -1 +1 @@
-{"metadata": {"id": "foo"}, "data": {}}
\ No newline at end of file
+{"data": {}, "metadata": {"id": "foo"}}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/workspaces_handler.py new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/workspaces_handler.py
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher/workspaces_handler.py 2018-02-13 18:35:18.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher/workspaces_handler.py 2018-07-20 12:50:50.000000000 +0200
@@ -17,30 +17,63 @@
def initialize(self, path, default_filename=None, workspaces_url=None):
self.workspaces_dir = path
+ def ensure_directory(self):
+ if not self.workspaces_dir:
+ raise web.HTTPError(500, 'Workspaces directory is not set')
+
+ return self.workspaces_dir
+
+ @json_errors
+ @web.authenticated
+ def delete(self, space_name):
+ directory = self.ensure_directory()
+
+ if not space_name:
+ raise web.HTTPError(400, 'Workspace name is required for DELETE')
+
+ workspace_path = os.path.join(directory, space_name + _file_extension)
+ if not os.path.exists(workspace_path):
+ raise web.HTTPError(404, 'Workspace %r not found' % space_name)
+
+ try: # to delete the workspace file.
+ os.remove(workspace_path)
+ return self.set_status(204)
+ except Exception as e:
+ raise web.HTTPError(500, str(e))
+
@json_errors
@web.authenticated
- def get(self, space_name):
- directory = self.workspaces_dir
- self.set_header('Content-Type', 'application/json')
+ def get(self, space_name=''):
+ directory = self.ensure_directory()
+
+ if not space_name:
+ if not os.path.exists(directory):
+ return self.finish(json.dumps(dict(workspaces=[])))
+
+ try: # to read the contents of the workspaces directory.
+ items = [item[:-len(_file_extension)]
+ for item in os.listdir(directory)
+ if item.endswith(_file_extension)]
+ items.sort()
+
+ return self.finish(json.dumps(dict(workspaces=items)))
+ except Exception as e:
+ raise web.HTTPError(500, str(e))
+
workspace_path = os.path.join(directory, space_name + _file_extension)
if os.path.exists(workspace_path):
with open(workspace_path) as fid:
- # Attempt to load and parse the workspace file.
- try:
- workspace = json.load(fid)
+ try: # to load and parse the workspace file.
+ return self.finish(json.dumps(json.load(fid)))
except Exception as e:
raise web.HTTPError(500, str(e))
else:
- raise web.HTTPError(404, 'Workspace not found: %r' % space_name)
-
- self.finish(json.dumps(workspace))
+ raise web.HTTPError(404, 'Workspace %r not found' % space_name)
@json_errors
@web.authenticated
def put(self, space_name):
- directory = self.workspaces_dir
- if not directory:
- raise web.HTTPError(500, 'No current workspaces directory')
+ directory = self.ensure_directory()
if not os.path.exists(directory):
try:
@@ -59,8 +92,10 @@
raise web.HTTPError(400, str(e))
# Make sure metadata ID matches the workspace name.
- if workspace['metadata']['id'] != space_name:
- message = 'Workspace metadata ID mismatch: %r' % space_name
+ metadata_id = workspace['metadata']['id']
+ if metadata_id != space_name:
+ message = ('Workspace metadata ID mismatch: expected %r got %r'
+ % (space_name, metadata_id))
raise web.HTTPError(400, message)
# Write the workspace data to a file.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher.egg-info/PKG-INFO new/jupyterlab_launcher-0.11.2/jupyterlab_launcher.egg-info/PKG-INFO
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher.egg-info/PKG-INFO 2018-02-20 19:04:12.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher.egg-info/PKG-INFO 2018-07-20 13:01:43.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: jupyterlab-launcher
-Version: 0.10.5
+Version: 0.11.2
Summary: Jupyter Launcher
Home-page: http://jupyter.org
Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/jupyterlab_launcher.egg-info/SOURCES.txt new/jupyterlab_launcher-0.11.2/jupyterlab_launcher.egg-info/SOURCES.txt
--- old/jupyterlab_launcher-0.10.5/jupyterlab_launcher.egg-info/SOURCES.txt 2018-02-20 19:04:12.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/jupyterlab_launcher.egg-info/SOURCES.txt 2018-07-20 13:01:43.000000000 +0200
@@ -1,6 +1,7 @@
LICENSE
MANIFEST.in
README.md
+setup.cfg
setup.py
setupbase.py
jupyterlab_launcher/__init__.py
@@ -10,6 +11,8 @@
jupyterlab_launcher/handlers.py
jupyterlab_launcher/index.html
jupyterlab_launcher/json_minify.py
+jupyterlab_launcher/process.py
+jupyterlab_launcher/process_app.py
jupyterlab_launcher/settings_handler.py
jupyterlab_launcher/themes_handler.py
jupyterlab_launcher/workspaces_handler.py
@@ -25,4 +28,5 @@
jupyterlab_launcher/tests/schemas/@jupyterlab/apputils-extension/themes.json
jupyterlab_launcher/tests/schemas/@jupyterlab/codemirror-extension/commands.json
jupyterlab_launcher/tests/schemas/@jupyterlab/shortcuts-extension/plugin.json
+jupyterlab_launcher/tests/workspaces/bar.jupyterlab-workspace
jupyterlab_launcher/tests/workspaces/foo.jupyterlab-workspace
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyterlab_launcher-0.10.5/setup.cfg new/jupyterlab_launcher-0.11.2/setup.cfg
--- old/jupyterlab_launcher-0.10.5/setup.cfg 2018-02-20 19:04:12.000000000 +0100
+++ new/jupyterlab_launcher-0.11.2/setup.cfg 2018-07-20 13:01:43.000000000 +0200
@@ -1,3 +1,6 @@
+[metadata]
+license_file = LICENSE
+
[egg_info]
tag_build =
tag_date = 0