diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd2f52c..446f797 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,7 +44,7 @@ jobs: uses: mamba-org/provision-with-micromamba@main with: environment-file: environment-dev.yml - environment-name: ypy-websocket + environment-name: pycrdt-websocket extra-specs: python=${{ matrix.python-version }} - name: Install dependencies run: | @@ -52,7 +52,7 @@ jobs: cd tests; npm install - name: Check types run: | - mypy ypy_websocket tests + mypy pycrdt_websocket tests - name: Run tests run: | pytest -v diff --git a/.gitignore b/.gitignore index b41cf15..cf07a60 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,5 @@ dmypy.json # test JS dependencies tests/node_modules tests/package-lock.json +# pixi environments +.pixi diff --git a/README.md b/README.md index cd5a679..12b6ee5 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# Ypy-websocket +# Pycrdt-websocket -Ypy-websocket is an async WebSocket connector for Ypy. +Pycrdt-websocket is an async WebSocket connector for pycrdt. -[![Build Status](https://github.com/y-crdt/ypy-websocket/workflows/CI/badge.svg)](https://github.com/y-crdt/ypy-websocket/actions) +[![Build Status](https://github.com/davidbrochart/pycrdt-websocket/workflows/CI/badge.svg)](https://github.com/davidbrochart/pycrdt-websocket/actions) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) --- -**Documentation**: https://davidbrochart.github.io/ypy-websocket +**Documentation**: https://davidbrochart.github.io/pycrdt-websocket -**Source Code**: https://github.com/y-crdt/ypy-websocket +**Source Code**: https://github.com/davidbrochart/pycrdt-websocket --- -Ypy-websocket is a Python library for building WebSocket servers and clients that connect and synchronize shared documents. +Pycrdt-websocket is a Python library for building WebSocket servers and clients that connect and synchronize shared documents. It can be used to create collaborative web applications. The following diagram illustrates a typical architecture. The goal is to share a document among several clients. diff --git a/RELEASE.md b/RELEASE.md index a54715d..52e11bb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,4 +1,4 @@ -# Making a ypy-websocket release +# Making a pycrdt-websocket release ## Using `jupyter_releaser` diff --git a/docs/install.md b/docs/install.md index 165b951..a61e6b9 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,13 +1,13 @@ -Ypy-websocket can be installed from [PyPI](https://pypi.org) using `pip` or from [conda-forge](https://conda-forge.org) using `micromamba`. +Pycrdt-websocket can be installed from [PyPI](https://pypi.org) using `pip` or from [conda-forge](https://conda-forge.org) using `micromamba`. ## With `pip` ```console -pip install ypy-websocket +pip install pycrdt-websocket ``` ## With `micromamba` ```console -micromamba install -c conda-forge ypy-websocket +micromamba install -c conda-forge pycrdt-websocket ``` diff --git a/docs/reference/ASGI_server.md b/docs/reference/ASGI_server.md index e4ef3a7..ef4e1c9 100644 --- a/docs/reference/ASGI_server.md +++ b/docs/reference/ASGI_server.md @@ -1 +1 @@ -::: ypy_websocket.asgi_server.ASGIServer +::: pycrdt_websocket.asgi_server.ASGIServer diff --git a/docs/reference/Django_Channels_consumer.md b/docs/reference/Django_Channels_consumer.md index 0eef186..8548b4e 100644 --- a/docs/reference/Django_Channels_consumer.md +++ b/docs/reference/Django_Channels_consumer.md @@ -1 +1 @@ -::: ypy_websocket.django_channels_consumer.YjsConsumer +::: pycrdt_websocket.django_channels_consumer.YjsConsumer diff --git a/docs/reference/Room.md b/docs/reference/Room.md index 62ebaae..a92b5ce 100644 --- a/docs/reference/Room.md +++ b/docs/reference/Room.md @@ -1 +1 @@ -::: ypy_websocket.yroom.YRoom +::: pycrdt_websocket.yroom.YRoom diff --git a/docs/reference/Store.md b/docs/reference/Store.md index cafbf85..de53434 100644 --- a/docs/reference/Store.md +++ b/docs/reference/Store.md @@ -1,15 +1,15 @@ ## BaseYStore -::: ypy_websocket.ystore.BaseYStore +::: pycrdt_websocket.ystore.BaseYStore ## FileYStore -::: ypy_websocket.ystore.FileYStore +::: pycrdt_websocket.ystore.FileYStore ## TempFileYStore -::: ypy_websocket.ystore.TempFileYStore +::: pycrdt_websocket.ystore.TempFileYStore ## SQLiteYStore -::: ypy_websocket.ystore.SQLiteYStore +::: pycrdt_websocket.ystore.SQLiteYStore diff --git a/docs/reference/WebSocket.md b/docs/reference/WebSocket.md index 98702b0..3aef1ae 100644 --- a/docs/reference/WebSocket.md +++ b/docs/reference/WebSocket.md @@ -1 +1 @@ -::: ypy_websocket.websocket.Websocket +::: pycrdt_websocket.websocket.Websocket diff --git a/docs/reference/WebSocket_provider.md b/docs/reference/WebSocket_provider.md index bb2a8dd..d96408b 100644 --- a/docs/reference/WebSocket_provider.md +++ b/docs/reference/WebSocket_provider.md @@ -1 +1 @@ -::: ypy_websocket.websocket_provider.WebsocketProvider +::: pycrdt_websocket.websocket_provider.WebsocketProvider diff --git a/docs/reference/WebSocket_server.md b/docs/reference/WebSocket_server.md index dab6145..51cfe75 100644 --- a/docs/reference/WebSocket_server.md +++ b/docs/reference/WebSocket_server.md @@ -1 +1 @@ -::: ypy_websocket.websocket_server.WebsocketServer +::: pycrdt_websocket.websocket_server.WebsocketServer diff --git a/docs/usage/client.md b/docs/usage/client.md index 58a0200..6cc2183 100644 --- a/docs/usage/client.md +++ b/docs/usage/client.md @@ -5,7 +5,7 @@ Here is a code example using the [websockets](https://websockets.readthedocs.io) import asyncio import y_py as Y from websockets import connect -from ypy_websocket import WebsocketProvider +from pycrdt_websocket import WebsocketProvider async def client(): ydoc = Y.YDoc() diff --git a/docs/usage/server.md b/docs/usage/server.md index 5b1d5e0..cb53d3c 100644 --- a/docs/usage/server.md +++ b/docs/usage/server.md @@ -4,7 +4,7 @@ Here is a code example using the [websockets](https://websockets.readthedocs.io) ```py import asyncio from websockets import serve -from ypy_websocket import WebsocketServer +from pycrdt_websocket import WebsocketServer async def server(): async with ( @@ -15,12 +15,12 @@ async def server(): asyncio.run(server()) ``` -Ypy-websocket can also be used with an [ASGI](https://asgi.readthedocs.io) server. Here is a code example using [Uvicorn](https://www.uvicorn.org): +Pycrdt-websocket can also be used with an [ASGI](https://asgi.readthedocs.io) server. Here is a code example using [Uvicorn](https://www.uvicorn.org): ```py # main.py import asyncio import uvicorn -from ypy_websocket import ASGIServer, WebsocketServer +from pycrdt_websocket import ASGIServer, WebsocketServer websocket_server = WebsocketServer() app = ASGIServer(websocket_server) diff --git a/environment-dev.yml b/environment-dev.yml index 29f5f37..b39383f 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -1,4 +1,4 @@ -name: ypy-websocket +name: pycrdt-websocket channels: - conda-forge dependencies: diff --git a/mkdocs.yml b/mkdocs.yml index 01c586a..5e15754 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,6 +1,6 @@ -site_name: ypy-websocket -site_description: Async WebSocket connector for Ypy -repo_url: https://github.com/y-crdt/ypy-websocket +site_name: pycrdt-websocket +site_description: Async WebSocket connector for pycrdt +repo_url: https://github.com/davidbrochart/pycrdt-websocket theme: name: 'material' diff --git a/ypy_websocket/__init__.py b/pycrdt_websocket/__init__.py similarity index 100% rename from ypy_websocket/__init__.py rename to pycrdt_websocket/__init__.py diff --git a/ypy_websocket/asgi_server.py b/pycrdt_websocket/asgi_server.py similarity index 100% rename from ypy_websocket/asgi_server.py rename to pycrdt_websocket/asgi_server.py diff --git a/ypy_websocket/awareness.py b/pycrdt_websocket/awareness.py similarity index 100% rename from ypy_websocket/awareness.py rename to pycrdt_websocket/awareness.py diff --git a/ypy_websocket/django_channels_consumer.py b/pycrdt_websocket/django_channels_consumer.py similarity index 97% rename from ypy_websocket/django_channels_consumer.py rename to pycrdt_websocket/django_channels_consumer.py index 874493d..c2e6e21 100644 --- a/ypy_websocket/django_channels_consumer.py +++ b/pycrdt_websocket/django_channels_consumer.py @@ -82,8 +82,8 @@ class YjsConsumer(AsyncWebsocketConsumer): from pycrdt import Doc from asgiref.sync import async_to_sync from channels.layers import get_channel_layer - from ypy_websocket.django_channels_consumer import YjsConsumer - from ypy_websocket.yutils import create_update_message + from pycrdt_websocket.django_channels_consumer import YjsConsumer + from pycrdt_websocket.yutils import create_update_message class DocConsumer(YjsConsumer): diff --git a/ypy_websocket/py.typed b/pycrdt_websocket/py.typed similarity index 100% rename from ypy_websocket/py.typed rename to pycrdt_websocket/py.typed diff --git a/ypy_websocket/websocket.py b/pycrdt_websocket/websocket.py similarity index 100% rename from ypy_websocket/websocket.py rename to pycrdt_websocket/websocket.py diff --git a/ypy_websocket/websocket_provider.py b/pycrdt_websocket/websocket_provider.py similarity index 100% rename from ypy_websocket/websocket_provider.py rename to pycrdt_websocket/websocket_provider.py diff --git a/ypy_websocket/websocket_server.py b/pycrdt_websocket/websocket_server.py similarity index 100% rename from ypy_websocket/websocket_server.py rename to pycrdt_websocket/websocket_server.py diff --git a/ypy_websocket/yroom.py b/pycrdt_websocket/yroom.py similarity index 100% rename from ypy_websocket/yroom.py rename to pycrdt_websocket/yroom.py diff --git a/ypy_websocket/ystore.py b/pycrdt_websocket/ystore.py similarity index 100% rename from ypy_websocket/ystore.py rename to pycrdt_websocket/ystore.py diff --git a/ypy_websocket/yutils.py b/pycrdt_websocket/yutils.py similarity index 96% rename from ypy_websocket/yutils.py rename to pycrdt_websocket/yutils.py index b7d9f6a..56611c3 100644 --- a/ypy_websocket/yutils.py +++ b/pycrdt_websocket/yutils.py @@ -99,7 +99,7 @@ def read_var_string(self): def put_updates(update_send_stream: MemoryObjectSendStream, event: TransactionEvent) -> None: try: - update = event.get_update() # type: ignore + update = event.update # type: ignore update_send_stream.send_nowait(update) except Exception: pass @@ -128,7 +128,6 @@ async def process_sync_message(message: bytes, ydoc: Doc, websocket, log) -> Non YSyncMessageType.SYNC_UPDATE, ): update = read_message(msg) - # Ignore empty updates (see https://github.com/y-crdt/ypy/issues/98) if update != b"\x00\x00": ydoc.apply_update(update) diff --git a/pyproject.toml b/pyproject.toml index 7d2aecf..baf0faf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,12 +3,12 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "ypy-websocket" +name = "pycrdt-websocket" dynamic = ["version"] -description = "WebSocket connector for Ypy" +description = "WebSocket connector for pycrdt" license = { file = "LICENSE" } readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" authors = [ { name = "David Brochart", email = "david.brochart@gmail.com" }, ] @@ -21,17 +21,16 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] dependencies = [ "anyio >=3.6.2,<5", "aiosqlite >=0.18.0,<1", - "pycrdt >=0.4.1,<0.5.0", - "typing_extensions; python_version < '3.8'", + "pycrdt >=0.7.0,<0.8.0", ] [project.optional-dependencies] @@ -53,17 +52,17 @@ django = [ ] [project.urls] -Homepage = "https://github.com/y-crdt/ypy-websocket" -Source = "https://github.com/y-crdt/ypy-websocket" -Issues = "https://github.com/y-crdt/ypy-websocket/issues" -Pypi = "https://pypi.org/project/ypy-websocket" +Homepage = "https://github.com/davidbrochart/pycrdt-websocket" +Source = "https://github.com/davidbrochart/pycrdt-websocket" +Issues = "https://github.com/davidbrochart/pycrdt-websocket/issues" +Pypi = "https://pypi.org/project/pycrdt-websocket" [tool.hatch.version] -path = "ypy_websocket/__init__.py" +path = "pycrdt_websocket/__init__.py" [tool.hatch.build.targets.sdist] include = [ - "/ypy_websocket", + "/pycrdt_websocket", "/tests", ] diff --git a/tests/conftest.py b/tests/conftest.py index 32b7b92..0aedcc9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,14 +4,13 @@ from pycrdt import Array, Doc from websockets import serve # type: ignore -from ypy_websocket import WebsocketServer +from pycrdt_websocket import WebsocketServer class TestYDoc: def __init__(self): self.ydoc = Doc() - self.array = Array() - self.ydoc["array"] = self.array + self.ydoc["array"] = self.array = Array() self.state = None self.value = 0 diff --git a/tests/test_asgi.py b/tests/test_asgi.py index fed32dd..000649c 100644 --- a/tests/test_asgi.py +++ b/tests/test_asgi.py @@ -4,7 +4,7 @@ from pycrdt import Doc, Map from websockets import connect # type: ignore -from ypy_websocket import ASGIServer, WebsocketProvider, WebsocketServer +from pycrdt_websocket import ASGIServer, WebsocketProvider, WebsocketServer websocket_server = WebsocketServer(auto_clean_rooms=False) app = ASGIServer(websocket_server) @@ -23,8 +23,7 @@ async def test_asgi(unused_tcp_port): # clients # client 1 ydoc1 = Doc() - ymap1 = Map() - ydoc1["map"] = ymap1 + ydoc1["map"] = ymap1 = Map() ymap1["key"] = "value" async with connect( f"ws://localhost:{unused_tcp_port}/my-roomname" # noqa @@ -38,8 +37,7 @@ async def test_asgi(unused_tcp_port): ) as websocket2, WebsocketProvider(ydoc2, websocket2): await sleep(0.1) - ymap2 = Map() - ydoc2["map"] = ymap2 + ydoc2["map"] = ymap2 = Map() assert str(ymap2) == '{"key":"value"}' tg.cancel_scope.cancel() diff --git a/tests/test_ypy_yjs.py b/tests/test_pycrdt_yjs.py similarity index 79% rename from tests/test_ypy_yjs.py rename to tests/test_pycrdt_yjs.py index b962d2a..2664a5b 100644 --- a/tests/test_ypy_yjs.py +++ b/tests/test_pycrdt_yjs.py @@ -5,15 +5,14 @@ from pycrdt import Array, Doc, Map from websockets import connect # type: ignore -from ypy_websocket import WebsocketProvider +from pycrdt_websocket import WebsocketProvider class YTest: def __init__(self, ydoc: Doc, timeout: float = 1.0): self.ydoc = ydoc self.timeout = timeout - self.ytest = Map() - self.ydoc["_test"] = self.ytest + self.ydoc["_test"] = self.ytest = Map() self.clock = -1.0 def run_clock(self): @@ -40,14 +39,13 @@ def callback(event): @pytest.mark.anyio @pytest.mark.parametrize("yjs_client", "0", indirect=True) -async def test_ypy_yjs_0(yws_server, yjs_client): +async def test_pycrdt_yjs_0(yws_server, yjs_client): ydoc = Doc() ytest = YTest(ydoc) async with connect("ws://127.0.0.1:1234/my-roomname") as websocket, WebsocketProvider( ydoc, websocket ): - ymap = Map() - ydoc["map"] = ymap + ydoc["map"] = ymap = Map() # set a value in "in" for v_in in range(10): ymap["in"] = float(v_in) @@ -59,7 +57,7 @@ async def test_ypy_yjs_0(yws_server, yjs_client): @pytest.mark.anyio @pytest.mark.parametrize("yjs_client", "1", indirect=True) -async def test_ypy_yjs_1(yws_server, yjs_client): +async def test_pycrdt_yjs_1(yws_server, yjs_client): # wait for the JS client to connect tt, dt = 0, 0.1 while True: @@ -73,9 +71,7 @@ async def test_ypy_yjs_1(yws_server, yjs_client): ytest = YTest(ydoc) ytest.run_clock() await ytest.clock_run() - ycells = Array() - ystate = Map() - ydoc["cells"] = ycells - ydoc["state"] = ystate - assert json.loads(str(ycells)) == [{"metadata": {"foo": "bar"}, "source": "1 + 2"}] - assert json.loads(str(ystate)) == {"state": {"dirty": False}} + ydoc["cells"] = ycells = Array() + ydoc["state"] = ystate = Map() + assert list(ycells) == [{"metadata": {"foo": "bar"}, "source": "1 + 2"}] + assert dict(ystate) == {"state": {"dirty": False}} diff --git a/tests/test_ystore.py b/tests/test_ystore.py index 39208bd..952284d 100644 --- a/tests/test_ystore.py +++ b/tests/test_ystore.py @@ -7,7 +7,7 @@ import aiosqlite import pytest -from ypy_websocket.ystore import SQLiteYStore, TempFileYStore +from pycrdt_websocket.ystore import SQLiteYStore, TempFileYStore class MetadataCallback: