Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-jupyter-server for 
openSUSE:Factory checked in at 2022-01-04 19:37:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jupyter-server (Old)
 and      /work/SRC/openSUSE:Factory/.python-jupyter-server.new.1896 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-jupyter-server"

Tue Jan  4 19:37:38 2022 rev:20 rq:943591 version:1.13.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-jupyter-server/python-jupyter-server.changes  
    2021-11-17 01:14:46.966184007 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-jupyter-server.new.1896/python-jupyter-server.changes
    2022-01-04 19:37:48.933955647 +0100
@@ -1,0 +2,29 @@
+Fri Dec 31 15:55:54 UTC 2021 - Ben Greiner <c...@bnavigator.de>
+
+- Update to 1.13.1
+  * nudge both the shell and control channels #636 (@Zsailer)
+  * Persistent session storage #614 (@Zsailer)
+  * Nudge on the control channel instead of the shell #628
+    (@JohanMabille)
+  * Clean up downstream tests #629 (@blink1073)
+  * Clean up version info handling #620 (@blink1073)
+  * Await _finish_kernel_start #617 (@jtpio)
+  * Update to Python 3.10 in the CI workflows #618 (@jtpio)
+  * Use maintainer-tools base setup action #616 (@blink1073)
+  * Consistent logging method #607 (@mwakaba2)
+  * Use pending kernels #593 (@blink1073)
+  * Set xsrf cookie on base url #612 (@minrk)
+  * Update jpserver_extensions trait to work with traitlets 5.x
+    #610 (@Zsailer)
+  * Fix allow_origin_pat property to properly parse regex #603
+    (@havok2063)
+  * Enforce labels on PRs #613 (@blink1073)
+  * Normalize file name and path in test_api #608 (@toonn)
+
+-------------------------------------------------------------------
+Mon Nov 15 18:42:07 UTC 2021 - Ben Greiner <c...@bnavigator.de>
+
+- Multibuild :test flavor, avoid possible buildcycles
+- Fix libalternatives in test flavor
+
+-------------------------------------------------------------------

Old:
----
  jupyter_server-1.11.2.tar.gz

New:
----
  _multibuild
  jupyter_server-1.13.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-jupyter-server.spec ++++++
--- /var/tmp/diff_new_pack.k5UPm6/_old  2022-01-04 19:37:49.509956401 +0100
+++ /var/tmp/diff_new_pack.k5UPm6/_new  2022-01-04 19:37:49.513956406 +0100
@@ -1,5 +1,5 @@
 #
-# spec file for package python-jupyter-server
+# spec file
 #
 # Copyright (c) 2021 SUSE LLC
 #
@@ -17,6 +17,15 @@
 
 
 #
+%global flavor @BUILD_FLAVOR@%{nil}
+%if "%{flavor}" == "test"
+%define psuffix -test
+%bcond_without test
+%else
+%define psuffix %{nil}
+%bcond_with test
+%endif
+
 %if 0%{?suse_version} > 1500
 %bcond_without libalternatives
 %else
@@ -25,8 +34,8 @@
 
 %{?!python_module:%define python_module() python3-%{**}}
 %define         skip_python2 1
-Name:           python-jupyter-server
-Version:        1.11.2
+Name:           python-jupyter-server%{psuffix}
+Version:        1.13.1
 Release:        0
 Summary:        The backend to Jupyter web applications
 License:        BSD-3-Clause
@@ -34,23 +43,9 @@
 URL:            https://github.com/jupyter-server/jupyter_server
 # need the release tarball for the static stylesheets
 Source:         
https://github.com/jupyter-server/jupyter_server/releases/download/v%{version}/jupyter_server-%{version}.tar.gz
-BuildRequires:  %{python_module Jinja2}
-BuildRequires:  %{python_module Send2Trash}
-BuildRequires:  %{python_module anyio >= 3.1.0}
-BuildRequires:  %{python_module argon2-cffi}
-BuildRequires:  %{python_module ipython_genutils}
-BuildRequires:  %{python_module jupyter-client >= 6.1.1}
-BuildRequires:  %{python_module jupyter-core >= 4.4.0}
 BuildRequires:  %{python_module jupyter_packaging}
-BuildRequires:  %{python_module nbconvert}
-BuildRequires:  %{python_module nbformat}
-BuildRequires:  %{python_module prometheus_client}
-BuildRequires:  %{python_module pyzmq >= 17}
 BuildRequires:  %{python_module setuptools}
-BuildRequires:  %{python_module terminado >= 0.8.3}
-BuildRequires:  %{python_module tornado >= 6.1}
-BuildRequires:  %{python_module traitlets >= 4.2.1}
-BuildRequires:  %{python_module websocket-client}
+
 # We need the full stdlib
 BuildRequires:  %{pythons}
 BuildRequires:  fdupes
@@ -79,14 +74,9 @@
 %endif
 Provides:       python-jupyter_server = %{version}-%{release}
 Obsoletes:      python-jupyter_server < %{version}-%{release}
-# SECTION extras_require test
-BuildRequires:  %{python_module ipykernel}
-BuildRequires:  %{python_module pytest >= 6}
-BuildRequires:  %{python_module pytest-console-scripts}
-BuildRequires:  %{python_module pytest-mock}
-BuildRequires:  %{python_module pytest-tornasync}
-BuildRequires:  %{python_module requests}
-# /SECTION
+%if %{with test}
+BuildRequires:  %{python_module jupyter-server-test = %{version}}
+%endif
 %if "%{python_flavor}" == "python3" || "%{python_provides}" == "python3"
 Provides:       jupyter-jupyter-server = %{version}-%{release}
 Obsoletes:      jupyter-jupyter-server < %{version}-%{release}
@@ -117,6 +107,7 @@
 %prep
 %setup -q -n jupyter_server-%{version}
 
+%if ! %{with test}
 %build
 %python_build
 
@@ -124,11 +115,22 @@
 %python_install
 %python_clone -a  %{buildroot}%{_bindir}/jupyter-server
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
+%endif
 
+%if %{with test}
 %check
 %{python_expand # provide u-a entrypoints in the correct flavor version -- 
installed packages and jupyter-server
-mkdir build/testbin
-for bin in %{_bindir}/*-%{$python_bin_suffix} 
%{buildroot}%{_bindir}/*-%{$python_bin_suffix} ; do
+mkdir -p build/xdgflavorconfig
+export XDG_CONFIG_HOME=$PWD/build/xdgflavorconfig
+if [ -d /usr/share/libalternatives/ ]; then
+  for b in /usr/share/libalternatives/*; do
+    if [ -e "${b}/%{$python_version_nodots}.conf" ]; then
+        alts -n $(basename ${b}) -p %{$python_version_nodots}
+    fi
+  done
+fi
+mkdir -p build/testbin
+for bin in %{_bindir}/*-%{$python_bin_suffix}; do
   # four percent into 1 by rpm/python expansions
   ln -s ${bin} build/testbin/$(basename ${bin%%%%-%{$python_bin_suffix}})
 done
@@ -140,7 +142,9 @@
     echo "You might need to delete ~/.local/share/jupyter in order to avoid 
test failures."
 fi
 %pytest jupyter_server
+%endif
 
+%if ! %{with test}
 %pre
 # If libalternatives is used: Removing old update-alternatives entries.
 %python_libalternatives_reset_alternative jupyter-server
@@ -160,5 +164,6 @@
 
 %files %{python_files test}
 %license COPYING.md
+%endif
 
 %changelog

++++++ _multibuild ++++++
<multibuild>
  <package>test</package>
</multibuild>
(No newline at EOF)

++++++ jupyter_server-1.11.2.tar.gz -> jupyter_server-1.13.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.11.2/CHANGELOG.md 
new/jupyter_server-1.13.1/CHANGELOG.md
--- old/jupyter_server-1.11.2/CHANGELOG.md      2021-11-01 21:22:56.000000000 
+0100
+++ new/jupyter_server-1.13.1/CHANGELOG.md      2021-12-09 20:36:05.000000000 
+0100
@@ -4,6 +4,94 @@
 
 <!-- <START NEW CHANGELOG ENTRY> -->
 
+## 1.13.1
+
+([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.13.0...affd5d9a2e6d718baa2185518256f51921fd4484))
+
+### Bugs fixed
+
+- nudge both the shell and control channels 
[#636](https://github.com/jupyter-server/jupyter_server/pull/636) 
([@Zsailer](https://github.com/Zsailer))
+
+### Maintenance and upkeep improvements
+
+- Fix macos pypy check 
[#632](https://github.com/jupyter-server/jupyter_server/pull/632) 
([@blink1073](https://github.com/blink1073))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2021-12-06&to=2021-12-09&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2021-12-06..2021-12-09&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-12-06..2021-12-09&type=Issues)
 | 
[@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2021-12-06..2021-12-09&type=Issues)
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
+## 1.13.0
+
+([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.12.1...b51969f16f04375d52cb029d72f90174141c760d))
+
+### Enhancements made
+
+- Persistent session storage 
[#614](https://github.com/jupyter-server/jupyter_server/pull/614) 
([@Zsailer](https://github.com/Zsailer))
+
+### Bugs fixed
+
+- Nudge on the control channel instead of the shell 
[#628](https://github.com/jupyter-server/jupyter_server/pull/628) 
([@JohanMabille](https://github.com/JohanMabille))
+
+### Maintenance and upkeep improvements
+
+- Clean up downstream tests 
[#629](https://github.com/jupyter-server/jupyter_server/pull/629) 
([@blink1073](https://github.com/blink1073))
+- Clean up version info handling 
[#620](https://github.com/jupyter-server/jupyter_server/pull/620) 
([@blink1073](https://github.com/blink1073))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2021-11-26&to=2021-12-06&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2021-11-26..2021-12-06&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-11-26..2021-12-06&type=Issues)
 | 
[@echarles](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aecharles+updated%3A2021-11-26..2021-12-06&type=Issues)
 | 
[@JohanMabille](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AJohanMabille+updated%3A2021-11-26..2021-12-06&type=Issues)
 | 
[@jtpio](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ajtpio+updated%3A2021-11-26..2021-12-06&type=Issues)
 | 
[@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2021-11-26..2021-12-06&type=Issues)
+
+## 1.12.1
+
+([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.12.0...ead83374b3b874bdf4ea47fca5aee1ecb5940a85))
+
+### Bugs fixed
+
+- Await `_finish_kernel_start` 
[#617](https://github.com/jupyter-server/jupyter_server/pull/617) 
([@jtpio](https://github.com/jtpio))
+
+### Maintenance and upkeep improvements
+
+- Update to Python 3.10 in the CI workflows 
[#618](https://github.com/jupyter-server/jupyter_server/pull/618) 
([@jtpio](https://github.com/jtpio))
+- Use `maintainer-tools` base setup action 
[#616](https://github.com/jupyter-server/jupyter_server/pull/616) 
([@blink1073](https://github.com/blink1073))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2021-11-23&to=2021-11-26&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2021-11-23..2021-11-26&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-11-23..2021-11-26&type=Issues)
 | 
[@jtpio](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ajtpio+updated%3A2021-11-23..2021-11-26&type=Issues)
+
+## 1.12.0
+
+([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.11.2...758dba6f8873f60c1ca41057b4be108da5a6ff1a))
+
+### Enhancements made
+
+- Consistent logging method 
[#607](https://github.com/jupyter-server/jupyter_server/pull/607) 
([@mwakaba2](https://github.com/mwakaba2))
+- Use pending kernels 
[#593](https://github.com/jupyter-server/jupyter_server/pull/593) 
([@blink1073](https://github.com/blink1073))
+
+### Bugs fixed
+
+- Set `xsrf` cookie on base url 
[#612](https://github.com/jupyter-server/jupyter_server/pull/612) 
([@minrk](https://github.com/minrk))
+- Update `jpserver_extensions` trait to work with `traitlets` 5.x 
[#610](https://github.com/jupyter-server/jupyter_server/pull/610) 
([@Zsailer](https://github.com/Zsailer))
+- Fix `allow_origin_pat` property to properly parse regex 
[#603](https://github.com/jupyter-server/jupyter_server/pull/603) 
([@havok2063](https://github.com/havok2063))
+
+### Maintenance and upkeep improvements
+
+- Enforce labels on PRs 
[#613](https://github.com/jupyter-server/jupyter_server/pull/613) 
([@blink1073](https://github.com/blink1073))
+- Normalize file name and path in `test_api` 
[#608](https://github.com/jupyter-server/jupyter_server/pull/608) 
([@toonn](https://github.com/toonn))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2021-11-01&to=2021-11-23&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2021-11-01..2021-11-23&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-11-01..2021-11-23&type=Issues)
 | 
[@havok2063](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ahavok2063+updated%3A2021-11-01..2021-11-23&type=Issues)
 | 
[@minrk](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aminrk+updated%3A2021-11-01..2021-11-23&type=Issues)
 | 
[@mwakaba2](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amwakaba2+updated%3A2021-11-01..2021-11-23&type=Issues)
 | 
[@toonn](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Atoonn+updated%3A2021-11-01..2021-11-23&type=Issues)
 | 
[@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Awelcome+updated%
 3A2021-11-01..2021-11-23&type=Issues) | 
[@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2021-11-01..2021-11-23&type=Issues)
+
 ## 1.11.2
 
 ([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.11.1...fda4cc5a96703bb4e871a5a622ef6031c7f6385b))
@@ -27,8 +115,6 @@
 
 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-10-04..2021-11-01&type=Issues)
 | 
[@dependabot](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Adependabot+updated%3A2021-10-04..2021-11-01&type=Issues)
 | 
[@kevin-bates](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akevin-bates+updated%3A2021-10-04..2021-11-01&type=Issues)
 | 
[@stdll00](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Astdll00+updated%3A2021-10-04..2021-11-01&type=Issues)
 | 
[@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Awelcome+updated%3A2021-10-04..2021-11-01&type=Issues)
 | 
[@Wh1isper](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AWh1isper+updated%3A2021-10-04..2021-11-01&type=Issues)
 | 
[@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZs
 ailer+updated%3A2021-10-04..2021-11-01&type=Issues)
 
-<!-- <END NEW CHANGELOG ENTRY> -->
-
 ## 1.11.1
 
 ([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.11.0...f4c3889658c1daad1d8966438d1f1b98b3f60641))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.11.2/PKG-INFO 
new/jupyter_server-1.13.1/PKG-INFO
--- old/jupyter_server-1.11.2/PKG-INFO  2021-11-01 21:23:33.281527500 +0100
+++ new/jupyter_server-1.13.1/PKG-INFO  2021-12-09 20:36:35.956748700 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jupyter_server
-Version: 1.11.2
+Version: 1.13.1
 Summary: The backend???i.e. core services, APIs, and REST endpoints???to 
Jupyter web applications.
 Home-page: https://jupyter.org
 Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.11.2/jupyter_server/_version.py 
new/jupyter_server-1.13.1/jupyter_server/_version.py
--- old/jupyter_server-1.11.2/jupyter_server/_version.py        2021-11-01 
21:23:18.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/_version.py        2021-12-09 
20:36:21.000000000 +0100
@@ -2,15 +2,5 @@
 store the current version info of the server.
 
 """
-import re
-
-# Version string must appear intact for tbump versioning
-__version__ = "1.11.2"
-
-# Build up version_info tuple for backwards compatibility
-pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
-match = re.match(pattern, __version__)
-parts = [int(match[part]) for part in ["major", "minor", "patch"]]
-if match["rest"]:
-    parts.append(match["rest"])
-version_info = tuple(parts)
+version_info = (1, 13, 1, "", "")
+__version__ = ".".join(map(str, version_info[:3])) + "".join(version_info[3:])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.11.2/jupyter_server/auth/login.py 
new/jupyter_server-1.13.1/jupyter_server/auth/login.py
--- old/jupyter_server-1.11.2/jupyter_server/auth/login.py      2021-11-01 
21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/auth/login.py      2021-12-09 
20:36:05.000000000 +0100
@@ -53,7 +53,7 @@
                 if self.allow_origin:
                     allow = self.allow_origin == origin
                 elif self.allow_origin_pat:
-                    allow = bool(self.allow_origin_pat.match(origin))
+                    allow = bool(re.match(self.allow_origin_pat, origin))
             if not allow:
                 # not allowed, use default
                 self.log.warning("Not allowing login redirect to %r" % url)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/base/handlers.py 
new/jupyter_server-1.13.1/jupyter_server/base/handlers.py
--- old/jupyter_server-1.11.2/jupyter_server/base/handlers.py   2021-11-01 
21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/base/handlers.py   2021-12-09 
20:36:05.000000000 +0100
@@ -309,7 +309,7 @@
             self.set_header("Access-Control-Allow-Origin", self.allow_origin)
         elif self.allow_origin_pat:
             origin = self.get_origin()
-            if origin and self.allow_origin_pat.match(origin):
+            if origin and re.match(self.allow_origin_pat, origin):
                 self.set_header("Access-Control-Allow-Origin", origin)
         elif self.token_authenticated and "Access-Control-Allow-Origin" not in 
self.settings.get(
             "headers", {}
@@ -382,7 +382,7 @@
         if self.allow_origin:
             allow = self.allow_origin == origin
         elif self.allow_origin_pat:
-            allow = bool(self.allow_origin_pat.match(origin))
+            allow = bool(re.match(self.allow_origin_pat, origin))
         else:
             # No CORS headers deny the request
             allow = False
@@ -427,7 +427,7 @@
         if self.allow_origin:
             allow = self.allow_origin == origin
         elif self.allow_origin_pat:
-            allow = bool(self.allow_origin_pat.match(origin))
+            allow = bool(re.match(self.allow_origin_pat, origin))
         else:
             # No CORS settings, deny the request
             allow = False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/base/zmqhandlers.py 
new/jupyter_server-1.13.1/jupyter_server/base/zmqhandlers.py
--- old/jupyter_server-1.11.2/jupyter_server/base/zmqhandlers.py        
2021-11-01 21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/base/zmqhandlers.py        
2021-12-09 20:36:05.000000000 +0100
@@ -3,6 +3,7 @@
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 import json
+import re
 import struct
 import sys
 from urllib.parse import urlparse
@@ -139,7 +140,7 @@
         if self.allow_origin:
             allow = self.allow_origin == origin
         elif self.allow_origin_pat:
-            allow = bool(self.allow_origin_pat.match(origin))
+            allow = bool(re.match(self.allow_origin_pat, origin))
         else:
             # No CORS headers deny the request
             allow = False
@@ -176,6 +177,10 @@
             self.ping_callback.stop()
             return
 
+        if self.ws_connection.client_terminated:
+            self.close()
+            return
+
         # check for timeout on pong.  Make sure that we really have sent a 
recent ping in
         # case the machine with both server and client has been suspended 
since the last ping.
         now = ioloop.IOLoop.current().time()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/pytest_plugin.py 
new/jupyter_server-1.13.1/jupyter_server/pytest_plugin.py
--- old/jupyter_server-1.11.2/jupyter_server/pytest_plugin.py   2021-11-01 
21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/pytest_plugin.py   2021-12-09 
20:36:05.000000000 +0100
@@ -416,13 +416,16 @@
 @pytest.fixture
 def jp_kernelspecs(jp_data_dir):
     """Configures some sample kernelspecs in the Jupyter data directory."""
-    spec_names = ["sample", "sample 2"]
+    spec_names = ["sample", "sample 2", "bad"]
     for name in spec_names:
         sample_kernel_dir = jp_data_dir.joinpath("kernels", name)
         sample_kernel_dir.mkdir(parents=True)
         # Create kernel json file
         sample_kernel_file = sample_kernel_dir.joinpath("kernel.json")
-        sample_kernel_file.write_text(json.dumps(sample_kernel_json))
+        kernel_json = sample_kernel_json.copy()
+        if name == "bad":
+            kernel_json["argv"] = ["non_existent_path"]
+        sample_kernel_file.write_text(json.dumps(kernel_json))
         # Create resources text
         sample_kernel_resources = sample_kernel_dir.joinpath("resource.txt")
         sample_kernel_resources.write_text(some_resource)
@@ -474,12 +477,24 @@
         terminal_cleanup = 
jp_serverapp.web_app.settings["terminal_manager"].terminate_all
         kernel_cleanup = jp_serverapp.kernel_manager.shutdown_all
         if asyncio.iscoroutinefunction(terminal_cleanup):
-            await terminal_cleanup()
+            try:
+                await terminal_cleanup()
+            except Exception as e:
+                print(e)
         else:
-            terminal_cleanup()
+            try:
+                await terminal_cleanup()
+            except Exception as e:
+                print(e)
         if asyncio.iscoroutinefunction(kernel_cleanup):
-            await kernel_cleanup()
+            try:
+                await kernel_cleanup()
+            except Exception as e:
+                print(e)
         else:
-            kernel_cleanup()
+            try:
+                kernel_cleanup()
+            except Exception as e:
+                print(e)
 
     return _
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.11.2/jupyter_server/serverapp.py 
new/jupyter_server-1.13.1/jupyter_server/serverapp.py
--- old/jupyter_server-1.11.2/jupyter_server/serverapp.py       2021-11-01 
21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/serverapp.py       2021-12-09 
20:36:05.000000000 +0100
@@ -356,6 +356,10 @@
 
         # allow custom overrides for the tornado web app.
         settings.update(settings_overrides)
+
+        if base_url and "xsrf_cookie_kwargs" not in settings:
+            # default: set xsrf cookie on base_url
+            settings["xsrf_cookie_kwargs"] = {"path": base_url}
         return settings
 
     def init_handlers(self, default_services, settings):
@@ -558,7 +562,7 @@
             pass
 
     def start(self):
-        servers = list(list_running_servers(self.runtime_dir))
+        servers = list(list_running_servers(self.runtime_dir, log=self.log))
         if not servers:
             self.exit("There are no running servers (per %s)" % 
self.runtime_dir)
         for server in servers:
@@ -619,7 +623,7 @@
     )
 
     def start(self):
-        serverinfo_list = list(list_running_servers(self.runtime_dir))
+        serverinfo_list = list(list_running_servers(self.runtime_dir, 
log=self.log))
         if self.jsonlist:
             print(json.dumps(serverinfo_list, indent=2))
         elif self.json:
@@ -1590,7 +1594,8 @@
         self.server_extensions = change["new"]
 
     jpserver_extensions = Dict(
-        {},
+        default_value={},
+        value_trait=Bool(),
         config=True,
         help=(
             _i18n(
@@ -2682,7 +2687,7 @@
                 self.io_loop.add_callback(self._stop)
 
 
-def list_running_servers(runtime_dir=None):
+def list_running_servers(runtime_dir=None, log=None):
     """Iterate over the server info files of running Jupyter servers.
 
     Given a runtime directory, find jpserver-* files in the security directory,
@@ -2709,8 +2714,9 @@
                 # If the process has died, try to delete its info file
                 try:
                     os.unlink(os.path.join(runtime_dir, file_name))
-                except OSError:
-                    pass  # TODO: This should warn or log or something
+                except OSError as e:
+                    if log:
+                        log.warning(_i18n("Deleting server info file failed: 
%s.") % e)
 
 
 # -----------------------------------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/services/kernels/handlers.py 
new/jupyter_server-1.13.1/jupyter_server/services/kernels/handlers.py
--- old/jupyter_server-1.11.2/jupyter_server/services/kernels/handlers.py       
2021-11-01 21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/services/kernels/handlers.py       
2021-12-09 20:36:05.000000000 +0100
@@ -5,8 +5,8 @@
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 import json
-import logging
 from textwrap import dedent
+from traceback import format_tb
 
 from ipython_genutils.py3compat import cast_unicode
 from jupyter_client import protocol_version as client_protocol_version
@@ -79,7 +79,10 @@
             try:
                 await km.restart_kernel(kernel_id)
             except Exception as e:
-                self.log.error("Exception restarting kernel", exc_info=True)
+                message = "Exception restarting kernel"
+                self.log.error(message, exc_info=True)
+                traceback = format_tb(e.__traceback__)
+                self.write(json.dumps(dict(message=message, 
traceback=traceback)))
                 self.set_status(500)
             else:
                 model = await ensure_async(km.kernel_model(kernel_id))
@@ -128,7 +131,7 @@
     def nudge(self):
         """Nudge the zmq connections with kernel_info_requests
         Returns a Future that will resolve when we have received
-        a shell reply and at least one iopub message,
+        a shell or control reply and at least one iopub message,
         ensuring that zmq subscriptions are established,
         sockets are fully connected, and kernel is responsive.
         Keeps retrying kernel_info_request until these are both received.
@@ -146,10 +149,12 @@
             f = Future()
             f.set_result(None)
             return f
-
         # Use a transient shell channel to prevent leaking
         # shell responses to the front-end.
         shell_channel = kernel.connect_shell()
+        # Use a transient control channel to prevent leaking
+        # control responses to the front-end.
+        control_channel = kernel.connect_control()
         # The IOPub used by the client, whose subscriptions we are verifying.
         iopub_channel = self.channels["iopub"]
 
@@ -171,6 +176,8 @@
             iopub_channel.stop_on_recv()
             if not shell_channel.closed():
                 shell_channel.close()
+            if not control_channel.closed():
+                control_channel.close()
 
         # trigger cleanup when both message futures are resolved
         both_done.add_done_callback(cleanup)
@@ -181,6 +188,12 @@
                 self.log.debug("Nudge: resolving shell future: %s", 
self.kernel_id)
                 info_future.set_result(None)
 
+        def on_control_reply(msg):
+            self.log.debug("Nudge: control info reply received: %s", 
self.kernel_id)
+            if not info_future.done():
+                self.log.debug("Nudge: resolving control future: %s", 
self.kernel_id)
+                info_future.set_result(None)
+
         def on_iopub(msg):
             self.log.debug("Nudge: IOPub received: %s", self.kernel_id)
             if not iopub_future.done():
@@ -190,6 +203,7 @@
 
         iopub_channel.on_recv(on_iopub)
         shell_channel.on_recv(on_shell_reply)
+        control_channel.on_recv(on_control_reply)
         loop = IOLoop.current()
 
         # Nudge the kernel with kernel info requests until we get an IOPub 
message
@@ -215,10 +229,17 @@
                 finish()
                 return
 
+            # check for closed zmq socket
+            if control_channel.closed():
+                self.log.debug("Nudge: cancelling on closed zmq socket: %s", 
self.kernel_id)
+                finish()
+                return
+
             if not both_done.done():
                 log = self.log.warning if count % 10 == 0 else self.log.debug
                 log("Nudge: attempt %s on kernel %s" % (count, self.kernel_id))
                 self.session.send(shell_channel, "kernel_info_request")
+                self.session.send(control_channel, "kernel_info_request")
                 nonlocal nudge_handle
                 nudge_handle = loop.call_later(0.5, nudge, count)
 
@@ -326,6 +347,15 @@
         # We don't want to wait forever, because browsers don't take it well 
when
         # servers never respond to websocket connection requests.
         kernel = self.kernel_manager.get_kernel(self.kernel_id)
+
+        if hasattr(kernel, "ready"):
+            try:
+                await kernel.ready
+            except Exception as e:
+                kernel.execution_state = "dead"
+                kernel.reason = str(e)
+                raise web.HTTPError(500, str(e)) from e
+
         self.session.key = kernel.session.key
         future = self.request_kernel_info()
 
@@ -446,6 +476,7 @@
     def _on_zmq_reply(self, stream, msg_list):
         idents, fed_msg_list = self.session.feed_identities(msg_list)
         msg = self.session.deserialize(fed_msg_list)
+
         parent = msg["parent_header"]
 
         def write_stderr(error_message):
@@ -624,11 +655,11 @@
         self.write_message(json.dumps(msg, default=json_default))
 
     def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
+        self.log.warning("kernel %s restarted", self.kernel_id)
         self._send_status_message("restarting")
 
     def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
+        self.log.error("kernel %s restarted failed!", self.kernel_id)
         self._send_status_message("dead")
 
     def _on_error(self, msg):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/services/kernels/kernelmanager.py 
new/jupyter_server-1.13.1/jupyter_server/services/kernels/kernelmanager.py
--- old/jupyter_server-1.11.2/jupyter_server/services/kernels/kernelmanager.py  
2021-11-01 21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/services/kernels/kernelmanager.py  
2021-12-09 20:36:05.000000000 +0100
@@ -5,6 +5,7 @@
 """
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
+import asyncio
 import os
 from collections import defaultdict
 from datetime import datetime
@@ -209,16 +210,16 @@
                 kwargs["kernel_id"] = kernel_id
             kernel_id = await 
ensure_async(self.pinned_superclass.start_kernel(self, **kwargs))
             self._kernel_connections[kernel_id] = 0
-            self._kernel_ports[kernel_id] = self._kernels[kernel_id].ports
-            self.start_watching_activity(kernel_id)
+            fut = asyncio.ensure_future(self._finish_kernel_start(kernel_id))
+            if not getattr(self, "use_pending_kernels", None):
+                await fut
+            # add busy/activity markers:
+            kernel = self.get_kernel(kernel_id)
+            kernel.execution_state = "starting"
+            kernel.reason = ""
+            kernel.last_activity = utcnow()
             self.log.info("Kernel started: %s" % kernel_id)
             self.log.debug("Kernel args: %r" % kwargs)
-            # register callback for failed auto-restart
-            self.add_restart_callback(
-                kernel_id,
-                lambda: self._handle_kernel_died(kernel_id),
-                "dead",
-            )
 
             # Increase the metric of number of kernels running
             # for the relevant kernel type by 1
@@ -233,6 +234,24 @@
 
         return kernel_id
 
+    async def _finish_kernel_start(self, kernel_id):
+        km = self.get_kernel(kernel_id)
+        if hasattr(km, "ready"):
+            try:
+                await km.ready
+            except Exception:
+                self.log.exception(km.ready.exception())
+                return
+
+        self._kernel_ports[kernel_id] = km.ports
+        self.start_watching_activity(kernel_id)
+        # register callback for failed auto-restart
+        self.add_restart_callback(
+            kernel_id,
+            lambda: self._handle_kernel_died(kernel_id),
+            "dead",
+        )
+
     def ports_changed(self, kernel_id):
         """Used by ZMQChannelsHandler to determine how to coordinate nudge and 
replays.
 
@@ -448,6 +467,8 @@
             "execution_state": kernel.execution_state,
             "connections": self._kernel_connections.get(kernel_id, 0),
         }
+        if getattr(kernel, "reason", None):
+            model["reason"] = kernel.reason
         return model
 
     def list_kernels(self):
@@ -479,6 +500,7 @@
         kernel = self._kernels[kernel_id]
         # add busy/activity markers:
         kernel.execution_state = "starting"
+        kernel.reason = ""
         kernel.last_activity = utcnow()
         kernel._activity_stream = kernel.connect_iopub()
         session = Session(
@@ -507,7 +529,7 @@
     def stop_watching_activity(self, kernel_id):
         """Stop watching IOPub messages on a kernel for activity."""
         kernel = self._kernels[kernel_id]
-        if kernel._activity_stream:
+        if getattr(kernel, "_activity_stream", None):
             kernel._activity_stream.close()
             kernel._activity_stream = None
 
@@ -561,6 +583,17 @@
 
     async def cull_kernel_if_idle(self, kernel_id):
         kernel = self._kernels[kernel_id]
+
+        if getattr(kernel, "execution_state") == "dead":
+            self.log.warning(
+                "Culling '%s' dead kernel '%s' (%s).",
+                kernel.execution_state,
+                kernel.kernel_name,
+                kernel_id,
+            )
+            await ensure_async(self.shutdown_kernel(kernel_id))
+            return
+
         if hasattr(
             kernel, "last_activity"
         ):  # last_activity is monkey-patched, so ensure that has occurred
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/services/sessions/handlers.py 
new/jupyter_server-1.13.1/jupyter_server/services/sessions/handlers.py
--- old/jupyter_server-1.11.2/jupyter_server/services/sessions/handlers.py      
2021-11-01 21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/services/sessions/handlers.py      
2021-12-09 20:36:05.000000000 +0100
@@ -4,6 +4,7 @@
 """
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
+import asyncio
 import json
 
 try:
@@ -78,6 +79,8 @@
                 self.set_status(501)
                 self.finish(json.dumps(dict(message=msg, 
short_message=status_msg)))
                 return
+            except Exception as e:
+                raise web.HTTPError(500, str(e)) from e
 
         location = url_path_join(self.base_url, "api", "sessions", model["id"])
         self.set_header("Location", location)
@@ -144,7 +147,10 @@
         if model["kernel"]["id"] != before["kernel"]["id"]:
             # kernel_id changed because we got a new kernel
             # shutdown the old one
-            await ensure_async(km.shutdown_kernel(before["kernel"]["id"]))
+            fut = 
asyncio.ensure_future(ensure_async(km.shutdown_kernel(before["kernel"]["id"])))
+            # If we are not using pending kernels, wait for the kernel to shut 
down
+            if not getattr(km, "use_pending_kernels", None):
+                await fut
         self.finish(json.dumps(model, default=json_default))
 
     @web.authenticated
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/services/sessions/sessionmanager.py 
new/jupyter_server-1.13.1/jupyter_server/services/sessions/sessionmanager.py
--- 
old/jupyter_server-1.11.2/jupyter_server/services/sessions/sessionmanager.py    
    2021-11-01 21:22:56.000000000 +0100
+++ 
new/jupyter_server-1.13.1/jupyter_server/services/sessions/sessionmanager.py    
    2021-12-09 20:36:05.000000000 +0100
@@ -1,6 +1,7 @@
 """A base class session manager."""
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
+import pathlib
 import uuid
 
 try:
@@ -13,6 +14,9 @@
 
 from traitlets.config.configurable import LoggingConfigurable
 from traitlets import Instance
+from traitlets import Unicode
+from traitlets import validate
+from traitlets import TraitError
 
 from jupyter_server.utils import ensure_async
 from jupyter_server.traittypes import InstanceFromClasses
@@ -20,6 +24,36 @@
 
 class SessionManager(LoggingConfigurable):
 
+    database_filepath = Unicode(
+        default_value=":memory:",
+        help=(
+            "Th filesystem path to SQLite Database file "
+            "(e.g. /path/to/session_database.db). By default, the session "
+            "database is stored in-memory (i.e. `:memory:` setting from 
sqlite3) "
+            "and does not persist when the current Jupyter Server shuts down."
+        ),
+    ).tag(config=True)
+
+    @validate("database_filepath")
+    def _validate_database_filepath(self, proposal):
+        value = proposal["value"]
+        if value == ":memory:":
+            return value
+        path = pathlib.Path(value)
+        if path.exists():
+            # Verify that the database path is not a directory.
+            if path.is_dir():
+                raise TraitError(
+                    "`database_filepath` expected a file path, but the given 
path is a directory."
+                )
+            # Verify that database path is an SQLite 3 Database by checking 
its header.
+            with open(value, "rb") as f:
+                header = f.read(100)
+
+            if not header.startswith(b"SQLite format 3") and not header == b"":
+                raise TraitError("The given file is not an SQLite database 
file.")
+        return value
+
     kernel_manager = 
Instance("jupyter_server.services.kernels.kernelmanager.MappingKernelManager")
     contents_manager = InstanceFromClasses(
         [
@@ -39,7 +73,7 @@
         if self._cursor is None:
             self._cursor = self.connection.cursor()
             self._cursor.execute(
-                """CREATE TABLE session
+                """CREATE TABLE IF NOT EXISTS session
                 (session_id, path, name, type, kernel_id)"""
             )
         return self._cursor
@@ -48,7 +82,8 @@
     def connection(self):
         """Start a database connection"""
         if self._connection is None:
-            self._connection = sqlite3.connect(":memory:")
+            # Set isolation level to None to autocommit all changes to the 
database.
+            self._connection = sqlite3.connect(self.database_filepath, 
isolation_level=None)
             self._connection.row_factory = sqlite3.Row
         return self._connection
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/tests/services/contents/test_api.py 
new/jupyter_server-1.13.1/jupyter_server/tests/services/contents/test_api.py
--- 
old/jupyter_server-1.11.2/jupyter_server/tests/services/contents/test_api.py    
    2021-11-01 21:22:56.000000000 +0100
+++ 
new/jupyter_server-1.13.1/jupyter_server/tests/services/contents/test_api.py    
    2021-12-09 20:36:05.000000000 +0100
@@ -3,6 +3,7 @@
 import sys
 from base64 import decodebytes
 from base64 import encodebytes
+from unicodedata import normalize
 
 import pytest
 import tornado
@@ -101,8 +102,8 @@
     data = json.loads(response.body.decode())
     nbs = notebooks_only(data)
     assert len(nbs) > 0
-    assert name + ".ipynb" in [n["name"] for n in nbs]
-    assert url_path_join(path, name + ".ipynb") in [n["path"] for n in nbs]
+    assert name + ".ipynb" in [normalize("NFC", n["name"]) for n in nbs]
+    assert url_path_join(path, name + ".ipynb") in [normalize("NFC", 
n["path"]) for n in nbs]
 
 
 @pytest.mark.parametrize("path,name", dirs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/tests/services/kernels/test_api.py 
new/jupyter_server-1.13.1/jupyter_server/tests/services/kernels/test_api.py
--- old/jupyter_server-1.11.2/jupyter_server/tests/services/kernels/test_api.py 
2021-11-01 21:22:56.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server/tests/services/kernels/test_api.py 
2021-12-09 20:36:05.000000000 +0100
@@ -4,13 +4,29 @@
 import pytest
 import tornado
 from jupyter_client.kernelspec import NATIVE_KERNEL_NAME
+from tornado.httpclient import HTTPClientError
 
 from ...utils import expected_http_error
+from jupyter_server.services.kernels.kernelmanager import 
AsyncMappingKernelManager
 from jupyter_server.utils import url_path_join
 
 
-@pytest.fixture(params=["MappingKernelManager", "AsyncMappingKernelManager"])
+class TestMappingKernelManager(AsyncMappingKernelManager):
+    """A no-op subclass to use in a fixture"""
+
+
+@pytest.fixture(
+    params=["MappingKernelManager", "AsyncMappingKernelManager", 
"TestMappingKernelManager"]
+)
 def jp_argv(request):
+    if request.param == "TestMappingKernelManager":
+        extra = []
+        if hasattr(AsyncMappingKernelManager, "use_pending_kernels"):
+            extra = ["--AsyncMappingKernelManager.use_pending_kernels=True"]
+        return [
+            
"--ServerApp.kernel_manager_class=jupyter_server.tests.services.kernels.test_api."
+            + request.param
+        ] + extra
     return [
         
"--ServerApp.kernel_manager_class=jupyter_server.services.kernels.kernelmanager."
         + request.param
@@ -38,7 +54,7 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_main_kernel_handler(jp_fetch, jp_base_url, 
jp_cleanup_subprocesses):
+async def test_main_kernel_handler(jp_fetch, jp_base_url, 
jp_cleanup_subprocesses, jp_serverapp):
     # Start the first kernel
     r = await jp_fetch(
         "api", "kernels", method="POST", body=json.dumps({"name": 
NATIVE_KERNEL_NAME})
@@ -83,6 +99,10 @@
     assert r.code == 204
 
     # Restart a kernel
+    kernel = jp_serverapp.kernel_manager.get_kernel(kernel2["id"])
+    if hasattr(kernel, "ready"):
+        await kernel.ready
+
     r = await jp_fetch(
         "api", "kernels", kernel2["id"], "restart", method="POST", 
allow_nonstandard_methods=True
     )
@@ -143,6 +163,32 @@
     await jp_cleanup_subprocesses()
 
 
+async def test_kernel_handler_startup_error(
+    jp_fetch, jp_cleanup_subprocesses, jp_serverapp, jp_kernelspecs
+):
+    if getattr(jp_serverapp.kernel_manager, "use_pending_kernels", False):
+        return
+
+    # Create a kernel
+    with pytest.raises(HTTPClientError):
+        await jp_fetch("api", "kernels", method="POST", 
body=json.dumps({"name": "bad"}))
+
+
+async def test_kernel_handler_startup_error_pending(
+    jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses, jp_serverapp, 
jp_kernelspecs
+):
+    if not getattr(jp_serverapp.kernel_manager, "use_pending_kernels", False):
+        return
+
+    jp_serverapp.kernel_manager.use_pending_kernels = True
+    # Create a kernel
+    r = await jp_fetch("api", "kernels", method="POST", 
body=json.dumps({"name": "bad"}))
+    kid = json.loads(r.body.decode())["id"]
+
+    with pytest.raises(HTTPClientError):
+        await jp_ws_fetch("api", "kernels", kid, "channels")
+
+
 async def test_connection(
     jp_fetch, jp_ws_fetch, jp_http_port, jp_auth_header, 
jp_cleanup_subprocesses
 ):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/tests/services/kernels/test_cull.py 
new/jupyter_server-1.13.1/jupyter_server/tests/services/kernels/test_cull.py
--- 
old/jupyter_server-1.11.2/jupyter_server/tests/services/kernels/test_cull.py    
    2021-11-01 21:22:56.000000000 +0100
+++ 
new/jupyter_server-1.13.1/jupyter_server/tests/services/kernels/test_cull.py    
    2021-12-09 20:36:05.000000000 +0100
@@ -34,7 +34,7 @@
     )
 
 
-async def test_culling(jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses):
+async def test_cull_idle(jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses):
     r = await jp_fetch("api", "kernels", method="POST", 
allow_nonstandard_methods=True)
     kernel = json.loads(r.body.decode())
     kid = kernel["id"]
@@ -52,6 +52,30 @@
     assert culled
     await jp_cleanup_subprocesses()
 
+
+async def test_cull_dead(
+    jp_fetch, jp_ws_fetch, jp_serverapp, jp_cleanup_subprocesses, 
jp_kernelspecs
+):
+    if not hasattr(jp_serverapp.kernel_manager, "use_pending_kernels"):
+        return
+
+    jp_serverapp.kernel_manager.use_pending_kernels = True
+    jp_serverapp.kernel_manager.default_kernel_name = "bad"
+    r = await jp_fetch("api", "kernels", method="POST", 
allow_nonstandard_methods=True)
+    kernel = json.loads(r.body.decode())
+    kid = kernel["id"]
+
+    # Open a websocket connection.
+    with pytest.raises(HTTPClientError):
+        await jp_ws_fetch("api", "kernels", kid, "channels")
+
+    r = await jp_fetch("api", "kernels", kid, method="GET")
+    model = json.loads(r.body.decode())
+    assert model["connections"] == 0
+    culled = await get_cull_status(kid, jp_fetch)  # connected, should not be 
culled
+    assert culled
+    await jp_cleanup_subprocesses()
+
 
 async def get_cull_status(kid, jp_fetch):
     frequency = 0.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/tests/services/kernelspecs/test_api.py 
new/jupyter_server-1.13.1/jupyter_server/tests/services/kernelspecs/test_api.py
--- 
old/jupyter_server-1.11.2/jupyter_server/tests/services/kernelspecs/test_api.py 
    2021-11-01 21:22:56.000000000 +0100
+++ 
new/jupyter_server-1.13.1/jupyter_server/tests/services/kernelspecs/test_api.py 
    2021-12-09 20:36:05.000000000 +0100
@@ -9,7 +9,7 @@
 
 
 async def test_list_kernelspecs_bad(jp_fetch, jp_kernelspecs, jp_data_dir):
-    bad_kernel_dir = jp_data_dir.joinpath(jp_data_dir, "kernels", "bad")
+    bad_kernel_dir = jp_data_dir.joinpath(jp_data_dir, "kernels", "bad2")
     bad_kernel_dir.mkdir(parents=True)
     bad_kernel_json = bad_kernel_dir.joinpath("kernel.json")
     bad_kernel_json.write_text("garbage")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/tests/services/sessions/test_api.py 
new/jupyter_server-1.13.1/jupyter_server/tests/services/sessions/test_api.py
--- 
old/jupyter_server-1.11.2/jupyter_server/tests/services/sessions/test_api.py    
    2021-11-01 21:22:56.000000000 +0100
+++ 
new/jupyter_server-1.13.1/jupyter_server/tests/services/sessions/test_api.py    
    2021-12-09 20:36:05.000000000 +0100
@@ -1,4 +1,5 @@
 import json
+import os
 import shutil
 import time
 
@@ -7,6 +8,7 @@
 from jupyter_client.ioloop import AsyncIOLoopKernelManager
 from nbformat import writes
 from nbformat.v4 import new_notebook
+from tornado.httpclient import HTTPClientError
 from traitlets import default
 
 from ...utils import expected_http_error
@@ -39,10 +41,13 @@
 )
 def jp_argv(request):
     if request.param == "NewPortsMappingKernelManager":
+        extra = []
+        if hasattr(AsyncMappingKernelManager, "use_pending_kernels"):
+            extra = ["--AsyncMappingKernelManager.use_pending_kernels=True"]
         return [
             
"--ServerApp.kernel_manager_class=jupyter_server.tests.services.sessions.test_api."
             + request.param
-        ]
+        ] + extra
     return [
         
"--ServerApp.kernel_manager_class=jupyter_server.services.kernels.kernelmanager."
         + request.param
@@ -68,7 +73,7 @@
     async def get(self, id):
         return await self._req(id, method="GET")
 
-    async def create(self, path, type="notebook", kernel_name="python", 
kernel_id=None):
+    async def create(self, path, type="notebook", kernel_name=None, 
kernel_id=None):
         body = {"path": path, "type": type, "kernel": {"name": kernel_name, 
"id": kernel_id}}
         return await self._req(method="POST", body=body)
 
@@ -151,7 +156,7 @@
     assert_kernel_equality(actual["kernel"], expected["kernel"])
 
 
-async def test_create(session_client, jp_base_url, jp_cleanup_subprocesses):
+async def test_create(session_client, jp_base_url, jp_cleanup_subprocesses, 
jp_serverapp):
     # Make sure no sessions exist.
     resp = await session_client.list()
     sessions = j(resp)
@@ -168,6 +173,17 @@
         jp_base_url, "/api/sessions/", new_session["id"]
     )
 
+    # Make sure kernel is in expected state
+    kid = new_session["kernel"]["id"]
+    kernel = jp_serverapp.kernel_manager.get_kernel(kid)
+
+    if hasattr(kernel, "ready") and os.name != "nt":
+        km = jp_serverapp.kernel_manager
+        if isinstance(km, AsyncMappingKernelManager):
+            assert kernel.ready.done() == (not km.use_pending_kernels)
+        else:
+            assert kernel.ready.done()
+
     # Check that the new session appears in list.
     resp = await session_client.list()
     sessions = j(resp)
@@ -185,7 +201,61 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_create_file_session(session_client, jp_cleanup_subprocesses):
+async def test_create_bad(
+    session_client, jp_base_url, jp_cleanup_subprocesses, jp_serverapp, 
jp_kernelspecs
+):
+    if getattr(jp_serverapp.kernel_manager, "use_pending_kernels", False):
+        return
+
+    # Make sure no sessions exist.
+    jp_serverapp.kernel_manager.default_kernel_name = "bad"
+    resp = await session_client.list()
+    sessions = j(resp)
+    assert len(sessions) == 0
+
+    # Create a session.
+    with pytest.raises(HTTPClientError):
+        await session_client.create("foo/nb1.ipynb")
+
+    # Need to find a better solution to this.
+    await session_client.cleanup()
+    await jp_cleanup_subprocesses()
+
+
+async def test_create_bad_pending(
+    session_client, jp_base_url, jp_ws_fetch, jp_cleanup_subprocesses, 
jp_serverapp, jp_kernelspecs
+):
+    if not getattr(jp_serverapp.kernel_manager, "use_pending_kernels", False):
+        return
+
+    # Make sure no sessions exist.
+    jp_serverapp.kernel_manager.default_kernel_name = "bad"
+    resp = await session_client.list()
+    sessions = j(resp)
+    assert len(sessions) == 0
+
+    # Create a session.
+    resp = await session_client.create("foo/nb1.ipynb")
+    assert resp.code == 201
+
+    # Open a websocket connection.
+    kid = j(resp)["kernel"]["id"]
+    with pytest.raises(HTTPClientError):
+        await jp_ws_fetch("api", "kernels", kid, "channels")
+
+    # Get the updated kernel state
+    resp = await session_client.list()
+    session = j(resp)[0]
+    assert session["kernel"]["execution_state"] == "dead"
+    if os.name != "nt":
+        assert "non_existent_path" in session["kernel"]["reason"]
+
+    # Need to find a better solution to this.
+    await session_client.cleanup()
+    await jp_cleanup_subprocesses()
+
+
+async def test_create_file_session(session_client, jp_cleanup_subprocesses, 
jp_serverapp):
     resp = await session_client.create("foo/nb1.py", type="file")
     assert resp.code == 201
     newsession = j(resp)
@@ -195,7 +265,7 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_create_console_session(session_client, jp_cleanup_subprocesses):
+async def test_create_console_session(session_client, jp_cleanup_subprocesses, 
jp_serverapp):
     resp = await session_client.create("foo/abc123", type="console")
     assert resp.code == 201
     newsession = j(resp)
@@ -206,7 +276,7 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_create_deprecated(session_client, jp_cleanup_subprocesses):
+async def test_create_deprecated(session_client, jp_cleanup_subprocesses, 
jp_serverapp):
     resp = await session_client.create_deprecated("foo/nb1.ipynb")
     assert resp.code == 201
     newsession = j(resp)
@@ -219,7 +289,7 @@
 
 
 async def test_create_with_kernel_id(
-    session_client, jp_fetch, jp_base_url, jp_cleanup_subprocesses
+    session_client, jp_fetch, jp_base_url, jp_cleanup_subprocesses, 
jp_serverapp
 ):
     # create a new kernel
     resp = await jp_fetch("api/kernels", method="POST", 
allow_nonstandard_methods=True)
@@ -250,7 +320,18 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_delete(session_client, jp_cleanup_subprocesses):
+async def test_create_with_bad_kernel_id(session_client, 
jp_cleanup_subprocesses, jp_serverapp):
+    resp = await session_client.create("foo/nb1.py", type="file")
+    assert resp.code == 201
+    newsession = j(resp)
+    # TODO
+    assert newsession["path"] == "foo/nb1.py"
+    assert newsession["type"] == "file"
+    await session_client.cleanup()
+    await jp_cleanup_subprocesses()
+
+
+async def test_delete(session_client, jp_cleanup_subprocesses, jp_serverapp):
     resp = await session_client.create("foo/nb1.ipynb")
     newsession = j(resp)
     sid = newsession["id"]
@@ -270,7 +351,7 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_modify_path(session_client, jp_cleanup_subprocesses):
+async def test_modify_path(session_client, jp_cleanup_subprocesses, 
jp_serverapp):
     resp = await session_client.create("foo/nb1.ipynb")
     newsession = j(resp)
     sid = newsession["id"]
@@ -284,7 +365,7 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_modify_path_deprecated(session_client, jp_cleanup_subprocesses):
+async def test_modify_path_deprecated(session_client, jp_cleanup_subprocesses, 
jp_serverapp):
     resp = await session_client.create("foo/nb1.ipynb")
     newsession = j(resp)
     sid = newsession["id"]
@@ -298,7 +379,7 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_modify_type(session_client, jp_cleanup_subprocesses):
+async def test_modify_type(session_client, jp_cleanup_subprocesses, 
jp_serverapp):
     resp = await session_client.create("foo/nb1.ipynb")
     newsession = j(resp)
     sid = newsession["id"]
@@ -312,7 +393,7 @@
     await jp_cleanup_subprocesses()
 
 
-async def test_modify_kernel_name(session_client, jp_fetch, 
jp_cleanup_subprocesses):
+async def test_modify_kernel_name(session_client, jp_fetch, 
jp_cleanup_subprocesses, jp_serverapp):
     resp = await session_client.create("foo/nb1.ipynb")
     before = j(resp)
     sid = before["id"]
@@ -329,13 +410,15 @@
     kernel_list = j(resp)
     after["kernel"].pop("last_activity")
     [k.pop("last_activity") for k in kernel_list]
-    assert kernel_list == [after["kernel"]]
+    if not getattr(jp_serverapp.kernel_manager, "use_pending_kernels", False):
+        assert kernel_list == [after["kernel"]]
+
     # Need to find a better solution to this.
     await session_client.cleanup()
     await jp_cleanup_subprocesses()
 
 
-async def test_modify_kernel_id(session_client, jp_fetch, 
jp_cleanup_subprocesses):
+async def test_modify_kernel_id(session_client, jp_fetch, 
jp_cleanup_subprocesses, jp_serverapp):
     resp = await session_client.create("foo/nb1.ipynb")
     before = j(resp)
     sid = before["id"]
@@ -359,7 +442,8 @@
 
     kernel.pop("last_activity")
     [k.pop("last_activity") for k in kernel_list]
-    assert kernel_list == [kernel]
+    if not getattr(jp_serverapp.kernel_manager, "use_pending_kernels", False):
+        assert kernel_list == [kernel]
 
     # Need to find a better solution to this.
     await session_client.cleanup()
@@ -369,7 +453,6 @@
 async def test_restart_kernel(
     session_client, jp_base_url, jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses
 ):
-
     # Create a session.
     resp = await session_client.create("foo/nb1.ipynb")
     assert resp.code == 201
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server/tests/services/sessions/test_manager.py
 
new/jupyter_server-1.13.1/jupyter_server/tests/services/sessions/test_manager.py
--- 
old/jupyter_server-1.11.2/jupyter_server/tests/services/sessions/test_manager.py
    2021-11-01 21:22:56.000000000 +0100
+++ 
new/jupyter_server-1.13.1/jupyter_server/tests/services/sessions/test_manager.py
    2021-12-09 20:36:05.000000000 +0100
@@ -1,5 +1,6 @@
 import pytest
 from tornado import web
+from traitlets import TraitError
 
 from jupyter_server._tz import isoformat
 from jupyter_server._tz import utcnow
@@ -264,3 +265,101 @@
         await session_manager.delete_session(bad_kwarg="23424")  # Bad keyword
     with pytest.raises(web.HTTPError):
         await session_manager.delete_session(session_id="23424")  # nonexistent
+
+
+async def test_bad_database_filepath(jp_runtime_dir):
+    kernel_manager = DummyMKM()
+
+    # Try to write to a path that's a directory, not a file.
+    path_id_directory = str(jp_runtime_dir)
+    # Should raise an error because the path is a directory.
+    with pytest.raises(TraitError) as err:
+        SessionManager(
+            kernel_manager=kernel_manager,
+            contents_manager=ContentsManager(),
+            database_filepath=str(path_id_directory),
+        )
+
+    # Try writing to file that's not a valid SQLite 3 database file.
+    non_db_file = jp_runtime_dir.joinpath("non_db_file.db")
+    non_db_file.write_bytes(b"this is a bad file")
+
+    # Should raise an error because the file doesn't
+    # start with an SQLite database file header.
+    with pytest.raises(TraitError) as err:
+        SessionManager(
+            kernel_manager=kernel_manager,
+            contents_manager=ContentsManager(),
+            database_filepath=str(non_db_file),
+        )
+
+
+async def test_good_database_filepath(jp_runtime_dir):
+    kernel_manager = DummyMKM()
+
+    # Try writing to an empty file.
+    empty_file = jp_runtime_dir.joinpath("empty.db")
+    empty_file.write_bytes(b"")
+
+    session_manager = SessionManager(
+        kernel_manager=kernel_manager,
+        contents_manager=ContentsManager(),
+        database_filepath=str(empty_file),
+    )
+
+    await session_manager.create_session(
+        path="/path/to/test.ipynb", kernel_name="python", type="notebook"
+    )
+    # Assert that the database file exists
+    assert empty_file.exists()
+
+    # Close the current session manager
+    del session_manager
+
+    # Try writing to a file that already exists.
+    session_manager = SessionManager(
+        kernel_manager=kernel_manager,
+        contents_manager=ContentsManager(),
+        database_filepath=str(empty_file),
+    )
+
+    assert session_manager.database_filepath == str(empty_file)
+
+
+async def test_session_persistence(jp_runtime_dir):
+    session_db_path = jp_runtime_dir.joinpath("test-session.db")
+    # Kernel manager needs to persist.
+    kernel_manager = DummyMKM()
+
+    # Initialize a session and start a connection.
+    # This should create the session database the first time.
+    session_manager = SessionManager(
+        kernel_manager=kernel_manager,
+        contents_manager=ContentsManager(),
+        database_filepath=str(session_db_path),
+    )
+
+    session = await session_manager.create_session(
+        path="/path/to/test.ipynb", kernel_name="python", type="notebook"
+    )
+
+    # Assert that the database file exists
+    assert session_db_path.exists()
+
+    with open(session_db_path, "rb") as f:
+        header = f.read(100)
+
+    assert header.startswith(b"SQLite format 3")
+
+    # Close the current session manager
+    del session_manager
+
+    # Get a new session_manager
+    session_manager = SessionManager(
+        kernel_manager=kernel_manager,
+        contents_manager=ContentsManager(),
+        database_filepath=str(session_db_path),
+    )
+
+    # Assert that the session database persists.
+    session = await session_manager.get_session(session_id=session["id"])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.11.2/jupyter_server.egg-info/PKG-INFO 
new/jupyter_server-1.13.1/jupyter_server.egg-info/PKG-INFO
--- old/jupyter_server-1.11.2/jupyter_server.egg-info/PKG-INFO  2021-11-01 
21:23:33.000000000 +0100
+++ new/jupyter_server-1.13.1/jupyter_server.egg-info/PKG-INFO  2021-12-09 
20:36:35.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jupyter-server
-Version: 1.11.2
+Version: 1.13.1
 Summary: The backend???i.e. core services, APIs, and REST endpoints???to 
Jupyter web applications.
 Home-page: https://jupyter.org
 Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.11.2/pyproject.toml 
new/jupyter_server-1.13.1/pyproject.toml
--- old/jupyter_server-1.11.2/pyproject.toml    2021-11-01 21:23:18.000000000 
+0100
+++ new/jupyter_server-1.13.1/pyproject.toml    2021-12-09 20:36:21.000000000 
+0100
@@ -18,7 +18,7 @@
 skip = ["check-links"]
 
 [tool.tbump.version]
-current = "1.11.2"
+current = "1.13.1"
 regex = '''
   (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
   ((?P<channel>a|b|rc|.dev)(?P<release>\d+))?
@@ -30,3 +30,12 @@
 
 [[tool.tbump.file]]
 src = "jupyter_server/_version.py"
+version_template = '({major}, {minor}, {patch}, "{channel}", "{release}")'
+
+[[tool.tbump.field]]
+name = "channel"
+default = ""
+
+[[tool.tbump.field]]
+name = "release"
+default = ""

Reply via email to