GithubHelp home page GithubHelp logo

jupyter-server / jupyter_ydoc Goto Github PK

View Code? Open in Web Editor NEW
25.0 6.0 16.0 1.38 MB

Jupyter document structures for collaborative editing using Yjs/pycrdt

Home Page: https://jupyter-ydoc.readthedocs.io

License: BSD 3-Clause "New" or "Revised" License

Python 15.63% Jupyter Notebook 14.56% JavaScript 3.23% TypeScript 66.58%

jupyter_ydoc's Introduction

Build Status Code style: black PyPI npm (scoped)

jupyter_ydoc

jupyter_ydoc provides pycrdt-based data structures for various documents used in the Jupyter ecosystem. Built-in documents include:

  • YBlob: a generic immutable binary document.
  • YUnicode: a generic UTF8-encoded text document (YFile is an alias to YUnicode).
  • YNotebook: a Jupyter notebook document.

These documents are registered via an entry point under the "jupyter_ydoc" group as "blob", "unicode" (or "file"), and "notebook", respectively. You can access them as follows:

from jupyter_ydoc import ydocs

print(ydocs)
# {
#     'blob': <class 'jupyter_ydoc.yblob.YBlob'>,
#     'file': <class 'jupyter_ydoc.yfile.YFile'>,
#     'notebook': <class 'jupyter_ydoc.ynotebook.YNotebook'>,
#     'unicode': <class 'jupyter_ydoc.yunicode.YUnicode'>
# }

Which is just a shortcut to:

from importlib.metadata import entry_points
# for Python < 3.10, install importlib_metadata and do:
# from importlib_metadata import entry_points

ydocs = {ep.name: ep.load() for ep in entry_points(group="jupyter_ydoc")}

Or directly import them:

from jupyter_ydoc import YBlob, YUnicode, YNotebook

The "jupyter_ydoc" entry point group can be populated with your own documents, e.g. by adding the following to your package's pyproject.toml:

[project.entry-points.jupyter_ydoc]
my_document = "my_package.my_file:MyDocumentClass"

jupyter_ydoc's People

Contributors

agoose77 avatar davidbrochart avatar dependabot[bot] avatar fcollonval avatar github-actions[bot] avatar hbcarlos avatar jtpio avatar krassowski avatar martinrenou avatar pre-commit-ci[bot] avatar trungleduc avatar vidartf avatar wh1isper avatar zsailer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

jupyter_ydoc's Issues

Can't package jupyter-ydoc 1.1.1 with jupyter-collaboration 1.2.1

Hi,

openSUSE rpm package maintainer for the jupyter ecosystem here.

I am at an impasse:

  1. jupyter-collaboration v2 and jupyter-ydoc v2 require pycrdt which is declared to be not ready for production yet
  2. jupyter-collaboration v1.2.1 requires
    "jupyter_ydoc>=1.0.1,<2.0.0", 
    "ypy-websocket>=0.12.1,<0.13.0",
    
  3. jupyter-ydoc 1.1.1 requires ypy-websocket >=0.8.3,<0.9.0 for testing.

Number 2 and 3 conflict with each other. Any advice?

Notebook cell execution

Problem

Today at the Jupyter Server meeting we talked about notebook restoration, where RTC could be a solution (see jupyter-server/jupyter_server#900). In that solution, the frontend just displays live changes to the notebook shared model. But currently, a notebook cell execution is either None or an integer greater than 0, so there is no way to show that a cell is executing.

Proposed Solution

We could encode the "cell executing" state in the execution count, for instance as "*". I think it could even be part of nbformat, because it brings some information about the state of the notebook at the time it was saved.

Allow users to provide an external `ydoc` to `YDocument`

Problem

The constructor of YDocument is empty since the internal YJS doc is created by the class. It would be useful if users can provide an existing ydoc to the YDocument constructor.

Proposed Solution

Update the YDocument constructor signature to allow overriding the ydoc property.

Additional context

  • This would help ypywidgets project to create frontend widgets.

y-py v0.5.5 from conda-forge breaks ydoc

Due to the problem with the y-py aarch64 build (here: y-crdt/ypy#119). We published a new build of ypy v0.5.5 in conda-forge. This new build includes y-crdt/ypy#102, which changes the return type of the to_json and breaks jupyter_ydoc.

Task exception was never retrieved
future: <Task finished name='Task-175' coro=<YDocWebSocketHandler.maybe_save_document() done, defined at /Users/carlos/Documents/qs/rtc_dev/jupyterlab_collaboration/jupyter_collaboration/handlers.py:342> exception=TypeError('string indices must be integers')>
Traceback (most recent call last):
  File "/Users/carlos/Documents/qs/rtc_dev/jupyterlab_collaboration/jupyter_collaboration/handlers.py", line 365, in maybe_save_document
    if model["content"] != self.room.document.source:
  File "/Users/carlos/Documents/qs/rtc_dev/jupyter_ydoc/jupyter_ydoc/ybasedoc.py", line 60, in source
    return self.get()
  File "/Users/carlos/Documents/qs/rtc_dev/jupyter_ydoc/jupyter_ydoc/ynotebook.py", line 215, in get
    if "id" in cell and meta["nbformat"] == 4 and meta["nbformat_minor"] <= 4:
TypeError: string indices must be integers

The error above is caused by

meta = self._ymeta.to_json()
, because when installing y-py v0.5.5 from conda-forge, the to_json method returns a string.

Support cell.source as [list, of, strings]

Description

https://nbformat.readthedocs.io/en/latest/format_description.html#cell-types

There are a few basic cell types for encapsulating code and text. All cells have the following basic structure:

{
"cell_type" : "type",
"metadata" : {},
"source" : "single string or [list, of, strings]",
}

In Jupyterlab3.x, cell.source is [list, of, strings], which causes

  File "/opt/conda/lib/python3.9/site-packages/jupyter_ydoc/ydoc.py", line 30, in source
    return self.set(value)
  File "/opt/conda/lib/python3.9/site-packages/jupyter_ydoc/ydoc.py", line 197, in set
    self._ycells.extend(t, [self.create_ycell(cell) for cell in cells])
  File "/opt/conda/lib/python3.9/site-packages/jupyter_ydoc/ydoc.py", line 197, in <listcomp>
    self._ycells.extend(t, [self.create_ycell(cell) for cell in cells])
  File "/opt/conda/lib/python3.9/site-packages/jupyter_ydoc/ydoc.py", line 127, in create_ycell
    cell["source"] = Y.YText(cell["source"])
TypeError: argument 'init': 'list' object cannot be converted to 'PyString'

Reproduce

example ipynb json:

{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c8d2523-c9e8-4102-8949-8ec23e2be509",
   "metadata": {},
   "outputs": [],
   "source": [
    "print('hello world')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}

Expected behavior

Context

  • Operating System and version:
  • Browser and version:
  • Jupyter Server version:
Troubleshoot Output
Paste the output from running `jupyter troubleshoot` from the command line here.
You may want to sanitize the paths in the output.
Command Line Output
Paste the output from your command line running `jupyter lab` here, use `--debug` if possible.
Browser Output
Paste the output from your browser Javascript console here, if applicable.

Casting numbers

Could we add code comments for the reason to cast number float <-> int? Like at

cast_all(cell, float, int)

Is it possible to reduce the scope of conversion to reduce the load and avoid troubles? At first glance, that cast sounds like a source of hard-to-find errors in the future especially when applying it to cell serialization.

Improve observe API

The observe API is not aligned with what is defined in the TypeScript API; in particular I don't see how a callback can determine which parts (like ycells, ymeta, ystate for YNotebook) has triggered the event without testing for target (not knowing for sure which are the available event targets).

def observe(self, callback):
self.unobserve()
self._subscriptions[self._ystate] = self._ystate.observe(callback)
self._subscriptions[self._ysource] = self._ysource.observe(callback)

def observe(self, callback):
self.unobserve()
self._subscriptions[self._ystate] = self._ystate.observe(callback)
self._subscriptions[self._ymeta] = self._ymeta.observe(callback)
self._subscriptions[self._ycells] = self._ycells.observe_deep(callback)

For reference, in the frontend, the events are pipe through a dictionary with key highlighting the event target (source, cells,...); see for example: https://github.com/jupyterlab/jupyterlab/blob/c1126de609dd3bcf585a00ecaa8a1104906dd8e7/packages/shared-models/src/api.ts#L488

ModuleNotFoundError: No module named 'pkg_resources'

Description

With jupyter_ydoc==0.2.0 there seems to be an issue with missing pkg_resources:

lerna ERR! yarn run build:prod stderr:
Traceback (most recent call last):
  File "/tmp/build-env-sga8vbqf/bin/jupyter-labextension", line 5, in <module>
    from jupyterlab.labextensions import main
  File "/tmp/build-env-sga8vbqf/lib/python3.10/site-packages/jupyterlab/labextensions.py", line 37, in <module>
    from .labapp import LabApp
  File "/tmp/build-env-sga8vbqf/lib/python3.10/site-packages/jupyterlab/labapp.py", line 15, in <module>
    from jupyter_server_ydoc.ydoc import JupyterSQLiteYStore
  File "/tmp/build-env-sga8vbqf/lib/python3.10/site-packages/jupyter_server_ydoc/__init__.py", line 6, in <module>
    from .ydoc import YDocWebSocketHandler
  File "/tmp/build-env-sga8vbqf/lib/python3.10/site-packages/jupyter_server_ydoc/ydoc.py", line 11, in <module>
    from jupyter_ydoc import ydocs as YDOCS  # type: ignore
  File "/tmp/build-env-sga8vbqf/lib/python3.10/site-packages/jupyter_ydoc/__init__.py", line 1, in <module>
    import pkg_resources

Reproduce

This was noticed in jupyter/notebook#6539 (comment), with this example run: https://github.com/jupyter/notebook/actions/runs/3145280672/jobs/5112365511

In that case notebook uses hatch as the build backend but triggers commands from JupyterLab to build the extension, which probably explains why it hits the jupyter_ydoc path since there is a top-level import.

Expected behavior

Probably there should not be a dependency on pkg_resources?

Context

jupyter/notebook#6539 (comment)

  • Operating System and version: Ubuntu GitHub Actions
  • Browser and version: N/A
  • Jupyter Server version: 1.19.1

Y bridge between YDocs

Problem

One document may have different YDoc structures, e.g. a Jupyter notebook can be opened in JupyterLab as a notebook (and it will be shown nicely with cells etc.) or as a plain text document. While the text document has a relatively simple YDoc, with only a YText for the whole content, the notebook has a YArray for cells, a YMap for metadata, etc. This finer-grained structure allows to observe changes to e.g. a cell source only, and react accordingly.
While a YDoc can be synced with another YDoc of the same structure, for instance between two browsers showing the same notebook, the same document cannot be synced when opened as two different YDocs (e.g. in text/as a notebook) in the same JupyterLab.

Proposed Solution

We could create a "Y bridge", that would make syncing the two "incompatible" YDocs possible. Something that observes changes to a YDoc and translates them to the other. It will require some interpretation, e.g. when changing the cell source in a text document, we need to do the update in the appropriate cell structure in a notebook.

cc @dmonad @hbcarlos

Additional context

See jupyterlab/jupyterlab#12360 (comment).

YNotebook error with y-py v0.6.0

Description

Trying to instantiate and set a YNotebook fails with jupyter_ydoc v0.3.2. Downgrading y-py from v0.6.0 to v0.5.5 fixes the issue.

Reproduce

  1. pip install jupyter_ydoc==0.3.2
  2. Execute the following Python code:
from jupyter_ydoc import YNotebook

nb = {"cells": []}

ynb = YNotebook()
ynb.set(nb)
  1. See error:
Err(PyErr { type: <class 'y_py.MultipleIntegrationError'>, value: MultipleIntegrationError("Cannot integrate a nested Ypy object because is already integrated into a YDoc: {'id': '1d9dbf0a-34ac-46a4-bef9-101fb83d4cea', 'cell_type': 'code', 'execution_count': 1, 'metadata': YMap({}), 'outputs': YArray([]), 'source': YText(1 + 1)}"), traceback: None })

Expected behavior

No error.

Allow for document-agnostic undo/redo

Problem

Currently, data that can be undone (using an undo manager) is not specified. For instance, the content of a YFile is stored in a Text root type under "source", and the cells of a YNotebook are stored in an Array root type under "cells". This prevent an undo manager to operate on a document in an agnostic way, i.e. without knowing what it is manipulating. Indeed, the undo manager needs to be scoped to one or several shared types (see here).

Proposed Solution

We could either:

  • have all the undoable data of a shared document under a root type, for instance "data". For a notebook, that would look like:
{
  "meta": {},
  "data": {
    "cells": []
  },
}

That would be a breaking change.

  • have a special field (maybe under the "state" root Map), that would list the root types that can be undone. For a notebook, that would look like this:
{
  "state": {
    "undo": "cells"
  }
}

That would be an additive change.

Additional context

This is particularly useful in the context of a document timeline explorer.

cc @Meriem-BenIsmail

Invalid notebook in test expectation

Note: the underlying issue might be the cause of jupyter/notebook#6828.

The nb0.ipynb notebook used as an expectation in tests is invalid and does not even render on GitHub:

Screenshot from 2023-04-08 16-08-49

It is used in this test:

@pytest.mark.asyncio
@pytest.mark.parametrize("yjs_client", "0", indirect=True)
async def test_ypy_yjs_0(yws_server, yjs_client):
ydoc = Y.YDoc()
ynotebook = YNotebook(ydoc)
websocket = await connect("ws://localhost:1234/my-roomname")
WebsocketProvider(ydoc, websocket)
nb = stringify_source(json.loads((files_dir / "nb0.ipynb").read_text()))
ynotebook.source = nb
ytest = YTest(ydoc, 3.0)
await ytest.change()
assert ytest.source == nb

And contains two code cells with id set to null:

{
    "cells": [
        {
            "cell_type": "code",
            "source": [
                "print('Hello, World!')"
            ],
            "metadata": {},
            "outputs": [],
            "execution_count": null,
            "id": null
        },
        {
            "cell_type": "code",
            "source": "print('Hello, World!')",
            "metadata": {},
            "outputs": [],
            "execution_count": null,
            "id": null
        }
    ],
    "metadata": {
        "kernelspec": {
            "display_name": "Python 3 (ipykernel)",
            "language": "python",
            "name": "python3"
        },
        "language_info": {
            "codemirror_mode": {
                "name": "ipython",
                "version": 3
            },
            "file_extension": ".py",
            "mimetype": "text/x-python",
            "name": "python",
            "nbconvert_exporter": "python",
            "pygments_lexer": "ipython3",
            "version": "3.10.2"
        }
    },
    "nbformat": 4,
    "nbformat_minor": 5
}

While execution_count can be null, id cannot, compare specification:

Code cells are the primary content of Jupyter notebooks. They contain source code in the language of the documentโ€™s associated kernel, and a list of outputs associated with executing that code. They also have an execution_count, which must be an integer or null.

Since the 4.5 schema release, all cells have an id field which must be a string of length 1-64 with alphanumeric, -, and _ as legal characters to use. These ids must be unique to any given Notebook following the nbformat spec.

And schema:

"execution_count": {
  "description": "The code cell's prompt number. Will be null if the cell has not been run.",
  "type": ["integer", "null"],
  "minimum": 0
}
"definitions": {
    "cell_id": {
      "description": "A string field representing the identifier of this particular cell.",
      "type": "string",
      "pattern": "^[a-zA-Z0-9-_]+$",
      "minLength": 1,
      "maxLength": 64
    }
}

YBaseDoc as abstract class

Should we use an abstract class instead of:

def get(self):
raise RuntimeError("Y document get not implemented")
def set(self, value):
raise RuntimeError("Y document set not implemented")
def observe(self, callback):
raise RuntimeError("Y document observe not implemented")

At least the Python practice is such case advice to raise the more specific NotImplementedError (that derives from RunTimeError).

Dependency on ypy-websocket

Hello.

Why is the dependency on ypy-websocket pinned to the old version?

"ypy-websocket >=0.3.1,<0.4.0",

I'm packaging JupyterLab into Fedora and it seems that the packages work together also with the latest version of the ypy-websocket 0.8.2.

Undo/redo does not scroll to selection

Description

This is one of the most frustrating things for me when using JupyterLab and it prevents me from working productively with larger files - see jupyterlab/jupyterlab#15102

Reproduce

See jupyterlab/jupyterlab#15102

Expected behavior

Editor scrolls on undo/redo if the new selection is outside of the viewport.

Context

This was just fixed in y-codemirror.next: yjs/y-codemirror.next#34 but jupyter_ydoc does not use that and has its own integration of undo manager.

Add YDirectory

Problem

Currently in RTC mode, directories are handled differently than files. The latter use the YDocWebSocketHandler, while the former use HTTP.
With jupyterlab/jupyterlab#13527, JupyterLab drives will use the y-websocket, that will allow to drop the HTTP API for directories.

Proposed Solution

I think we should create a new YDirectory document for these "collaborative drives". It will allow listing directories, renaming/deleting files and directories, etc.

Get/set notebook cell

Problem

Currently, a notebook source can only be changed/retrieved globally through its source attribute, but it could be interesting to be able to change/retrieve only a particular cell.

Proposed Solution

Add YNotebook.set_cell(index, cell) and YNotebook.get_cell(index).

Additional context

In jupyter-server/jupyverse#191 we are experimenting with a kernels REST API that allows to execute and update a cell from the back-end, where this could be useful.

Tests

As mentioned in this comment, we should create tests that:

  • start from a source (text/JSON),
  • create a Ypy document from it,
  • read the Y document using Yjs,
  • generate the source back,
  • check that the sources are identical.

Add type/version information to Y documents

Problem

Currently, a YFile represents a plain text document. This seems to assume that a document with no particular structure should be of text type, while it could also be binary.
Even a YNotebook seems to assume that a notebook will always have the JSON type that we know, but it might not be the case anymore in the future (Jupyter notebooks could evolve towards a Markdown-based format).
These two Y documents were derived from jupyter-server, where we have a file model and a notebook model. The file model can be of type text or binary, but in the latter case it is base64-encoded. The notebook model has a JSON type.

Proposed Solution

I think we should not stick to what jupyter-server is doing, and have:

  • YText: a plain text document.
  • YBytes: a binary document.
  • YJsonNotebook: a JSON-encoded notebook.
  • YMdNotebook: a Markdown-encoded notebook.

Or include the type/version in the Y document instantiation. For instance, keep YFile and YNotebook but instantiate with:

  • YFile("text")/YFile("bytes")
  • YNotebook(version="5.7.1")

Thoughts?

Notebook code cell stream output schema

Problem

Currently, a notebook code cell defines outputs as an untyped YArray where we store the output list as-is. Each item of the outputs can be of different type:

  • stream,
  • display_data,
  • execute_result,
  • error.

But for a stream output type, the structure looks like this (for e.g. an stdout stream):

{
  "output_type": "stream",
  "name": "stdout",
  "text": ["some", "content"]
}

Since the "text" list can be appended to as the cell is executing (by the kernel printing to stdout), the current situation is not great because we cannot observe changes to "text": the whole output in the YArray has to be set, with just that new item in the "text".

Proposed Solution

We should define an output of type stream as follows:

YMap[
  "output_type": "stream",
  "name": str,
  "text": YArray[str]
]

0.3.4: pytest is failing because missing `node_modules/y-websocket/package.json` file

I'm packaging your module as an rpm package so I'm using the typical PEP517 based build, install and test cycle used on building packages from non-root account.

  • python3 -sBm build -w --no-isolation
  • because I'm calling build with --no-isolation I'm using during all processes only locally installed modules
  • install .whl file in </install/prefix>
  • run pytest with $PYTHONPATH pointing to sitearch and sitelib inside </install/prefix>
  • build is performed in env which is cut off from access to the public network (pytest is executed with -m "not network")

On first look looks like some files are missing in git repo.
Here is pytest output:

+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-jupyter-ydoc-0.3.4-2.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-jupyter-ydoc-0.3.4-2.fc35.x86_64/usr/lib/python3.8/site-packages
+ /usr/bin/pytest -ra -m 'not network'
ImportError while loading conftest '/home/tkloczko/rpmbuild/BUILD/jupyter_ydoc-0.3.4/tests/conftest.py'.
tests/conftest.py:26: in <module>
    update_json_file(here.parent / "node_modules/y-websocket/package.json", d)
tests/conftest.py:17: in update_json_file
    with open(path, "rb") as f:
E   FileNotFoundError: [Errno 2] No such file or directory: '/home/tkloczko/rpmbuild/BUILD/jupyter_ydoc-0.3.4/node_modules/y-websocket/package.json'

Here is list of installed modules in build env

Package                       Version
----------------------------- -----------------
aiofiles                      23.1.0
alabaster                     0.7.13
attrs                         22.2.0
Babel                         2.12.1
build                         0.10.0
charset-normalizer            3.1.0
distro                        1.8.0
docutils                      0.19
editables                     0.3
exceptiongroup                1.0.0
gpg                           1.18.0-unknown
hatchling                     1.13.0
idna                          3.4
imagesize                     1.4.1
importlib-metadata            6.1.0
iniconfig                     2.0.0
Jinja2                        3.1.2
libcomps                      0.1.19
markdown-it-py                2.2.0
MarkupSafe                    2.1.2
mdit-py-plugins               0.3.5
mdurl                         0.1.2
myst-parser                   1.0.0
packaging                     23.0
pathspec                      0.11.0
pip                           23.0.1
pluggy                        1.0.0
Pygments                      2.14.0
pyproject_hooks               1.0.0
pytest                        7.2.2
python-dateutil               2.8.2
pytz                          2023.2
PyYAML                        6.0
requests                      2.28.2
rpm                           4.17.0
six                           1.16.0
snowballstemmer               2.2.0
Sphinx                        6.1.3
sphinxcontrib-applehelp       1.0.4
sphinxcontrib-devhelp         1.0.2.dev20230202
sphinxcontrib-htmlhelp        2.0.0
sphinxcontrib-jsmath          1.0.1.dev20230128
sphinxcontrib-qthelp          1.0.3.dev20230128
sphinxcontrib-serializinghtml 1.1.5
tomli                         2.0.1
typing_extensions             4.5.0
urllib3                       1.26.15
websockets                    10.4
wheel                         0.38.4
y-py                          0.6.1
ypy-websocket                 0.8.4
zipp                          3.15.0

Unable to open Notebook without metadata

Description

I am getting the following:

    Traceback (most recent call last):
      File "/home/martinrenou/micromamba/envs/glue-lab/lib/python3.11/site-packages/tornado/websocket.py", line 944, in _accept_connection
        await open_result
      File "/home/martinrenou/micromamba/envs/glue-lab/lib/python3.11/site-packages/jupyter_collaboration/handlers.py", line 236, in open
        if self.room.document.source != model["content"]:
           ^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/martinrenou/micromamba/envs/glue-lab/lib/python3.11/site-packages/jupyter_ydoc/ybasedoc.py", line 60, in source
        return self.get()
               ^^^^^^^^^^
      File "/home/martinrenou/micromamba/envs/glue-lab/lib/python3.11/site-packages/jupyter_ydoc/ynotebook.py", line 213, in get
        metadata=meta["metadata"],
                 ~~~~^^^^^^^^^^^^
    KeyError: 'metadata'

Leading to not being able to open the Notebook (it's an empty area on Jupyterlab)

Add model version

Problem

If we ever want to change or extend the document models, they will become incompatible when collaborating with others (independent of which nbformat they are using). We should attach some sort of version to document models and disallow editing of documents that use other models.

From jupyterlab/jupyterlab#2475

Proposed Solution

Add version class attribute to the document.

Flaky test failure: Duplicate cells due to lingering node processes

Description

test_ypy_yjs_0[0] randomly fails in comparing the generated code cells to the reference. This behavior is flaky, though. Sometimes there is only one cell, sometimes there are multiple duplicates:

Reproduce

wget https://github.com/jupyter-server/jupyter_ydoc/archive/refs/tags/v1.0.2.tar.gz
tar xf v1.0.2.tar.gz
pushd jupyter_ydoc-1.0.2
pushd javascript
yarn
yarn build
popd
python3 -m venv testenv
testenv/bin/pip install '.[test]'
for i in 1 2 3 4 5 6; do
  testenv/bin/python -m pytest -vv
done

Result:


=================================================================== test session starts ====================================================================
platform linux -- Python 3.10.11, pytest-7.3.2, pluggy-1.0.0 -- /tmp/jupyter_ydoc-1.0.2/testenv/bin/python
cachedir: .pytest_cache
rootdir: /tmp/jupyter_ydoc-1.0.2
configfile: pytest.ini
plugins: asyncio-0.21.0
asyncio: mode=auto
collected 2 items                                                                                                                                          

tests/test_ypy_yjs.py::test_ypy_yjs_0[0] FAILED                                                                                                      [ 50%]
tests/test_ypy_yjs.py::test_plotly_renderer PASSED                                                                                                   [100%]

========================================================================= FAILURES =========================================================================
____________________________________________________________________ test_ypy_yjs_0[0] _____________________________________________________________________

yws_server = <ypy_websocket.websocket_server.WebsocketServer object at 0x7fc166656800>
yjs_client = <Popen: returncode: None args: 'yarn node /tmp/jupyter_ydoc-1.0.2/tests/yjs_...>

    @pytest.mark.asyncio
    @pytest.mark.parametrize("yjs_client", "0", indirect=True)
    async def test_ypy_yjs_0(yws_server, yjs_client):
        ydoc = Y.YDoc()
        ynotebook = YNotebook(ydoc)
        websocket = await connect("ws://localhost:1234/my-roomname")
        WebsocketProvider(ydoc, websocket)
        nb = stringify_source(json.loads((files_dir / "nb0.ipynb").read_text()))
        ynotebook.source = nb
        ytest = YTest(ydoc, 3.0)
        await ytest.change()
>       assert ytest.source == nb
E       assert {'nbformat_minor': 5, 'metadata': {'language_info': {'codemirror_mode': {'version': 3, 'name': 'ipython'}, 'file_extension': '.py', 'mimetype': 'text/x-python', 'version': '3.10.2', 'nbconvert_exporter': 'python', 'name': 'python', 'pygments_lexer': 'ipython3'}, 'kernelspec': {'language': 'python', 'display_name': 'Python 3 (ipykernel)', 'name': 'python3'}}, 'cells': [{'metadata': {}, 'source': "print('Hello, World!')", 'cell_type': 'code', 'execution_count': None, 'outputs': [], 'id': None}, {'id': None, 'metadata': {}, 'cell_type': 'code', 'execution_count': None, 'source': "print('Hello, World!')", 'outputs': []}, {'metadata': {}, 'cell_type': 'code', 'source': "print('Hello, World!')", 'outputs': [], 'id': None, 'execution_count': None}, {'source': "print('Hello, World!')", 'metadata': {}, 'outputs': [], 'id': None, 'execution_count': None, 'cell_type': 'code'}, {'metadata': {}, 'source': "print('Hello, World!')", 'outputs': [], 'execution_count': None, 'id': None, 'cell_type': 'code'}, {'execution_count': None, 'cell_type': 'code', 'source': "print('Hello, World!')", 'outputs': [], 'id': None, 'metadata': {}}], 'nbformat': 4} == {'cells': [{'cell_type': 'code', 'source': "print('Hello, World!')", 'metadata': {}, 'outputs': [], 'execution_count': None, 'id': None}, {'cell_type': 'code', 'source': "print('Hello, World!')", 'metadata': {}, 'outputs': [], 'execution_count': None, 'id': None}], 'metadata': {'kernelspec': {'display_name': 'Python 3 (ipykernel)', 'language': 'python', 'name': 'python3'}, 'language_info': {'codemirror_mode': {'name': 'ipython', 'version': 3}, 'file_extension': '.py', 'mimetype': 'text/x-python', 'name': 'python', 'nbconvert_exporter': 'python', 'pygments_lexer': 'ipython3', 'version': '3.10.2'}}, 'nbformat': 4, 'nbformat_minor': 5}
E         Common items:
E         {'metadata': {'kernelspec': {'display_name': 'Python 3 (ipykernel)',
E                                      'language': 'python',
E                                      'name': 'python3'},
E                       'language_info': {'codemirror_mode': {'name': 'ipython',
E                                                             'version': 3},
E                                         'file_extension': '.py',
E                                         'mimetype': 'text/x-python',
E                                         'name': 'python',
E                                         'nbconvert_exporter': 'python',
E                                         'pygments_lexer': 'ipython3',
E                                         'version': '3.10.2'}},
E          'nbformat': 4,
E          'nbformat_minor': 5}
E         Differing items:
E         {'cells': [{'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}, {'cell_type': 'code', 'exe...ne, 'id': None, 'metadata': {}, ...}, {'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}]} != {'cells': [{'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}, {'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}]}
E         Full diff:
E           {
E            'cells': [{'cell_type': 'code',
E         +             'execution_count': None,
E         +             'id': None,
E         +             'metadata': {},
E         +             'outputs': [],
E         +             'source': "print('Hello, World!')"},
E         +            {'cell_type': 'code',
E         +             'execution_count': None,
E         +             'id': None,
E         +             'metadata': {},
E         +             'outputs': [],
E         +             'source': "print('Hello, World!')"},
E         +            {'cell_type': 'code',
E         +             'execution_count': None,
E         +             'id': None,
E         +             'metadata': {},
E         +             'outputs': [],
E         +             'source': "print('Hello, World!')"},
E         +            {'cell_type': 'code',
E         +             'execution_count': None,
E         +             'id': None,
E         +             'metadata': {},
E         +             'outputs': [],
E         +             'source': "print('Hello, World!')"},
E         +            {'cell_type': 'code',
E                       'execution_count': None,
E                       'id': None,
E                       'metadata': {},
E                       'outputs': [],
E                       'source': "print('Hello, World!')"},
E                      {'cell_type': 'code',
E                       'execution_count': None,
E                       'id': None,
E                       'metadata': {},
E                       'outputs': [],
E                       'source': "print('Hello, World!')"}],
E            'metadata': {'kernelspec': {'display_name': 'Python 3 (ipykernel)',
E                                        'language': 'python',
E                                        'name': 'python3'},
E                         'language_info': {'codemirror_mode': {'name': 'ipython',
E                                                               'version': 3},
E                                           'file_extension': '.py',
E                                           'mimetype': 'text/x-python',
E                                           'name': 'python',
E                                           'nbconvert_exporter': 'python',
E                                           'pygments_lexer': 'ipython3',
E                                           'version': '3.10.2'}},
E            'nbformat': 4,
E            'nbformat_minor': 5,
E           }

tests/test_ypy_yjs.py:62: AssertionError
================================================================= short test summary info ==================================================================
FAILED tests/test_ypy_yjs.py::test_ypy_yjs_0[0] - assert {'nbformat_minor': 5, 'metadata': {'language_info': {'codemirror_mode': {'version': 3, 'name': 'ipython'}, 'file_extension': '.py', 'mimetype': ...
=============================================================== 1 failed, 1 passed in 0.36s ================================================================
=================================================================== test session starts ====================================================================
platform linux -- Python 3.10.11, pytest-7.3.2, pluggy-1.0.0 -- /tmp/jupyter_ydoc-1.0.2/testenv/bin/python
cachedir: .pytest_cache
rootdir: /tmp/jupyter_ydoc-1.0.2
configfile: pytest.ini
plugins: asyncio-0.21.0
asyncio: mode=auto
collected 2 items                                                                                                                                          

tests/test_ypy_yjs.py::test_ypy_yjs_0[0] PASSED                                                                                                      [ 50%]
tests/test_ypy_yjs.py::test_plotly_renderer PASSED                                                                                                   [100%]

==================================================================== 2 passed in 0.12s =====================================================================
=================================================================== test session starts ====================================================================
platform linux -- Python 3.10.11, pytest-7.3.2, pluggy-1.0.0 -- /tmp/jupyter_ydoc-1.0.2/testenv/bin/python
cachedir: .pytest_cache
rootdir: /tmp/jupyter_ydoc-1.0.2
configfile: pytest.ini
plugins: asyncio-0.21.0
asyncio: mode=auto
collected 2 items                                                                                                                                          

tests/test_ypy_yjs.py::test_ypy_yjs_0[0] PASSED                                                                                                      [ 50%]
tests/test_ypy_yjs.py::test_plotly_renderer PASSED                                                                                                   [100%]

==================================================================== 2 passed in 0.15s =====================================================================
=================================================================== test session starts ====================================================================
platform linux -- Python 3.10.11, pytest-7.3.2, pluggy-1.0.0 -- /tmp/jupyter_ydoc-1.0.2/testenv/bin/python
cachedir: .pytest_cache
rootdir: /tmp/jupyter_ydoc-1.0.2
configfile: pytest.ini
plugins: asyncio-0.21.0
asyncio: mode=auto
collected 2 items                                                                                                                                          

tests/test_ypy_yjs.py::test_ypy_yjs_0[0] PASSED                                                                                                      [ 50%]
tests/test_ypy_yjs.py::test_plotly_renderer PASSED                                                                                                   [100%]

==================================================================== 2 passed in 0.14s =====================================================================
=================================================================== test session starts ====================================================================
platform linux -- Python 3.10.11, pytest-7.3.2, pluggy-1.0.0 -- /tmp/jupyter_ydoc-1.0.2/testenv/bin/python
cachedir: .pytest_cache
rootdir: /tmp/jupyter_ydoc-1.0.2
configfile: pytest.ini
plugins: asyncio-0.21.0
asyncio: mode=auto
collected 2 items                                                                                                                                          

tests/test_ypy_yjs.py::test_ypy_yjs_0[0] PASSED                                                                                                      [ 50%]
tests/test_ypy_yjs.py::test_plotly_renderer PASSED                                                                                                   [100%]

==================================================================== 2 passed in 0.14s =====================================================================
Task exception was never retrieved
future: <Task finished name='Task-70' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
=================================================================== test session starts ====================================================================
platform linux -- Python 3.10.11, pytest-7.3.2, pluggy-1.0.0 -- /tmp/jupyter_ydoc-1.0.2/testenv/bin/python
cachedir: .pytest_cache
rootdir: /tmp/jupyter_ydoc-1.0.2
configfile: pytest.ini
plugins: asyncio-0.21.0
asyncio: mode=auto
collected 2 items                                                                                                                                          

tests/test_ypy_yjs.py::test_ypy_yjs_0[0] FAILED                                                                                                      [ 50%]
tests/test_ypy_yjs.py::test_plotly_renderer PASSED                                                                                                   [100%]

========================================================================= FAILURES =========================================================================
____________________________________________________________________ test_ypy_yjs_0[0] _____________________________________________________________________

yws_server = <ypy_websocket.websocket_server.WebsocketServer object at 0x7f60ceffebf0>
yjs_client = <Popen: returncode: None args: 'yarn node /tmp/jupyter_ydoc-1.0.2/tests/yjs_...>

    @pytest.mark.asyncio
    @pytest.mark.parametrize("yjs_client", "0", indirect=True)
    async def test_ypy_yjs_0(yws_server, yjs_client):
        ydoc = Y.YDoc()
        ynotebook = YNotebook(ydoc)
        websocket = await connect("ws://localhost:1234/my-roomname")
        WebsocketProvider(ydoc, websocket)
        nb = stringify_source(json.loads((files_dir / "nb0.ipynb").read_text()))
        ynotebook.source = nb
        ytest = YTest(ydoc, 3.0)
        await ytest.change()
>       assert ytest.source == nb
E       assert {'nbformat_minor': 5, 'cells': [{'id': None, 'execution_count': None, 'source': "print('Hello, World!')", 'metadata': {}, 'cell_type': 'code', 'outputs': []}, {'outputs': [], 'id': None, 'execution_count': None, 'cell_type': 'code', 'source': "print('Hello, World!')", 'metadata': {}}, {'source': "print('Hello, World!')", 'cell_type': 'code', 'id': None, 'outputs': [], 'metadata': {}, 'execution_count': None}, {'source': "print('Hello, World!')", 'outputs': [], 'metadata': {}, 'id': None, 'cell_type': 'code', 'execution_count': None}], 'metadata': {'language_info': {'name': 'python', 'file_extension': '.py', 'version': '3.10.2', 'nbconvert_exporter': 'python', 'pygments_lexer': 'ipython3', 'mimetype': 'text/x-python', 'codemirror_mode': {'version': 3, 'name': 'ipython'}}, 'kernelspec': {'language': 'python', 'name': 'python3', 'display_name': 'Python 3 (ipykernel)'}}, 'nbformat': 4} == {'cells': [{'cell_type': 'code', 'source': "print('Hello, World!')", 'metadata': {}, 'outputs': [], 'execution_count': None, 'id': None}, {'cell_type': 'code', 'source': "print('Hello, World!')", 'metadata': {}, 'outputs': [], 'execution_count': None, 'id': None}], 'metadata': {'kernelspec': {'display_name': 'Python 3 (ipykernel)', 'language': 'python', 'name': 'python3'}, 'language_info': {'codemirror_mode': {'name': 'ipython', 'version': 3}, 'file_extension': '.py', 'mimetype': 'text/x-python', 'name': 'python', 'nbconvert_exporter': 'python', 'pygments_lexer': 'ipython3', 'version': '3.10.2'}}, 'nbformat': 4, 'nbformat_minor': 5}
E         Common items:
E         {'metadata': {'kernelspec': {'display_name': 'Python 3 (ipykernel)',
E                                      'language': 'python',
E                                      'name': 'python3'},
E                       'language_info': {'codemirror_mode': {'name': 'ipython',
E                                                             'version': 3},
E                                         'file_extension': '.py',
E                                         'mimetype': 'text/x-python',
E                                         'name': 'python',
E                                         'nbconvert_exporter': 'python',
E                                         'pygments_lexer': 'ipython3',
E                                         'version': '3.10.2'}},
E          'nbformat': 4,
E          'nbformat_minor': 5}
E         Differing items:
E         {'cells': [{'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}, {'cell_type': 'code', 'exe...ne, 'id': None, 'metadata': {}, ...}, {'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}]} != {'cells': [{'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}, {'cell_type': 'code', 'execution_count': None, 'id': None, 'metadata': {}, ...}]}
E         Full diff:
E           {
E            'cells': [{'cell_type': 'code',
E         +             'execution_count': None,
E         +             'id': None,
E         +             'metadata': {},
E         +             'outputs': [],
E         +             'source': "print('Hello, World!')"},
E         +            {'cell_type': 'code',
E         +             'execution_count': None,
E         +             'id': None,
E         +             'metadata': {},
E         +             'outputs': [],
E         +             'source': "print('Hello, World!')"},
E         +            {'cell_type': 'code',
E                       'execution_count': None,
E                       'id': None,
E                       'metadata': {},
E                       'outputs': [],
E                       'source': "print('Hello, World!')"},
E                      {'cell_type': 'code',
E                       'execution_count': None,
E                       'id': None,
E                       'metadata': {},
E                       'outputs': [],
E                       'source': "print('Hello, World!')"}],
E            'metadata': {'kernelspec': {'display_name': 'Python 3 (ipykernel)',
E                                        'language': 'python',
E                                        'name': 'python3'},
E                         'language_info': {'codemirror_mode': {'name': 'ipython',
E                                                               'version': 3},
E                                           'file_extension': '.py',
E                                           'mimetype': 'text/x-python',
E                                           'name': 'python',
E                                           'nbconvert_exporter': 'python',
E                                           'pygments_lexer': 'ipython3',
E                                           'version': '3.10.2'}},
E            'nbformat': 4,
E            'nbformat_minor': 5,
E           }

tests/test_ypy_yjs.py:62: AssertionError
------------------------------------------------------------------ Captured log teardown -------------------------------------------------------------------
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-128' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 935, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-116' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-120' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-124' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-115' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-119' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-123' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-127' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-114' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-118' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-122' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-126' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-117' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-121' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
future: <Task finished name='Task-129' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
================================================================= short test summary info ==================================================================
FAILED tests/test_ypy_yjs.py::test_ypy_yjs_0[0] - assert {'nbformat_minor': 5, 'cells': [{'id': None, 'execution_count': None, 'source': "print('Hello, World!')", 'metadata': {}, 'cell_type': 'code', '...
=============================================================== 1 failed, 1 passed in 0.16s ================================================================
Task exception was never retrieved
future: <Task finished name='Task-125' coro=<WebSocketCommonProtocol.send() done, defined at /tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py:580> exception=ConnectionClosedOK(Close(code=1001, reason=''), Close(code=1001, reason=''), False)>
Traceback (most recent call last):
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
    await self.ensure_open()
  File "/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages/websockets/legacy/protocol.py", line 944, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1001 (going away); then received 1001 (going away)
[ben@skylab:/tmp/jupyter_ydoc-1.0.2]%          

Full OpenSUSE Build Service log: jupyter-ydoc-obs-build.txt

Expected behavior

Consistent test suite sucess

Context

  • Operating System and version: openSUSE Tumbleweed
  • Browser and version: N/A
  • Jupyter Server version: N/A
Troubleshoot Output
[ben@skylab:/tmp/jupyter_ydoc-1.0.2]% testenv/bin/jupyter-troubleshoot

[notice] A new release of pip is available: 23.0.1 -> 23.1.2
[notice] To update, run: python3 -m pip install --upgrade pip
$PATH:
/home/ben/bin
/home/ben/go/bin
/home/ben/.local/bin
/usr/local/bin
/usr/bin
/bin

sys.path:
/tmp/jupyter_ydoc-1.0.2/testenv/bin
/home/ben/.python-bens-sitelib
/usr/lib64/python310.zip
/usr/lib64/python3.10
/usr/lib64/python3.10/lib-dynload
/tmp/jupyter_ydoc-1.0.2/testenv/lib64/python3.10/site-packages
/tmp/jupyter_ydoc-1.0.2/testenv/lib/python3.10/site-packages

sys.executable:
/tmp/jupyter_ydoc-1.0.2/testenv/bin/python3

sys.version:
3.10.11 (main, Apr 27 2023, 21:52:36) [GCC]

platform.platform():
Linux-6.3.4-1-default-x86_64-with-glibc2.37

which -a jupyter:
/usr/bin/jupyter
/bin/jupyter

pip list:
Package Version
-------------- -------
aiofiles 22.1.0
aiosqlite 0.19.0
cfgv 3.3.1
distlib 0.3.6
exceptiongroup 1.1.1
filelock 3.12.2
identify 2.5.24
iniconfig 2.0.0
jupyter_core 5.3.0
jupyter-ydoc 1.0.2
nodeenv 1.8.0
packaging 23.1
pip 23.0.1
platformdirs 3.5.3
pluggy 1.0.0
pre-commit 3.3.2
pytest 7.3.2
pytest-asyncio 0.21.0
PyYAML 6.0
setuptools 65.5.0
tomli 2.0.1
traitlets 5.9.0
virtualenv 20.23.0
websockets 11.0.3
y-py 0.6.0
ypy-websocket 0.8.4

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.