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 2026-06-08 14:24:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jupyter-server (Old)
and /work/SRC/openSUSE:Factory/.python-jupyter-server.new.2375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jupyter-server"
Mon Jun 8 14:24:14 2026 rev:47 rq:1357929 version:2.19.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-jupyter-server/python-jupyter-server.changes
2026-05-06 19:20:36.651792482 +0200
+++
/work/SRC/openSUSE:Factory/.python-jupyter-server.new.2375/python-jupyter-server.changes
2026-06-08 14:28:54.385984049 +0200
@@ -1,0 +2,27 @@
+Mon Jun 8 09:33:44 UTC 2026 - Daniel Garcia <[email protected]>
+
+- Update to 2.19.0 (CVE-2026-5422, bsc#1267357):
+ # Enhancements made
+ - Return `unresolved` stanza when kernel scope is unavailable for
+ `resolvePath` (instead of failing with 404) [#1641]
+ # Bugs fixed
+ - Recreate notary store on failure to prevent save deadlock and data
+ loss [#1640]
+ # Maintenance and upkeep improvements
+ - Restore the test skip marks with updated compatibility reason [#1646]
+ - Drop Python 3.9 [#1644]
+ - Bump minimatch from 3.1.2 to 3.1.5 [#1606]
+ # Other merged PRs
+ - Update pytest requirement from \<9,>=7.0 to >=7.0,\<10 [#1647]
+ - fix: use percent-placeholder args in HTTPError to prevent Tornado
+ from doubling percent signs in gateway URLs [#1642]
+
+- 2.18.2:
+ # Bugs fixed
+ - Fix saving user avatar URL [#1637]
+ - Fix path resolution if `root_dir` is a filesystem root [#1636]
+ # Maintenance and upkeep improvements
+ - Add Zulip notification when a release is complete [#1634]
+ - chore: update pre-commit hooks [#1633]
+
+-------------------------------------------------------------------
Old:
----
jupyter_server-2.18.1.tar.gz
New:
----
jupyter_server-2.19.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jupyter-server.spec ++++++
--- /var/tmp/diff_new_pack.SEhWjP/_old 2026-06-08 14:28:55.118014423 +0200
+++ /var/tmp/diff_new_pack.SEhWjP/_new 2026-06-08 14:28:55.122014590 +0200
@@ -33,7 +33,7 @@
%{?sle15_python_module_pythons}
Name: python-jupyter-server%{psuffix}
-Version: 2.18.1
+Version: 2.19.0
Release: 0
Summary: The backend to Jupyter web applications
License: BSD-3-Clause
++++++ jupyter_server-2.18.1.tar.gz -> jupyter_server-2.19.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/.github/workflows/publish-release.yml
new/jupyter_server-2.19.0/.github/workflows/publish-release.yml
--- old/jupyter_server-2.18.1/.github/workflows/publish-release.yml
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/.github/workflows/publish-release.yml
2020-02-02 01:00:00.000000000 +0100
@@ -54,3 +54,17 @@
run: |
echo "Failed to Publish the Draft Release Url:"
echo ${{ steps.populate-release.outputs.release_url }}
+ - name: Send Zulip notification
+ if: startsWith(github.ref, 'refs/tags/')
+ continue-on-error: true
+ uses:
zulip/github-actions-zulip/send-message@08b6fbd07f5834e5b930a85bc7740e9fd44ab2e7
+ with:
+ api-key: ${{ secrets.ZULIP_API_KEY }}
+ email: ${{ secrets.ZULIP_EMAIL }}
+ organization-url: ${{ vars.ZULIP_ORGANIZATION_URL }}
+ to: "Releases"
+ type: "stream"
+ topic: "jupyter_server"
+ content: | # pypi strips the v from the tag name in urls
+ Jupyter Server ${{ github.ref_name }} was just released on PyPI! 🎉
+ https://pypi.org/project/jupyter-server/${{ github.ref_name }}/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/.github/workflows/python-tests.yml
new/jupyter_server-2.19.0/.github/workflows/python-tests.yml
--- old/jupyter_server-2.18.1/.github/workflows/python-tests.yml
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/.github/workflows/python-tests.yml
2020-02-02 01:00:00.000000000 +0100
@@ -18,10 +18,7 @@
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
- python-version: ["3.9", "3.11", "3.12", "3.13", "3.14"]
- exclude:
- - os: windows-latest
- python-version: "3.9"
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
include:
- os: macos-latest
python-version: "3.10"
@@ -196,7 +193,7 @@
fail-fast: false
matrix:
os: [ubuntu-latest]
- python-version: ["3.9", "3.10", "3.11", "3.12"]
+ python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/.pre-commit-config.yaml
new/jupyter_server-2.19.0/.pre-commit-config.yaml
--- old/jupyter_server-2.18.1/.pre-commit-config.yaml 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/.pre-commit-config.yaml 2020-02-02
01:00:00.000000000 +0100
@@ -22,7 +22,7 @@
- id: trailing-whitespace
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.37.1
+ rev: 0.37.2
hooks:
- id: check-github-workflows
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/.readthedocs.yaml
new/jupyter_server-2.19.0/.readthedocs.yaml
--- old/jupyter_server-2.18.1/.readthedocs.yaml 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server-2.19.0/.readthedocs.yaml 2020-02-02 01:00:00.000000000
+0100
@@ -2,7 +2,7 @@
build:
os: ubuntu-22.04
tools:
- python: "3.9"
+ python: "3.10"
sphinx:
configuration: docs/source/conf.py
python:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/CHANGELOG.md
new/jupyter_server-2.19.0/CHANGELOG.md
--- old/jupyter_server-2.18.1/CHANGELOG.md 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server-2.19.0/CHANGELOG.md 2020-02-02 01:00:00.000000000
+0100
@@ -4,6 +4,63 @@
<!-- <START NEW CHANGELOG ENTRY> -->
+## 2.19.0
+
+([Full
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.18.2...664e2255c71efe963f397b9f803dbcf503b5a920))
+
+### Enhancements made
+
+- Return `unresolved` stanza when kernel scope is unavailable for
`resolvePath` (instead of failing with 404)
[#1641](https://github.com/jupyter-server/jupyter_server/pull/1641)
([@MUFFANUJ](https://github.com/MUFFANUJ),
[@Carreau](https://github.com/Carreau),
[@krassowski](https://github.com/krassowski))
+
+### Bugs fixed
+
+- Recreate notary store on failure to prevent save deadlock and data loss
[#1640](https://github.com/jupyter-server/jupyter_server/pull/1640)
([@krassowski](https://github.com/krassowski),
[@Carreau](https://github.com/Carreau))
+
+### Maintenance and upkeep improvements
+
+- Restore the test skip marks with updated compatibility reason
[#1646](https://github.com/jupyter-server/jupyter_server/pull/1646)
([@krassowski](https://github.com/krassowski),
[@Carreau](https://github.com/Carreau))
+- Drop Python 3.9
[#1644](https://github.com/jupyter-server/jupyter_server/pull/1644)
([@krassowski](https://github.com/krassowski),
[@Zsailer](https://github.com/Zsailer))
+- Bump minimatch from 3.1.2 to 3.1.5
[#1606](https://github.com/jupyter-server/jupyter_server/pull/1606)
([@Carreau](https://github.com/Carreau), [@minrk](https://github.com/minrk))
+
+### Other merged PRs
+
+- Update pytest requirement from \<9,>=7.0 to >=7.0,\<10
[#1647](https://github.com/jupyter-server/jupyter_server/pull/1647)
([@Carreau](https://github.com/Carreau))
+- fix: use percent-placeholder args in HTTPError to prevent Tornado from
doubling percent signs in gateway URLs
[#1642](https://github.com/jupyter-server/jupyter_server/pull/1642)
([@terminalchai](https://github.com/terminalchai),
[@Carreau](https://github.com/Carreau))
+
+### Contributors to this release
+
+The following people contributed discussions, new ideas, code and
documentation contributions, and review.
+See [our definition of
contributors](https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports).
+
+([GitHub contributors page for this
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2026-05-06&to=2026-05-29&type=c))
+
+@Carreau
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ACarreau+updated%3A2026-05-06..2026-05-29&type=Issues))
| @ianthomas23
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aianthomas23+updated%3A2026-05-06..2026-05-29&type=Issues))
| @krassowski
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2026-05-06..2026-05-29&type=Issues))
| @minrk
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aminrk+updated%3A2026-05-06..2026-05-29&type=Issues))
| @MUFFANUJ
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AMUFFANUJ+updated%3A2026-05-06..2026-05-29&type=Issues))
| @terminalchai
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aterminalchai+updated%3A2026-05-06..2026-05-29&type=Issues))
| @Zsailer ([activity](https://github.com
/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2026-05-06..2026-05-29&type=Issues))
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
+## 2.18.2
+
+([Full
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.18.1...0d829f2c35a481c3b24ecbe1e25a6f79954e88f2))
+
+### Bugs fixed
+
+- Fix saving user avatar URL
[#1637](https://github.com/jupyter-server/jupyter_server/pull/1637)
([@brichet](https://github.com/brichet),
[@krassowski](https://github.com/krassowski))
+- Fix path resolution if `root_dir` is a filesystem root
[#1636](https://github.com/jupyter-server/jupyter_server/pull/1636)
([@Yann-P](https://github.com/Yann-P),
[@krassowski](https://github.com/krassowski))
+
+### Maintenance and upkeep improvements
+
+- Add Zulip notification when a release is complete
[#1634](https://github.com/jupyter-server/jupyter_server/pull/1634)
([@Yann-P](https://github.com/Yann-P), [@Carreau](https://github.com/Carreau))
+- chore: update pre-commit hooks
[#1633](https://github.com/jupyter-server/jupyter_server/pull/1633)
([@Carreau](https://github.com/Carreau))
+
+### Contributors to this release
+
+The following people contributed discussions, new ideas, code and
documentation contributions, and review.
+See [our definition of
contributors](https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports).
+
+([GitHub contributors page for this
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2026-05-05&to=2026-05-06&type=c))
+
+@brichet
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Abrichet+updated%3A2026-05-05..2026-05-06&type=Issues))
| @Carreau
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ACarreau+updated%3A2026-05-05..2026-05-06&type=Issues))
| @krassowski
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2026-05-05..2026-05-06&type=Issues))
| @Yann-P
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AYann-P+updated%3A2026-05-05..2026-05-06&type=Issues))
+
## 2.18.1
([Full
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.18.0...8f0b2b31822b89046c66a9dd24cfd4cf7e0249c1))
@@ -21,8 +78,6 @@
@Copilot
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ACopilot+updated%3A2026-05-04..2026-05-05&type=Issues))
| @jtpio
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ajtpio+updated%3A2026-05-04..2026-05-05&type=Issues))
| @krassowski
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2026-05-04..2026-05-05&type=Issues))
| @tonyx93
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Atonyx93+updated%3A2026-05-04..2026-05-05&type=Issues))
-<!-- <END NEW CHANGELOG ENTRY> -->
-
## 2.18.0
([Full
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.9.1...49b34392feaa97735b3b777e3baf8f22f2a14ed8))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/PKG-INFO
new/jupyter_server-2.19.0/PKG-INFO
--- old/jupyter_server-2.18.1/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: jupyter_server
-Version: 2.18.1
+Version: 2.19.0
Summary: The backend—i.e. core services, APIs, and REST endpoints—to Jupyter
web applications.
Project-URL: Homepage, https://jupyter-server.readthedocs.io
Project-URL: Documentation, https://jupyter-server.readthedocs.io
@@ -50,7 +50,7 @@
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Requires-Python: >=3.9
+Requires-Python: >=3.10
Requires-Dist: anyio>=3.1.0
Requires-Dist: argon2-cffi>=21.1
Requires-Dist: jinja2>=3.0.3
@@ -94,7 +94,7 @@
Requires-Dist: pytest-console-scripts; extra == 'test'
Requires-Dist: pytest-jupyter[server]>=0.7; extra == 'test'
Requires-Dist: pytest-timeout; extra == 'test'
-Requires-Dist: pytest<9,>=7.0; extra == 'test'
+Requires-Dist: pytest<10,>=7.0; extra == 'test'
Requires-Dist: requests; extra == 'test'
Description-Content-Type: text/markdown
@@ -116,7 +116,7 @@
pip install jupyter_server
```
-Jupyter Server currently supports Python>=3.9 on Linux, OSX and Windows.
+Jupyter Server currently supports Python>=3.10 on Linux, OSX and Windows.
### Versioning and Branches
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/README.md
new/jupyter_server-2.19.0/README.md
--- old/jupyter_server-2.18.1/README.md 2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/README.md 2020-02-02 01:00:00.000000000 +0100
@@ -16,7 +16,7 @@
pip install jupyter_server
```
-Jupyter Server currently supports Python>=3.9 on Linux, OSX and Windows.
+Jupyter Server currently supports Python>=3.10 on Linux, OSX and Windows.
### Versioning and Branches
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/examples/simple/README.md
new/jupyter_server-2.19.0/examples/simple/README.md
--- old/jupyter_server-2.18.1/examples/simple/README.md 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/examples/simple/README.md 2020-02-02
01:00:00.000000000 +0100
@@ -10,7 +10,7 @@
# Clone, create a conda env and install from source.
git clone https://github.com/jupyter/jupyter_server && \
cd jupyter_server/examples/simple && \
- conda create -y -n jupyter-server-example python=3.9 && \
+ conda create -y -n jupyter-server-example python=3.10 && \
conda activate jupyter-server-example && \
pip install -e .[test]
```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/examples/simple/pyproject.toml
new/jupyter_server-2.19.0/examples/simple/pyproject.toml
--- old/jupyter_server-2.18.1/examples/simple/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/examples/simple/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
@@ -7,7 +7,7 @@
description = "Jupyter Server Example"
readme = "README.md"
license = "BSD-3-Clause"
-requires-python = ">=3.9"
+requires-python = ">=3.10"
dependencies = [
"jinja2",
"jupyter_server",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/jupyter_server/_version.py
new/jupyter_server-2.19.0/jupyter_server/_version.py
--- old/jupyter_server-2.18.1/jupyter_server/_version.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/_version.py 2020-02-02
01:00:00.000000000 +0100
@@ -6,7 +6,7 @@
import re
# Version string must appear intact for automatic versioning
-__version__ = "2.18.1"
+__version__ = "2.19.0"
# Build up version_info tuple for backwards compatibility
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/auth/decorator.py
new/jupyter_server-2.19.0/jupyter_server/auth/decorator.py
--- old/jupyter_server-2.18.1/jupyter_server/auth/decorator.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/auth/decorator.py 2020-02-02
01:00:00.000000000 +0100
@@ -3,8 +3,9 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import asyncio
+from collections.abc import Callable
from functools import wraps
-from typing import Any, Callable, Optional, TypeVar, Union, cast
+from typing import Any, TypeVar, cast
from jupyter_core.utils import ensure_async
from tornado.log import app_log
@@ -16,9 +17,9 @@
def authorized(
- action: Optional[Union[str, FuncT]] = None,
- resource: Optional[str] = None,
- message: Optional[str] = None,
+ action: str | FuncT | None = None,
+ resource: str | None = None,
+ message: str | None = None,
) -> FuncT:
"""A decorator for tornado.web.RequestHandler methods
that verifies whether the current user is authorized
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/auth/identity.py
new/jupyter_server-2.19.0/jupyter_server/auth/identity.py
--- old/jupyter_server-2.18.1/jupyter_server/auth/identity.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/auth/identity.py 2020-02-02
01:00:00.000000000 +0100
@@ -235,7 +235,7 @@
raise TraitError(msg)
return proposal["value"]
- need_token: bool | Bool[bool, t.Union[bool, int]] = Bool(True)
+ need_token: bool | Bool[bool, bool | int] = Bool(True)
def get_user(self, handler: web.RequestHandler) -> User | None |
t.Awaitable[User | None]:
"""Get the authenticated user for a request
@@ -354,6 +354,7 @@
"display_name": user.display_name,
"initials": user.initials,
"color": user.color,
+ "avatar_url": user.avatar_url,
}
)
return cookie
@@ -366,7 +367,7 @@
user["name"],
user["display_name"],
user["initials"],
- None,
+ user["avatar_url"],
user["color"],
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/base/websocket.py
new/jupyter_server-2.19.0/jupyter_server/base/websocket.py
--- old/jupyter_server-2.18.1/jupyter_server/base/websocket.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/base/websocket.py 2020-02-02
01:00:00.000000000 +0100
@@ -1,7 +1,7 @@
"""Base websocket classes."""
import warnings
-from typing import Optional, no_type_check
+from typing import no_type_check
from urllib.parse import urlparse
from tornado import ioloop, web
@@ -20,7 +20,7 @@
ping_callback = None
last_ping = 0.0
last_pong = 0.0
- stream: Optional[IOStream] = None
+ stream: IOStream | None = None
@property
def ping_interval(self):
@@ -41,7 +41,7 @@
)
@no_type_check
- def check_origin(self, origin: Optional[str] = None) -> bool:
+ def check_origin(self, origin: str | None = None) -> bool:
"""Check Origin == Host or Access-Control-Allow-Origin.
Tornado >= 4 calls this method automatically, raising 403 if it
returns False.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/gateway/gateway_client.py
new/jupyter_server-2.19.0/jupyter_server/gateway/gateway_client.py
--- old/jupyter_server-2.18.1/jupyter_server/gateway/gateway_client.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/gateway/gateway_client.py
2020-02-02 01:00:00.000000000 +0100
@@ -60,7 +60,7 @@
def get_token(
self,
auth_header_key: str,
- auth_scheme: ty.Union[str, None],
+ auth_scheme: str | None,
auth_token: str,
**kwargs: ty.Any,
) -> str:
@@ -78,7 +78,7 @@
def get_token(
self,
auth_header_key: str,
- auth_scheme: ty.Union[str, None],
+ auth_scheme: str | None,
auth_token: str,
**kwargs: ty.Any,
) -> str:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/gateway/handlers.py
new/jupyter_server-2.19.0/jupyter_server/gateway/handlers.py
--- old/jupyter_server-2.18.1/jupyter_server/gateway/handlers.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/gateway/handlers.py
2020-02-02 01:00:00.000000000 +0100
@@ -10,7 +10,7 @@
import os
import random
import warnings
-from typing import Any, Optional, cast
+from typing import Any, cast
from jupyter_client.session import Session
from tornado import web
@@ -290,7 +290,7 @@
@web.authenticated
async def get(self, kernel_name, path, include_body=True):
"""Get a gateway resource by name and path."""
- mimetype: Optional[str] = None
+ mimetype: str | None = None
ksm = self.kernel_spec_manager
kernel_spec_res = await ksm.get_kernel_spec_resource( #
type:ignore[attr-defined]
kernel_name, path
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/gateway/managers.py
new/jupyter_server-2.19.0/jupyter_server/gateway/managers.py
--- old/jupyter_server-2.18.1/jupyter_server/gateway/managers.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/gateway/managers.py
2020-02-02 01:00:00.000000000 +0100
@@ -11,7 +11,7 @@
from queue import Empty, Queue
from threading import Thread
from time import monotonic
-from typing import TYPE_CHECKING, Any, Optional, cast
+from typing import TYPE_CHECKING, Any, cast
import websocket
from jupyter_client.asynchronous.client import AsyncKernelClient
@@ -361,7 +361,7 @@
async def kernel_culled(self, kernel_id: str) -> bool: # typing: ignore
"""Checks if the kernel is still considered alive and returns true if
it's not found."""
- km: Optional[GatewayKernelManager] = None
+ km: GatewayKernelManager | None = None
try:
# Since we keep the models up-to-date via client polling, use that
state to determine
# if this kernel no longer exists on the gateway server rather
than perform a redundant
@@ -380,7 +380,7 @@
class GatewayKernelManager(ServerKernelManager):
"""Manages a single kernel remotely via a Gateway Server."""
- kernel_id: Optional[str] = None
+ kernel_id: str | None = None
kernel = None
@default("cache_ports")
@@ -596,7 +596,7 @@
class ChannelQueue(Queue): # type:ignore[type-arg]
"""A queue for a named channel."""
- channel_name: Optional[str] = None
+ channel_name: str | None = None
response_router_finished: bool
def __init__(self, channel_name: str, channel_socket: websocket.WebSocket,
log: Logger):
@@ -713,19 +713,19 @@
# flag for whether execute requests should be allowed to call raw_input:
allow_stdin = False
_channels_stopped: bool
- _channel_queues: Optional[dict[str, ChannelQueue]]
- _control_channel: Optional[ChannelQueue]
- _hb_channel: Optional[ChannelQueue]
- _stdin_channel: Optional[ChannelQueue]
- _iopub_channel: Optional[ChannelQueue]
- _shell_channel: Optional[ChannelQueue]
+ _channel_queues: dict[str, ChannelQueue] | None
+ _control_channel: ChannelQueue | None
+ _hb_channel: ChannelQueue | None
+ _stdin_channel: ChannelQueue | None
+ _iopub_channel: ChannelQueue | None
+ _shell_channel: ChannelQueue | None
def __init__(self, kernel_id, **kwargs):
"""Initialize a gateway kernel client."""
super().__init__(**kwargs)
self.kernel_id = kernel_id
- self.channel_socket: Optional[websocket.WebSocket] = None
- self.response_router: Optional[Thread] = None
+ self.channel_socket: websocket.WebSocket | None = None
+ self.response_router: Thread | None = None
self._channels_stopped = False
self._channel_queues = {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/jupyter_server/serverapp.py
new/jupyter_server-2.19.0/jupyter_server/serverapp.py
--- old/jupyter_server-2.18.1/jupyter_server/serverapp.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/serverapp.py 2020-02-02
01:00:00.000000000 +0100
@@ -558,9 +558,7 @@
sources.extend(self.settings["last_activity_times"].values())
return max(sources)
- def _check_handler_auth(
- self, matcher: t.Union[str, Matcher], handler: type[web.RequestHandler]
- ):
+ def _check_handler_auth(self, matcher: str | Matcher, handler:
type[web.RequestHandler]):
missing_authentication = []
for method_name in handler.SUPPORTED_METHODS:
method = getattr(handler, method_name.lower())
@@ -1216,7 +1214,7 @@
)
@default("min_open_files_limit")
- def _default_min_open_files_limit(self) -> t.Optional[int]:
+ def _default_min_open_files_limit(self) -> int | None:
if resource is None:
# Ignoring min_open_files_limit because the limit cannot be
adjusted (for example, on Windows)
return None # type:ignore[unreachable]
@@ -1276,7 +1274,7 @@
)
def _warn_deprecated_config(
- self, change: t.Any, clsname: str, new_name: t.Optional[str] = None
+ self, change: t.Any, clsname: str, new_name: str | None = None
) -> None:
"""Warn on deprecated config."""
if new_name is None:
@@ -1620,7 +1618,7 @@
)
@default("kernel_manager_class")
- def _default_kernel_manager_class(self) -> t.Union[str,
type[AsyncMappingKernelManager]]:
+ def _default_kernel_manager_class(self) -> str |
type[AsyncMappingKernelManager]:
if self.gateway_config.gateway_enabled:
return
"jupyter_server.gateway.managers.GatewayMappingKernelManager"
return AsyncMappingKernelManager
@@ -1631,7 +1629,7 @@
)
@default("session_manager_class")
- def _default_session_manager_class(self) -> t.Union[str,
type[SessionManager]]:
+ def _default_session_manager_class(self) -> str | type[SessionManager]:
if self.gateway_config.gateway_enabled:
return "jupyter_server.gateway.managers.GatewaySessionManager"
return SessionManager
@@ -1645,7 +1643,7 @@
@default("kernel_websocket_connection_class")
def _default_kernel_websocket_connection_class(
self,
- ) -> t.Union[str, type[ZMQChannelsWebsocketConnection]]:
+ ) -> str | type[ZMQChannelsWebsocketConnection]:
if self.gateway_config.gateway_enabled:
return
"jupyter_server.gateway.connections.GatewayWebSocketConnection"
return ZMQChannelsWebsocketConnection
@@ -1696,7 +1694,7 @@
)
@default("kernel_spec_manager_class")
- def _default_kernel_spec_manager_class(self) -> t.Union[str,
type[KernelSpecManager]]:
+ def _default_kernel_spec_manager_class(self) -> str |
type[KernelSpecManager]:
if self.gateway_config.gateway_enabled:
return "jupyter_server.gateway.managers.GatewayKernelSpecManager"
return KernelSpecManager
@@ -2049,7 +2047,7 @@
"""Get the Extension that started this server."""
return self._starter_app
- def parse_command_line(self, argv: t.Optional[list[str]] = None) -> None:
+ def parse_command_line(self, argv: list[str] | None = None) -> None:
"""Parse the command line options."""
super().parse_command_line(argv)
@@ -2343,7 +2341,7 @@
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
def _get_urlparts(
- self, path: t.Optional[str] = None, include_token: bool = False
+ self, path: str | None = None, include_token: bool = False
) -> urllib.parse.ParseResult:
"""Constructs a urllib named tuple, ParseResult,
with default values set by server config.
@@ -2782,7 +2780,7 @@
@catch_config_error
def initialize(
self,
- argv: t.Optional[list[str]] = None,
+ argv: list[str] | None = None,
find_extensions: bool = True,
new_httpserver: bool = True,
starter_extension: t.Any = None,
@@ -3028,7 +3026,7 @@
if e.errno != errno.ENOENT:
raise
- def _prepare_browser_open(self) -> tuple[str, t.Optional[str]]:
+ def _prepare_browser_open(self) -> tuple[str, str | None]:
"""Prepare to open the browser."""
if not self.use_redirect_file:
uri = self.default_url[len(self.base_url) :]
@@ -3245,7 +3243,7 @@
def list_running_servers(
- runtime_dir: t.Optional[str] = None, log: t.Optional[logging.Logger] = None
+ runtime_dir: str | None = None, log: logging.Logger | None = None
) -> t.Generator[t.Any, None, None]:
"""Iterate over the server info files of running Jupyter servers.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/services/api/api.yaml
new/jupyter_server-2.19.0/jupyter_server/services/api/api.yaml
--- old/jupyter_server-2.18.1/jupyter_server/services/api/api.yaml
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/services/api/api.yaml
2020-02-02 01:00:00.000000000 +0100
@@ -379,7 +379,7 @@
in: query
description: file path
type: string
- - name: kernel_id
+ - name: kernel
required: false
in: query
description: kernel uuid
@@ -389,7 +389,7 @@
summary: Resolve path to a file
responses:
200:
- description: Resolved path with scope details
+ description: Resolved path with scope details and optional
unresolved scopes
schema:
$ref: "#/definitions/ResolvedPath"
/api/sessions/{session}:
@@ -1014,3 +1014,18 @@
path:
type: string
description: fully specified path for file
+ unresolved:
+ type: array
+ description: scopes for which path resolution could not be completed
+ items:
+ type: object
+ required:
+ - scope
+ - reason
+ properties:
+ scope:
+ type: string
+ description: the scope that failed to resolve
+ reason:
+ type: string
+ description: human-readable reason for the unresolved scope
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/services/api/handlers.py
new/jupyter_server-2.19.0/jupyter_server/services/api/handlers.py
--- old/jupyter_server-2.18.1/jupyter_server/services/api/handlers.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/services/api/handlers.py
2020-02-02 01:00:00.000000000 +0100
@@ -153,14 +153,25 @@
path = self.get_query_argument("path")
kernel_uuid = self.get_query_argument("kernel", default=None)
scopes: dict[str, Any] = {"server": self.contents_manager}
+ unresolved: list[dict[str, str]] = []
if kernel_uuid:
- scopes["kernel"] = self.kernel_manager.get_kernel(kernel_uuid)
+ try:
+ scopes["kernel"] = self.kernel_manager.get_kernel(kernel_uuid)
+ except web.HTTPError as e:
+ if e.status_code == 404:
+ unresolved.append(
+ {"scope": "kernel", "reason": f"Kernel {kernel_uuid}
could not be found"}
+ )
+ else:
+ raise
resolved = [
{"scope": name, "path": await
ensure_async(scope.resolve_path(path))}
for name, scope in scopes.items()
if hasattr(scope, "resolve_path")
]
response = {"resolved": [entry for entry in resolved if entry["path"]
is not None]}
+ if unresolved:
+ response["unresolved"] = unresolved
self.finish(json.dumps(response))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/services/contents/fileio.py
new/jupyter_server-2.19.0/jupyter_server/services/contents/fileio.py
--- old/jupyter_server-2.18.1/jupyter_server/services/contents/fileio.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/services/contents/fileio.py
2020-02-02 01:00:00.000000000 +0100
@@ -322,8 +322,10 @@
except ValueError:
raise HTTPError(404, f"{path} is not a valid path") from None
- if not (os.path.abspath(os_path) + os.path.sep).startswith(root +
os.path.sep):
- raise HTTPError(404, "%s is outside root contents directory" %
path)
+ # Corner case: when root_dir is the filesystem root, root + sep
produces an invalid prefix
+ if os.path.dirname(root) != root:
+ if not (os.path.abspath(os_path) + os.path.sep).startswith(root +
os.path.sep):
+ raise HTTPError(404, "%s is outside root contents directory" %
path)
return os_path
def _read_notebook(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/services/contents/manager.py
new/jupyter_server-2.19.0/jupyter_server/services/contents/manager.py
--- old/jupyter_server-2.18.1/jupyter_server/services/contents/manager.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/services/contents/manager.py
2020-02-02 01:00:00.000000000 +0100
@@ -705,7 +705,7 @@
self.notary.mark_cells(nb, True)
self.check_and_sign(nb, path)
- def check_and_sign(self, nb, path=""):
+ def check_and_sign(self, nb, path="", *, _retrying=False):
"""Check for trusted cells, and sign the notebook.
Called as a part of saving notebooks.
@@ -717,10 +717,26 @@
path : str
The notebook's path (for logging)
"""
- if self.notary.check_cells(nb):
- self.notary.sign(nb)
- else:
- self.log.warning("Notebook %s is not trusted", path)
+ try:
+ if self.notary.check_cells(nb):
+ self.notary.sign(nb)
+ else:
+ self.log.warning("Notebook %s is not trusted", path)
+ except Exception:
+ if _retrying:
+ raise
+ self.log.warning(
+ "Signature store for notebook %s is corrupted or unavailable; "
+ "recreating the store.",
+ path,
+ exc_info=True,
+ )
+ # The default implementation uses SQLiteSignatureStore if SQLite3
is available
+ # and falls back to MemorySignatureStore if not;
SQLiteSignatureStore will
+ # attempt to recreate the database if it detects errors during
initialization,
+ # and fallback to in-memory (`:memory:`) SQLite database if
necessary.
+ self.notary.store = self.notary.store_factory()
+ self.check_and_sign(nb, path, _retrying=True)
def mark_trusted_cells(self, nb, path=""):
"""Mark cells as trusted if the notebook signature matches.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/services/events/handlers.py
new/jupyter_server-2.19.0/jupyter_server/services/events/handlers.py
--- old/jupyter_server-2.18.1/jupyter_server/services/events/handlers.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/jupyter_server/services/events/handlers.py
2020-02-02 01:00:00.000000000 +0100
@@ -7,7 +7,7 @@
import json
from datetime import datetime
-from typing import TYPE_CHECKING, Any, Optional, cast
+from typing import TYPE_CHECKING, Any, cast
from jupyter_core.utils import ensure_async
from tornado import web, websocket
@@ -92,7 +92,7 @@
raise Exception(message)
-def get_timestamp(data: dict[str, Any]) -> Optional[datetime]:
+def get_timestamp(data: dict[str, Any]) -> datetime | None:
"""Parses timestamp from the JSON request body"""
try:
if "timestamp" in data:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/services/kernels/connection/base.py
new/jupyter_server-2.19.0/jupyter_server/services/kernels/connection/base.py
---
old/jupyter_server-2.18.1/jupyter_server/services/kernels/connection/base.py
2020-02-02 01:00:00.000000000 +0100
+++
new/jupyter_server-2.19.0/jupyter_server/services/kernels/connection/base.py
2020-02-02 01:00:00.000000000 +0100
@@ -68,7 +68,7 @@
offsets = list(struct.unpack("!" + "I" * nbufs, bmsg[4 : 4 * (nbufs + 1)]))
offsets.append(None)
bufs = []
- for start, stop in zip(offsets[:-1], offsets[1:]):
+ for start, stop in zip(offsets[:-1], offsets[1:], strict=False):
bufs.append(bmsg[start:stop])
msg = json.loads(bufs[0].decode("utf8"))
msg["header"] = extract_dates(msg["header"])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/jupyter_server/services/sessions/sessionmanager.py
new/jupyter_server-2.19.0/jupyter_server/services/sessions/sessionmanager.py
---
old/jupyter_server-2.18.1/jupyter_server/services/sessions/sessionmanager.py
2020-02-02 01:00:00.000000000 +0100
+++
new/jupyter_server-2.19.0/jupyter_server/services/sessions/sessionmanager.py
2020-02-02 01:00:00.000000000 +0100
@@ -5,7 +5,7 @@
import os
import pathlib
import uuid
-from typing import Any, NewType, Optional, Union, cast
+from typing import Any, NewType, cast
KernelName = NewType("KernelName", str)
ModelName = NewType("ModelName", str)
@@ -41,8 +41,8 @@
associated with them.
"""
- session_id: Optional[str] = None
- kernel_id: Optional[str] = None
+ session_id: str | None = None
+ kernel_id: str | None = None
def __eq__(self, other: object) -> bool:
"""Whether a record equals another."""
@@ -112,7 +112,7 @@
"""The string representation of a record list."""
return str(self._records)
- def __contains__(self, record: Union[KernelSessionRecord, str]) -> bool:
+ def __contains__(self, record: KernelSessionRecord | str) -> bool:
"""Search for records by kernel_id and session_id"""
if isinstance(record, KernelSessionRecord) and record in self._records:
return True
@@ -127,7 +127,7 @@
"""The length of the record list."""
return len(self._records)
- def get(self, record: Union[KernelSessionRecord, str]) ->
KernelSessionRecord:
+ def get(self, record: KernelSessionRecord | str) -> KernelSessionRecord:
"""Return a full KernelSessionRecord from a session_id, kernel_id, or
incomplete KernelSessionRecord.
"""
@@ -262,11 +262,11 @@
async def create_session(
self,
- path: Optional[str] = None,
- name: Optional[ModelName] = None,
- type: Optional[str] = None,
- kernel_name: Optional[KernelName] = None,
- kernel_id: Optional[str] = None,
+ path: str | None = None,
+ name: ModelName | None = None,
+ type: str | None = None,
+ kernel_name: KernelName | None = None,
+ kernel_id: str | None = None,
) -> dict[str, Any]:
"""Creates a session and returns its model
@@ -293,9 +293,7 @@
self._pending_sessions.remove(record)
return cast("dict[str, Any]", result)
- def get_kernel_env(
- self, path: Optional[str], name: Optional[ModelName] = None
- ) -> dict[str, str]:
+ def get_kernel_env(self, path: str | None, name: ModelName | None = None)
-> dict[str, str]:
"""Return the environment variables that need to be set in the kernel
Parameters
@@ -315,10 +313,10 @@
async def start_kernel_for_session(
self,
session_id: str,
- path: Optional[str],
- name: Optional[ModelName],
- type: Optional[str],
- kernel_name: Optional[KernelName],
+ path: str | None,
+ name: ModelName | None,
+ type: str | None,
+ kernel_name: KernelName | None,
) -> str:
"""Start a new kernel for a given session.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/package-lock.json
new/jupyter_server-2.19.0/package-lock.json
--- old/jupyter_server-2.18.1/package-lock.json 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server-2.19.0/package-lock.json 2020-02-02 01:00:00.000000000
+0100
@@ -184,9 +184,9 @@
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity":
"sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity":
"sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -538,9 +538,9 @@
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity":
"sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity":
"sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"requires": {
"brace-expansion": "^1.1.7"
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/pyproject.toml
new/jupyter_server-2.19.0/pyproject.toml
--- old/jupyter_server-2.18.1/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server-2.19.0/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
@@ -21,7 +21,7 @@
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
]
-requires-python = ">=3.9"
+requires-python = ">=3.10"
dependencies = [
"anyio>=3.1.0",
"argon2-cffi>=21.1",
@@ -57,7 +57,7 @@
"pytest-console-scripts",
"pytest-timeout",
"pytest-jupyter[server]>=0.7",
- "pytest>=7.0,<9",
+ "pytest>=7.0,<10",
"requests",
"pre-commit",
'flaky'
@@ -139,7 +139,7 @@
docstring-code-format = true
[tool.ruff.lint]
-ignore = ["ARG", "TRY", "RUF012", "TID252", "G", "INP001", "E402", "F401",
"BLE001", "UP045", "PLC0415"]
+ignore = ["ARG", "TRY", "RUF012", "TID252", "G", "INP001", "E402", "F401",
"BLE001", "PLC0415"]
extend-select = [
"B", # flake8-bugbear
"I", # isort
@@ -157,7 +157,7 @@
[tool.ruff.lint.extend-per-file-ignores]
"jupyter_server/*" = ["S101", "RET", "S110", "UP031", "FBT", "FA100",
"SLF001", "A002",
- "SIM105", "A001", "UP007", "PLR2004", "T201", "N818",
"F403"]
+ "SIM105", "A001", "PLR2004", "T201", "N818", "F403"]
"jupyter_server/gateway/*" = ["TCH" ]
"tests/*" = ["UP031", "PT", 'EM', "TRY", "RET", "SLF", "C408", "F841", "FBT",
"A002", "FLY", "N",
"PERF", "ASYNC", "T201", "FA100", "E711", "INP", "TCH", "SIM105",
"A001", "PLW0603",
@@ -216,7 +216,7 @@
post-version-spec = "dev"
[tool.mypy]
-python_version = "3.9"
+python_version = "3.10"
explicit_package_bases = true
strict = true
pretty = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/tests/auth/test_identity.py
new/jupyter_server-2.19.0/tests/auth/test_identity.py
--- old/jupyter_server-2.18.1/tests/auth/test_identity.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/tests/auth/test_identity.py 2020-02-02
01:00:00.000000000 +0100
@@ -178,6 +178,30 @@
idp.validate_security(app, ssl_options=None)
[email protected](
+ "user_fields",
+ [
+ {"username": "user"},
+ {"username": "user", "avatar_url": "https://example.com/avatar.png"},
+ {
+ "username": "user",
+ "name": "Real Name",
+ "display_name": "RN",
+ "initials": "RN",
+ "avatar_url": "img/avatar.jpg",
+ "color": "#abc",
+ },
+ ],
+)
+def test_user_cookie_roundtrip(user_fields):
+ """All user fields must survive user_to_cookie -> user_from_cookie."""
+ idp = IdentityProvider()
+ user = User(**user_fields)
+ cookie = idp.user_to_cookie(user)
+ restored = idp.user_from_cookie(cookie)
+ assert restored == user
+
+
async def test_auth_disabled(request, jp_serverapp, jp_fetch):
idp = PasswordIdentityProvider(
parent=jp_serverapp,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/tests/services/api/test_api.py
new/jupyter_server-2.19.0/tests/services/api/test_api.py
--- old/jupyter_server-2.18.1/tests/services/api/test_api.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/tests/services/api/test_api.py 2020-02-02
01:00:00.000000000 +0100
@@ -227,6 +227,7 @@
"name": "Updated Name",
"display_name": "Updated Display Name",
"color": "#000000",
+ "avatar_url": "some/url",
},
)
],
@@ -236,11 +237,12 @@
):
"""Test successful user update."""
password_identity_provider.mock_user = MockUser(**identity)
- identity_provider.updatable_fields = ["name", "display_name", "color"]
+ password_identity_provider.updatable_fields = ["name", "display_name",
"color", "avatar_url"]
payload = {
"name": expected["name"],
"display_name": expected["display_name"],
"color": expected["color"],
+ "avatar_url": expected["avatar_url"],
}
r = await jp_fetch(
"/api/me",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/tests/services/contents/test_fileio.py
new/jupyter_server-2.19.0/tests/services/contents/test_fileio.py
--- old/jupyter_server-2.18.1/tests/services/contents/test_fileio.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/tests/services/contents/test_fileio.py
2020-02-02 01:00:00.000000000 +0100
@@ -299,3 +299,48 @@
assert err.value.status_code == 404
assert "outside root contents directory" in str(err.value)
+
+
+def test_get_os_path_with_root_dir_slash(tmp_path):
+ """root_dir set to filesystem root should not raise for any valid path
(issue #1635)."""
+
+ fs_root = os.path.abspath(os.sep)
+
+ class FileManagerMixinTest(FileManagerMixin):
+ root_dir = fs_root
+
+ mixin = FileManagerMixinTest()
+ mixin.log = logging.getLogger()
+
+ assert mixin._get_os_path("work/notebook.ipynb") == os.path.join(
+ fs_root, "work", "notebook.ipynb"
+ )
+
+
[email protected](os.name != "nt", reason="Windows UNC paths only")
[email protected]("root_dir", ["\\\\server\\share",
"\\\\server\\share\\"])
+def test_get_os_path_with_unc_root(root_dir):
+ """UNC root_dir (with or without trailing backslash) should resolve paths
correctly."""
+
+ class FileManagerMixinTest(FileManagerMixin):
+ pass
+
+ mixin = FileManagerMixinTest()
+ mixin.root_dir = root_dir
+ mixin.log = logging.getLogger()
+
+ assert mixin._get_os_path("work/notebook.ipynb") ==
"\\\\server\\share\\work\\notebook.ipynb"
+
+
[email protected](os.name != "nt", reason="Windows only")
+def test_get_os_path_with_non_current_drive_root():
+ """root_dir set to a non-current drive root should not raise"""
+
+ class FileManagerMixinTest(FileManagerMixin):
+ pass
+
+ mixin = FileManagerMixinTest()
+ mixin.root_dir = "W:\\"
+ mixin.log = logging.getLogger()
+
+ assert mixin._get_os_path("work/notebook.ipynb") ==
"W:\\work\\notebook.ipynb"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/tests/services/contents/test_manager.py
new/jupyter_server-2.19.0/tests/services/contents/test_manager.py
--- old/jupyter_server-2.18.1/tests/services/contents/test_manager.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/tests/services/contents/test_manager.py
2020-02-02 01:00:00.000000000 +0100
@@ -4,13 +4,13 @@
import sys
import time
from itertools import combinations
-from typing import Optional
from unittest.mock import MagicMock, patch
import pytest
from jupyter_core.utils import ensure_async
from nbformat import ValidationError
from nbformat import v4 as nbformat
+from nbformat.sign import NotebookNotary
from tornado.web import HTTPError
from traitlets import TraitError
@@ -113,7 +113,7 @@
async def prepare_notebook(
- jp_contents_manager: FileContentsManager, make_invalid: Optional[bool] =
False
+ jp_contents_manager: FileContentsManager, make_invalid: bool | None = False
) -> tuple[dict, str]:
cm = jp_contents_manager
model = await ensure_async(cm.new_untitled(type="notebook"))
@@ -1152,6 +1152,77 @@
assert updated_model["last_modified"] > model["last_modified"]
[email protected](params=[FileContentsManager, AsyncFileContentsManager])
+def file_manager_with_notary(request, tmp_path):
+ """Both sync and async file managers, each with a notary using a dedicated
temp directory."""
+ notary_dir = tmp_path / "notary"
+ notary_dir.mkdir()
+ root_dir = tmp_path / "root"
+ root_dir.mkdir()
+ notary = NotebookNotary(data_dir=str(notary_dir))
+ cm = request.param(root_dir=str(root_dir))
+ cm.notary = notary
+ return cm
+
+
+async def
test_save_before_and_after_signature_store_corruption(file_manager_with_notary,
caplog):
+ cm = file_manager_with_notary
+ db_path = cm.notary.db_file
+
+ # First save
+ model = await ensure_async(cm.new_untitled(type="notebook"))
+ path = model["path"]
+ full_model = await ensure_async(cm.get(path))
+ result = await ensure_async(cm.save(full_model, path))
+ assert isinstance(result, dict)
+ assert result["path"] == path
+ assert os.path.exists(db_path)
+
+ # Fetch the model before corrupting so that cm.get() does not itself hit
the DB
+ full_model = await ensure_async(cm.get(path))
+
+ # Corrupt the signature store by truncating to the SQLite file header size
(100 bytes),
+ # leaving the magic bytes intact but removing all data pages.
+ with open(db_path, "r+b") as f:
+ f.truncate(100)
+
+ # Second save with the same notary: signature store corruption should be
handled
+ # gracefully and the notebook should still be saved successfully.
+ with caplog.at_level("WARNING"):
+ result = await ensure_async(cm.save(full_model, path))
+ assert isinstance(result, dict)
+ assert result["path"] == path
+ assert "corrupted or unavailable" in caplog.text
+
+
+async def
test_save_raises_when_signature_store_unrecoverable(file_manager_with_notary,
caplog):
+ cm = file_manager_with_notary
+ db_path = cm.notary.db_file
+
+ # First save
+ model = await ensure_async(cm.new_untitled(type="notebook"))
+ path = model["path"]
+ full_model = await ensure_async(cm.get(path))
+ await ensure_async(cm.save(full_model, path))
+
+ # Fetch the model before corrupting so that cm.get() does not itself hit
the DB
+ full_model = await ensure_async(cm.get(path))
+
+ # Corrupt the store, and also replace store_factory so that the recovery
attempt
+ # returns a broken store, causing the retry to also fail.
+ with open(db_path, "r+b") as f:
+ f.truncate(100)
+ broken_store = MagicMock()
+ broken_store.store_signature.side_effect = RuntimeError("store is broken")
+ cm.notary.store_factory = lambda: broken_store
+
+ with caplog.at_level("WARNING"):
+ with pytest.raises(HTTPError) as exc_info:
+ await ensure_async(cm.save(full_model, path))
+ assert exc_info.value.status_code == 500
+ assert "corrupted or unavailable" in caplog.text
+
+
@pytest.mark.skipif(sys.platform != "darwin", reason="macOS only -
st_birthtime test")
async def test_created_uses_birthtime_on_macos(jp_contents_manager):
"""Test that on macOS, created timestamp uses st_birthtime (actual
creation time)."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.18.1/tests/services/kernels/test_api.py
new/jupyter_server-2.19.0/tests/services/kernels/test_api.py
--- old/jupyter_server-2.18.1/tests/services/kernels/test_api.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/tests/services/kernels/test_api.py
2020-02-02 01:00:00.000000000 +0100
@@ -186,7 +186,7 @@
@pytest.mark.skipif(
ipykernel.version_info < (7, 0),
- reason="requires ipykernel 7, which is not compatible with Python 3.9",
+ reason="requires ipykernel 7, which is not compatible with Python 3.10",
)
@pytest.mark.timeout(TEST_TIMEOUT)
async def test_resolve_path_kernel(jp_fetch, jp_serverapp, jp_root_dir,
pending_kernel_is_ready):
@@ -269,7 +269,7 @@
@pytest.mark.skipif(
ipykernel.version_info < (7, 0),
- reason="requires ipykernel 7, which is not compatible with Python 3.9",
+ reason="requires ipykernel 7, which is not compatible with Python 3.10",
)
@pytest.mark.timeout(TEST_TIMEOUT)
async def test_resolve_path_server_and_kernel(
@@ -306,7 +306,7 @@
resolution = json.loads(r.body.decode())
# Should present candidates for both server and kernel
- assert len(resolution["resolved"]) == 2
+ assert len(resolution["resolved"]) == 2, resolution["resolved"]
assert {k["scope"] for k in resolution["resolved"]} == {"server", "kernel"}
@@ -345,6 +345,23 @@
@pytest.mark.timeout(TEST_TIMEOUT)
+async def test_resolve_path_missing_kernel_reports_unresolved(jp_fetch,
jp_serverapp):
+ bad_id = "111-111-111-111-111"
+ r = await jp_fetch(
+ "api",
+ "resolvePath",
+ params={"kernel": bad_id, "path": "hello.py"},
+ method="GET",
+ )
+ assert r.code == 200
+ resolution = json.loads(r.body.decode())
+ assert resolution["resolved"] == []
+ assert resolution["unresolved"] == [
+ {"scope": "kernel", "reason": f"Kernel {bad_id} could not be found"}
+ ]
+
+
[email protected](TEST_TIMEOUT)
async def test_kernel_handler(jp_fetch, jp_serverapp, pending_kernel_is_ready):
# Create a kernel
r = await jp_fetch(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.18.1/tests/test_gateway.py
new/jupyter_server-2.19.0/tests/test_gateway.py
--- old/jupyter_server-2.18.1/tests/test_gateway.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.19.0/tests/test_gateway.py 2020-02-02
01:00:00.000000000 +0100
@@ -8,7 +8,7 @@
from datetime import datetime, timedelta, timezone
from io import BytesIO
from queue import Empty
-from typing import Any, Union
+from typing import Any
from unittest.mock import MagicMock, patch
import pytest
@@ -222,7 +222,7 @@
config_var_2: str = Unicode(config=True) # type:ignore[assignment]
def get_token(
- self, auth_header_key: str, auth_scheme: Union[str, None], auth_token:
str, **kwargs: Any
+ self, auth_header_key: str, auth_scheme: str | None, auth_token: str,
**kwargs: Any
) -> str:
return f"{self.config_var_2}{self.config_var_1}"