pycrdt

repository·main·Indexed 41 days ago

https://github.com/y-crdt/pycrdt

Python 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.

Tokens
11.8K
Snippets
52
Records
58
Agent score
52%

What's inside pycrdt

  1. What is Pycrdt?

    main
    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.
  2. Manage transactions in Pycrdt

    main

    Every change to shared data occurs within a document transaction. Pycrdt supports two types of transactions:

    1. 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.
    2. 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 optional timeout.

    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.
  3. Quickstart with Pycrdt shared data types

    main

    Pycrdt 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 Doc instance.

    Supported types:

    • Text: similar to str.
    • Array: similar to list.
    • Map: similar to dict.
    • 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"
  4. Install pycrdt via micromamba

    main

    To install pycrdt using micromamba and conda-forge, create and activate a new environment before installing the package.

    micromamba create -n my_env
    micromamba activate my_env
    micromamba install pycrdt
  5. Synchronize documents using binary updates

    main

    To synchronize state between different machines, you can extract a binary encoded update from a Doc and apply it to a remote Doc. 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"]
  6. Set up a development environment for pycrdt

    main

    To develop on pycrdt, clone the repository and set up a conda environment containing pip and the Rust compiler. Install the package in editable mode to include test and type groups.

    Note: This process uses maturin to 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 develop without 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 develop
  7. Inspect TextEvent details

    main

    A TextEvent provides metadata about a change. Key properties include:

    • transaction: The transaction that caused the event.
    • target: The Text object 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.delta
  8. Initialize a new Doc

    main

    The Doc class is the central container for all CRDT types in pycrdt. 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 valid u64 and 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()
  9. Use TypedMap and TypedDoc for schema-based typing

    main

    If you need to associate specific types with specific keys (rather than a uniform type for all values), use TypedMap and TypedDoc.

    1. Define a class inheriting from TypedMap (for maps) or TypedDoc (for documents) with type-annotated attributes.
    2. Instantiate the class to create a typed CRDT structure.
    3. Access attributes directly; type checkers will validate assignments and attribute existence.

    Note: TypedMap and TypedDoc are special container types. They are not subclasses of Map or Doc, but they contain them. You can access the underlying untyped Map or Doc via 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._