Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package jupyter-jupyterlab-server for
openSUSE:Factory checked in at 2022-01-04 19:37:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/jupyter-jupyterlab-server (Old)
and /work/SRC/openSUSE:Factory/.jupyter-jupyterlab-server.new.1896 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "jupyter-jupyterlab-server"
Tue Jan 4 19:37:27 2022 rev:8 rq:943347 version:2.10.2
Changes:
--------
---
/work/SRC/openSUSE:Factory/jupyter-jupyterlab-server/jupyter-jupyterlab-server.changes
2021-10-18 22:02:40.174114293 +0200
+++
/work/SRC/openSUSE:Factory/.jupyter-jupyterlab-server.new.1896/jupyter-jupyterlab-server.changes
2022-01-04 19:37:29.721930513 +0100
@@ -1,0 +2,25 @@
+Fri Dec 31 18:04:31 UTC 2021 - Ben Greiner <[email protected]>
+
+- Update to 2.10.2
+ * Revert "Delay list -> dict conversation to end (#192)" #234
+ (@blink1073)
+ * Fix issue where preferredPath is incorrectly calculated #233
+ (@mlucool) Maintenance and upkeep improvements
+ * Add downstream test workflow #232 (@blink1073)
+- Release 2.10.1
+ * Restore back older export for workspaces #229 (@fcollonval)
+- Release 2.10.0
+ * Move workspace management in jupyterlab_server #227
+ (@fcollonval)
+ * Delay list -> dict conversation to end #192 (@vidartf)
+- Release 2.9.0
+ * Add overrides.d for settings defaults #224 (@bollwyvl)
+ * Add page_config_hook #220 (@minrk)
+ * Enforce labels on PRs #221 (@blink1073)
+ * Fix caching of conda env in CI #218 (@blink1073)
+ * pyproject.toml: clarify build system version #222
+ (@adamjstewart)
+- Fix import of mistune in tests due to our mitigation of
+ gh#jupyter/nbconvert#1685
+
+-------------------------------------------------------------------
Old:
----
jupyterlab_server-2.8.2.tar.gz
New:
----
jupyterlab_server-2.10.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ jupyter-jupyterlab-server.spec ++++++
--- /var/tmp/diff_new_pack.DUIQU8/_old 2022-01-04 19:37:30.329931309 +0100
+++ /var/tmp/diff_new_pack.DUIQU8/_new 2022-01-04 19:37:30.333931314 +0100
@@ -19,7 +19,7 @@
%define skip_python2 1
%define oldpython python
Name: jupyter-jupyterlab-server
-Version: 2.8.2
+Version: 2.10.2
Release: 0
Summary: Server components for JupyterLab and JupyterLab-like
applications
License: BSD-3-Clause
@@ -33,7 +33,7 @@
BuildRequires: %{python_module json5}
BuildRequires: %{python_module jsonschema >= 3.0.1}
BuildRequires: %{python_module jupyter_server >= 1.4}
-BuildRequires: %{python_module packaging}
+BuildRequires: %{python_module packaging > 0.9}
BuildRequires: %{python_module requests}
BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
@@ -87,7 +87,7 @@
This package is used to launch an application built using JupyterLab.
%package test
-Summary: jupyterlab_server[test] requirements
+Summary: The jupyterlab_server[test] requirements
Provides: python-jupyterlab-server-test = %{version}-%{release}
Obsoletes: python-jupyterlab-server-test < %{version}-%{release}
Requires: python-ipykernel
@@ -107,6 +107,10 @@
%autosetup -p1 -n jupyterlab_server-%{version}
# remove color and coverage flags from pytest
sed -i '/addopts/ d' pyproject.toml
+# mistune is imported in tests and normally installed as transitive requirement
+# from jupyter-server/nbconvert, but we had to vendorize it --
gh#jupyter/nbconvert#1685
+sed -i jupyterlab_server/tests/test_licenses_api.py \
+ -e 's/import mistune/from nbconvert.filters.markdown_mistune import mistune/'
%build
%python_build
++++++ jupyterlab_server-2.8.2.tar.gz -> jupyterlab_server-2.10.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyterlab_server-2.8.2/CHANGELOG.md
new/jupyterlab_server-2.10.2/CHANGELOG.md
--- old/jupyterlab_server-2.8.2/CHANGELOG.md 2021-09-25 17:44:23.000000000
+0200
+++ new/jupyterlab_server-2.10.2/CHANGELOG.md 2021-12-29 00:44:34.000000000
+0100
@@ -6,6 +6,85 @@
<!-- <START NEW CHANGELOG ENTRY> -->
+## 2.10.2
+
+([Full
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.10.1...cbe3b92d732b327a695d832c66f00e096d47ea2b))
+
+### Bugs fixed
+
+- Revert "Delay list -> dict conversation to end (#192)"
[#234](https://github.com/jupyterlab/jupyterlab_server/pull/234)
([@blink1073](https://github.com/blink1073))
+- Fix issue where preferredPath is incorrectly calculated
[#233](https://github.com/jupyterlab/jupyterlab_server/pull/233)
([@mlucool](https://github.com/mlucool))
+
+### Maintenance and upkeep improvements
+
+- Add downstream test workflow
[#232](https://github.com/jupyterlab/jupyterlab_server/pull/232)
([@blink1073](https://github.com/blink1073))
+
+### Contributors to this release
+
+([GitHub contributors page for this
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-12-22&to=2021-12-28&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-12-22..2021-12-28&type=Issues)
|
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-12-22..2021-12-28&type=Issues)
|
[@mlucool](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Amlucool+updated%3A2021-12-22..2021-12-28&type=Issues)
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
+## 2.10.1
+
+([Full
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.10.0...65027f3ca0a2c5846ba8381c47809e5d04eb05c7))
+
+### Bugs fixed
+
+- Restore back older export for workspaces
[#229](https://github.com/jupyterlab/jupyterlab_server/pull/229)
([@fcollonval](https://github.com/fcollonval))
+
+### Contributors to this release
+
+([GitHub contributors page for this
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-12-22&to=2021-12-22&type=c))
+
+[@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-12-22..2021-12-22&type=Issues)
+
+## 2.10.0
+
+([Full
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.9.0...6fa038f12ad7946e16c5e18665e8a88177080264))
+
+### Enhancements made
+
+- Move workspace management in jupyterlab_server
[#227](https://github.com/jupyterlab/jupyterlab_server/pull/227)
([@fcollonval](https://github.com/fcollonval))
+
+### Bugs fixed
+
+- Delay list -> dict conversation to end
[#192](https://github.com/jupyterlab/jupyterlab_server/pull/192)
([@vidartf](https://github.com/vidartf))
+
+### Contributors to this release
+
+([GitHub contributors page for this
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-12-13&to=2021-12-22&type=c))
+
+[@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-12-13..2021-12-22&type=Issues)
|
[@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-12-13..2021-12-22&type=Issues)
|
[@vidartf](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Avidartf+updated%3A2021-12-13..2021-12-22&type=Issues)
+
+## 2.9.0
+
+([Full
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.8.2...ea75fc04f9a4d29b9c22c9ec769835df3ba6a0e4))
+
+### Enhancements made
+
+- Add `overrides.d` for settings defaults
[#224](https://github.com/jupyterlab/jupyterlab_server/pull/224)
([@bollwyvl](https://github.com/bollwyvl))
+- Add `page_config_hook`
[#220](https://github.com/jupyterlab/jupyterlab_server/pull/220)
([@minrk](https://github.com/minrk))
+
+### Maintenance and upkeep improvements
+
+- Enforce labels on PRs
[#221](https://github.com/jupyterlab/jupyterlab_server/pull/221)
([@blink1073](https://github.com/blink1073))
+- Fix caching of conda env in CI
[#218](https://github.com/jupyterlab/jupyterlab_server/pull/218)
([@blink1073](https://github.com/blink1073))
+- `pyproject.toml`: clarify build system version
[#222](https://github.com/jupyterlab/jupyterlab_server/pull/222)
([@adamjstewart](https://github.com/adamjstewart))
+
+### Documentation improvements
+
+- Fix typo in README
[#216](https://github.com/jupyterlab/jupyterlab_server/pull/216)
([@Mithil467](https://github.com/Mithil467))
+- Clean up Readme
[#215](https://github.com/jupyterlab/jupyterlab_server/pull/215)
([@blink1073](https://github.com/blink1073))
+
+### Contributors to this release
+
+([GitHub contributors page for this
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-09-25&to=2021-12-13&type=c))
+
+[@adamjstewart](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aadamjstewart+updated%3A2021-09-25..2021-12-13&type=Issues)
|
[@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-09-25..2021-12-13&type=Issues)
|
[@bollwyvl](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abollwyvl+updated%3A2021-09-25..2021-12-13&type=Issues)
|
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-09-25..2021-12-13&type=Issues)
|
[@minrk](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aminrk+updated%3A2021-09-25..2021-12-13&type=Issues)
|
[@Mithil467](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3AMithil467+updated%3A2021-09-25..2021-12-13&type=Issues)
|
[@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+u
pdated%3A2021-09-25..2021-12-13&type=Issues)
+
## 2.8.2
([Full
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.8.1...27cead5e506882c1cf999cb8ca48a94031064c9a))
@@ -21,8 +100,6 @@
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-09-07..2021-09-25&type=Issues)
|
[@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-09-07..2021-09-25&type=Issues)
|
[@krassowski](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Akrassowski+updated%3A2021-09-07..2021-09-25&type=Issues)
-<!-- <END NEW CHANGELOG ENTRY> -->
-
## 2.8.1
([Full
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.8.0...680f4fe1c8c7d1d7841e14562720d81a27e6d0ad))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyterlab_server-2.8.2/PKG-INFO
new/jupyterlab_server-2.10.2/PKG-INFO
--- old/jupyterlab_server-2.8.2/PKG-INFO 2021-09-25 17:45:13.462953000
+0200
+++ new/jupyterlab_server-2.10.2/PKG-INFO 2021-12-29 00:45:27.393445700
+0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: jupyterlab_server
-Version: 2.8.2
+Version: 2.10.2
Summary: A set of server components for JupyterLab and JupyterLab like
applications .
Home-page: https://jupyter.org
Author: Jupyter Development Team
@@ -30,6 +30,11 @@
[](https://github.com/jupyterlab/jupyterlab_server/actions?query=branch%3Amaster+workflow%3A%22Tests%22)
[](http://jupyterlab_server.readthedocs.io/en/stable/)
+## Motivation
+
+JupyterLab Server sits between JupyterLab and Jupyter Server, and provides a
+set of REST API handlers and utilities that are used by JupyterLab. It is a
separate project in order to
+accommodate creating JupyterLab-like applications from a more limited scope.
## Install
@@ -37,11 +42,11 @@
## Usage
-The application author creates a JupyterLab build on their machine
-using the core JupyterLab application. They can then serve their
-files by subclassing the `LabServerApp` with the appropriate
-configuration and creating a Python entry point that launches the app.
+See the full documentation for [API
docs](https://jupyterlab-server.readthedocs.io/en/stable/api/index.html) and
[REST endpoint
descriptions](https://jupyterlab-server.readthedocs.io/en/stable/api/rest.html).
+
+## Extending the Application
+Subclass the `LabServerApp` and provide additional traits and handlers as
appropriate for your application.
## Contribution
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyterlab_server-2.8.2/README.md
new/jupyterlab_server-2.10.2/README.md
--- old/jupyterlab_server-2.8.2/README.md 2021-09-25 17:44:23.000000000
+0200
+++ new/jupyterlab_server-2.10.2/README.md 2021-12-29 00:44:34.000000000
+0100
@@ -4,6 +4,11 @@
[](https://github.com/jupyterlab/jupyterlab_server/actions?query=branch%3Amaster+workflow%3A%22Tests%22)
[](http://jupyterlab_server.readthedocs.io/en/stable/)
+## Motivation
+
+JupyterLab Server sits between JupyterLab and Jupyter Server, and provides a
+set of REST API handlers and utilities that are used by JupyterLab. It is a
separate project in order to
+accommodate creating JupyterLab-like applications from a more limited scope.
## Install
@@ -11,11 +16,11 @@
## Usage
-The application author creates a JupyterLab build on their machine
-using the core JupyterLab application. They can then serve their
-files by subclassing the `LabServerApp` with the appropriate
-configuration and creating a Python entry point that launches the app.
+See the full documentation for [API
docs](https://jupyterlab-server.readthedocs.io/en/stable/api/index.html) and
[REST endpoint
descriptions](https://jupyterlab-server.readthedocs.io/en/stable/api/rest.html).
+
+## Extending the Application
+Subclass the `LabServerApp` and provide additional traits and handlers as
appropriate for your application.
## Contribution
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server/__init__.py
new/jupyterlab_server-2.10.2/jupyterlab_server/__init__.py
--- old/jupyterlab_server-2.8.2/jupyterlab_server/__init__.py 2021-09-25
17:44:23.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server/__init__.py 2021-12-29
00:44:34.000000000 +0100
@@ -4,6 +4,8 @@
from .app import LabServerApp
from .licenses_app import LicensesApp
from .handlers import add_handlers, LabHandler, LabConfig
+from .translation_utils import translator
+from .workspaces_app import WorkspaceExportApp, WorkspaceImportApp,
WorkspaceListApp
from .workspaces_handler import slugify, WORKSPACE_EXTENSION
from ._version import __version__
@@ -13,9 +15,14 @@
'LabConfig',
'LabHandler',
'LabServerApp',
- 'slugify',
+ 'LicensesApp',
'SETTINGS_EXTENSION',
- 'WORKSPACE_EXTENSION'
+ 'slugify',
+ 'translator',
+ 'WORKSPACE_EXTENSION',
+ 'WorkspaceExportApp',
+ 'WorkspaceImportApp',
+ 'WorkspaceListApp'
]
def _jupyter_server_extension_points():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server/_version.py
new/jupyterlab_server-2.10.2/jupyterlab_server/_version.py
--- old/jupyterlab_server-2.8.2/jupyterlab_server/_version.py 2021-09-25
17:44:55.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server/_version.py 2021-12-29
00:45:06.000000000 +0100
@@ -4,7 +4,7 @@
"""
import re
-__version__ = '2.8.2'
+__version__ = '2.10.2'
# Build up version_info tuple for backwards compatibility
pattern = r'(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server/handlers.py
new/jupyterlab_server-2.10.2/jupyterlab_server/handlers.py
--- old/jupyterlab_server-2.8.2/jupyterlab_server/handlers.py 2021-09-25
17:44:23.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server/handlers.py 2021-12-29
00:44:34.000000000 +0100
@@ -5,6 +5,7 @@
# Distributed under the terms of the Modified BSD License.
import os
from urllib.parse import urlparse
+from functools import lru_cache
from tornado import template, web
@@ -17,7 +18,7 @@
from .settings_handler import SettingsHandler
from .themes_handler import ThemesHandler
from .translations_handler import TranslationsHandler
-from .workspaces_handler import WorkspacesHandler
+from .workspaces_handler import WorkspacesHandler, WorkspacesManager
from .licenses_handler import LicensesHandler, LicensesManager
# -----------------------------------------------------------------------------
@@ -57,18 +58,13 @@
class LabHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin,
JupyterHandler):
"""Render the JupyterLab View."""
- @web.authenticated
- @web.removeslash
- def get(self, mode = None, workspace = None, tree = None):
- """Get the JupyterLab html page."""
- workspace = 'default' if workspace is None else
workspace.replace('/workspaces/','')
- tree_path = '' if tree is None else tree.replace('/tree/','')
-
+ @lru_cache()
+ def get_page_config(self):
+ """Construct the page config object"""
self.application.store_id = getattr(self.application, 'store_id', 0)
config = LabConfig()
app = self.extensionapp
settings_dir = app.app_settings_dir
-
# Handle page config data.
page_config = self.settings.setdefault('page_config_data', {})
terminals = self.settings.get('terminals_available', False)
@@ -76,7 +72,7 @@
server_root = server_root.replace(os.sep, '/')
base_url = self.settings.get('base_url')
- # Remove the trailing slash for compatibiity with html-webpack-plugin.
+ # Remove the trailing slash for compatibility with html-webpack-plugin.
full_static_url = self.static_url_prefix.rstrip('/')
page_config.setdefault('fullStaticUrl', full_static_url)
@@ -87,10 +83,12 @@
server_root = os.path.normpath(os.path.expanduser(server_root))
try:
- page_config['preferredDir'] = self.serverapp.preferred_dir
- page_config['preferredPath'] =
self.serverapp.preferred_dir.replace(server_root, "")
+ # Remove the server_root from pref dir
+ if self.serverapp.preferred_dir != server_root:
+ page_config['preferredPath'] = '/' +
os.path.relpath(self.serverapp.preferred_dir, server_root)
+ else:
+ page_config['preferredPath'] = '/'
except Exception:
- page_config['preferredDir'] = server_root
page_config['preferredPath'] = '/'
self.application.store_id += 1
@@ -105,14 +103,6 @@
page_config.setdefault('mathjaxConfig', mathjax_config)
page_config.setdefault('fullMathjaxUrl', mathjax_url)
- # Add parameters parsed from the URL
- if mode == 'doc':
- page_config['mode'] = 'single-document'
- else:
- page_config['mode'] = 'multiple-document'
- page_config['workspace'] = workspace
- page_config['treePath'] = tree_path
-
# Put all our config in page_config
for name in config.trait_names():
page_config[_camelCase(name)] = getattr(app, name)
@@ -132,17 +122,43 @@
labextensions_path = app.extra_labextensions_path +
app.labextensions_path
recursive_update(page_config, get_page_config(labextensions_path,
settings_dir, logger=self.log))
+ # modify page config with custom hook
+ page_config_hook = self.settings.get("page_config_hook", None)
+ if page_config_hook:
+ page_config = page_config_hook(self, page_config)
+
+ return page_config
+
+ @web.authenticated
+ @web.removeslash
+ def get(self, mode=None, workspace=None, tree=None):
+ """Get the JupyterLab html page."""
+ workspace = (
+ "default" if workspace is None else
workspace.replace("/workspaces/", "")
+ )
+ tree_path = "" if tree is None else tree.replace("/tree/", "")
+
+ page_config = self.get_page_config()
+
+ # Add parameters parsed from the URL
+ if mode == "doc":
+ page_config["mode"] = "single-document"
+ else:
+ page_config["mode"] = "multiple-document"
+ page_config["workspace"] = workspace
+ page_config["treePath"] = tree_path
+
# Write the template with the config.
tpl = self.render_template('index.html', page_config=page_config)
self.write(tpl)
class NotFoundHandler(LabHandler):
- def render_template(self, name, **ns):
- if 'page_config' in ns:
- ns['page_config'] = ns['page_config'].copy()
- ns['page_config']['notFoundUrl'] = self.request.path
- return super().render_template(name, **ns)
+ @lru_cache()
+ def get_page_config(self):
+ page_config = super().get_page_config()
+ page_config["notFoundUrl"] = self.request.path
+ return page_config
def add_handlers(handlers, extension_app):
@@ -218,7 +234,7 @@
if extension_app.workspaces_dir:
workspaces_config = {
- 'path': extension_app.workspaces_dir
+ 'manager': WorkspacesManager(extension_app.workspaces_dir)
}
# Handle requests for the list of workspaces. Make slash optional.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server/settings_utils.py
new/jupyterlab_server-2.10.2/jupyterlab_server/settings_utils.py
--- old/jupyterlab_server-2.8.2/jupyterlab_server/settings_utils.py
2021-09-25 17:44:23.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server/settings_utils.py
2021-12-29 00:44:34.000000000 +0100
@@ -266,26 +266,50 @@
return path
-
def _get_overrides(app_settings_dir):
- """Get overrides settings from `app_settings_dir`."""
+ """Get overrides settings from `app_settings_dir`.
+
+ The ordering of paths is:
+ - {app_settings_dir}/overrides.d/*.{json,json5} (many, namespaced by
package)
+ - {app_settings_dir}/overrides.{json,json5} (singleton, owned by the user)
+ """
overrides, error = {}, ""
- overrides_path = os.path.join(app_settings_dir, 'overrides.json')
- if not os.path.exists(overrides_path):
- overrides_path = os.path.join(app_settings_dir, 'overrides.json5')
+ overrides_d = os.path.join(app_settings_dir, 'overrides.d')
+
+ # find (and sort) the conf.d overrides files
+ all_override_paths = sorted([
+ *(glob(os.path.join(overrides_d, '*.json'))),
+ *(glob(os.path.join(overrides_d, '*.json5'))),
+ ])
+
+ all_override_paths += [
+ os.path.join(app_settings_dir, 'overrides.json'),
+ os.path.join(app_settings_dir, 'overrides.json5')
+ ]
+
+ for overrides_path in all_override_paths:
+ if not os.path.exists(overrides_path):
+ continue
- if os.path.exists(overrides_path):
with open(overrides_path, encoding='utf-8') as fid:
try:
- overrides = json5.load(fid)
+ if overrides_path.endswith('.json5'):
+ path_overrides = json5.load(fid)
+ else:
+ path_overrides = json.load(fid)
+ for plugin_id, config in path_overrides.items():
+ recursive_update(overrides.setdefault(plugin_id, {}),
config)
+ print(overrides_path, overrides)
except Exception as e:
error = e
# Allow `default_settings_overrides.json` files in
<jupyter_config>/labconfig dirs
# to allow layering of defaults
cm = ConfigManager(config_dir_name="labconfig")
- recursive_update(overrides, cm.get('default_setting_overrides'))
+
+ for plugin_id, config in cm.get('default_setting_overrides').items():
+ recursive_update(overrides.setdefault(plugin_id, {}), config)
return overrides, error
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server/tests/test_settings_api.py
new/jupyterlab_server-2.10.2/jupyterlab_server/tests/test_settings_api.py
--- old/jupyterlab_server-2.8.2/jupyterlab_server/tests/test_settings_api.py
2021-09-25 17:44:23.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server/tests/test_settings_api.py
2021-12-29 00:44:34.000000000 +0100
@@ -1,8 +1,9 @@
"""Test the Settings service API.
"""
+from pathlib import Path
+import json
import pytest
-import json
import json5
import tornado
@@ -28,6 +29,28 @@
assert len(schema['properties']['codeCellConfig']['default']) == 15
[email protected]('ext', ['json', 'json5'])
+async def test_get_settings_overrides_d_dicts(jp_fetch, labserverapp, ext):
+ # Check that values that are dictionaries in overrides.d/*.json are
+ # merged with the schema.
+ id = '@jupyterlab/apputils-extension:themes'
+ overrides_d = Path(labserverapp.app_settings_dir) / "overrides.d"
+ overrides_d.mkdir(exist_ok=True, parents=True)
+ for i in range(10):
+ text = json.dumps({id: {'codeCellConfig': {'cursorBlinkRate': 530 +
i}}})
+ if ext == 'json5':
+ text += '\n// a comment'
+ (overrides_d / f"foo-{i}.{ext}").write_text(text, encoding='utf-8')
+ r = await jp_fetch('lab', 'api', 'settings', id)
+ validate_request(r)
+ res = r.body.decode()
+ data = json.loads(res)
+ assert data['id'] == id
+ schema = data['schema']
+ # Check that the last overrides.d/*.json file is respected.
+ assert
schema['properties']['codeCellConfig']['default']['cursorBlinkRate'] == 539
+
+
async def test_get_settings(jp_fetch, labserverapp):
id = '@jupyterlab/apputils-extension:themes'
r = await jp_fetch('lab', 'api', 'settings', id)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server/workspaces_app.py
new/jupyterlab_server-2.10.2/jupyterlab_server/workspaces_app.py
--- old/jupyterlab_server-2.8.2/jupyterlab_server/workspaces_app.py
1970-01-01 01:00:00.000000000 +0100
+++ new/jupyterlab_server-2.10.2/jupyterlab_server/workspaces_app.py
2021-12-29 00:44:34.000000000 +0100
@@ -0,0 +1,171 @@
+"""A workspace management CLI"""
+import json
+import sys
+from pathlib import Path
+
+from jupyter_core.application import JupyterApp
+from traitlets import Bool, Unicode
+
+from ._version import __version__
+from .config import LabConfig
+from .workspaces_handler import WorkspacesManager
+
+
+class WorkspaceListApp(JupyterApp, LabConfig):
+ version = __version__
+ description = """
+ Print all the workspaces available
+
+ If '--json' flag is passed in, a single 'json' object is printed.
+ If '--jsonlines' flag is passed in, 'json' object of each workspace
separated by a new line is printed.
+ If nothing is passed in, workspace ids list is printed.
+ """
+ flags = dict(
+ jsonlines=(
+ {"WorkspaceListApp": {"jsonlines": True}},
+ ("Produce machine-readable JSON Lines output."),
+ ),
+ json=(
+ {"WorkspaceListApp": {"json": True}},
+ ("Produce machine-readable JSON object output."),
+ ),
+ )
+
+ jsonlines = Bool(
+ False,
+ config=True,
+ help=(
+ "If True, the output will be a newline-delimited JSON (see
https://jsonlines.org/) of objects, "
+ "one per JupyterLab workspace, each with the details of the
relevant workspace"
+ ),
+ )
+ json = Bool(
+ False,
+ config=True,
+ help=(
+ "If True, each line of output will be a JSON object with the "
+ "details of the workspace."
+ ),
+ )
+
+ def initialize(self, *args, **kwargs):
+ super().initialize(*args, **kwargs)
+ self.manager = WorkspacesManager(self.workspaces_dir)
+
+ def start(self):
+ workspaces = self.manager.list_workspaces()
+ if self.jsonlines:
+ for workspace in workspaces:
+ print(json.dumps(workspace))
+ elif self.json:
+ print(json.dumps(workspaces))
+ else:
+ for workspace in workspaces:
+ print(workspace["metadata"]["id"])
+
+
+class WorkspaceExportApp(JupyterApp, LabConfig):
+ version = __version__
+ description = """
+ Export a JupyterLab workspace
+
+ If no arguments are passed in, this command will export the default
+ workspace.
+ If a workspace name is passed in, this command will export that workspace.
+ If no workspace is found, this command will export an empty workspace.
+ """
+
+ def initialize(self, *args, **kwargs):
+ super().initialize(*args, **kwargs)
+ self.manager = WorkspacesManager(self.workspaces_dir)
+
+ def start(self):
+ if len(self.extra_args) > 1:
+ print("Too many arguments were provided for workspace export.")
+ self.exit(1)
+
+ raw = self.extra_args[0]
+ try:
+ workspace = self.manager.load(raw)
+ print(json.dumps(workspace))
+ except Exception as e:
+ print(json.dumps(dict(data=dict(), metadata=dict(id=raw))))
+
+
+class WorkspaceImportApp(JupyterApp, LabConfig):
+ version = __version__
+ description = """
+ Import a JupyterLab workspace
+
+ This command will import a workspace from a JSON file. The format of the
+ file must be the same as what the export functionality emits.
+ """
+ workspace_name = Unicode(
+ None,
+ config=True,
+ allow_none=True,
+ help="""
+ Workspace name. If given, the workspace ID in the imported
+ file will be replaced with a new ID pointing to this
+ workspace name.
+ """,
+ )
+
+ aliases = {"name": "WorkspaceImportApp.workspace_name"}
+
+ def initialize(self, *args, **kwargs):
+ super().initialize(*args, **kwargs)
+ self.manager = WorkspacesManager(self.workspaces_dir)
+
+ def start(self):
+
+ if len(self.extra_args) != 1:
+ print("One argument is required for workspace import.")
+ self.exit(1)
+
+ with self._smart_open() as fid:
+ try: # to load, parse, and validate the workspace file.
+ workspace = self._validate(fid)
+ except Exception as e:
+ print("%s is not a valid workspace:\n%s" % (fid.name, e))
+ self.exit(1)
+
+ try:
+ workspace_path = self.manager.save(
+ workspace["metadata"]["id"], json.dumps(workspace)
+ )
+ except Exception as e:
+ print(f"Workspace could not be exported:\n{e!s}")
+ self.exit(1)
+
+ print(f"Saved workspace: {workspace_path!s}")
+
+ def _smart_open(self):
+ file_name = self.extra_args[0]
+
+ if file_name == "-":
+ return sys.stdin
+ else:
+ file_path = Path(file_name).resolve()
+
+ if not file_path.exists():
+ print(f"{file_name!s} does not exist.")
+ self.exit(1)
+
+ return file_path.open(encoding="utf-8")
+
+ def _validate(self, data):
+ workspace = json.load(data)
+
+ if "data" not in workspace:
+ raise Exception("The `data` field is missing.")
+
+ # If workspace_name is set in config, inject the
+ # name into the workspace metadata.
+ if self.workspace_name is not None and self.workspace_name != "":
+ workspace["metadata"] = {"id": self.workspace_name}
+ else:
+ if "id" not in workspace["metadata"]:
+ raise Exception("The `id` field is missing in `metadata`.")
+
+ return workspace
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server/workspaces_handler.py
new/jupyterlab_server-2.10.2/jupyterlab_server/workspaces_handler.py
--- old/jupyterlab_server-2.8.2/jupyterlab_server/workspaces_handler.py
2021-09-25 17:44:23.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server/workspaces_handler.py
2021-12-29 00:44:34.000000000 +0100
@@ -4,64 +4,66 @@
# Distributed under the terms of the Modified BSD License.
import hashlib
import json
-import os
import re
import unicodedata
import urllib
+from pathlib import Path
-from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin,
ExtensionHandlerMixin
+from jupyter_server.extension.handler import (
+ ExtensionHandlerJinjaMixin,
+ ExtensionHandlerMixin,
+)
from tornado import web
+from traitlets.config import LoggingConfigurable
from .server import APIHandler, tz
from .server import url_path_join as ujoin
# The JupyterLab workspace file extension.
-WORKSPACE_EXTENSION = '.jupyterlab-workspace'
+WORKSPACE_EXTENSION = ".jupyterlab-workspace"
-def _list_workspaces(directory, prefix):
+
+def _list_workspaces(directory: Path, prefix: str) -> list:
"""
Return the list of workspaces in a given directory beginning with the
given prefix.
"""
- workspaces = { 'ids': [], 'values': [] }
- if not os.path.exists(directory):
+ workspaces = []
+ if not directory.exists():
return workspaces
- items = [item
- for item in os.listdir(directory)
- if item.startswith(prefix) and
- item.endswith(WORKSPACE_EXTENSION)]
+ items = [
+ item
+ for item in directory.iterdir()
+ if item.name.startswith(prefix) and
item.name.endswith(WORKSPACE_EXTENSION)
+ ]
items.sort()
for slug in items:
- workspace_path = os.path.join(directory, slug)
- if os.path.exists(workspace_path):
- try:
- workspace = _load_with_file_times(workspace_path)
- workspaces.get('ids').append(workspace['metadata']['id'])
- workspaces.get('values').append(workspace)
- except Exception as e:
- raise web.HTTPError(500, str(e))
+ workspace_path: Path = directory / slug
+ if workspace_path.exists():
+ workspace = _load_with_file_times(workspace_path)
+ workspaces.append(workspace)
return workspaces
-def _load_with_file_times(workspace_path):
+def _load_with_file_times(workspace_path: Path) -> dict:
"""
Load workspace JSON from disk, overwriting the `created` and
`last_modified`
metadata with current file stat information
"""
- stat = os.stat(workspace_path)
- with open(workspace_path, encoding='utf-8') as fid:
+ stat = workspace_path.stat()
+ with workspace_path.open(encoding="utf-8") as fid:
workspace = json.load(fid)
workspace["metadata"].update(
last_modified=tz.utcfromtimestamp(stat.st_mtime).isoformat(),
- created=tz.utcfromtimestamp(stat.st_ctime).isoformat()
+ created=tz.utcfromtimestamp(stat.st_ctime).isoformat(),
)
return workspace
-def slugify(raw, base='', sign=True, max_length=128 -
len(WORKSPACE_EXTENSION)):
+def slugify(raw, base="", sign=True, max_length=128 -
len(WORKSPACE_EXTENSION)):
"""
Use the common superset of raw and base values to build a slug shorter
than max_length. By default, base value is an empty string.
@@ -72,12 +74,12 @@
Modified from Django utils:
https://github.com/django/django/blob/master/django/utils/text.py
"""
- raw = raw if raw.startswith('/') else '/' + raw
- signature = ''
+ raw = raw if raw.startswith("/") else "/" + raw
+ signature = ""
if sign:
data = raw[1:] # Remove initial slash that always exists for digest.
- signature = '-' + hashlib.sha256(data.encode('utf-8')).hexdigest()[:4]
- base = (base if base.startswith('/') else '/' + base).lower()
+ signature = "-" + hashlib.sha256(data.encode("utf-8")).hexdigest()[:4]
+ base = (base if base.startswith("/") else "/" + base).lower()
raw = raw.lower()
common = 0
limit = min(len(base), len(raw))
@@ -85,114 +87,141 @@
common += 1
value = ujoin(base[common:], raw)
value = urllib.parse.unquote(value)
- value = (unicodedata
- .normalize('NFKC', value)
- .encode('ascii', 'ignore')
- .decode('ascii'))
- value = re.sub(r'[^\w\s-]', '', value).strip()
- value = re.sub(r'[-\s]+', '-', value)
- return value[:max_length - len(signature)] + signature
+ value = (
+ unicodedata.normalize("NFKC", value).encode("ascii",
"ignore").decode("ascii")
+ )
+ value = re.sub(r"[^\w\s-]", "", value).strip()
+ value = re.sub(r"[-\s]+", "-", value)
+ return value[: max_length - len(signature)] + signature
+
+
+class WorkspacesManager(LoggingConfigurable):
+ """A manager for workspaces."""
+
+ def __init__(self, path):
+ """Initialize a workspaces manager with content in ``path``."""
+ super()
+ if not path:
+ raise ValueError("Workspaces directory is not set")
+ self.workspaces_dir = Path(path)
+ def delete(self, space_name):
+ """Remove a workspace ``space_name``."""
+ slug = slugify(space_name)
+ workspace_path = self.workspaces_dir / (slug + WORKSPACE_EXTENSION)
-class WorkspacesHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin,
APIHandler):
+ if not workspace_path.exists():
+ raise FileNotFoundError(f"Workspace {space_name!r} ({slug!r}) not
found")
- def initialize(self, name, path, workspaces_url=None, **kwargs):
- super().initialize(name)
- self.workspaces_dir = path
+ # to delete the workspace file.
+ workspace_path.unlink()
- def ensure_directory(self):
- """Return the workspaces directory if set or raise error if not set"""
- if not self.workspaces_dir:
- raise web.HTTPError(500, 'Workspaces directory is not set')
+ def list_workspaces(self) -> list:
+ """List all available workspaces."""
+ prefix = slugify("", sign=False)
+ return _list_workspaces(self.workspaces_dir, prefix)
- return self.workspaces_dir
+ def load(self, space_name: str) -> dict:
+ """Load the workspace ``space_name``."""
+ slug = slugify(space_name)
+ workspace_path = self.workspaces_dir / (slug + WORKSPACE_EXTENSION)
- @web.authenticated
- def delete(self, space_name):
- """Remove a workspace"""
- directory = self.ensure_directory()
+ if workspace_path.exists():
+ # to load and parse the workspace file.
+ return _load_with_file_times(workspace_path)
+ else:
+ id = space_name if space_name.startswith("/") else "/" + space_name
+ return dict(data=dict(), metadata=dict(id=id))
- if not space_name:
- raise web.HTTPError(400, 'Workspace name is required for DELETE')
+ def save(self, space_name, raw) -> Path:
+ """Save the ``raw`` data as workspace ``space_name``."""
+ if not self.workspaces_dir.exists():
+ self.workspaces_dir.mkdir(parents=True)
+
+ workspace = dict()
+
+ # Make sure the data is valid JSON.
+ try:
+ decoder = json.JSONDecoder()
+ workspace = decoder.decode(raw)
+ except Exception as e:
+ raise ValueError(str(e)) from e
+
+ # Make sure metadata ID matches the workspace name.
+ # Transparently support an optional initial root `/`.
+ metadata_id = workspace["metadata"]["id"]
+ metadata_id = metadata_id if metadata_id.startswith("/") else "/" +
metadata_id
+ metadata_id = urllib.parse.unquote(metadata_id)
+ if metadata_id != "/" + space_name:
+ message = "Workspace metadata ID mismatch: expected %r got %r" % (
+ space_name,
+ metadata_id,
+ )
+ raise ValueError(message)
slug = slugify(space_name)
- workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION)
+ workspace_path = self.workspaces_dir / (slug + WORKSPACE_EXTENSION)
+
+ # Write the workspace data to a file.
+ workspace_path.write_text(raw, encoding="utf-8")
- if not os.path.exists(workspace_path):
- raise web.HTTPError(404, 'Workspace %r (%r) not found' %
- (space_name, slug))
+ return workspace_path
- try: # to delete the workspace file.
- os.remove(workspace_path)
+
+class WorkspacesHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin,
APIHandler):
+ def initialize(self, name, manager: WorkspacesManager, **kwargs):
+ super().initialize(name)
+ self.manager = manager
+
+ @web.authenticated
+ def delete(self, space_name):
+ """Remove a workspace"""
+ if not space_name:
+ raise web.HTTPError(400, "Workspace name is required for DELETE")
+
+ try:
+ self.manager.delete(space_name)
return self.set_status(204)
+ except FileNotFoundError as e:
+ raise web.HTTPError(404, str(e)) from e
except Exception as e:
- raise web.HTTPError(500, str(e))
+ raise web.HTTPError(500, str(e)) from e
@web.authenticated
- def get(self, space_name=''):
+ def get(self, space_name=""):
"""Get workspace(s) data"""
- directory = self.ensure_directory()
- if not space_name:
- prefix = slugify('', sign=False)
- workspaces = _list_workspaces(directory, prefix)
- return self.finish(json.dumps(dict(workspaces=workspaces)))
-
- slug = slugify(space_name)
- workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION)
-
- if os.path.exists(workspace_path):
- try: # to load and parse the workspace file.
- workspace = _load_with_file_times(workspace_path)
- except Exception as e:
- raise web.HTTPError(500, str(e))
- else:
- id = (space_name if space_name.startswith('/')
- else '/' + space_name)
- workspace = dict(data=dict(), metadata=dict(id=id))
+ try:
+ if not space_name:
+ workspaces = self.manager.list_workspaces()
+ ids = []
+ values = []
+ for workspace in workspaces:
+ ids.append(workspace["metadata"]["id"])
+ values.append(workspace)
+ return self.finish(
+ json.dumps({"workspaces": {"ids": ids, "values": values}})
+ )
- return self.finish(json.dumps(workspace))
+ workspace = self.manager.load(space_name)
+ return self.finish(json.dumps(workspace))
+ except Exception as e:
+ raise web.HTTPError(500, str(e)) from e
@web.authenticated
- def put(self, space_name=''):
+ def put(self, space_name=""):
"""Update workspace data"""
if not space_name:
- raise web.HTTPError(400, 'Workspace name is required for PUT.')
-
- directory = self.ensure_directory()
+ raise web.HTTPError(400, "Workspace name is required for PUT.")
- if not os.path.exists(directory):
- try:
- os.makedirs(directory)
- except Exception as e:
- raise web.HTTPError(500, str(e))
-
- raw = self.request.body.strip().decode('utf-8')
- workspace = dict()
+ raw = self.request.body.strip().decode("utf-8")
# Make sure the data is valid JSON.
try:
- decoder = json.JSONDecoder()
- workspace = decoder.decode(raw)
+ self.manager.save(space_name, raw)
+ except ValueError as e:
+ raise web.HTTPError(400, str(e)) from e
except Exception as e:
- raise web.HTTPError(400, str(e))
-
- # Make sure metadata ID matches the workspace name.
- # Transparently support an optional inital root `/`.
- metadata_id = workspace['metadata']['id']
- metadata_id = (metadata_id if metadata_id.startswith('/')
- else '/' + metadata_id)
- metadata_id = urllib.parse.unquote(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)
-
- slug = slugify(space_name)
- workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION)
-
- # Write the workspace data to a file.
- with open(workspace_path, 'w', encoding='utf-8') as fid:
- fid.write(raw)
+ raise web.HTTPError(500, str(e)) from e
self.set_status(204)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server.egg-info/PKG-INFO
new/jupyterlab_server-2.10.2/jupyterlab_server.egg-info/PKG-INFO
--- old/jupyterlab_server-2.8.2/jupyterlab_server.egg-info/PKG-INFO
2021-09-25 17:45:13.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server.egg-info/PKG-INFO
2021-12-29 00:45:27.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: jupyterlab-server
-Version: 2.8.2
+Version: 2.10.2
Summary: A set of server components for JupyterLab and JupyterLab like
applications .
Home-page: https://jupyter.org
Author: Jupyter Development Team
@@ -30,6 +30,11 @@
[](https://github.com/jupyterlab/jupyterlab_server/actions?query=branch%3Amaster+workflow%3A%22Tests%22)
[](http://jupyterlab_server.readthedocs.io/en/stable/)
+## Motivation
+
+JupyterLab Server sits between JupyterLab and Jupyter Server, and provides a
+set of REST API handlers and utilities that are used by JupyterLab. It is a
separate project in order to
+accommodate creating JupyterLab-like applications from a more limited scope.
## Install
@@ -37,11 +42,11 @@
## Usage
-The application author creates a JupyterLab build on their machine
-using the core JupyterLab application. They can then serve their
-files by subclassing the `LabServerApp` with the appropriate
-configuration and creating a Python entry point that launches the app.
+See the full documentation for [API
docs](https://jupyterlab-server.readthedocs.io/en/stable/api/index.html) and
[REST endpoint
descriptions](https://jupyterlab-server.readthedocs.io/en/stable/api/rest.html).
+
+## Extending the Application
+Subclass the `LabServerApp` and provide additional traits and handlers as
appropriate for your application.
## Contribution
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyterlab_server-2.8.2/jupyterlab_server.egg-info/SOURCES.txt
new/jupyterlab_server-2.10.2/jupyterlab_server.egg-info/SOURCES.txt
--- old/jupyterlab_server-2.8.2/jupyterlab_server.egg-info/SOURCES.txt
2021-09-25 17:45:13.000000000 +0200
+++ new/jupyterlab_server-2.10.2/jupyterlab_server.egg-info/SOURCES.txt
2021-12-29 00:45:27.000000000 +0100
@@ -38,6 +38,7 @@
jupyterlab_server/themes_handler.py
jupyterlab_server/translation_utils.py
jupyterlab_server/translations_handler.py
+jupyterlab_server/workspaces_app.py
jupyterlab_server/workspaces_handler.py
jupyterlab_server.egg-info/PKG-INFO
jupyterlab_server.egg-info/SOURCES.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyterlab_server-2.8.2/pyproject.toml
new/jupyterlab_server-2.10.2/pyproject.toml
--- old/jupyterlab_server-2.8.2/pyproject.toml 2021-09-25 17:44:55.000000000
+0200
+++ new/jupyterlab_server-2.10.2/pyproject.toml 2021-12-29 00:45:06.000000000
+0100
@@ -1,5 +1,5 @@
[build-system]
-requires = ["jupyter_packaging~=0.9,<2", "jupyter_server"]
+requires = ["jupyter_packaging>0.9,<2", "jupyter_server"]
build-backend = "setuptools.build_meta"
[tool.jupyter-releaser]
@@ -10,7 +10,7 @@
ignore-bad-ideas = ["jupyterlab_server/tests/translations/**/*.mo"]
[tool.tbump.version]
-current = "2.8.2"
+current = "2.10.2"
regex = '''
(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
((?P<channel>a|b|rc|.dev)(?P<release>\d+))?