Skip to content

Commit 3b7dabc

Browse files
committed
only configure the classes if they haven't been previously configured
1 parent 6f698fa commit 3b7dabc

File tree

4 files changed

+86
-72
lines changed

4 files changed

+86
-72
lines changed

jupyter_server_documents/app.py

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
from traitlets.config import Config
33
from traitlets import Instance, Type
44

5-
from nextgen_kernels_api.services.kernels.client_manager import KernelClientManager
6-
75
from .handlers import RouteHandler, FileIDIndexHandler
86
from .websockets import YRoomWebsocket
97
from .rooms.yroom_manager import YRoomManager
@@ -49,8 +47,6 @@ class ServerDocsApp(ExtensionApp):
4947

5048
yroom_manager = Instance(klass=YRoomManager, allow_none=True)
5149

52-
client_manager = Instance(klass=KernelClientManager, allow_none=True)
53-
5450
def initialize(self):
5551
super().initialize()
5652

@@ -75,17 +71,6 @@ def get_fileid_manager():
7571
self.outputs_manager = self.outputs_manager_class(parent=self)
7672
self.settings["outputs_manager"] = self.outputs_manager
7773

78-
# Initialize KernelClientRegistry as singleton
79-
# The KernelClientWebsocketConnection from nextgen-kernels-api will access this via .instance()
80-
self.client_manager = KernelClientManager.instance(
81-
parent=self,
82-
multi_kernel_manager=self.serverapp.kernel_manager
83-
)
84-
self.settings["client_manager"] = self.client_manager
85-
86-
# Register event listener for client management
87-
self.client_manager.register_event_listener(self.serverapp.event_logger)
88-
8974
# Serve Jupyter Collaboration API on
9075
# `self.settings["jupyter_server_ydoc"]` for compatibility with
9176
# extensions depending on Jupyter Collaboration
@@ -95,35 +80,42 @@ def get_fileid_manager():
9580
)
9681

9782
def _link_jupyter_server_extension(self, server_app):
98-
"""Setup custom config needed by this extension."""
83+
"""Setup custom config needed by this extension.
84+
85+
Only applies configuration if not already set by user config.
86+
"""
9987
c = Config()
100-
# Use nextgen-kernels-api's multi-kernel manager
101-
# This manager disables activity watching and buffering (which we don't need)
102-
c.ServerApp.kernel_manager_class = "nextgen_kernels_api.services.kernels.kernelmanager.MultiKernelManager"
103-
c.ServerApp.kernel_websocket_connection_class = 'nextgen_kernels_api.services.kernels.connection.kernel_client_connection.KernelClientWebsocketConnection'
104-
105-
# Configure MultiKernelManager to use nextgen's KernelManager
106-
c.MultiKernelManager.kernel_manager_class = "nextgen_kernels_api.services.kernels.kernelmanager.KernelManager"
107-
108-
# Configure the KernelManager to use DocumentAwareKernelClient
109-
c.KernelManager.client_class = "jupyter_server_documents.kernel_client.DocumentAwareKernelClient"
110-
c.KernelManager.client_factory = "jupyter_server_documents.kernel_client.DocumentAwareKernelClient"
111-
112-
# Use custom session manager for YRoom integration
113-
c.ServerApp.session_manager_class = "jupyter_server_documents.session_manager.YDocSessionManager"
114-
115-
# Configure websocket connection to exclude output messages
116-
# Output messages are handled by DocumentAwareKernelClient's output processor
117-
# and should not be sent to websocket clients to avoid duplicate processing
118-
c.KernelClientWebsocketConnection.exclude_msg_types = [
119-
("status", "iopub"),
120-
("stream", "iopub"),
121-
("display_data", "iopub"),
122-
("execute_result", "iopub"),
123-
("error", "iopub"),
124-
("update_display_data", "iopub"),
125-
("clear_output", "iopub")
126-
]
88+
89+
# Configure kernel manager classes to use nextgen-kernels-api
90+
if not server_app.config.ServerApp.get("kernel_manager_class"):
91+
c.ServerApp.kernel_manager_class = "nextgen_kernels_api.services.kernels.kernelmanager.MultiKernelManager"
92+
93+
if not server_app.config.ServerApp.get("kernel_websocket_connection_class"):
94+
c.ServerApp.kernel_websocket_connection_class = "nextgen_kernels_api.services.kernels.connection.kernel_client_connection.KernelClientWebsocketConnection"
95+
96+
if not server_app.config.ServerApp.get("session_manager_class"):
97+
c.ServerApp.session_manager_class = "jupyter_server_documents.session_manager.YDocSessionManager"
98+
99+
# Configure kernel manager hierarchy
100+
if not server_app.config.MultiKernelManager.get("kernel_manager_class"):
101+
c.MultiKernelManager.kernel_manager_class = "nextgen_kernels_api.services.kernels.kernelmanager.KernelManager"
102+
103+
# Configure kernel client
104+
if not server_app.config.KernelManager.get("client_class"):
105+
c.KernelManager.client_class = "jupyter_server_documents.kernel_client.DocumentAwareKernelClient"
106+
c.KernelManager.client_factory = "jupyter_server_documents.kernel_client.DocumentAwareKernelClient"
107+
108+
# Configure websocket message filtering
109+
if not server_app.config.KernelClientWebsocketConnection.get("exclude_msg_types"):
110+
c.KernelClientWebsocketConnection.exclude_msg_types = [
111+
("status", "iopub"),
112+
("stream", "iopub"),
113+
("display_data", "iopub"),
114+
("execute_result", "iopub"),
115+
("error", "iopub"),
116+
("update_display_data", "iopub"),
117+
("clear_output", "iopub"),
118+
]
127119

128120
server_app.update_config(c)
129121
super()._link_jupyter_server_extension(server_app)

jupyter_server_documents/rooms/yroom_file_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ async def save(self, jupyter_ydoc: YBaseDoc):
546546

547547
# Set most recent `last_modified` timestamp
548548
if file_data['last_modified']:
549-
self.log.info(f"Reseting last_modified to {file_data['last_modified']}")
549+
self.log.debug(f"Resetting last_modified to {file_data['last_modified']}")
550550
self._last_modified = file_data['last_modified']
551551

552552
# Set `dirty` to `False` to hide the "unsaved changes" icon in the

jupyter_server_documents/session_manager.py

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from jupyter_server_documents.rooms.yroom_manager import YRoomManager
77
from jupyter_server_documents.rooms.yroom import YRoom
88
from jupyter_server_documents.kernel_client import DocumentAwareKernelClient
9-
from nextgen_kernels_api.services.kernels.client_manager import KernelClientManager
109

1110

1211
class YDocSessionManager(SessionManager):
@@ -31,11 +30,6 @@ def yroom_manager(self) -> YRoomManager:
3130
"""The Jupyter Server's YRoom Manager."""
3231
return self.serverapp.web_app.settings["yroom_manager"]
3332

34-
@property
35-
def client_manager(self) -> KernelClientManager:
36-
"""The Kernel Client Manager."""
37-
return self.serverapp.web_app.settings["client_manager"]
38-
3933
_room_ids: dict[str, str]
4034
"""
4135
Dictionary of room IDs, keyed by session ID.
@@ -83,7 +77,34 @@ async def create_session(
8377
) -> dict[str, Any]:
8478
"""
8579
After creating a session, connects the yroom to the kernel client.
80+
Sets kernel status to "starting" before kernel launch.
8681
"""
82+
# For notebooks, set up the YRoom and set initial status before starting kernel
83+
should_setup_yroom = (
84+
type == "notebook" and
85+
name is not None and
86+
path is not None
87+
)
88+
89+
yroom = None
90+
if should_setup_yroom:
91+
# Calculate the real path
92+
real_path = os.path.join(os.path.split(path)[0], name)
93+
94+
# Initialize the YRoom before starting the kernel
95+
file_id = self.file_id_manager.index(real_path)
96+
room_id = f"json:notebook:{file_id}"
97+
yroom = self.yroom_manager.get_room(room_id)
98+
99+
# Set initial kernel status to "starting" in awareness
100+
awareness = yroom.get_awareness()
101+
if awareness is not None:
102+
self.log.info("Setting kernel execution_state to 'starting' before kernel launch")
103+
awareness.set_local_state_field(
104+
"kernel", {"execution_state": "starting"}
105+
)
106+
107+
# Now create the session and start the kernel
87108
session_model = await super().create_session(
88109
path,
89110
name,
@@ -108,27 +129,21 @@ async def create_session(
108129
self.log.warning(f"`name` or `path` was not given for new session at '{path}'.")
109130
return session_model
110131

111-
# Otherwise, get a `YRoom` and add it to this session's kernel client.
112-
113-
# When JupyterLab creates a session, it uses a fake path
114-
# which is the relative path + UUID, i.e. the notebook
115-
# name is incorrect temporarily. It later makes multiple
116-
# updates to the session to correct the path.
117-
#
118-
# Here, we create the true path to store in the fileID service
119-
# by dropping the UUID and appending the file name.
120-
real_path = os.path.join(os.path.split(path)[0], name)
132+
# Otherwise, add the YRoom to this session's kernel client.
121133

122-
# Get YRoom for this session and store its ID in `self._room_ids`
123-
yroom = self._init_session_yroom(session_id, real_path)
134+
# Store the room ID for this session
135+
if yroom:
136+
self._room_ids[session_id] = yroom.room_id
137+
else:
138+
# Shouldn't happen, but handle it anyway
139+
real_path = os.path.join(os.path.split(path)[0], name)
140+
yroom = self._init_session_yroom(session_id, real_path)
124141

125142
# Add YRoom to this session's kernel client
126-
# TODO: we likely have a race condition here... need to
127-
# think about it more. Currently, the kernel client gets
128-
# created after the kernel starts fully. We need the
129-
# kernel client instantiated _before_ trying to connect
130-
# the yroom.
131-
kernel_client = self.client_manager.get_client(kernel_id)
143+
# Ensure the kernel client is fully connected before proceeding
144+
# to avoid queuing messages on first execution
145+
kernel_manager = self.serverapp.kernel_manager.get_kernel(kernel_id)
146+
kernel_client = kernel_manager.kernel_client
132147
await kernel_client.add_yroom(yroom)
133148
self.log.info(f"Connected yroom {yroom.room_id} to kernel {kernel_id}. yroom: {yroom}")
134149
return session_model
@@ -158,10 +173,12 @@ async def update_session(self, session_id: str, **update) -> None:
158173
)
159174
yroom = self.get_yroom(session_id)
160175
if old_kernel_id:
161-
old_kernel_client = self.client_manager.get_client(old_kernel_id)
176+
old_kernel_manager = self.serverapp.kernel_manager.get_kernel(old_kernel_id)
177+
old_kernel_client = old_kernel_manager.kernel_client
162178
await old_kernel_client.remove_yroom(yroom=yroom)
163179
if new_kernel_id:
164-
new_kernel_client = self.client_manager.get_client(new_kernel_id)
180+
new_kernel_manager = self.serverapp.kernel_manager.get_kernel(new_kernel_id)
181+
new_kernel_client = new_kernel_manager.kernel_client
165182
await new_kernel_client.add_yroom(yroom=yroom)
166183

167184
# Apply update and return
@@ -177,7 +194,8 @@ async def delete_session(self, session_id):
177194

178195
# Remove YRoom from session's kernel client
179196
yroom = self.get_yroom(session_id)
180-
kernel_client = self.client_manager.get_client(kernel_id)
197+
kernel_manager = self.serverapp.kernel_manager.get_kernel(kernel_id)
198+
kernel_client = kernel_manager.kernel_client
181199
await kernel_client.remove_yroom(yroom)
182200

183201
# Remove room ID stored for the session

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies = [
3232
"jupyter_server_fileid>=0.9.0,<0.10.0",
3333
"pycrdt>=0.12.0,<0.13.0",
3434
"jupyter_ydoc>=3.0.0,<4.0.0",
35-
"nextgen-kernels-api>=0.6.0",
35+
"nextgen-kernels-api>=0.9.0",
3636
]
3737
dynamic = ["version", "description", "authors", "urls", "keywords"]
3838

@@ -97,3 +97,7 @@ before-build-python = ["jlpm clean:all"]
9797

9898
[tool.check-wheel-contents]
9999
ignore = ["W002"]
100+
101+
[tool.pytest.ini_options]
102+
testpaths = ["jupyter_server_documents/tests"]
103+
norecursedirs = ["repos", ".git", ".pixi", "node_modules"]

0 commit comments

Comments
 (0)