pycrdt
repository·main·Indexed 41 days ago
https://github.com/y-crdt/pycrdtPython bindings for CRDTs (Conflict-free Replicated Data Types) based on the Yrs implementation. It provides high-performance shared-state data types including Text, Array, Map, and XML types for collaborative applications, ensuring eventual convergence of data state in distributed systems. Version 0.14.5.
What's inside pycrdt
- Pycrdt is a Python library providing high-performance bindings for Yrs, which is the Rust port of the Yjs framework. It enables the use of Conflict-free Replicated Data Types (CRDTs) in Python to create shared documents that can automatically merge concurrent changes across different replicas, ensuring eventual convergence of data state in distributed systems.
Overview of pycrdt
mainpycrdt provides Python bindings for CRDTs (Conflict-free Replicated Data Types) based on the Yrs library. It allows developers to use high-performance CRDTs within Python applications.Manage transactions in Pycrdt
mainEvery change to shared data occurs within a document transaction. Pycrdt supports two types of transactions:
doc.transaction(): Non-blocking. Creates a new transaction if none exists, or uses the current one. This is the preferred method for most use cases. It can be used as a synchronous or asynchronous context manager.doc.new_transaction(): Blocking. Always attempts to create a new transaction. If a transaction is already in progress, this method will block (in threads) or yield (in async) until the transaction is released. It accepts an optionaltimeout.
Important Rules:
- Grouping multiple changes in a single transaction makes them atomic and improves performance.
- Transactions can be nested; nested changes are applied to the outermost transaction.
- If you register an async callback via
doc.observe(), you must use an async context manager for transactions (async with doc.transaction()). - You cannot have a nested async transaction if the root transaction is synchronous.
Quickstart with Pycrdt shared data types
mainPycrdt provides several shared data types that behave similarly to Python built-ins but are designed for CRDT synchronization. To make them useful, they must be inserted into a
Docinstance.Supported types:
Text: similar tostr.Array: similar tolist.Map: similar todict.XmlFragment,XmlElement,XmlText: XML-based types.
Once inserted into a
Doc, you can perform standard operations (like+=,.append(), or key assignment) which will generate binary updates that can be synchronized with remote documents.from pycrdt import Doc, Text, Array, Map # Initialize types text0 = Text("Hello") array0 = Array([0, "foo"]) map0 = Map({"key0": "value0"}) # Insert into a document doc = Doc() doc["text0"] = text0 doc["array0"] = array0 doc["map0"] = map0 # Operate on them text0 += ", World!" array0.append("bar") map0["key1"] = "value1"Install pycrdt via micromamba
mainTo install
pycrdtusingmicromambaandconda-forge, create and activate a new environment before installing the package.micromamba create -n my_env micromamba activate my_env micromamba install pycrdtSynchronize documents using binary updates
mainTo synchronize state between different machines, you can extract a binary encoded update from a
Docand apply it to a remoteDoc. This ensures that concurrent changes eventually converge to the same state across all participants.# On the local machine: update = doc.get_update() # Send 'update' (bytes) over the wire (e.g., WebSocket) # On the remote machine: remote_doc = Doc() remote_doc.apply_update(update) # Retrieve root types by the names they were inserted under text0 = remote_doc["text0"]Set up a development environment for pycrdt
mainTo develop on
pycrdt, clone the repository and set up a conda environment containingpipand the Rust compiler. Install the package in editable mode to include test and type groups.Note: This process uses
maturinto build the Rust extension.If you modify only Python code, changes are reflected immediately. If you modify Rust code, you must re-build the extension using
maturin developwithout needing to reinstall the package.git clone https://github.com/y-crdt/pycrdt.git cd pycrdt # Setup environment micromamba create -n pycrdt-dev micromamba activate pycrdt-dev micromamba install pip rust # Install in editable mode pip install -e . --group test --group types # If Rust code changes, re-build the extension: pip install maturin maturin developInstall pycrdt via pip
mainYou can install
pycrdtdirectly from PyPI usingpip.pip install pycrdtRun pycrdt tests
mainUse
pytestto run the test suite in your development environment.pytest -vInspect TextEvent details
mainA
TextEventprovides metadata about a change. Key properties include:transaction: The transaction that caused the event.target: TheTextobject that was modified.path: The path within the structure where the change occurred.delta: A list representing the change (insertions/deletions).
# Accessing properties on a TextEvent 'e' txn = e.transaction target = e.target path = e.path delta = e.deltaInitialize a new Doc
mainThe
Docclass is the central container for all CRDT types inpycrdt. You can create a new instance by providing optional configuration parameters.Parameters:
client_id(int, optional): A unique identifier for the client. Must be a validu64and cannot exceed $2^{53} - 1$ (to ensure compatibility with JavaScript numbers).skip_gc(bool, optional): Whether to skip garbage collection.guid(str, optional): A unique identifier for the document.
Note: If you do not provide these, default values will be used.
from pycrdt import Doc # Create a doc with custom configuration doc = Doc(client_id=12345, skip_gc=False, guid="my-unique-doc-id") # Create a default doc doc_default = Doc()Use TypedMap and TypedDoc for schema-based typing
mainIf you need to associate specific types with specific keys (rather than a uniform type for all values), use
TypedMapandTypedDoc.- Define a class inheriting from
TypedMap(for maps) orTypedDoc(for documents) with type-annotated attributes. - Instantiate the class to create a typed CRDT structure.
- Access attributes directly; type checkers will validate assignments and attribute existence.
Note:
TypedMapandTypedDocare special container types. They are not subclasses ofMaporDoc, but they contain them. You can access the underlying untypedMaporDocvia the._property.from pycrdt import Array, Text, TypedDoc, TypedMap class MyMap(TypedMap): name: str toggle: bool nested: Array[bool] class MyDoc(TypedDoc): map0: MyMap array0: Array[int] text0: Text doc = MyDoc() # Direct attribute access with type safety doc.map0.name = "foo" doc.map0.toggle = False doc.array0 = Array([1, 2, 3]) # Accessing the underlying untyped objects untyped_doc = doc._ untyped_map = doc.map0._- Define a class inheriting from