Skip to content

Commit

Permalink
Merge branch 'jupyter-server:main' into cors_files
Browse files Browse the repository at this point in the history
  • Loading branch information
gogasca authored Jan 10, 2025
2 parents d75bf80 + 952782b commit 115a973
Show file tree
Hide file tree
Showing 47 changed files with 858 additions and 163 deletions.
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
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.4
rev: 0.28.6
hooks:
- id: check-github-workflows

Expand Down Expand Up @@ -52,7 +52,7 @@ repos:
- id: rst-inline-touching-normal

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

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.7
rev: v0.5.0
hooks:
- id: ruff
types_or: [python, jupyter]
Expand Down
126 changes: 124 additions & 2 deletions CHANGELOG.md

Large diffs are not rendered by default.

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: 2 additions & 1 deletion examples/simple/simple_ext1/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def get(self):
self.log.info(f"Extension Name in {self.name} Default Handler: {self.name}")
# A method for getting the url to static files (prefixed with /static/<name>).
self.log.info(
"Static URL for / in simple_ext1 Default Handler: {}".format(self.static_url(path="/"))
"Static URL for / in simple_ext1 Default Handler: %s",
self.static_url(path="/"),
)
self.write("<h1>Hello Simple 1 - I am the default...</h1>")
self.write(f"Config in {self.name} Default Handler: {self.config}")
Expand Down
5 changes: 2 additions & 3 deletions jupyter_server/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
"""

import re
from typing import List

# Version string must appear intact for automatic versioning
__version__ = "2.15.0.dev0"
__version__ = "2.16.0.dev0"

# Build up version_info tuple for backwards compatibility
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
match = re.match(pattern, __version__)
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
22 changes: 19 additions & 3 deletions jupyter_server/extension/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from logging import Logger
from typing import TYPE_CHECKING, Any, cast

from jinja2 import Template
from jinja2.exceptions import TemplateNotFound

from jupyter_server.base.handlers import FileFindHandler
Expand All @@ -21,13 +22,14 @@ class ExtensionHandlerJinjaMixin:
template rendering.
"""

def get_template(self, name: str) -> str:
def get_template(self, name: str) -> Template:
"""Return the jinja template object for a given name"""
try:
env = f"{self.name}_jinja2_env" # type:ignore[attr-defined]
return cast(str, self.settings[env].get_template(name)) # type:ignore[attr-defined]
template = cast(Template, self.settings[env].get_template(name)) # type:ignore[attr-defined]
return template
except TemplateNotFound:
return cast(str, super().get_template(name)) # type:ignore[misc]
return cast(Template, super().get_template(name)) # type:ignore[misc]


class ExtensionHandlerMixin:
Expand Down Expand Up @@ -81,6 +83,20 @@ def server_config(self) -> Config:
def base_url(self) -> str:
return cast(str, self.settings.get("base_url", "/"))

def render_template(self, name: str, **ns) -> str:
"""Override render template to handle static_paths
If render_template is called with a template from the base environment
(e.g. default error pages)
make sure our extension-specific static_url is _not_ used.
"""
template = cast(Template, self.get_template(name)) # type:ignore[attr-defined]
ns.update(self.template_namespace) # type:ignore[attr-defined]
if template.environment is self.settings["jinja2_env"]:
# default template environment, use default static_url
ns["static_url"] = super().static_url # type:ignore[misc]
return cast(str, template.render(**ns))

@property
def static_url_prefix(self) -> str:
return self.extensionapp.static_url_prefix
Expand Down
16 changes: 7 additions & 9 deletions jupyter_server/extension/serverextension.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def toggle_server_extension(self, import_name: str) -> None:
# If successful, let's log.
self.log.info(f" - Extension successfully {self._toggle_post_message}.")
except Exception as err:
self.log.info(f" {RED_X} Validation failed: {err}")
self.log.error(f" {RED_X} Validation failed: {err}")

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

for option in configurations:
config_dir = _get_config_dir(**option)
self.log.info(f"Config dir: {config_dir}")
print(f"Config dir: {config_dir}")
write_dir = "jupyter_server_config.d"
config_manager = ExtensionConfigManager(
read_config_path=[config_dir],
Expand All @@ -345,20 +345,18 @@ def list_server_extensions(self) -> None:
jpserver_extensions = config_manager.get_jpserver_extensions()
for name, enabled in jpserver_extensions.items():
# Attempt to get extension metadata
self.log.info(f" {name} {GREEN_ENABLED if enabled else RED_DISABLED}")
print(f" {name} {GREEN_ENABLED if enabled else RED_DISABLED}")
try:
self.log.info(f" - Validating {name}...")
print(f" - Validating {name}...")
extension = ExtensionPackage(name=name, enabled=enabled)
if not extension.validate():
msg = "validation failed"
raise ValueError(msg)
version = extension.version
self.log.info(f" {name} {version} {GREEN_OK}")
print(f" {name} {version} {GREEN_OK}")
except Exception as err:
exc_info = False
if int(self.log_level) <= logging.DEBUG: # type:ignore[call-overload]
exc_info = True
self.log.warning(f" {RED_X} {err}", exc_info=exc_info)
self.log.debug("", exc_info=True)
print(f" {RED_X} {err}")
# Add a blank line between paths.
self.log.info("")

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
2 changes: 2 additions & 0 deletions jupyter_server/gateway/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ async def connect(self):
url_escape(self.kernel_id),
"channels",
)
if self.session_id:
ws_url += f"?session_id={url_escape(self.session_id)}"
self.log.info(f"Connecting to {ws_url}")
kwargs: dict[str, Any] = {}
kwargs = GatewayClient.instance().load_connection_args(**kwargs)
Expand Down
14 changes: 8 additions & 6 deletions jupyter_server/gateway/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,9 +632,10 @@ async def get_msg(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
timeout = kwargs.get("timeout", 1)
msg = await self._async_get(timeout=timeout)
self.log.debug(
"Received message on channel: {}, msg_id: {}, msg_type: {}".format(
self.channel_name, msg["msg_id"], msg["msg_type"] if msg else "null"
)
"Received message on channel: %s, msg_id: %s, msg_type: %s",
self.channel_name,
msg["msg_id"],
msg["msg_type"] if msg else "null",
)
self.task_done()
return cast("dict[str, Any]", msg)
Expand All @@ -643,9 +644,10 @@ def send(self, msg: dict[str, Any]) -> None:
"""Send a message to the queue."""
message = json.dumps(msg, default=ChannelQueue.serialize_datetime).replace("</", "<\\/")
self.log.debug(
"Sending message on channel: {}, msg_id: {}, msg_type: {}".format(
self.channel_name, msg["msg_id"], msg["msg_type"] if msg else "null"
)
"Sending message on channel: %s, msg_id: %s, msg_type: %s",
self.channel_name,
msg["msg_id"],
msg["msg_type"] if msg else "null",
)
self.channel_socket.send(message)

Expand Down
8 changes: 6 additions & 2 deletions jupyter_server/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ def _scrub_uri(uri: str) -> str:
return uri


def log_request(handler):
def log_request(handler, record_prometheus_metrics=True):
"""log a bit more information about each request than tornado's default
- move static file get success to debug-level (reduces noise)
- get proxied IP instead of proxy IP
- log referer for redirect and failed requests
- log user-agent for failed requests
if record_prometheus_metrics is true, will record a histogram prometheus
metric (http_request_duration_seconds) for each request handler
"""
status = handler.get_status()
request = handler.request
Expand Down Expand Up @@ -97,4 +100,5 @@ def log_request(handler):
headers[header] = request.headers[header]
log_method(json.dumps(headers, indent=2))
log_method(msg.format(**ns))
prometheus_log_method(handler)
if record_prometheus_metrics:
prometheus_log_method(handler)
Loading

0 comments on commit 115a973

Please sign in to comment.