Skip to content

Commit 2497cf2

Browse files
authored
Feat/ws url (#41)
* chore: add proxy method * makefile * lint * fix: stop * stop: revert
1 parent 6050311 commit 2497cf2

File tree

7 files changed

+77
-25
lines changed

7 files changed

+77
-25
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ build:
2828
clean: ## clean
2929
git clean -fdx
3030

31+
jupyter-server: ## jupyter-server
32+
jupyter server --port 8888 --ServerApp.port_retries 0 --IdentityProvider.token MY_TOKEN --ServerApp.root_dir ./dev
33+
3134
jupyterlab: ## jupyterlab
3235
jupyter lab --port 8888 --ServerApp.port_retries 0 --IdentityProvider.token MY_TOKEN --ServerApp.root_dir ./dev
3336

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pip uninstall -y pycrdt datalayer_pycrdt
2929
pip install datalayer_pycrdt
3030
```
3131

32-
## Usage
32+
## Usage with Jupyter
3333

3434
1. Ensure you have the needed packages in your environment to run the example here after.
3535

@@ -125,7 +125,7 @@ with KernelClient(server_url="http://localhost:8888", token="MY_TOKEN") as kerne
125125
126126
> [!NOTE]
127127
>
128-
> Instead of using the clients as context manager, you can call the `start()` and `stop()` methods.
128+
> Instead of using the nbmodel clients as context manager, you can call the `start()` and `stop()` methods.
129129
130130
```py
131131
from jupyter_nbmodel_client import NbModelClient, get_jupyter_notebook_websocket_url
@@ -150,21 +150,27 @@ finally:
150150
kernel.stop()
151151
```
152152

153-
> [!NOTE]
154-
>
155-
> To connect to Datalayer collaborative room, you can use the helper function `get_datalayer_websocket_url`:
153+
## Usage with Datalayer
154+
155+
To connect to a [Datalayer collaborative room](https://docs.datalayer.app/platform#notebook-editor), you can use the helper function `get_datalayer_notebook_websocket_url`:
156+
157+
- The `server` is `https://prod1.datalayer.run` for the Datalayer production SaaS.
158+
- The `room_id` is the id of your notebook shown in the URL browser bar.
159+
- The `token` is the assigned token for the notebook.
160+
161+
All those details can be retrieved from a Notebook sidebar on the Datalayer SaaS.
156162

157163
```py
158-
from jupyter_nbmodel_client import NbModelClient, get_datalayer_websocket_url
164+
from jupyter_nbmodel_client import NbModelClient, get_datalayer_notebook_websocket_url
159165

160-
ws_url = get_datalayer_websocket_url(
166+
ws_url = get_datalayer_notebook_websocket_url(
161167
server_url=server,
162168
room_id=room_id,
163169
token=token
164170
)
165171

166172
async with NbModelClient(ws_url) as notebook:
167-
notebook.add_code_cell(CODE)
173+
notebook.add_code_cell("1+1")
168174
```
169175

170176
## Uninstall
@@ -177,7 +183,7 @@ pip uninstall jupyter_nbmodel_client
177183

178184
## Data Models
179185

180-
The following json schema describe the data model used in cells and notebook metadata to communicate between user clients and an Jupyter AI Agent.
186+
The following json schema describes the data model used in cells and notebook metadata to communicate between user clients and an Jupyter AI Agent.
181187

182188
For that, you will need the [Jupyter AI Agents](https://github.com/datalayer/jupyter-ai-agents) extension installed.
183189

jupyter_nbmodel_client/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
from jupyter_nbmodel_client._version import VERSION as __version__ # noqa: N811
1010
from jupyter_nbmodel_client.agent import AIMessageType, BaseNbAgent
1111
from jupyter_nbmodel_client.client import NbModelClient
12-
from jupyter_nbmodel_client.helpers import get_datalayer_websocket_url, get_jupyter_notebook_websocket_url
12+
from jupyter_nbmodel_client.helpers import (
13+
get_notebook_websocket_url,
14+
get_datalayer_notebook_websocket_url,
15+
get_jupyter_notebook_websocket_url,
16+
)
1317
from jupyter_nbmodel_client.model import KernelClient, NotebookModel
1418

1519

@@ -21,6 +25,7 @@
2125
"NotebookModel",
2226
"NotebookNode",
2327
"__version__",
24-
"get_datalayer_websocket_url",
28+
"get_notebook_websocket_url",
29+
"get_datalayer_notebook_websocket_url",
2530
"get_jupyter_notebook_websocket_url",
2631
]

jupyter_nbmodel_client/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
#
33
# BSD 3-Clause License
44

5-
VERSION = "0.13.0"
5+
VERSION = "0.13.4"

jupyter_nbmodel_client/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# BSD 3-Clause License
44

5-
"""This module provides a base class agent to interact with collaborative Jupyter notebook."""
5+
"""This module provides a base class AI agent to interact with collaborative Jupyter notebook."""
66

77
from __future__ import annotations
88

@@ -106,7 +106,7 @@ class BaseNbAgent(NbModelClient):
106106
"""
107107

108108
user_agent: str = f"Datalayer-BaseNbAgent/{VERSION}"
109-
"""User agent used to identify the client type in the awareness state."""
109+
"""User agent used to identify the nbmodel client type in the awareness state."""
110110

111111
def __init__(
112112
self,

jupyter_nbmodel_client/client.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ class NbModelClient(NotebookModel):
129129
#
130130
# Document changes callback is only placing event in a queue as it is a blocking operation.
131131
#
132-
# When using the client as a context manager or the start/stop methods, the `run` method will be
132+
# When using the nbmodel client as a context manager or the start/stop methods, the `run` method will be
133133
# executed in a task.
134134

135135
user_agent: str = f"Datalayer-NbModelClient/{VERSION}"
136-
"""User agent used to identify the client type in the awareness state."""
136+
"""User agent used to identify the nbmodel client type in the awareness state."""
137137

138138
def __init__(
139139
self,
@@ -190,7 +190,8 @@ async def __aexit__(self, exc_type, exc_value, exc_tb) -> None:
190190
await self.stop()
191191

192192
async def run(self) -> None:
193-
"""Run the client."""
193+
"""Run the nbmodel client."""
194+
self._log.info("Starting the nbmodel client…")
194195
if self.__is_running:
195196
raise RuntimeError("NbModelClient is already connected.")
196197

@@ -253,7 +254,7 @@ async def run(self) -> None:
253254
# Wait forever and prevent the forwarder to be cancelled to avoid losing changes
254255
await asyncio.gather(awareness_ping, listener, asyncio.shield(sender))
255256
finally:
256-
self._log.info("Stop the client…")
257+
self._log.info("Stopping the nbmodel client…")
257258

258259
# Stop listening to incoming messages
259260
if listener.cancel():
@@ -300,7 +301,7 @@ async def run(self) -> None:
300301
def get_local_client_id(self) -> int:
301302
"""Get the local client ID.
302303
303-
This is the identifier of the client communicated to all peers.
304+
This is the identifier of the nbmodel client communicated to all peers.
304305
305306
Returns:
306307
The local client ID.
@@ -338,7 +339,7 @@ def set_local_state_field(self, key: str, value: Any) -> None:
338339
cast(Awareness, self._doc.awareness).set_local_state_field(key, value)
339340

340341
async def start(self) -> None:
341-
"""Start the client."""
342+
"""Start the nbmodel client."""
342343
if self.__run is not None:
343344
raise RuntimeError("The client is already connected.")
344345

@@ -358,9 +359,15 @@ def callback(_: asyncio.Task) -> None:
358359
self._log.warning("Document %s not yet synced.", self._path)
359360

360361
async def stop(self) -> None:
361-
"""Stop and reset the client."""
362-
if self.__run is not None and self.__run.cancel():
363-
await asyncio.wait([self.__run])
362+
"""Stop and reset the nbmodel client."""
363+
if self.__run is not None:
364+
if self.__run.cancel():
365+
# TODO without timeout, stop() sometimes hangs indefinitely.
366+
# try:
367+
# await asyncio.wait_for(self.__run, timeout=1.0)
368+
# except TimeoutError:
369+
# self._log.warning('Timeout with stopping the nbmodel client "%s".', self._path)
370+
await asyncio.wait([self.__run])
364371

365372
async def wait_until_synced(self) -> None:
366373
"""Wait until the model is synced."""

jupyter_nbmodel_client/helpers.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,37 @@
1111
from jupyter_nbmodel_client.utils import fetch, url_path_join
1212

1313

14+
def get_notebook_websocket_url(
15+
server_url: str,
16+
path: str,
17+
provider: "jupyter" | "datalayer" = "jupyter",
18+
token: str | None = None,
19+
timeout: float = REQUEST_TIMEOUT,
20+
log: logging.Logger | None = None,
21+
) -> str:
22+
"""Get the websocket endpoint to connect to a collaborative Jupyter notebook.
23+
24+
Args:
25+
server_url: Jupyter Server URL
26+
provider: jupyter or datalayer
27+
path: Notebook path relative to the server root directory
28+
token: [optional] Jupyter Server authentication token; default None
29+
timeout: [optional] Request timeout in seconds; default to environment variable REQUEST_TIMEOUT
30+
log: [optional] Custom logger; default local logger
31+
32+
Returns:
33+
The websocket endpoint
34+
"""
35+
if provider == "jupyter":
36+
return get_jupyter_notebook_websocket_url(
37+
server_url, path, token, timeout, log
38+
)
39+
elif provider == "datalayer":
40+
return get_datalayer_notebook_websocket_url(
41+
server_url, path, token, timeout, log
42+
)
43+
44+
1445
def get_jupyter_notebook_websocket_url(
1546
server_url: str,
1647
path: str,
@@ -31,7 +62,7 @@ def get_jupyter_notebook_websocket_url(
3162
The websocket endpoint
3263
"""
3364
(log or DEFAULT_LOGGER).debug("Request the session ID from the Jupyter server.")
34-
# Fetch a session ID
65+
# Fetch a session ID.
3566
response = fetch(
3667
url_path_join(server_url, "/api/collaboration/session", quote(path)),
3768
token,
@@ -54,7 +85,7 @@ def get_jupyter_notebook_websocket_url(
5485
return room_url
5586

5687

57-
def get_datalayer_websocket_url(
88+
def get_datalayer_notebook_websocket_url(
5889
server_url: str,
5990
room_id: str,
6091
token: str | None = None,

0 commit comments

Comments
 (0)