Skip to content

Handle newer jupyter_events wants string version, drop 3.8 #1481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions examples/simple/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions jupyter_server/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""

import re
from typing import List

# Version string must appear intact for automatic versioning
__version__ = "2.15.0.dev0"
Expand All @@ -13,7 +12,7 @@
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
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)
4 changes: 3 additions & 1 deletion jupyter_server/auth/authorizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
# 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

from .identity import IdentityProvider, User

if TYPE_CHECKING:
from collections.abc import Awaitable

from jupyter_server.base.handlers import JupyterHandler


Expand Down
8 changes: 4 additions & 4 deletions jupyter_server/base/call_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion jupyter_server/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/event_schemas/contents_service/v1.yaml
Original file line number Diff line number Diff line change
@@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/event_schemas/gateway_client/v1.yaml
Original file line number Diff line number Diff line change
@@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/event_schemas/kernel_actions/v1.yaml
Original file line number Diff line number Diff line change
@@ -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: |
Expand Down
5 changes: 4 additions & 1 deletion jupyter_server/files/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@

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

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"


Expand Down
4 changes: 1 addition & 3 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions jupyter_server/services/api/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/services/config/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions jupyter_server/services/contents/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
"""
Expand Down
8 changes: 4 additions & 4 deletions jupyter_server/services/events/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions jupyter_server/services/kernels/connection/abc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Any, List
from typing import Any


class KernelWebsocketConnectionABC(ABC):
Expand All @@ -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."""
6 changes: 3 additions & 3 deletions jupyter_server/services/kernels/connection/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions jupyter_server/services/sessions/sessionmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
19 changes: 7 additions & 12 deletions jupyter_server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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():
Expand Down
Loading
Loading