diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 83c949e6ca..709a190ac6 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -18,12 +18,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.8", "3.11"] + python-version: ["3.9", "3.11", "3.12"] include: - os: windows-latest python-version: "3.9" - os: ubuntu-latest - python-version: "pypy-3.8" + python-version: "pypy-3.9" - os: macos-latest python-version: "3.10" - os: ubuntu-latest @@ -180,7 +180,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -194,7 +194,7 @@ jobs: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: - python_version: "pypy-3.8" + python_version: "pypy-3.9" - name: Run the tests run: hatch -v run test:nowarn --integration_tests=true diff --git a/examples/simple/pyproject.toml b/examples/simple/pyproject.toml index 38ae8e71a7..9dec3e55c7 100644 --- a/examples/simple/pyproject.toml +++ b/examples/simple/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "hatchling.build" name = "jupyter-server-example" description = "Jupyter Server Example" readme = "README.md" -license = "" -requires-python = ">=3.8" +license = "MIT" +requires-python = ">=3.9" dependencies = [ "jinja2", "jupyter_server", diff --git a/jupyter_server/_version.py b/jupyter_server/_version.py index fa814fbb2f..20f829fe92 100644 --- a/jupyter_server/_version.py +++ b/jupyter_server/_version.py @@ -4,7 +4,6 @@ """ import re -from typing import List # Version string must appear intact for automatic versioning __version__ = "2.15.0.dev0" @@ -13,7 +12,7 @@ pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, __version__) assert match is not None -parts: List[object] = [int(match[part]) for part in ["major", "minor", "patch"]] +parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]] if match["rest"]: parts.append(match["rest"]) version_info = tuple(parts) diff --git a/jupyter_server/auth/authorizer.py b/jupyter_server/auth/authorizer.py index 10414e2c39..fcebc3404b 100644 --- a/jupyter_server/auth/authorizer.py +++ b/jupyter_server/auth/authorizer.py @@ -10,7 +10,7 @@ # Distributed under the terms of the Modified BSD License. from __future__ import annotations -from typing import TYPE_CHECKING, Awaitable +from typing import TYPE_CHECKING from traitlets import Instance from traitlets.config import LoggingConfigurable @@ -18,6 +18,8 @@ from .identity import IdentityProvider, User if TYPE_CHECKING: + from collections.abc import Awaitable + from jupyter_server.base.handlers import JupyterHandler diff --git a/jupyter_server/base/call_context.py b/jupyter_server/base/call_context.py index cf71256235..4e80be8a7d 100644 --- a/jupyter_server/base/call_context.py +++ b/jupyter_server/base/call_context.py @@ -3,7 +3,7 @@ # Distributed under the terms of the Modified BSD License. from contextvars import Context, ContextVar, copy_context -from typing import Any, Dict, List +from typing import Any class CallContext: @@ -22,7 +22,7 @@ class CallContext: # easier management over maintaining a set of ContextVar instances, since the Context is a # map of ContextVar instances to their values, and the "name" is no longer a lookup key. _NAME_VALUE_MAP = "_name_value_map" - _name_value_map: ContextVar[Dict[str, Any]] = ContextVar(_NAME_VALUE_MAP) + _name_value_map: ContextVar[dict[str, Any]] = ContextVar(_NAME_VALUE_MAP) @classmethod def get(cls, name: str) -> Any: @@ -65,7 +65,7 @@ def set(cls, name: str, value: Any) -> None: name_value_map[name] = value @classmethod - def context_variable_names(cls) -> List[str]: + def context_variable_names(cls) -> list[str]: """Returns a list of variable names set for this call context. Returns @@ -77,7 +77,7 @@ def context_variable_names(cls) -> List[str]: return list(name_value_map.keys()) @classmethod - def _get_map(cls) -> Dict[str, Any]: + def _get_map(cls) -> dict[str, Any]: """Get the map of names to their values from the _NAME_VALUE_MAP context var. If the map does not exist in the current context, an empty map is created and returned. diff --git a/jupyter_server/base/handlers.py b/jupyter_server/base/handlers.py index 770fff1866..6f4977145d 100644 --- a/jupyter_server/base/handlers.py +++ b/jupyter_server/base/handlers.py @@ -13,9 +13,10 @@ import re import types import warnings +from collections.abc import Awaitable, Coroutine, Sequence from http.client import responses from logging import Logger -from typing import TYPE_CHECKING, Any, Awaitable, Coroutine, Sequence, cast +from typing import TYPE_CHECKING, Any, cast from urllib.parse import urlparse import prometheus_client diff --git a/jupyter_server/config_manager.py b/jupyter_server/config_manager.py index 4a0bff4015..8f49cb7bd6 100644 --- a/jupyter_server/config_manager.py +++ b/jupyter_server/config_manager.py @@ -14,7 +14,7 @@ from traitlets.config import LoggingConfigurable from traitlets.traitlets import Bool, Unicode -StrDict = t.Dict[str, t.Any] +StrDict = dict[str, t.Any] def recursive_update(target: StrDict, new: StrDict) -> None: diff --git a/jupyter_server/event_schemas/contents_service/v1.yaml b/jupyter_server/event_schemas/contents_service/v1.yaml index a787f9b2b0..d049005e01 100644 --- a/jupyter_server/event_schemas/contents_service/v1.yaml +++ b/jupyter_server/event_schemas/contents_service/v1.yaml @@ -1,5 +1,5 @@ "$id": https://events.jupyter.org/jupyter_server/contents_service/v1 -version: 1 +version: "1" title: Contents Manager activities personal-data: true description: | diff --git a/jupyter_server/event_schemas/gateway_client/v1.yaml b/jupyter_server/event_schemas/gateway_client/v1.yaml index 0a35d2464d..0257ce071a 100644 --- a/jupyter_server/event_schemas/gateway_client/v1.yaml +++ b/jupyter_server/event_schemas/gateway_client/v1.yaml @@ -1,5 +1,5 @@ "$id": https://events.jupyter.org/jupyter_server/gateway_client/v1 -version: 1 +version: "1" title: Gateway Client activities. personal-data: true description: | diff --git a/jupyter_server/event_schemas/kernel_actions/v1.yaml b/jupyter_server/event_schemas/kernel_actions/v1.yaml index e0375e5aaa..66c13802c2 100644 --- a/jupyter_server/event_schemas/kernel_actions/v1.yaml +++ b/jupyter_server/event_schemas/kernel_actions/v1.yaml @@ -1,5 +1,5 @@ "$id": https://events.jupyter.org/jupyter_server/kernel_actions/v1 -version: 1 +version: "1" title: Kernel Manager activities personal-data: true description: | diff --git a/jupyter_server/files/handlers.py b/jupyter_server/files/handlers.py index 2c1dc5adf6..749328438e 100644 --- a/jupyter_server/files/handlers.py +++ b/jupyter_server/files/handlers.py @@ -6,7 +6,7 @@ import mimetypes from base64 import decodebytes -from typing import Awaitable +from typing import TYPE_CHECKING from jupyter_core.utils import ensure_async from tornado import web @@ -14,6 +14,9 @@ from jupyter_server.auth.decorator import authorized from jupyter_server.base.handlers import JupyterHandler +if TYPE_CHECKING: + from collections.abc import Awaitable + AUTH_RESOURCE = "contents" diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 13fa256397..8aa3dbd082 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -2552,8 +2552,6 @@ def init_mime_overrides(self) -> None: # ensure css, js are correct, which are required for pages to function mimetypes.add_type("text/css", ".css") mimetypes.add_type("application/javascript", ".js") - # for python <3.8 - mimetypes.add_type("application/wasm", ".wasm") def shutdown_no_activity(self) -> None: """Shutdown server on timeout when there are no kernels or terminals.""" @@ -2718,7 +2716,7 @@ def _init_asyncio_patch() -> None: at least until asyncio adds *_reader methods to proactor. """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): + if sys.platform.startswith("win"): import asyncio try: diff --git a/jupyter_server/services/api/handlers.py b/jupyter_server/services/api/handlers.py index 22904fdb07..f61d9dd10f 100644 --- a/jupyter_server/services/api/handlers.py +++ b/jupyter_server/services/api/handlers.py @@ -4,7 +4,7 @@ # Distributed under the terms of the Modified BSD License. import json import os -from typing import Any, Dict, List +from typing import Any from jupyter_core.utils import ensure_async from tornado import web @@ -87,7 +87,7 @@ async def get(self): else: permissions_to_check = {} - permissions: Dict[str, List[str]] = {} + permissions: dict[str, list[str]] = {} user = self.current_user for resource, actions in permissions_to_check.items(): @@ -106,7 +106,7 @@ async def get(self): if authorized: allowed.append(action) - identity: Dict[str, Any] = self.identity_provider.identity_model(user) + identity: dict[str, Any] = self.identity_provider.identity_model(user) model = { "identity": identity, "permissions": permissions, diff --git a/jupyter_server/services/config/manager.py b/jupyter_server/services/config/manager.py index d4e207e247..223d4fed2c 100644 --- a/jupyter_server/services/config/manager.py +++ b/jupyter_server/services/config/manager.py @@ -23,7 +23,7 @@ class ConfigManager(LoggingConfigurable): def get(self, section_name): """Get the config from all config sections.""" - config: t.Dict[str, t.Any] = {} + config: dict[str, t.Any] = {} # step through back to front, to ensure front of the list is top priority for p in self.read_config_path[::-1]: cm = BaseJSONConfigManager(config_dir=p) diff --git a/jupyter_server/services/contents/handlers.py b/jupyter_server/services/contents/handlers.py index 13e987809b..0a6110ee3d 100644 --- a/jupyter_server/services/contents/handlers.py +++ b/jupyter_server/services/contents/handlers.py @@ -7,7 +7,7 @@ # Distributed under the terms of the Modified BSD License. import json from http import HTTPStatus -from typing import Any, Dict, List +from typing import Any try: from jupyter_client.jsonutil import json_default @@ -24,7 +24,7 @@ AUTH_RESOURCE = "contents" -def _validate_keys(expect_defined: bool, model: Dict[str, Any], keys: List[str]): +def _validate_keys(expect_defined: bool, model: dict[str, Any], keys: list[str]): """ Validate that the keys are defined (i.e. not None) or not (i.e. None) """ diff --git a/jupyter_server/services/events/handlers.py b/jupyter_server/services/events/handlers.py index 41e3d0d53f..fbc007341d 100644 --- a/jupyter_server/services/events/handlers.py +++ b/jupyter_server/services/events/handlers.py @@ -7,7 +7,7 @@ import json from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, Optional, cast +from typing import TYPE_CHECKING, Any, Optional, cast from jupyter_core.utils import ensure_async from tornado import web, websocket @@ -86,9 +86,9 @@ def validate_model( # jupyter_events raises a useful error, so there's no need to # handle that case here. schema = registry.get(schema_id) - version = int(cast(int, data.get("version"))) + version = str(cast(str, data.get("version"))) if schema.version != version: - message = f"Unregistered version: {version}≠{schema.version} for `{schema_id}`" + message = f"Unregistered version: {version!r}≠{schema.version!r} for `{schema_id}`" raise Exception(message) @@ -127,7 +127,7 @@ async def post(self): validate_model(payload, self.event_logger.schemas) self.event_logger.emit( schema_id=cast(str, payload.get("schema_id")), - data=cast("Dict[str, Any]", payload.get("data")), + data=cast("dict[str, Any]", payload.get("data")), timestamp_override=get_timestamp(payload), ) self.set_status(204) diff --git a/jupyter_server/services/kernels/connection/abc.py b/jupyter_server/services/kernels/connection/abc.py index 71f9e8254f..61e11a948e 100644 --- a/jupyter_server/services/kernels/connection/abc.py +++ b/jupyter_server/services/kernels/connection/abc.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Any, List +from typing import Any class KernelWebsocketConnectionABC(ABC): @@ -25,5 +25,5 @@ def handle_incoming_message(self, incoming_msg: str) -> None: """Broker the incoming websocket message to the appropriate ZMQ channel.""" @abstractmethod - def handle_outgoing_message(self, stream: str, outgoing_msg: List[Any]) -> None: + def handle_outgoing_message(self, stream: str, outgoing_msg: list[Any]) -> None: """Broker outgoing ZMQ messages to the kernel websocket.""" diff --git a/jupyter_server/services/kernels/connection/base.py b/jupyter_server/services/kernels/connection/base.py index a0e0bae8b8..6af10444b5 100644 --- a/jupyter_server/services/kernels/connection/base.py +++ b/jupyter_server/services/kernels/connection/base.py @@ -2,7 +2,7 @@ import json import struct -from typing import Any, List +from typing import Any from jupyter_client.session import Session from tornado.websocket import WebSocketHandler @@ -89,7 +89,7 @@ def serialize_msg_to_ws_v1(msg_or_list, channel, pack=None): else: msg_list = msg_or_list channel = channel.encode("utf-8") - offsets: List[Any] = [] + offsets: list[Any] = [] offsets.append(8 * (1 + 1 + len(msg_list) + 1)) offsets.append(len(channel) + offsets[-1]) for msg in msg_list: @@ -173,7 +173,7 @@ def handle_incoming_message(self, incoming_msg: str) -> None: """Handle an incoming message.""" raise NotImplementedError - def handle_outgoing_message(self, stream: str, outgoing_msg: List[Any]) -> None: + def handle_outgoing_message(self, stream: str, outgoing_msg: list[Any]) -> None: """Handle an outgoing message.""" raise NotImplementedError diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 8b392b4e1b..3aac78a0a9 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -5,7 +5,7 @@ import os import pathlib import uuid -from typing import Any, Dict, List, NewType, Optional, Union, cast +from typing import Any, NewType, Optional, Union, cast KernelName = NewType("KernelName", str) ModelName = NewType("ModelName", str) @@ -100,7 +100,7 @@ class KernelSessionRecordList: it will be appended. """ - _records: List[KernelSessionRecord] + _records: list[KernelSessionRecord] def __init__(self, *records: KernelSessionRecord): """Initialize a record list.""" @@ -267,7 +267,7 @@ async def create_session( type: Optional[str] = None, kernel_name: Optional[KernelName] = None, kernel_id: Optional[str] = None, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Creates a session and returns its model Parameters @@ -291,11 +291,11 @@ async def create_session( session_id, path=path, name=name, type=type, kernel_id=kernel_id ) self._pending_sessions.remove(record) - return cast(Dict[str, Any], result) + return cast(dict[str, Any], result) def get_kernel_env( self, path: Optional[str], name: Optional[ModelName] = None - ) -> Dict[str, str]: + ) -> dict[str, str]: """Return the environment variables that need to be set in the kernel Parameters diff --git a/jupyter_server/utils.py b/jupyter_server/utils.py index 0c987bff25..d83e1be880 100644 --- a/jupyter_server/utils.py +++ b/jupyter_server/utils.py @@ -13,7 +13,7 @@ from _frozen_importlib_external import _NamespacePath from contextlib import contextmanager from pathlib import Path -from typing import Any, Generator, NewType, Sequence +from typing import TYPE_CHECKING, Any, NewType from urllib.parse import ( SplitResult, quote, @@ -32,6 +32,9 @@ from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest, HTTPResponse from tornado.netutil import Resolver +if TYPE_CHECKING: + from collections.abc import Generator, Sequence + ApiPath = NewType("ApiPath", str) # Re-export @@ -378,17 +381,9 @@ def filefind(filename: str, path_dirs: Sequence[str]) -> str: # os.path.abspath resolves '..', but Path.absolute() doesn't # Path.resolve() does, but traverses symlinks, which we don't want test_path = Path(os.path.abspath(test_path)) - if sys.version_info >= (3, 9): - if not test_path.is_relative_to(path): - # points outside root, e.g. via `filename='../foo'` - continue - else: - # is_relative_to is new in 3.9 - try: - test_path.relative_to(path) - except ValueError: - # points outside root, e.g. via `filename='../foo'` - continue + if not test_path.is_relative_to(path): + # points outside root, e.g. via `filename='../foo'` + continue # make sure we don't call is_file before we know it's a file within a prefix # GHSA-hrw6-wg82-cm62 - can leak password hash on windows. if test_path.is_file(): diff --git a/pyproject.toml b/pyproject.toml index 9bfcc74eef..21f701a220 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "anyio>=3.1.0", "argon2-cffi>=21.1", @@ -40,7 +40,7 @@ dependencies = [ "tornado>=6.2.0", "traitlets>=5.6.0", "websocket-client>=1.7", - "jupyter_events>=0.9.0", + "jupyter_events>=0.11.0", "overrides>=5.0" ] @@ -208,7 +208,7 @@ pydist_resource_paths = ["jupyter_server/static/style/bootstrap.min.css", "jupyt post-version-spec = "dev" [tool.mypy] -python_version = "3.8" +python_version = "3.9" explicit_package_bases = true strict = true pretty = true diff --git a/tests/auth/test_authorizer.py b/tests/auth/test_authorizer.py index 3ba6bef9f1..59a6aa7428 100644 --- a/tests/auth/test_authorizer.py +++ b/tests/auth/test_authorizer.py @@ -3,7 +3,7 @@ import asyncio import json import os -from typing import Awaitable +from collections.abc import Awaitable import pytest from jupyter_client.kernelspec import NATIVE_KERNEL_NAME diff --git a/tests/extension/mockextensions/app.py b/tests/extension/mockextensions/app.py index 26f38464cd..c4f7af099c 100644 --- a/tests/extension/mockextensions/app.py +++ b/tests/extension/mockextensions/app.py @@ -14,7 +14,7 @@ EVENT_SCHEMA = """\ $id: https://events.jupyter.org/mockapp/v1/test -version: 1 +version: '1' properties: msg: type: string diff --git a/tests/extension/test_app.py b/tests/extension/test_app.py index d1add54344..965fe2ca16 100644 --- a/tests/extension/test_app.py +++ b/tests/extension/test_app.py @@ -32,6 +32,7 @@ def mock_extension(extension_manager): pkg = extension_manager.extensions[name] point = pkg.extension_points["mockextension"] app = point.app + app.initialize() return app diff --git a/tests/services/api/test_api.py b/tests/services/api/test_api.py index 900280f67d..8339f6e4af 100644 --- a/tests/services/api/test_api.py +++ b/tests/services/api/test_api.py @@ -1,5 +1,5 @@ import json -from typing import Awaitable, Dict, List +from collections.abc import Awaitable from unittest import mock import pytest @@ -31,7 +31,7 @@ async def test_get_status(jp_fetch): class MockUser(User): - permissions: Dict[str, List[str]] + permissions: dict[str, list[str]] class MockIdentityProvider(IdentityProvider): diff --git a/tests/services/contents/test_manager.py b/tests/services/contents/test_manager.py index be79011be8..5c3e2eca50 100644 --- a/tests/services/contents/test_manager.py +++ b/tests/services/contents/test_manager.py @@ -3,7 +3,7 @@ import sys import time from itertools import combinations -from typing import Dict, Optional, Tuple +from typing import Optional from unittest.mock import patch import pytest @@ -112,7 +112,7 @@ def add_invalid_cell(notebook): async def prepare_notebook( jp_contents_manager: FileContentsManager, make_invalid: Optional[bool] = False -) -> Tuple[Dict, str]: +) -> tuple[dict, str]: cm = jp_contents_manager model = await ensure_async(cm.new_untitled(type="notebook")) name = model["name"] @@ -983,9 +983,10 @@ async def test_nb_validation(jp_contents_manager): # successful methods and ensure that calls to the aliased "validate_nb" are # zero. Note that since patching side-effects the validation error case, we'll # skip call-count assertions for that portion of the test. - with patch("nbformat.validate") as mock_validate, patch( - "jupyter_server.services.contents.manager.validate_nb" - ) as mock_validate_nb: + with ( + patch("nbformat.validate") as mock_validate, + patch("jupyter_server.services.contents.manager.validate_nb") as mock_validate_nb, + ): # Valid notebook, save, then get model = await ensure_async(cm.save(model, path)) assert "message" not in model diff --git a/tests/services/events/mock_event.yaml b/tests/services/events/mock_event.yaml index dabaa23db5..bf73915fb0 100644 --- a/tests/services/events/mock_event.yaml +++ b/tests/services/events/mock_event.yaml @@ -1,5 +1,5 @@ $id: http://event.mock.jupyter.org/message -version: 1 +version: "1" title: Message description: | Emit a message diff --git a/tests/services/events/mockextension/mock_extension_event.yaml b/tests/services/events/mockextension/mock_extension_event.yaml index b7c03d1a48..7354d6a094 100644 --- a/tests/services/events/mockextension/mock_extension_event.yaml +++ b/tests/services/events/mockextension/mock_extension_event.yaml @@ -1,5 +1,5 @@ $id: http://event.mockextension.jupyter.org/message -version: 1 +version: "1" title: Message description: | Emit a message diff --git a/tests/services/events/test_api.py b/tests/services/events/test_api.py index 49599e8380..40ad8b137b 100644 --- a/tests/services/events/test_api.py +++ b/tests/services/events/test_api.py @@ -45,7 +45,7 @@ async def test_subscribe_websocket(event_logger, jp_ws_fetch): payload_1 = """\ { "schema_id": "http://event.mock.jupyter.org/message", - "version": 1, + "version": "1", "data": { "event_message": "Hello, world!" }, @@ -56,7 +56,7 @@ async def test_subscribe_websocket(event_logger, jp_ws_fetch): payload_2 = """\ { "schema_id": "http://event.mock.jupyter.org/message", - "version": 1, + "version": "1", "data": { "event_message": "Hello, world!" } @@ -92,7 +92,7 @@ async def test_post_event(jp_fetch, event_logger_sink, payload): payload_4 = """\ { - "version": 1, + "version": "1", "data": { "event_message": "Hello, world!" } @@ -102,14 +102,14 @@ async def test_post_event(jp_fetch, event_logger_sink, payload): payload_5 = """\ { "schema_id": "http://event.mock.jupyter.org/message", - "version": 1 + "version": "1" } """ payload_6 = """\ { "schema_id": "event.mock.jupyter.org/message", - "version": 1, + "version": "1", "data": { "event_message": "Hello, world!" }, @@ -120,7 +120,7 @@ async def test_post_event(jp_fetch, event_logger_sink, payload): payload_7 = """\ { "schema_id": "http://event.mock.jupyter.org/UNREGISTERED-SCHEMA", - "version": 1, + "version": "1", "data": { "event_message": "Hello, world!" } @@ -130,7 +130,7 @@ async def test_post_event(jp_fetch, event_logger_sink, payload): payload_8 = """\ { "schema_id": "http://event.mock.jupyter.org/message", - "version": 1, + "version": "1", "data": { "message": "Hello, world!" } diff --git a/tests/test_gateway.py b/tests/test_gateway.py index 569268d833..00aa64f111 100644 --- a/tests/test_gateway.py +++ b/tests/test_gateway.py @@ -10,7 +10,7 @@ from http.cookies import SimpleCookie from io import BytesIO from queue import Empty -from typing import Any, Dict, Union +from typing import Any, Union from unittest.mock import MagicMock, patch import pytest @@ -74,7 +74,7 @@ def generate_kernelspec(name): # # This is used to simulate inconsistency in list results from the Gateway server # due to issues like race conditions, bugs, etc. -omitted_kernels: Dict[str, bool] = {} +omitted_kernels: dict[str, bool] = {} def generate_model(name): diff --git a/tests/test_serverapp.py b/tests/test_serverapp.py index 91fa33230c..f836d1b2f8 100644 --- a/tests/test_serverapp.py +++ b/tests/test_serverapp.py @@ -154,8 +154,9 @@ async def test_generate_config(tmp_path, jp_configurable_serverapp): def test_server_password(tmp_path, jp_configurable_serverapp): password = "secret" - with patch.dict("os.environ", {"JUPYTER_CONFIG_DIR": str(tmp_path)}), patch.object( - getpass, "getpass", return_value=password + with ( + patch.dict("os.environ", {"JUPYTER_CONFIG_DIR": str(tmp_path)}), + patch.object(getpass, "getpass", return_value=password), ): app = JupyterPasswordApp(log_level=logging.ERROR) app.initialize([])