Skip to content

Commit

Permalink
Add diagram explaining architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Jun 13, 2023
1 parent c257459 commit 6b07e59
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 6 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repos:
- id: check-case-conflict
- id: check-toml
- id: check-yaml
exclude: mkdocs.yml
- id: debug-statements
- id: forbid-new-submodules
- id: check-builtin-literals
Expand Down
24 changes: 23 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
Ypy-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.

Each client has an instance of a [YDoc](https://ypy.readthedocs.io/en/latest/autoapi/y_py/index.html#y_py.YDoc), representing their view of a document. A shared document also lives in a [room](./reference/Room.md) on the server side. Conceptually, a room can be seen as the place where clients collaborate on a document. The WebSocket to which a client connects points to the corresponding room through the endpoint path. In the example below, clients A and B connect to a WebSocket at path `room-1`, and thus both clients find themselves in a room called `room-1`. All the `YDoc` synchronization logic is taken care of by the [WebsocketProvider](./reference/WebSocket_provider.md).

Each update to a shared document can be persisted to disk using a [store](./reference/Store.md), which can be a file or a database.
```mermaid
flowchart TD
classDef room1 fill:#f96
classDef room2 fill:#bbf
A[Client A<br>room-1]:::room1 <-->|WebSocket<br>Provider| server(WebSocket Server)
B[Client B<br>room-1]:::room1 <-->|WebSocket<br>Provider| server
C[Client C<br>room-2]:::room2 <-->|WebSocket<br>Provider| server
D[Client D<br>room-2]:::room2 <-->|WebSocket<br>Provider| server
server <--> room1((room-1<br>clients: A, B)):::room1
server <--> room2((room-2<br>clients: C, D)):::room2
A <-..-> room1
B <-..-> room1
C <-..-> room2
D <-..-> room2
room1 ---> store1[(Store)]
room2 ---> store2[(Store)]
```
2 changes: 2 additions & 0 deletions docs/usage/client.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
A client connects their `YDoc` through a [WebsocketProvider](../reference/WebSocket_provider.md).

Here is a code example using the [websockets](https://websockets.readthedocs.io) library:
```py
import asyncio
Expand Down
2 changes: 2 additions & 0 deletions docs/usage/server.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
A server connects multiple `YDoc` through a [WebsocketServer](../reference/WebSocket_server.md).

Here is a code example using the [websockets](https://websockets.readthedocs.io) library:
```py
import asyncio
Expand Down
6 changes: 5 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ nav:
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format

plugins:
- mkdocstrings:
Expand Down
4 changes: 3 additions & 1 deletion ypy_websocket/websocket_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def started(self) -> Event:
self._started = Event()
return self._started

async def __aenter__(self):
async def __aenter__(self) -> WebsocketProvider:
if self._task_group is not None:
raise RuntimeError("WebsocketProvider already running")

Expand All @@ -77,6 +77,8 @@ async def __aenter__(self):
tg.start_soon(self._run)
self.started.set()

return self

async def __aexit__(self, exc_type, exc_value, exc_tb):
if self._task_group is None:
raise RuntimeError("WebsocketProvider not running")
Expand Down
4 changes: 3 additions & 1 deletion ypy_websocket/websocket_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ async def _serve(self, websocket, *, task_status: TaskStatus[None] = TASK_STATUS
tg.cancel_scope.cancel()
task_status.started()

async def __aenter__(self):
async def __aenter__(self) -> WebsocketServer:
if self._task_group is not None:
raise RuntimeError("WebsocketServer already running")

Expand All @@ -187,6 +187,8 @@ async def __aenter__(self):
self._exit_stack = exit_stack.pop_all()
self.started.set()

return self

async def __aexit__(self, exc_type, exc_value, exc_tb):
if self._task_group is None:
raise RuntimeError("WebsocketServer not running")
Expand Down
4 changes: 3 additions & 1 deletion ypy_websocket/yroom.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async def _broadcast_updates(self):
self.log.debug("Writing Y update to YStore")
self._task_group.start_soon(self.ystore.write, update)

async def __aenter__(self):
async def __aenter__(self) -> YRoom:
if self._task_group is not None:
raise RuntimeError("YRoom already running")

Expand All @@ -92,6 +92,8 @@ async def __aenter__(self):
tg.start_soon(self._broadcast_updates)
self.started.set()

return self

async def __aexit__(self, exc_type, exc_value, exc_tb):
if self._task_group is None:
raise RuntimeError("YRoom not running")
Expand Down
4 changes: 3 additions & 1 deletion ypy_websocket/ystore.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def started(self) -> Event:
self._started = Event()
return self._started

async def __aenter__(self):
async def __aenter__(self) -> BaseYStore:
if self._task_group is not None:
raise RuntimeError("YStore already running")

Expand All @@ -57,6 +57,8 @@ async def __aenter__(self):
self._exit_stack = exit_stack.pop_all()
tg.start_soon(self.start)

return self

async def __aexit__(self, exc_type, exc_value, exc_tb):
if self._task_group is None:
raise RuntimeError("YStore not running")
Expand Down

0 comments on commit 6b07e59

Please sign in to comment.