Skip to content

Commit 9203802

Browse files
authored
Merge pull request #20 from datalayer/maintenance/prep-first-release
Preparation for first release
2 parents da00ed8 + 29191f5 commit 9203802

File tree

6 files changed

+114
-21
lines changed

6 files changed

+114
-21
lines changed

.copier-answers.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
2-
_commit: v4.3.0
2+
_commit: v4.3.4
33
_src_path: https://github.com/jupyterlab/extension-template
44
author_email: ''
55
author_name: Datalayer
@@ -12,3 +12,4 @@ project_short_description: A Jupyter Server extension to execute code cell from
1212
python_name: jupyter_server_nbmodel
1313
repository: https://github.com/datalayer/jupyter-server-nbmodel
1414
test: true
15+

.github/workflows/update-integration-tests.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ permissions:
1010

1111
jobs:
1212
update-snapshots:
13-
if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'please update snapshots') }}
13+
if: >
14+
(
15+
github.event.issue.author_association == 'OWNER' ||
16+
github.event.issue.author_association == 'COLLABORATOR' ||
17+
github.event.issue.author_association == 'MEMBER'
18+
) && github.event.issue.pull_request && contains(github.event.comment.body, 'please update snapshots')
1419
runs-on: ubuntu-latest
1520

1621
steps:
@@ -25,10 +30,40 @@ jobs:
2530
with:
2631
token: ${{ secrets.GITHUB_TOKEN }}
2732

33+
- name: Get PR Info
34+
id: pr
35+
env:
36+
PR_NUMBER: ${{ github.event.issue.number }}
37+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
GH_REPO: ${{ github.repository }}
39+
COMMENT_AT: ${{ github.event.comment.created_at }}
40+
run: |
41+
pr="$(gh api /repos/${GH_REPO}/pulls/${PR_NUMBER})"
42+
head_sha="$(echo "$pr" | jq -r .head.sha)"
43+
pushed_at="$(echo "$pr" | jq -r .pushed_at)"
44+
45+
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
46+
echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
47+
exit 1
48+
fi
49+
50+
echo "head_sha=$head_sha" >> $GITHUB_OUTPUT
51+
2852
- name: Checkout the branch from the PR that triggered the job
29-
run: gh pr checkout ${{ github.event.issue.number }}
3053
env:
3154
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
run: gh pr checkout ${{ github.event.issue.number }}
56+
57+
- name: Validate the fetched branch HEAD revision
58+
env:
59+
EXPECTED_SHA: ${{ steps.pr.outputs.head_sha }}
60+
run: |
61+
actual_sha="$(git rev-parse HEAD)"
62+
63+
if [[ "$actual_sha" != "$EXPECTED_SHA" ]]; then
64+
echo "The HEAD of the checked out branch ($actual_sha) differs from the HEAD commit available at the time when trigger comment was submitted ($EXPECTED_SHA)"
65+
exit 1
66+
fi
3267
3368
- name: Base Setup
3469
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
@@ -48,3 +83,4 @@ jobs:
4883
# Playwright knows how to start JupyterLab server
4984
start_server_script: 'null'
5085
test_folder: ui-tests
86+
npm_client: jlpm

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
[![Github Actions Status](https://github.com/datalayer/jupyter-server-nbmodel/workflows/Build/badge.svg)](https://github.com/datalayer/jupyter-server-nbmodel/actions/workflows/build.yml)
44
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/datalayer/jupyter-server-nbmodel/main?urlpath=lab)
5+
56
A Jupyter Server extension to execute code cell from the server.
67

78
This extension is composed of a Python package named `jupyter_server_nbmodel`
@@ -11,7 +12,8 @@ for the frontend extension.
1112
## Requirements
1213

1314
- Jupyter Server
14-
- \[optional\] JupyterLab >= 4.0.0
15+
- \[recommended\] Real-time collaboration for JupyterLab/Notebook:
16+
This will push the kernels results in the notebook from the server.
1517

1618
## Install
1719

@@ -21,6 +23,12 @@ To install the extension, execute:
2123
pip install jupyter_server_nbmodel
2224
```
2325

26+
Or with recommendations:
27+
28+
```bash
29+
pip install jupyter_server_nbmodel[rtc]
30+
```
31+
2432
## Uninstall
2533

2634
To remove the extension, execute:

jupyter_server_nbmodel/handlers.py

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
if t.TYPE_CHECKING:
2525
import jupyter_client
26+
from nbformat import NotebookNode
2627

2728
try:
2829
import jupyter_server_ydoc
@@ -74,6 +75,14 @@ async def _get_ycell(
7475
ydoc: jupyter_server_ydoc.app.YDocExtension | None,
7576
metadata: dict | None,
7677
) -> y.Map | None:
78+
"""Get the cell from which the execution was triggered.
79+
80+
Args:
81+
ydoc: The YDoc jupyter server extension
82+
metadata: Execution context
83+
Returns:
84+
The cell
85+
"""
7786
if ydoc is None:
7887
msg = "jupyter-collaboration extension is not installed on the server. Outputs won't be written within the document." # noqa: E501
7988
get_logger().warning(msg)
@@ -89,7 +98,7 @@ async def _get_ycell(
8998
get_logger().debug(msg)
9099
return None
91100

92-
notebook: YNotebook = await ydoc.get_document(room_id=document_id, copy=False)
101+
notebook: YNotebook | None = await ydoc.get_document(room_id=document_id, copy=False)
93102

94103
if notebook is None:
95104
msg = f"Document with ID {document_id} not found."
@@ -118,7 +127,14 @@ async def _get_ycell(
118127
return ycell
119128

120129

121-
def _output_hook(ycell, outputs, msg) -> None:
130+
def _output_hook(outputs: list[NotebookNode], ycell: y.Map | None, msg: dict) -> None:
131+
"""Callback on execution request when an output is emitted.
132+
133+
Args:
134+
outputs: A list of previously emitted outputs
135+
ycell: The cell being executed
136+
msg: The output message
137+
"""
122138
msg_type = msg["header"]["msg_type"]
123139
if msg_type in ("display_data", "stream", "execute_result", "error"):
124140
# FIXME support for version
@@ -162,6 +178,13 @@ def _stdin_hook(kernel_id: str, request_id: str, pending_input: PendingInput, ms
162178
"""Callback on stdin message.
163179
164180
It will register the pending input as temporary answer to the execution request.
181+
182+
Args:
183+
kernel_id: The Kernel ID
184+
request_id: The request ID that triggers the input request
185+
pending_input: The pending input description.
186+
This object will be mutated with useful information from ``msg``.
187+
msg: The stdin msg
165188
"""
166189
get_logger().debug(f"Execution request {kernel_id} received a input request.")
167190
if PendingInput.request_id is not None:
@@ -184,6 +207,17 @@ async def _execute_snippet(
184207
metadata: dict | None,
185208
stdin_hook: t.Callable[[dict], None] | None,
186209
) -> dict[str, t.Any]:
210+
"""Snippet executor
211+
212+
Args:
213+
client: Kernel client
214+
ydoc: Jupyter server YDoc extension
215+
snippet: The code snippet to execute
216+
metadata: The code snippet metadata; e.g. to define the snippet context
217+
stdin_hook: The stdin message callback
218+
Returns:
219+
The execution status and outputs.
220+
"""
187221
ycell = None
188222
if metadata is not None:
189223
ycell = await _get_ycell(ydoc, metadata)
@@ -201,7 +235,7 @@ async def _execute_snippet(
201235
client.execute_interactive(
202236
snippet,
203237
# FIXME stream partial results
204-
output_hook=partial(_output_hook, ycell, outputs),
238+
output_hook=partial(_output_hook, outputs, ycell),
205239
stdin_hook=stdin_hook if client.allow_stdin else None,
206240
)
207241
)
@@ -327,21 +361,27 @@ async def cancel(self, kernel_id: str, timeout: float | None = None) -> None:
327361
328362
Args:
329363
kernel_id : Kernel identifier
364+
timeout: Timeout to await for completion in seconds
365+
366+
Raises:
367+
TimeoutError: if a task is not cancelled in time
330368
"""
331369
# FIXME connect this to kernel lifecycle
332370
get_logger().debug(f"Cancel execution for kernel {kernel_id}.")
333-
worker = self.__workers.pop(kernel_id, None)
334-
if worker is not None:
335-
worker.cancel()
336-
await asyncio.wait_for(worker, timeout=timeout)
337-
338-
queue = self.__tasks.pop(kernel_id, None)
339-
if queue is not None:
340-
await asyncio.wait_for(queue.join(), timeout=timeout)
341-
342-
client = self.__kernel_clients.pop(kernel_id, None)
343-
if client is not None:
344-
client.stop_channels()
371+
try:
372+
worker = self.__workers.pop(kernel_id, None)
373+
if worker is not None:
374+
worker.cancel()
375+
await asyncio.wait_for(worker, timeout=timeout)
376+
finally:
377+
try:
378+
queue = self.__tasks.pop(kernel_id, None)
379+
if queue is not None:
380+
await asyncio.wait_for(queue.join(), timeout=timeout)
381+
finally:
382+
client = self.__kernel_clients.pop(kernel_id, None)
383+
if client is not None:
384+
client.stop_channels()
345385

346386
async def send_input(self, kernel_id: str, value: str) -> None:
347387
"""Send input ``value`` to the kernel ``kernel_id``.
@@ -431,6 +471,13 @@ def put(self, kernel_id: str, snippet: str, metadata: dict | None = None) -> str
431471
return uid
432472

433473
def _get_client(self, kernel_id: str) -> jupyter_client.asynchronous.client.AsyncKernelClient:
474+
"""Get the cached kernel client for ``kernel_id``.
475+
476+
Args:
477+
kernel_id: The kernel ID
478+
Returns:
479+
The client for the given kernel.
480+
"""
434481
if kernel_id not in self.__kernel_clients:
435482
km = self.__manager.get_kernel(kernel_id)
436483
self.__kernel_clients[kernel_id] = km.client()

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ dependencies = [
2929
dynamic = ["version", "description", "authors", "urls", "keywords"]
3030

3131
[project.optional-dependencies]
32-
rtc = ["jupyterlab>=4.2.0", "jupyter_collaboration>=3.0.0a0"]
32+
# FIXME we should be able to only use the server part without UI
33+
rtc = ["jupyterlab>=4.2.0", "jupyter_collaboration>=3.0.0b0"]
3334
test = ["pytest~=8.2", "pytest-cov", "pytest-jupyter[server]>=0.6", "pytest-timeout"]
3435
lint = ["mdformat>0.7", "mdformat-gfm>=0.3.5", "ruff>=0.4.0"]
3536
typing = ["mypy>=0.990"]

ui-tests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ in [jupyter_server_test_config.py](./jupyter_server_test_config.py).
1212

1313
The default configuration will produce video for failing tests and an HTML report.
1414

15-
> There is a new experimental UI mode that you may fall in love with; see [that video](https://www.youtube.com/watch?v=jF0yA-JLQW0).
15+
> There is a UI mode that you may like; see [that video](https://www.youtube.com/watch?v=jF0yA-JLQW0).
1616
1717
## Run the tests
1818

0 commit comments

Comments
 (0)