From d9d7e32e734afb08e87ea2004fb5b0ea654128ea Mon Sep 17 00:00:00 2001 From: David Brochart Date: Thu, 16 May 2024 17:44:46 +0200 Subject: [PATCH 1/5] Support stdin --- jupyter_ydoc/__init__.py | 1 + jupyter_ydoc/ystdin.py | 36 ++++++++++++++++++++++++++++++++++++ tests/test_ydocs.py | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 jupyter_ydoc/ystdin.py diff --git a/jupyter_ydoc/__init__.py b/jupyter_ydoc/__init__.py index 9450058..72304f0 100644 --- a/jupyter_ydoc/__init__.py +++ b/jupyter_ydoc/__init__.py @@ -7,6 +7,7 @@ from .yblob import YBlob as YBlob from .yfile import YFile as YFile from .ynotebook import YNotebook as YNotebook +from .ystdin import add_stdin as add_stdin from .yunicode import YUnicode as YUnicode # See compatibility note on `group` keyword in diff --git a/jupyter_ydoc/ystdin.py b/jupyter_ydoc/ystdin.py new file mode 100644 index 0000000..514dc7a --- /dev/null +++ b/jupyter_ydoc/ystdin.py @@ -0,0 +1,36 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +from pycrdt import Map, Text + + +def add_stdin(cell: Map, prompt: str = "", password: bool = False) -> None: + """ + Adds an stdin Map in the cell outputs. + + Schema: + + .. code-block:: json + + { + "state": Map[ + "pending": bool, + "password": bool + ], + "prompt": str, + "input": Text + } + """ + stdin = Map( + { + "output_type": "stdin", + "state": { + "pending": True, + "password": password, + }, + "prompt": prompt, + "input": Text(), + } + ) + outputs = cell.get("outputs") + outputs.append(stdin) diff --git a/tests/test_ydocs.py b/tests/test_ydocs.py index c9fdd3e..e003dc5 100644 --- a/tests/test_ydocs.py +++ b/tests/test_ydocs.py @@ -1,7 +1,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from jupyter_ydoc import YBlob +from jupyter_ydoc import YBlob, YNotebook, add_stdin def test_yblob(): @@ -22,3 +22,34 @@ def callback(topic, event): assert topic == "source" assert event.keys["bytes"]["oldValue"] == b"012" assert event.keys["bytes"]["newValue"] == b"345" + + +def test_stdin(): + ynotebook = YNotebook() + ynotebook.append_cell( + { + "cell_type": "code", + "source": "", + } + ) + ycell = ynotebook.ycells[0] + add_stdin(ycell) + cell = ycell.to_py() + # cell ID is random, ignore that + del cell["id"] + assert cell == { + "outputs": [ + { + "output_type": "stdin", + "input": "", + "prompt": "", + "state": { + "password": False, + "pending": True, + }, + } + ], + "source": "", + "metadata": {}, + "cell_type": "code", + } From e0b747a16e2ada0b3a5d396075e4657f2a1c7b34 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Thu, 16 May 2024 18:17:20 +0200 Subject: [PATCH 2/5] Remove outputs of type stdin when converting cell to dict --- jupyter_ydoc/ynotebook.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jupyter_ydoc/ynotebook.py b/jupyter_ydoc/ynotebook.py index 5166af1..35037f2 100644 --- a/jupyter_ydoc/ynotebook.py +++ b/jupyter_ydoc/ynotebook.py @@ -109,6 +109,10 @@ def get_cell(self, index: int) -> Dict[str, Any]: and not cell["attachments"] ): del cell["attachments"] + outputs = cell.get("outputs", []) + for idx, output in enumerate(outputs): + if output["output_type"] == "stdin": + del outputs[idx] return cell def append_cell(self, value: Dict[str, Any]) -> None: From c0eaa854ee5cae4a312a5ce7ae71c6dbae1dbfff Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 17 May 2024 11:06:21 +0200 Subject: [PATCH 3/5] Add stdin ID, change structure --- jupyter_ydoc/ystdin.py | 23 +++++++++++++---------- tests/test_ydocs.py | 17 +++++++++-------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/jupyter_ydoc/ystdin.py b/jupyter_ydoc/ystdin.py index 514dc7a..9b451b9 100644 --- a/jupyter_ydoc/ystdin.py +++ b/jupyter_ydoc/ystdin.py @@ -1,36 +1,39 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +from uuid import uuid4 + from pycrdt import Map, Text -def add_stdin(cell: Map, prompt: str = "", password: bool = False) -> None: +def add_stdin(cell: Map, prompt: str = "", password: bool = False) -> str: """ - Adds an stdin Map in the cell outputs. + Adds an stdin Map in the cell outputs, and returns its ID. Schema: .. code-block:: json { - "state": Map[ - "pending": bool, - "password": bool - ], + "output_type": "stdin", + "id": str, + "submitted": bool, + "password": bool "prompt": str, "input": Text } """ + idx = uuid4().hex stdin = Map( { "output_type": "stdin", - "state": { - "pending": True, - "password": password, - }, + "id": idx, + "submitted": False, + "password": password, "prompt": prompt, "input": Text(), } ) outputs = cell.get("outputs") outputs.append(stdin) + return idx diff --git a/tests/test_ydocs.py b/tests/test_ydocs.py index e003dc5..6138e65 100644 --- a/tests/test_ydocs.py +++ b/tests/test_ydocs.py @@ -12,7 +12,6 @@ def test_yblob(): changes = [] def callback(topic, event): - print(topic, event) changes.append((topic, event)) yblob.observe(callback) @@ -33,20 +32,22 @@ def test_stdin(): } ) ycell = ynotebook.ycells[0] - add_stdin(ycell) + add_stdin(ycell, prompt="pwd:", password=True) + stdin = ycell["outputs"][0]["input"] + stdin += "mypassword" cell = ycell.to_py() # cell ID is random, ignore that del cell["id"] + # input ID is random, ignore that + del cell["outputs"][0]["id"] assert cell == { "outputs": [ { "output_type": "stdin", - "input": "", - "prompt": "", - "state": { - "password": False, - "pending": True, - }, + "input": "mypassword", + "prompt": "pwd:", + "password": True, + "submitted": False, } ], "source": "", From 04ab4dec7f42e4f1faca1b33f1efcc4bc656e056 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 17 May 2024 11:27:41 +0200 Subject: [PATCH 4/5] Fix deleting stdin outputs --- jupyter_ydoc/ynotebook.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jupyter_ydoc/ynotebook.py b/jupyter_ydoc/ynotebook.py index 35037f2..3ce4f4f 100644 --- a/jupyter_ydoc/ynotebook.py +++ b/jupyter_ydoc/ynotebook.py @@ -110,9 +110,14 @@ def get_cell(self, index: int) -> Dict[str, Any]: ): del cell["attachments"] outputs = cell.get("outputs", []) + del_outputs = [] for idx, output in enumerate(outputs): if output["output_type"] == "stdin": - del outputs[idx] + del_outputs.append(idx) + deleted = 0 + for idx in del_outputs: + del outputs[idx - deleted] + deleted += 1 return cell def append_cell(self, value: Dict[str, Any]) -> None: From 7c555322a2833d1297eae0a409cc486fc464a415 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Thu, 30 May 2024 10:48:11 +0200 Subject: [PATCH 5/5] Ignore stdin output when converting to JSON, change structure --- javascript/src/ycell.ts | 7 ++++++- jupyter_ydoc/__init__.py | 2 +- jupyter_ydoc/ynotebook.py | 1 + jupyter_ydoc/ystdin.py | 23 +++++++++-------------- tests/test_ydocs.py | 18 ++++++++++-------- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/javascript/src/ycell.ts b/javascript/src/ycell.ts index ccafbf8..1dab677 100644 --- a/javascript/src/ycell.ts +++ b/javascript/src/ycell.ts @@ -758,7 +758,12 @@ export class YCodeCell * Execution, display, or stream outputs. */ getOutputs(): Array { - return JSONExt.deepCopy(this._youtputs.toArray()); + return JSONExt.deepCopy( + this._youtputs.toArray().filter( + // Filter out stdin output. + el => !(el instanceof Y.Map && el.get('output_type') === 'stdin') + ) + ); } /** diff --git a/jupyter_ydoc/__init__.py b/jupyter_ydoc/__init__.py index 72304f0..9161b2b 100644 --- a/jupyter_ydoc/__init__.py +++ b/jupyter_ydoc/__init__.py @@ -7,7 +7,7 @@ from .yblob import YBlob as YBlob from .yfile import YFile as YFile from .ynotebook import YNotebook as YNotebook -from .ystdin import add_stdin as add_stdin +from .ystdin import add_stdin_output as add_stdin_output from .yunicode import YUnicode as YUnicode # See compatibility note on `group` keyword in diff --git a/jupyter_ydoc/ynotebook.py b/jupyter_ydoc/ynotebook.py index 3ce4f4f..f20bf7d 100644 --- a/jupyter_ydoc/ynotebook.py +++ b/jupyter_ydoc/ynotebook.py @@ -109,6 +109,7 @@ def get_cell(self, index: int) -> Dict[str, Any]: and not cell["attachments"] ): del cell["attachments"] + # filter out stdin output outputs = cell.get("outputs", []) del_outputs = [] for idx, output in enumerate(outputs): diff --git a/jupyter_ydoc/ystdin.py b/jupyter_ydoc/ystdin.py index 9b451b9..dac96a4 100644 --- a/jupyter_ydoc/ystdin.py +++ b/jupyter_ydoc/ystdin.py @@ -1,14 +1,12 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from uuid import uuid4 +from pycrdt import Array, Map, Text -from pycrdt import Map, Text - -def add_stdin(cell: Map, prompt: str = "", password: bool = False) -> str: +def add_stdin_output(outputs: Array, prompt: str = "", password: bool = False) -> int: """ - Adds an stdin Map in the cell outputs, and returns its ID. + Adds an stdin output Map in the cell outputs, and returns its index. Schema: @@ -16,24 +14,21 @@ def add_stdin(cell: Map, prompt: str = "", password: bool = False) -> str: { "output_type": "stdin", - "id": str, "submitted": bool, "password": bool "prompt": str, - "input": Text + "value": Text } """ - idx = uuid4().hex - stdin = Map( + stdin_output = Map( { "output_type": "stdin", - "id": idx, "submitted": False, "password": password, "prompt": prompt, - "input": Text(), + "value": Text(), } ) - outputs = cell.get("outputs") - outputs.append(stdin) - return idx + stdin_idx = len(outputs) + outputs.append(stdin_output) + return stdin_idx diff --git a/tests/test_ydocs.py b/tests/test_ydocs.py index 6138e65..7be48fe 100644 --- a/tests/test_ydocs.py +++ b/tests/test_ydocs.py @@ -1,7 +1,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from jupyter_ydoc import YBlob, YNotebook, add_stdin +from jupyter_ydoc import YBlob, YNotebook, add_stdin_output def test_yblob(): @@ -23,7 +23,7 @@ def callback(topic, event): assert event.keys["bytes"]["newValue"] == b"345" -def test_stdin(): +def test_stdin_output(): ynotebook = YNotebook() ynotebook.append_cell( { @@ -32,22 +32,24 @@ def test_stdin(): } ) ycell = ynotebook.ycells[0] - add_stdin(ycell, prompt="pwd:", password=True) - stdin = ycell["outputs"][0]["input"] + youtputs = ycell["outputs"] + stdin_idx = add_stdin_output(youtputs, prompt="pwd:", password=True) + stdin_output = youtputs[stdin_idx] + stdin = stdin_output["value"] stdin += "mypassword" + stdin_output["submitted"] = True + cell = ycell.to_py() # cell ID is random, ignore that del cell["id"] - # input ID is random, ignore that - del cell["outputs"][0]["id"] assert cell == { "outputs": [ { "output_type": "stdin", - "input": "mypassword", + "value": "mypassword", "prompt": "pwd:", "password": True, - "submitted": False, + "submitted": True, } ], "source": "",