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 @@
 [![Build 
Status](https://github.com/jupyterlab/jupyterlab_server/workflows/Tests/badge.svg?branch=master)](https://github.com/jupyterlab/jupyterlab_server/actions?query=branch%3Amaster+workflow%3A%22Tests%22)
 [![Documentation 
Status](https://readthedocs.org/projects/jupyterlab_server/badge/?version=stable)](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 @@
 [![Build 
Status](https://github.com/jupyterlab/jupyterlab_server/workflows/Tests/badge.svg?branch=master)](https://github.com/jupyterlab/jupyterlab_server/actions?query=branch%3Amaster+workflow%3A%22Tests%22)
 [![Documentation 
Status](https://readthedocs.org/projects/jupyterlab_server/badge/?version=stable)](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 @@
 [![Build 
Status](https://github.com/jupyterlab/jupyterlab_server/workflows/Tests/badge.svg?branch=master)](https://github.com/jupyterlab/jupyterlab_server/actions?query=branch%3Amaster+workflow%3A%22Tests%22)
 [![Documentation 
Status](https://readthedocs.org/projects/jupyterlab_server/badge/?version=stable)](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+))?

Reply via email to