Skip to content

Commit c372c05

Browse files
authored
Revert "Make preferred_dir content manager trait (#983)"
This reverts commit a55bc58.
1 parent 2dd116f commit c372c05

File tree

7 files changed

+94
-134
lines changed

7 files changed

+94
-134
lines changed

jupyter_server/serverapp.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,11 +1647,22 @@ def _normalize_dir(self, value):
16471647
value = os.path.abspath(value)
16481648
return value
16491649

1650+
# Because the validation of preferred_dir depends on root_dir and validation
1651+
# occurs when the trait is loaded, there are times when we should defer the
1652+
# validation of preferred_dir (e.g., when preferred_dir is defined via CLI
1653+
# and root_dir is defined via a config file).
1654+
_defer_preferred_dir_validation = False
1655+
16501656
@validate("root_dir")
16511657
def _root_dir_validate(self, proposal):
16521658
value = self._normalize_dir(proposal["value"])
16531659
if not os.path.isdir(value):
16541660
raise TraitError(trans.gettext("No such directory: '%r'") % value)
1661+
1662+
if self._defer_preferred_dir_validation:
1663+
# If we're here, then preferred_dir is configured on the CLI and
1664+
# root_dir is configured in a file
1665+
self._preferred_dir_validation(self.preferred_dir, value)
16551666
return value
16561667

16571668
preferred_dir = Unicode(
@@ -1668,8 +1679,39 @@ def _preferred_dir_validate(self, proposal):
16681679
value = self._normalize_dir(proposal["value"])
16691680
if not os.path.isdir(value):
16701681
raise TraitError(trans.gettext("No such preferred dir: '%r'") % value)
1682+
1683+
# Before we validate against root_dir, check if this trait is defined on the CLI
1684+
# and root_dir is not. If that's the case, we'll defer it's further validation
1685+
# until root_dir is validated or the server is starting (the latter occurs when
1686+
# the default root_dir (cwd) is used).
1687+
cli_config = self.cli_config.get("ServerApp", {})
1688+
if "preferred_dir" in cli_config and "root_dir" not in cli_config:
1689+
self._defer_preferred_dir_validation = True
1690+
1691+
if not self._defer_preferred_dir_validation: # Validate now
1692+
self._preferred_dir_validation(value, self.root_dir)
16711693
return value
16721694

1695+
def _preferred_dir_validation(self, preferred_dir: str, root_dir: str) -> None:
1696+
"""Validate preferred dir relative to root_dir - preferred_dir must be equal or a subdir of root_dir"""
1697+
if not preferred_dir.startswith(root_dir):
1698+
raise TraitError(
1699+
trans.gettext(
1700+
"preferred_dir must be equal or a subdir of root_dir. preferred_dir: '%r' root_dir: '%r'"
1701+
)
1702+
% (preferred_dir, root_dir)
1703+
)
1704+
self._defer_preferred_dir_validation = False
1705+
1706+
@observe("root_dir")
1707+
def _root_dir_changed(self, change):
1708+
self._root_dir_set = True
1709+
if not self.preferred_dir.startswith(change["new"]):
1710+
self.log.warning(
1711+
trans.gettext("Value of preferred_dir updated to use value of root_dir")
1712+
)
1713+
self.preferred_dir = change["new"]
1714+
16731715
@observe("server_extensions")
16741716
def _update_server_extensions(self, change):
16751717
self.log.warning(_i18n("server_extensions is deprecated, use jpserver_extensions"))
@@ -1851,9 +1893,6 @@ def init_configurables(self):
18511893
parent=self,
18521894
log=self.log,
18531895
)
1854-
# Trigger a default/validation here explicitly while we still support the
1855-
# deprecated trait on ServerApp (FIXME remove when deprecation finalized)
1856-
self.contents_manager.preferred_dir
18571896
self.session_manager = self.session_manager_class(
18581897
parent=self,
18591898
log=self.log,
@@ -2478,6 +2517,10 @@ def initialize(
24782517
# Parse command line, load ServerApp config files,
24792518
# and update ServerApp config.
24802519
super().initialize(argv=argv)
2520+
if self._defer_preferred_dir_validation:
2521+
# If we're here, then preferred_dir is configured on the CLI and
2522+
# root_dir has the default value (cwd)
2523+
self._preferred_dir_validation(self.preferred_dir, self.root_dir)
24812524
if self._dispatching:
24822525
return
24832526
# initialize io loop as early as possible,

jupyter_server/services/contents/fileio.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,6 @@ def _get_os_path(self, path):
254254
404: if path is outside root
255255
"""
256256
root = os.path.abspath(self.root_dir) # type:ignore
257-
# to_os_path is not safe if path starts with a drive, since os.path.join discards first part
258-
if os.path.splitdrive(path)[0]:
259-
raise HTTPError(404, "%s is not a relative API path" % path)
260257
os_path = to_os_path(path, root)
261258
if not (os.path.abspath(os_path) + os.path.sep).startswith(root):
262259
raise HTTPError(404, "%s is outside root contents directory" % path)

jupyter_server/services/contents/filemanager.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import shutil
88
import stat
99
import sys
10-
import warnings
1110
from datetime import datetime
12-
from pathlib import Path
1311

1412
import nbformat
1513
from anyio.to_thread import run_sync
@@ -21,7 +19,6 @@
2119
from jupyter_server import _tz as tz
2220
from jupyter_server.base.handlers import AuthenticatedFileHandler
2321
from jupyter_server.transutils import _i18n
24-
from jupyter_server.utils import to_api_path
2522

2623
from .filecheckpoints import AsyncFileCheckpoints, FileCheckpoints
2724
from .fileio import AsyncFileManagerMixin, FileManagerMixin
@@ -58,34 +55,6 @@ def _validate_root_dir(self, proposal):
5855
raise TraitError("%r is not a directory" % value)
5956
return value
6057

61-
@default("preferred_dir")
62-
def _default_preferred_dir(self):
63-
try:
64-
value = self.parent.preferred_dir
65-
if value == self.parent.root_dir:
66-
value = None
67-
except AttributeError:
68-
pass
69-
else:
70-
if value is not None:
71-
warnings.warn(
72-
"ServerApp.preferred_dir config is deprecated in jupyter-server 2.0. Use FileContentsManager.preferred_dir instead",
73-
FutureWarning,
74-
stacklevel=3,
75-
)
76-
try:
77-
path = Path(value)
78-
return path.relative_to(self.root_dir).as_posix()
79-
except ValueError:
80-
raise TraitError("%s is outside root contents directory" % value) from None
81-
return ""
82-
83-
@validate("preferred_dir")
84-
def _validate_preferred_dir(self, proposal):
85-
# It should be safe to pass an API path through this method:
86-
proposal["value"] = to_api_path(proposal["value"], self.root_dir)
87-
return super()._validate_preferred_dir(proposal)
88-
8958
@default("checkpoints_class")
9059
def _checkpoints_class_default(self):
9160
return FileCheckpoints

jupyter_server/services/contents/manager.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import warnings
88
from fnmatch import fnmatch
99

10-
from jupyter_client.utils import run_sync
1110
from jupyter_events import EventLogger
1211
from nbformat import ValidationError, sign
1312
from nbformat import validate as validate_nb
@@ -65,30 +64,10 @@ def emit(self, data):
6564

6665
root_dir = Unicode("/", config=True)
6766

68-
preferred_dir = Unicode(
69-
"",
70-
config=True,
71-
help=_i18n(
72-
"Preferred starting directory to use for notebooks. This is an API path (`/` separated, relative to root dir)"
73-
),
74-
)
75-
76-
@validate("preferred_dir")
77-
def _validate_preferred_dir(self, proposal):
78-
value = proposal["value"].strip("/")
79-
try:
80-
dir_exists = run_sync(self.dir_exists)(value)
81-
except HTTPError as e:
82-
raise TraitError(e.log_message) from e
83-
if not dir_exists:
84-
raise TraitError(_i18n("Preferred directory not found: %r") % value)
85-
return value
86-
8767
allow_hidden = Bool(False, config=True, help="Allow access to hidden files")
8868

8969
notary = Instance(sign.NotebookNotary)
9070

91-
@default("notary")
9271
def _notary_default(self):
9372
return sign.NotebookNotary(parent=self)
9473

tests/test_gateway.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ async def test_gateway_session_lifecycle(init_gateway, jp_root_dir, jp_fetch, cu
434434
# Validate session lifecycle functions; create and delete.
435435

436436
# create
437-
session_id, kernel_id = await create_session(jp_fetch, "kspec_foo")
437+
session_id, kernel_id = await create_session(jp_root_dir, jp_fetch, "kspec_foo")
438438

439439
# ensure kernel still considered running
440440
assert await is_session_active(jp_fetch, session_id) is True
@@ -629,12 +629,12 @@ async def is_session_active(jp_fetch, session_id):
629629
return False
630630

631631

632-
async def create_session(jp_fetch, kernel_name):
632+
async def create_session(root_dir, jp_fetch, kernel_name):
633633
"""Creates a session for a kernel. The session is created against the server
634634
which then uses the gateway for kernel management.
635635
"""
636636
with mocked_gateway:
637-
nb_path = "/testgw.ipynb"
637+
nb_path = root_dir / "testgw.ipynb"
638638
body = json.dumps(
639639
{"path": str(nb_path), "type": "notebook", "kernel": {"name": kernel_name}}
640640
)

0 commit comments

Comments
 (0)