Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions devenv-jcollab-backend.yml

This file was deleted.

11 changes: 0 additions & 11 deletions devenv-jcollab-frontend.yml

This file was deleted.

11 changes: 11 additions & 0 deletions devenv-jcollab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: serverdocs-jcollab
channels:
- conda-forge
dependencies:
- python
- nodejs=22
- uv
- jupyterlab
- pip:
- jupyterlab>=4.4.0,<5.0.0
- jupyter_collaboration~=4.0
3 changes: 2 additions & 1 deletion jupyter-config/server-config/jupyter_server_documents.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"ServerApp": {
"jpserver_extensions": {
"jupyter_server_documents": true
"jupyter_server_documents": true,
"jupyter_server_ydoc": false
}
}
}
1 change: 1 addition & 0 deletions jupyter_server_documents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@


from .app import ServerDocsApp
from .events import JSD_AWARENESS_EVENT_URI, JSD_ROOM_EVENT_URI


def _jupyter_labextension_paths():
Expand Down
17 changes: 16 additions & 1 deletion jupyter_server_documents/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .websockets import YRoomWebsocket
from .rooms.yroom_manager import YRoomManager
from .outputs import OutputsManager, outputs_handlers
from .events import JSD_AWARENESS_EVENT_SCHEMA, JSD_ROOM_EVENT_SCHEMA
from .jcollab_api import JCollabAPI

class ServerDocsApp(ExtensionApp):
name = "jupyter_server_documents"
Expand Down Expand Up @@ -51,6 +53,10 @@ def initialize(self):
super().initialize()

def initialize_settings(self):
# Register event schemas
self.serverapp.event_logger.register_event_schema(JSD_ROOM_EVENT_SCHEMA)
self.serverapp.event_logger.register_event_schema(JSD_AWARENESS_EVENT_SCHEMA)

# Get YRoomManager arguments from server extension context.
# We cannot access the 'file_id_manager' key immediately because server
# extensions initialize in alphabetical order. 'jupyter_server_documents' <
Expand All @@ -65,13 +71,22 @@ def get_fileid_manager():
self.settings["yroom_manager"] = YRoomManager(
get_fileid_manager=get_fileid_manager,
contents_manager=contents_manager,
event_logger=self.serverapp.event_logger,
loop=loop,
log=log
)

# Initialize OutputsManager
self.outputs_manager = self.outputs_manager_class(config=self.config)
self.settings["outputs_manager"] = self.outputs_manager

# Serve Jupyter Collaboration API on
# `self.settings["jupyter_server_ydoc"]` for compatibility with
# extensions depending on Jupyter Collaboration
self.settings["jupyter_server_ydoc"] = JCollabAPI(
get_fileid_manager=get_fileid_manager,
yroom_manager=self.settings["yroom_manager"]
)

def _link_jupyter_server_extension(self, server_app):
"""Setup custom config needed by this extension."""
Expand Down
10 changes: 10 additions & 0 deletions jupyter_server_documents/events/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pathlib import Path

EVENTS_DIR = Path(__file__).parent

# Use the same schema ID as `jupyter_collaboration` for compatibility
JSD_ROOM_EVENT_URI = "https://schema.jupyter.org/jupyter_collaboration/session/v1"
JSD_AWARENESS_EVENT_URI = "https://schema.jupyter.org/jupyter_collaboration/awareness/v1"

JSD_ROOM_EVENT_SCHEMA = EVENTS_DIR / "room.yaml"
JSD_AWARENESS_EVENT_SCHEMA = EVENTS_DIR / "awareness.yaml"
33 changes: 33 additions & 0 deletions jupyter_server_documents/events/awareness.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"$id": https://schema.jupyter.org/jupyter_collaboration/awareness/v1
"$schema": "http://json-schema.org/draft-07/schema"
version: "1"
title: Collaborative awareness events
personal-data: true
description: |
Awareness events emitted from server-side during a collaborative session.
type: object
required:
- roomid
- username
- action
properties:
roomid:
type: string
description: |
Room ID. Usually composed by the file type, format and ID.
username:
type: string
description: |
The name of the user who joined or left room.
action:
enum:
- join
- leave
description: |
Possible values:
1. join
2. leave
msg:
type: string
description: |
Optional event message.
60 changes: 60 additions & 0 deletions jupyter_server_documents/events/room.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"$id": https://schema.jupyter.org/jupyter_collaboration/session/v1
"$schema": "http://json-schema.org/draft-07/schema"
version: "1"
title: Room events
personal-data: true
description: |
An event emitted by a collaboration room.
type: object
required:
- level
- room
- path
properties:
level:
enum:
- INFO
- DEBUG
- WARNING
- ERROR
- CRITICAL
description: |
Message type.
room:
type: string
description: |
Room ID. This is a composite ID that takes the format `{file_format}:{file_type}:{file_id}`.
path:
type: string
description: |
Path of the file.
store:
type: string
description: |
The store used to track the document history.
action:
enum:
- initialize
- load
- save
- overwrite
- clean
description: |
Action performed in a room during a collaborative session.
Possible values:
1. initialize
Initialize a room by loading the content from the contents manager or a store.
2. load
Load the content from the contents manager.
3. save
Save the content with the contents manager.
4. overwrite
Overwrite the content in a room with content from the contents manager.
This can happen when multiple rooms access the same file or when a user
modifies the file outside Jupyter Server (e.g. using a different app).
5. clean
Clean the room once is empty (aka there is no more users connected to it).
msg:
type: string
description: |
Event message.
1 change: 1 addition & 0 deletions jupyter_server_documents/jcollab_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .jcollab_api import JCollabAPI
64 changes: 64 additions & 0 deletions jupyter_server_documents/jcollab_api/jcollab_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from __future__ import annotations
from jupyter_ydoc.ybasedoc import YBaseDoc
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Callable, Literal
from jupyter_server_fileid.manager import BaseFileIdManager
from ..rooms import YRoomManager


class JCollabAPI:
"""
Provides the Python API provided by `jupyter_collaboration~=4.0` under
`self.settings["jupyter_server_ydoc"]`.
"""
fileid_manager: BaseFileIdManager
yroom_manager: YRoomManager

def __init__(self, get_fileid_manager: Callable[[], BaseFileIdManager], yroom_manager: YRoomManager):
self._get_fileid_manager = get_fileid_manager
self.yroom_manager = yroom_manager

@property
def fileid_manager(self) -> BaseFileIdManager:
return self._get_fileid_manager()

async def get_document(
self,
*,
path: str | None = None,
content_type: str | None = None,
file_format: Literal["json", "text"] | None = None,
room_id: str | None = None,
copy: bool = True,
) -> YBaseDoc:
"""
Returns the Jupyter YDoc for a collaborative room.

You need to provide either a ``room_id`` or the ``path``,
the ``content_type`` and the ``file_format``.

The `copy` argument is ignored by `jupyter_server_documents`.
"""

# Raise exception if required arguments are not given
if room_id is None and (path is None or content_type is None or file_format is None):
raise ValueError(
"You need to provide either a ``room_id`` or the ``path``, the ``content_type`` and the ``file_format``."
)

# Compute room_id if not given
if room_id is None:
file_id = self.fileid_manager.index(path)
room_id = f"{file_format}:{content_type}:{file_id}"

# Get or create room using `room_id`
room = self.yroom_manager.get_room(room_id)
if not room:
raise ValueError(
f"Could not get room using room ID '{room_id}'."
)

# Return the Jupyter YDoc once ready
return await room.get_jupyter_ydoc()
Loading
Loading