Skip to content

Commit 3825bac

Browse files
authored
Merge branch 'jupyter-server:main' into feat-gateway-support-nbmodel
2 parents 3763b27 + 04dd3e7 commit 3825bac

File tree

12 files changed

+408
-13
lines changed

12 files changed

+408
-13
lines changed

CHANGELOG.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,45 @@ All notable changes to this project will be documented in this file.
44

55
<!-- <START NEW CHANGELOG ENTRY> -->
66

7+
## 2.16.0
8+
9+
([Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.15.0...fa41b3866a57ad51f74a15684183d833c0206f9c))
10+
11+
### Enhancements made
12+
13+
- If ServerApp.ip is ipv6 use [::1] as local_url [#1495](https://github.com/jupyter-server/jupyter_server/pull/1495) ([@manics](https://github.com/manics))
14+
- Don't hide .so,.dylib files by default [#1457](https://github.com/jupyter-server/jupyter_server/pull/1457) ([@nokados](https://github.com/nokados))
15+
- Add async start hook to ExtensionApp API [#1417](https://github.com/jupyter-server/jupyter_server/pull/1417) ([@Zsailer](https://github.com/Zsailer))
16+
17+
### Bugs fixed
18+
19+
- Update meetings notes link and zoom link [#1517](https://github.com/jupyter-server/jupyter_server/pull/1517) ([@krassowski](https://github.com/krassowski))
20+
- Fallback to direct write for readonly dirs and use temp path for checkpoints [#1516](https://github.com/jupyter-server/jupyter_server/pull/1516) ([@Darshan808](https://github.com/Darshan808))
21+
- Check file permissions before making tmp file [#1513](https://github.com/jupyter-server/jupyter_server/pull/1513) ([@RRosio](https://github.com/RRosio))
22+
- Validate extension name before toggling through CLI [#1509](https://github.com/jupyter-server/jupyter_server/pull/1509) ([@Darshan808](https://github.com/Darshan808))
23+
- Fix for #1479 : Incorrect usage of i18n format [#1500](https://github.com/jupyter-server/jupyter_server/pull/1500) ([@kjayan](https://github.com/kjayan))
24+
- Fix handling of missing parent header in record activity [#1498](https://github.com/jupyter-server/jupyter_server/pull/1498) ([@davidbrochart](https://github.com/davidbrochart))
25+
- display_url: Don't duplicate public_url and local_url if they are the same [#1494](https://github.com/jupyter-server/jupyter_server/pull/1494) ([@manics](https://github.com/manics))
26+
27+
### Maintenance and upkeep improvements
28+
29+
- tests: install test-functional requirements for Jupytext downstream tests [#1510](https://github.com/jupyter-server/jupyter_server/pull/1510) ([@MaicoTimmerman](https://github.com/MaicoTimmerman))
30+
31+
### Documentation improvements
32+
33+
- Update websocket-protocols documentation to reflect implementation [#1508](https://github.com/jupyter-server/jupyter_server/pull/1508) ([@ark-1](https://github.com/ark-1))
34+
- Update Security Section in the Jupyter Server Documentation [#1505](https://github.com/jupyter-server/jupyter_server/pull/1505) ([@kjayan](https://github.com/kjayan))
35+
- Update Contribution Page for Jupyter Server [#1499](https://github.com/jupyter-server/jupyter_server/pull/1499) ([@kjayan](https://github.com/kjayan))
36+
- Fix typo in metric description [#1486](https://github.com/jupyter-server/jupyter_server/pull/1486) ([@yuvipanda](https://github.com/yuvipanda))
37+
38+
### Contributors to this release
39+
40+
([GitHub contributors page for this release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2024-12-20&to=2025-05-12&type=c))
41+
42+
[@ark-1](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aark-1+updated%3A2024-12-20..2025-05-12&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Abollwyvl+updated%3A2024-12-20..2025-05-12&type=Issues) | [@Darshan808](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ADarshan808+updated%3A2024-12-20..2025-05-12&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Adavidbrochart+updated%3A2024-12-20..2025-05-12&type=Issues) | [@kjayan](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akjayan+updated%3A2024-12-20..2025-05-12&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2024-12-20..2025-05-12&type=Issues) | [@MaicoTimmerman](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AMaicoTimmerman+updated%3A2024-12-20..2025-05-12&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amanics+updated%3A2024-12-20..2025-05-12&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aminrk+updated%3A2024-12-20..2025-05-12&type=Issues) | [@nokados](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Anokados+updated%3A2024-12-20..2025-05-12&type=Issues) | [@RRosio](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ARRosio+updated%3A2024-12-20..2025-05-12&type=Issues) | [@vidartf](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Avidartf+updated%3A2024-12-20..2025-05-12&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ayuvipanda+updated%3A2024-12-20..2025-05-12&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2024-12-20..2025-05-12&type=Issues)
43+
44+
<!-- <END NEW CHANGELOG ENTRY> -->
45+
746
## 2.15.0
847

948
([Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.9.1...f23b3392624001c8fba6623e19f526a98b4a07ba))
@@ -100,8 +139,6 @@ All notable changes to this project will be documented in this file.
100139

101140
[@afshin](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aafshin+updated%3A2023-10-25..2024-12-20&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2023-10-25..2024-12-20&type=Issues) | [@bloomsa](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Abloomsa+updated%3A2023-10-25..2024-12-20&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Abollwyvl+updated%3A2023-10-25..2024-12-20&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ACarreau+updated%3A2023-10-25..2024-12-20&type=Issues) | [@cjwatson](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acjwatson+updated%3A2023-10-25..2024-12-20&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Adavidbrochart+updated%3A2023-10-25..2024-12-20&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Adependabot+updated%3A2023-10-25..2024-12-20&type=Issues) | [@epignot](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aepignot+updated%3A2023-10-25..2024-12-20&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Afcollonval+updated%3A2023-10-25..2024-12-20&type=Issues) | [@gogasca](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Agogasca+updated%3A2023-10-25..2024-12-20&type=Issues) | [@hansepac](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ahansepac+updated%3A2023-10-25..2024-12-20&type=Issues) | [@holzman](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aholzman+updated%3A2023-10-25..2024-12-20&type=Issues) | [@IITII](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AIITII+updated%3A2023-10-25..2024-12-20&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2023-10-25..2024-12-20&type=Issues) | [@lresende](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Alresende+updated%3A2023-10-25..2024-12-20&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amanics+updated%3A2023-10-25..2024-12-20&type=Issues) | [@markypizz](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amarkypizz+updated%3A2023-10-25..2024-12-20&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aminrk+updated%3A2023-10-25..2024-12-20&type=Issues) | [@mwouts](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amwouts+updated%3A2023-10-25..2024-12-20&type=Issues) | [@ojarjur](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aojarjur+updated%3A2023-10-25..2024-12-20&type=Issues) | [@oliver-sanders](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aoliver-sanders+updated%3A2023-10-25..2024-12-20&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Apre-commit-ci+updated%3A2023-10-25..2024-12-20&type=Issues) | [@Timeroot](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ATimeroot+updated%3A2023-10-25..2024-12-20&type=Issues) | [@tornaria](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Atornaria+updated%3A2023-10-25..2024-12-20&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Awelcome+updated%3A2023-10-25..2024-12-20&type=Issues) | [@Wh1isper](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AWh1isper+updated%3A2023-10-25..2024-12-20&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ayuvipanda+updated%3A2023-10-25..2024-12-20&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2023-10-25..2024-12-20&type=Issues)
102141

103-
<!-- <END NEW CHANGELOG ENTRY> -->
104-
105142
## 2.14.2
106143

107144
([Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.14.1...b961d4eb499071c0c60e24f429c20d1e6a908a32))

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ If you are interested in contributing to the project, see [`CONTRIBUTING.rst`](C
4747
## Team Meetings and Roadmap
4848

4949
- When: Thursdays [8:00am, Pacific time](https://www.thetimezoneconverter.com/?t=8%3A00%20am&tz=San%20Francisco&)
50-
- Where: [Jovyan Zoom](https://zoom.us/my/jovyan?pwd=c0JZTHlNdS9Sek9vdzR3aTJ4SzFTQT09)
51-
- What: [Meeting notes](https://github.com/jupyter-server/team-compass/issues/45)
50+
- Where: [Jovyan Zoom](https://zoom.us/j/95228013874?pwd=Ep7HIk8t9JP6VToxt1Wj4P7K5PshC0.1)
51+
- What:
52+
- [Meeting notes](https://github.com/jupyter-server/team-compass/issues?q=is%3Aissue%20%20Meeting%20Notes%20)
53+
- [Agenda](https://hackmd.io/Wmz_wjrLRHuUbgWphjwRWw)
5254

5355
See our tentative [roadmap here](https://github.com/jupyter/jupyter_server/issues/127).
5456

jupyter_server/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77

88
# Version string must appear intact for automatic versioning
9-
__version__ = "2.16.0.dev0"
9+
__version__ = "2.17.0.dev0"
1010

1111
# Build up version_info tuple for backwards compatibility
1212
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"

jupyter_server/auth/identity.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from http.cookies import Morsel
2121

2222
from tornado import escape, httputil, web
23-
from traitlets import Bool, Dict, Type, Unicode, default
23+
from traitlets import Bool, Dict, Enum, List, TraitError, Type, Unicode, default, validate
2424
from traitlets.config import LoggingConfigurable
2525

2626
from jupyter_server.transutils import _i18n
@@ -31,6 +31,10 @@
3131
_non_alphanum = re.compile(r"[^A-Za-z0-9]")
3232

3333

34+
# Define the User properties that can be updated
35+
UpdatableField = t.Literal["name", "display_name", "initials", "avatar_url", "color"]
36+
37+
3438
@dataclass
3539
class User:
3640
"""Object representing a User
@@ -188,6 +192,14 @@ class IdentityProvider(LoggingConfigurable):
188192
help=_i18n("The logout handler class to use."),
189193
)
190194

195+
# Define the fields that can be updated
196+
updatable_fields = List(
197+
trait=Enum(list(t.get_args(UpdatableField))),
198+
default_value=["color"], # Default updatable field
199+
config=True,
200+
help=_i18n("List of fields in the User model that can be updated."),
201+
)
202+
191203
token_generated = False
192204

193205
@default("token")
@@ -207,6 +219,18 @@ def _token_default(self):
207219
self.token_generated = True
208220
return binascii.hexlify(os.urandom(24)).decode("ascii")
209221

222+
@validate("updatable_fields")
223+
def _validate_updatable_fields(self, proposal):
224+
"""Validate that all fields in updatable_fields are valid."""
225+
valid_updatable_fields = list(t.get_args(UpdatableField))
226+
invalid_fields = [
227+
field for field in proposal["value"] if field not in valid_updatable_fields
228+
]
229+
if invalid_fields:
230+
msg = f"Invalid fields in updatable_fields: {invalid_fields}"
231+
raise TraitError(msg)
232+
return proposal["value"]
233+
210234
need_token: bool | Bool[bool, t.Union[bool, int]] = Bool(True)
211235

212236
def get_user(self, handler: web.RequestHandler) -> User | None | t.Awaitable[User | None]:
@@ -269,6 +293,31 @@ async def _get_user(self, handler: web.RequestHandler) -> User | None:
269293

270294
return user
271295

296+
def update_user(
297+
self, handler: web.RequestHandler, user_data: dict[UpdatableField, str]
298+
) -> User:
299+
"""Update user information and persist the user model."""
300+
self.check_update(user_data)
301+
current_user = t.cast(User, handler.current_user)
302+
updated_user = self.update_user_model(current_user, user_data)
303+
self.persist_user_model(handler)
304+
return updated_user
305+
306+
def check_update(self, user_data: dict[UpdatableField, str]) -> None:
307+
"""Raises if some fields to update are not updatable."""
308+
for field in user_data:
309+
if field not in self.updatable_fields:
310+
msg = f"Field {field} is not updatable"
311+
raise ValueError(msg)
312+
313+
def update_user_model(self, current_user: User, user_data: dict[UpdatableField, str]) -> User:
314+
"""Update user information."""
315+
raise NotImplementedError
316+
317+
def persist_user_model(self, handler: web.RequestHandler) -> None:
318+
"""Persist the user model (i.e. a cookie)."""
319+
raise NotImplementedError
320+
272321
def identity_model(self, user: User) -> dict[str, t.Any]:
273322
"""Return a User as an Identity model"""
274323
# TODO: validate?
@@ -617,6 +666,16 @@ class PasswordIdentityProvider(IdentityProvider):
617666
def _need_token_default(self):
618667
return not bool(self.hashed_password)
619668

669+
@default("updatable_fields")
670+
def _default_updatable_fields(self):
671+
return [
672+
"name",
673+
"display_name",
674+
"initials",
675+
"avatar_url",
676+
"color",
677+
]
678+
620679
@property
621680
def login_available(self) -> bool:
622681
"""Whether a LoginHandler is needed - and therefore whether the login page should be displayed."""
@@ -627,6 +686,17 @@ def auth_enabled(self) -> bool:
627686
"""Return whether any auth is enabled"""
628687
return bool(self.hashed_password or self.token)
629688

689+
def update_user_model(self, current_user: User, user_data: dict[UpdatableField, str]) -> User:
690+
"""Update user information."""
691+
for field in self.updatable_fields:
692+
if field in user_data:
693+
setattr(current_user, field, user_data[field])
694+
return current_user
695+
696+
def persist_user_model(self, handler: web.RequestHandler) -> None:
697+
"""Persist the user model to a cookie."""
698+
self.set_login_cookie(handler, handler.current_user)
699+
630700
def passwd_check(self, password):
631701
"""Check password against our stored hashed password"""
632702
return passwd_check(self.hashed_password, password)

jupyter_server/extension/serverextension.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,23 @@ def toggle_server_extension(self, import_name: str) -> None:
259259
self.log.info(f"- Writing config: {config_dir}")
260260
# Validate the server extension.
261261
self.log.info(f" - Validating {import_name}...")
262+
config = extension_manager.config_manager
263+
enabled = False
264+
if config:
265+
jpserver_extensions = config.get_jpserver_extensions()
266+
if import_name not in jpserver_extensions:
267+
msg = (
268+
f"The module '{import_name}' could not be found. Are you "
269+
"sure the extension is installed?"
270+
)
271+
raise ValueError(msg)
272+
enabled = jpserver_extensions[import_name]
273+
262274
# Interface with the Extension Package and validate.
263-
extpkg = ExtensionPackage(name=import_name)
264-
extpkg.validate()
275+
extpkg = ExtensionPackage(name=import_name, enabled=enabled)
276+
if not extpkg.validate():
277+
msg = "validation failed"
278+
raise ValueError(msg)
265279
version = extpkg.version
266280
self.log.info(f" {import_name} {version} {GREEN_OK}")
267281

jupyter_server/serverapp.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2386,7 +2386,9 @@ def display_url(self) -> str:
23862386
"""Human readable string with URLs for interacting
23872387
with the running Jupyter Server
23882388
"""
2389-
url = self.public_url + "\n " + self.local_url
2389+
url = self.public_url
2390+
if self.public_url != self.local_url:
2391+
url = f"{url}\n {self.local_url}"
23902392
return url
23912393

23922394
@property

jupyter_server/services/api/handlers.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
# Distributed under the terms of the Modified BSD License.
55
import json
66
import os
7-
from typing import Any
7+
from typing import Any, cast
88

99
from jupyter_core.utils import ensure_async
1010
from tornado import web
1111

1212
from jupyter_server._tz import isoformat, utcfromtimestamp
1313
from jupyter_server.auth.decorator import authorized
14+
from jupyter_server.auth.identity import IdentityProvider, UpdatableField
1415

1516
from ...base.handlers import APIHandler, JupyterHandler
1617

@@ -70,7 +71,7 @@ async def get(self):
7071

7172

7273
class IdentityHandler(APIHandler):
73-
"""Get the current user's identity model"""
74+
"""Get or patch the current user's identity model"""
7475

7576
@web.authenticated
7677
async def get(self):
@@ -106,13 +107,38 @@ async def get(self):
106107
if authorized:
107108
allowed.append(action)
108109

110+
# Add permission to user to update their own identity
111+
permissions["updatable_fields"] = self.identity_provider.updatable_fields
112+
109113
identity: dict[str, Any] = self.identity_provider.identity_model(user)
110114
model = {
111115
"identity": identity,
112116
"permissions": permissions,
113117
}
114118
self.write(json.dumps(model))
115119

120+
@web.authenticated
121+
async def patch(self):
122+
"""Update user information."""
123+
user_data = cast(dict[UpdatableField, str], self.get_json_body())
124+
if not user_data:
125+
raise web.HTTPError(400, "Invalid or missing JSON body")
126+
127+
# Update user information
128+
identity_provider = self.settings["identity_provider"]
129+
if not isinstance(identity_provider, IdentityProvider):
130+
raise web.HTTPError(500, "Identity provider not configured properly")
131+
132+
try:
133+
updated_user = identity_provider.update_user(self, user_data)
134+
self.write(
135+
{"status": "success", "identity": identity_provider.identity_model(updated_user)}
136+
)
137+
except ValueError as e:
138+
raise web.HTTPError(400, str(e)) from e
139+
except NotImplementedError as e:
140+
raise web.HTTPError(501, str(e)) from e
141+
116142

117143
default_handlers = [
118144
(r"/api/spec.yaml", APISpecHandler),

jupyter_server/services/contents/filecheckpoints.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import shutil
7+
import tempfile
78

89
from anyio.to_thread import run_sync
910
from jupyter_core.utils import ensure_dir_exists
@@ -111,6 +112,10 @@ def checkpoint_path(self, checkpoint_id, path):
111112
filename = f"{basename}-{checkpoint_id}{ext}"
112113
os_path = self._get_os_path(path=parent)
113114
cp_dir = os.path.join(os_path, self.checkpoint_dir)
115+
# If parent directory isn't writable, use system temp
116+
if not os.access(os.path.dirname(cp_dir), os.W_OK):
117+
rel = os.path.relpath(os_path, start=self.root_dir)
118+
cp_dir = os.path.join(tempfile.gettempdir(), "jupyter_checkpoints", rel)
114119
with self.perm_to_403():
115120
ensure_dir_exists(cp_dir)
116121
cp_path = os.path.join(cp_dir, filename)

0 commit comments

Comments
 (0)