Skip to content

Commit 115a973

Browse files
authored
Merge branch 'jupyter-server:main' into cors_files
2 parents d75bf80 + 952782b commit 115a973

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+858
-163
lines changed

.github/workflows/python-tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ jobs:
1818
fail-fast: false
1919
matrix:
2020
os: [ubuntu-latest, windows-latest, macos-latest]
21-
python-version: ["3.8", "3.11"]
21+
python-version: ["3.9", "3.11", "3.12"]
2222
include:
2323
- os: windows-latest
2424
python-version: "3.9"
2525
- os: ubuntu-latest
26-
python-version: "pypy-3.8"
26+
python-version: "pypy-3.9"
2727
- os: macos-latest
2828
python-version: "3.10"
2929
- os: ubuntu-latest
@@ -180,7 +180,7 @@ jobs:
180180
fail-fast: false
181181
matrix:
182182
os: [ubuntu-latest]
183-
python-version: ["3.8", "3.9", "3.10", "3.11"]
183+
python-version: ["3.9", "3.10", "3.11", "3.12"]
184184
steps:
185185
- uses: actions/checkout@v4
186186
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
@@ -194,7 +194,7 @@ jobs:
194194
- uses: actions/checkout@v4
195195
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
196196
with:
197-
python_version: "pypy-3.8"
197+
python_version: "pypy-3.9"
198198
- name: Run the tests
199199
run: hatch -v run test:nowarn --integration_tests=true
200200

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ repos:
2121
- id: trailing-whitespace
2222

2323
- repo: https://github.com/python-jsonschema/check-jsonschema
24-
rev: 0.28.4
24+
rev: 0.28.6
2525
hooks:
2626
- id: check-github-workflows
2727

@@ -52,7 +52,7 @@ repos:
5252
- id: rst-inline-touching-normal
5353

5454
- repo: https://github.com/pre-commit/mirrors-mypy
55-
rev: "v1.10.0"
55+
rev: "v1.10.1"
5656
hooks:
5757
- id: mypy
5858
files: jupyter_server
@@ -61,7 +61,7 @@ repos:
6161
["traitlets>=5.13", "jupyter_core>=5.5", "jupyter_client>=8.5"]
6262

6363
- repo: https://github.com/astral-sh/ruff-pre-commit
64-
rev: v0.4.7
64+
rev: v0.5.0
6565
hooks:
6666
- id: ruff
6767
types_or: [python, jupyter]

CHANGELOG.md

Lines changed: 124 additions & 2 deletions
Large diffs are not rendered by default.

examples/simple/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ build-backend = "hatchling.build"
66
name = "jupyter-server-example"
77
description = "Jupyter Server Example"
88
readme = "README.md"
9-
license = ""
10-
requires-python = ">=3.8"
9+
license = "MIT"
10+
requires-python = ">=3.9"
1111
dependencies = [
1212
"jinja2",
1313
"jupyter_server",

examples/simple/simple_ext1/handlers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ def get(self):
1818
self.log.info(f"Extension Name in {self.name} Default Handler: {self.name}")
1919
# A method for getting the url to static files (prefixed with /static/<name>).
2020
self.log.info(
21-
"Static URL for / in simple_ext1 Default Handler: {}".format(self.static_url(path="/"))
21+
"Static URL for / in simple_ext1 Default Handler: %s",
22+
self.static_url(path="/"),
2223
)
2324
self.write("<h1>Hello Simple 1 - I am the default...</h1>")
2425
self.write(f"Config in {self.name} Default Handler: {self.config}")

jupyter_server/_version.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44
"""
55

66
import re
7-
from typing import List
87

98
# Version string must appear intact for automatic versioning
10-
__version__ = "2.15.0.dev0"
9+
__version__ = "2.16.0.dev0"
1110

1211
# Build up version_info tuple for backwards compatibility
1312
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
1413
match = re.match(pattern, __version__)
1514
assert match is not None
16-
parts: List[object] = [int(match[part]) for part in ["major", "minor", "patch"]]
15+
parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]]
1716
if match["rest"]:
1817
parts.append(match["rest"])
1918
version_info = tuple(parts)

jupyter_server/auth/authorizer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@
1010
# Distributed under the terms of the Modified BSD License.
1111
from __future__ import annotations
1212

13-
from typing import TYPE_CHECKING, Awaitable
13+
from typing import TYPE_CHECKING
1414

1515
from traitlets import Instance
1616
from traitlets.config import LoggingConfigurable
1717

1818
from .identity import IdentityProvider, User
1919

2020
if TYPE_CHECKING:
21+
from collections.abc import Awaitable
22+
2123
from jupyter_server.base.handlers import JupyterHandler
2224

2325

jupyter_server/base/call_context.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Distributed under the terms of the Modified BSD License.
44

55
from contextvars import Context, ContextVar, copy_context
6-
from typing import Any, Dict, List
6+
from typing import Any
77

88

99
class CallContext:
@@ -22,7 +22,7 @@ class CallContext:
2222
# easier management over maintaining a set of ContextVar instances, since the Context is a
2323
# map of ContextVar instances to their values, and the "name" is no longer a lookup key.
2424
_NAME_VALUE_MAP = "_name_value_map"
25-
_name_value_map: ContextVar[Dict[str, Any]] = ContextVar(_NAME_VALUE_MAP)
25+
_name_value_map: ContextVar[dict[str, Any]] = ContextVar(_NAME_VALUE_MAP)
2626

2727
@classmethod
2828
def get(cls, name: str) -> Any:
@@ -65,7 +65,7 @@ def set(cls, name: str, value: Any) -> None:
6565
name_value_map[name] = value
6666

6767
@classmethod
68-
def context_variable_names(cls) -> List[str]:
68+
def context_variable_names(cls) -> list[str]:
6969
"""Returns a list of variable names set for this call context.
7070
7171
Returns
@@ -77,7 +77,7 @@ def context_variable_names(cls) -> List[str]:
7777
return list(name_value_map.keys())
7878

7979
@classmethod
80-
def _get_map(cls) -> Dict[str, Any]:
80+
def _get_map(cls) -> dict[str, Any]:
8181
"""Get the map of names to their values from the _NAME_VALUE_MAP context var.
8282
8383
If the map does not exist in the current context, an empty map is created and returned.

jupyter_server/base/handlers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
import re
1414
import types
1515
import warnings
16+
from collections.abc import Awaitable, Coroutine, Sequence
1617
from http.client import responses
1718
from logging import Logger
18-
from typing import TYPE_CHECKING, Any, Awaitable, Coroutine, Sequence, cast
19+
from typing import TYPE_CHECKING, Any, cast
1920
from urllib.parse import urlparse
2021

2122
import prometheus_client

jupyter_server/config_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from traitlets.config import LoggingConfigurable
1515
from traitlets.traitlets import Bool, Unicode
1616

17-
StrDict = t.Dict[str, t.Any]
17+
StrDict = dict[str, t.Any]
1818

1919

2020
def recursive_update(target: StrDict, new: StrDict) -> None:

jupyter_server/event_schemas/contents_service/v1.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"$id": https://events.jupyter.org/jupyter_server/contents_service/v1
2-
version: 1
2+
version: "1"
33
title: Contents Manager activities
44
personal-data: true
55
description: |

jupyter_server/event_schemas/gateway_client/v1.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"$id": https://events.jupyter.org/jupyter_server/gateway_client/v1
2-
version: 1
2+
version: "1"
33
title: Gateway Client activities.
44
personal-data: true
55
description: |

jupyter_server/event_schemas/kernel_actions/v1.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"$id": https://events.jupyter.org/jupyter_server/kernel_actions/v1
2-
version: 1
2+
version: "1"
33
title: Kernel Manager activities
44
personal-data: true
55
description: |

jupyter_server/extension/handler.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from logging import Logger
66
from typing import TYPE_CHECKING, Any, cast
77

8+
from jinja2 import Template
89
from jinja2.exceptions import TemplateNotFound
910

1011
from jupyter_server.base.handlers import FileFindHandler
@@ -21,13 +22,14 @@ class ExtensionHandlerJinjaMixin:
2122
template rendering.
2223
"""
2324

24-
def get_template(self, name: str) -> str:
25+
def get_template(self, name: str) -> Template:
2526
"""Return the jinja template object for a given name"""
2627
try:
2728
env = f"{self.name}_jinja2_env" # type:ignore[attr-defined]
28-
return cast(str, self.settings[env].get_template(name)) # type:ignore[attr-defined]
29+
template = cast(Template, self.settings[env].get_template(name)) # type:ignore[attr-defined]
30+
return template
2931
except TemplateNotFound:
30-
return cast(str, super().get_template(name)) # type:ignore[misc]
32+
return cast(Template, super().get_template(name)) # type:ignore[misc]
3133

3234

3335
class ExtensionHandlerMixin:
@@ -81,6 +83,20 @@ def server_config(self) -> Config:
8183
def base_url(self) -> str:
8284
return cast(str, self.settings.get("base_url", "/"))
8385

86+
def render_template(self, name: str, **ns) -> str:
87+
"""Override render template to handle static_paths
88+
89+
If render_template is called with a template from the base environment
90+
(e.g. default error pages)
91+
make sure our extension-specific static_url is _not_ used.
92+
"""
93+
template = cast(Template, self.get_template(name)) # type:ignore[attr-defined]
94+
ns.update(self.template_namespace) # type:ignore[attr-defined]
95+
if template.environment is self.settings["jinja2_env"]:
96+
# default template environment, use default static_url
97+
ns["static_url"] = super().static_url # type:ignore[misc]
98+
return cast(str, template.render(**ns))
99+
84100
@property
85101
def static_url_prefix(self) -> str:
86102
return self.extensionapp.static_url_prefix

jupyter_server/extension/serverextension.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def toggle_server_extension(self, import_name: str) -> None:
276276
# If successful, let's log.
277277
self.log.info(f" - Extension successfully {self._toggle_post_message}.")
278278
except Exception as err:
279-
self.log.info(f" {RED_X} Validation failed: {err}")
279+
self.log.error(f" {RED_X} Validation failed: {err}")
280280

281281
def start(self) -> None:
282282
"""Perform the App's actions as configured"""
@@ -336,7 +336,7 @@ def list_server_extensions(self) -> None:
336336

337337
for option in configurations:
338338
config_dir = _get_config_dir(**option)
339-
self.log.info(f"Config dir: {config_dir}")
339+
print(f"Config dir: {config_dir}")
340340
write_dir = "jupyter_server_config.d"
341341
config_manager = ExtensionConfigManager(
342342
read_config_path=[config_dir],
@@ -345,20 +345,18 @@ def list_server_extensions(self) -> None:
345345
jpserver_extensions = config_manager.get_jpserver_extensions()
346346
for name, enabled in jpserver_extensions.items():
347347
# Attempt to get extension metadata
348-
self.log.info(f" {name} {GREEN_ENABLED if enabled else RED_DISABLED}")
348+
print(f" {name} {GREEN_ENABLED if enabled else RED_DISABLED}")
349349
try:
350-
self.log.info(f" - Validating {name}...")
350+
print(f" - Validating {name}...")
351351
extension = ExtensionPackage(name=name, enabled=enabled)
352352
if not extension.validate():
353353
msg = "validation failed"
354354
raise ValueError(msg)
355355
version = extension.version
356-
self.log.info(f" {name} {version} {GREEN_OK}")
356+
print(f" {name} {version} {GREEN_OK}")
357357
except Exception as err:
358-
exc_info = False
359-
if int(self.log_level) <= logging.DEBUG: # type:ignore[call-overload]
360-
exc_info = True
361-
self.log.warning(f" {RED_X} {err}", exc_info=exc_info)
358+
self.log.debug("", exc_info=True)
359+
print(f" {RED_X} {err}")
362360
# Add a blank line between paths.
363361
self.log.info("")
364362

jupyter_server/files/handlers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@
66

77
import mimetypes
88
from base64 import decodebytes
9-
from typing import Awaitable
9+
from typing import TYPE_CHECKING
1010

1111
from jupyter_core.utils import ensure_async
1212
from tornado import web
1313

1414
from jupyter_server.auth.decorator import authorized
1515
from jupyter_server.base.handlers import JupyterHandler
1616

17+
if TYPE_CHECKING:
18+
from collections.abc import Awaitable
19+
1720
AUTH_RESOURCE = "contents"
1821

1922

jupyter_server/gateway/connections.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ async def connect(self):
4747
url_escape(self.kernel_id),
4848
"channels",
4949
)
50+
if self.session_id:
51+
ws_url += f"?session_id={url_escape(self.session_id)}"
5052
self.log.info(f"Connecting to {ws_url}")
5153
kwargs: dict[str, Any] = {}
5254
kwargs = GatewayClient.instance().load_connection_args(**kwargs)

jupyter_server/gateway/managers.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -632,9 +632,10 @@ async def get_msg(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
632632
timeout = kwargs.get("timeout", 1)
633633
msg = await self._async_get(timeout=timeout)
634634
self.log.debug(
635-
"Received message on channel: {}, msg_id: {}, msg_type: {}".format(
636-
self.channel_name, msg["msg_id"], msg["msg_type"] if msg else "null"
637-
)
635+
"Received message on channel: %s, msg_id: %s, msg_type: %s",
636+
self.channel_name,
637+
msg["msg_id"],
638+
msg["msg_type"] if msg else "null",
638639
)
639640
self.task_done()
640641
return cast("dict[str, Any]", msg)
@@ -643,9 +644,10 @@ def send(self, msg: dict[str, Any]) -> None:
643644
"""Send a message to the queue."""
644645
message = json.dumps(msg, default=ChannelQueue.serialize_datetime).replace("</", "<\\/")
645646
self.log.debug(
646-
"Sending message on channel: {}, msg_id: {}, msg_type: {}".format(
647-
self.channel_name, msg["msg_id"], msg["msg_type"] if msg else "null"
648-
)
647+
"Sending message on channel: %s, msg_id: %s, msg_type: %s",
648+
self.channel_name,
649+
msg["msg_id"],
650+
msg["msg_type"] if msg else "null",
649651
)
650652
self.channel_socket.send(message)
651653

jupyter_server/log.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ def _scrub_uri(uri: str) -> str:
4141
return uri
4242

4343

44-
def log_request(handler):
44+
def log_request(handler, record_prometheus_metrics=True):
4545
"""log a bit more information about each request than tornado's default
4646
4747
- move static file get success to debug-level (reduces noise)
4848
- get proxied IP instead of proxy IP
4949
- log referer for redirect and failed requests
5050
- log user-agent for failed requests
51+
52+
if record_prometheus_metrics is true, will record a histogram prometheus
53+
metric (http_request_duration_seconds) for each request handler
5154
"""
5255
status = handler.get_status()
5356
request = handler.request
@@ -97,4 +100,5 @@ def log_request(handler):
97100
headers[header] = request.headers[header]
98101
log_method(json.dumps(headers, indent=2))
99102
log_method(msg.format(**ns))
100-
prometheus_log_method(handler)
103+
if record_prometheus_metrics:
104+
prometheus_log_method(handler)

0 commit comments

Comments
 (0)