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 = ""