Skip to content

Commit

Permalink
Merge pull request #73 from y-crdt/after-transaction-update
Browse files Browse the repository at this point in the history
After Transaction Update
  • Loading branch information
Waidhoferj authored Jul 11, 2022
2 parents 41e2d1a + ad36b53 commit 2aba86e
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn y_py(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<y_map::YMapEvent>()?;
m.add_class::<y_xml::YXmlTextEvent>()?;
m.add_class::<y_xml::YXmlEvent>()?;
m.add_class::<y_doc::AfterTransactionEvent>()?;
// Functions
m.add_wrapped(wrap_pyfunction!(encode_state_vector))?;
m.add_wrapped(wrap_pyfunction!(encode_state_as_update))?;
Expand Down
5 changes: 1 addition & 4 deletions src/type_conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ pub trait ToPython {
fn into_py(self, py: Python) -> PyObject;
}

impl<T> ToPython for Vec<T>
where
T: ToPython,
{
impl ToPython for Vec<Any> {
fn into_py(self, py: Python) -> PyObject {
let elements = self.into_iter().map(|v| v.into_py(py));
let arr: PyObject = pyo3::types::PyList::new(py, elements).into();
Expand Down
40 changes: 24 additions & 16 deletions src/y_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ use crate::y_transaction::YTransaction;
use crate::y_xml::YXmlElement;
use crate::y_xml::YXmlText;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pyo3::types::PyTuple;
use yrs::updates::encoder::Encode;
use yrs::AfterTransactionEvent;
use yrs::AfterTransactionEvent as YrsAfterTransactionEvent;
use yrs::Doc;
use yrs::OffsetKind;
use yrs::Options;
Expand Down Expand Up @@ -96,7 +97,7 @@ impl YDoc {
/// with doc.begin_transaction() as txn:
/// text.insert(txn, 0, 'hello world')
/// ```
pub fn begin_transaction(&mut self) -> YTransaction {
pub fn begin_transaction(&self) -> YTransaction {
YTransaction::new(self.0.transact())
}

Expand Down Expand Up @@ -168,7 +169,7 @@ impl YDoc {
self.0
.observe_transaction_cleanup(move |txn, event| {
Python::with_gil(|py| {
let event = PyAfterTransactionEvent::new(event, txn);
let event = AfterTransactionEvent::new(event, txn);
if let Err(err) = callback.call1(py, (event,)) {
err.restore(py)
}
Expand Down Expand Up @@ -224,7 +225,7 @@ pub fn encode_state_vector(doc: &mut YDoc) -> PyObject {
/// apply_update(local_doc, remote_delta)
/// ```
#[pyfunction]
pub fn encode_state_as_update(doc: &mut YDoc, vector: Option<Vec<u8>>) -> PyResult<PyObject> {
pub fn encode_state_as_update(doc: &YDoc, vector: Option<Vec<u8>>) -> PyResult<PyObject> {
doc.begin_transaction().diff_v1(vector)
}

Expand Down Expand Up @@ -253,19 +254,19 @@ pub fn apply_update(doc: &mut YDoc, diff: Vec<u8>) -> PyResult<()> {
}

#[pyclass(unsendable)]
pub struct PyAfterTransactionEvent {
inner: *const AfterTransactionEvent,
pub struct AfterTransactionEvent {
inner: *const YrsAfterTransactionEvent,
txn: *const Transaction,
before_state: Option<PyObject>,
after_state: Option<PyObject>,
delete_set: Option<PyObject>,
}

impl PyAfterTransactionEvent {
fn new(event: &AfterTransactionEvent, txn: &Transaction) -> Self {
let inner = event as *const AfterTransactionEvent;
impl AfterTransactionEvent {
fn new(event: &YrsAfterTransactionEvent, txn: &Transaction) -> Self {
let inner = event as *const YrsAfterTransactionEvent;
let txn = txn as *const Transaction;
PyAfterTransactionEvent {
AfterTransactionEvent {
inner,
txn,
before_state: None,
Expand All @@ -274,7 +275,7 @@ impl PyAfterTransactionEvent {
}
}

fn inner(&self) -> &AfterTransactionEvent {
fn inner(&self) -> &YrsAfterTransactionEvent {
unsafe { self.inner.as_ref().unwrap() }
}

Expand All @@ -284,15 +285,16 @@ impl PyAfterTransactionEvent {
}

#[pymethods]
impl PyAfterTransactionEvent {
impl AfterTransactionEvent {
/// Returns a current shared type instance, that current event changes refer to.
#[getter]
pub fn before_state(&mut self) -> PyObject {
if let Some(before_state) = self.before_state.as_ref() {
before_state.clone()
} else {
let before_state = self.inner().before_state.encode_v1();
let before_state: PyObject =
Python::with_gil(|py| self.inner().before_state.encode_v1().into_py(py));
Python::with_gil(|py| PyBytes::new(py, &before_state).into());
self.before_state = Some(before_state.clone());
before_state
}
Expand All @@ -303,8 +305,9 @@ impl PyAfterTransactionEvent {
if let Some(after_state) = self.after_state.as_ref() {
after_state.clone()
} else {
let after_state = self.inner().after_state.encode_v1();
let after_state: PyObject =
Python::with_gil(|py| self.inner().after_state.encode_v1().into_py(py));
Python::with_gil(|py| PyBytes::new(py, &after_state).into());
self.after_state = Some(after_state.clone());
after_state
}
Expand All @@ -315,10 +318,15 @@ impl PyAfterTransactionEvent {
if let Some(delete_set) = self.delete_set.as_ref() {
delete_set.clone()
} else {
let delete_set: PyObject =
Python::with_gil(|py| self.inner().delete_set.encode_v1().into_py(py));
let delete_set = self.inner().delete_set.encode_v1();
let delete_set: PyObject = Python::with_gil(|py| PyBytes::new(py, &delete_set).into());
self.delete_set = Some(delete_set.clone());
delete_set
}
}

pub fn get_update(&self) -> PyObject {
let update = self.txn().encode_update_v1();
Python::with_gil(|py| PyBytes::new(py, &update).into())
}
}
2 changes: 1 addition & 1 deletion src/y_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,6 @@ impl YTransaction {
) -> PyResult<bool> {
self.commit();
drop(self);
Ok(exception_type.map_or(true, |_| false))
Ok(exception_type.is_none())
}
}
24 changes: 23 additions & 1 deletion tests/test_y_doc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from y_py import YDoc
from y_py import YDoc, AfterTransactionEvent

import y_py as Y
import pytest

Expand Down Expand Up @@ -104,3 +105,24 @@ def callback(event):
assert before_state != None
assert after_state != None
assert delete_set != None


def test_get_update():
"""
Ensures that developers can access the encoded update data in the `observe_after_transaction` event.
"""
d = Y.YDoc()
m = d.get_map("foo")
r = d.get_map("foo")
update: bytes = None

def get_update(event: AfterTransactionEvent) -> None:
nonlocal update
update = event.get_update()

d.observe_after_transaction(get_update)

with d.begin_transaction() as txn:
m.set(txn, "hi", "there")

assert type(update) == bytes
15 changes: 15 additions & 0 deletions y_py.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,23 @@ class AfterTransactionEvent:
"""

before_state: EncodedStateVector
"""
Encoded state of YDoc before the transaction.
"""
after_state: EncodedStateVector
"""
Encoded state of the YDoc after the transaction.
"""
delete_set: EncodedDeleteSet
"""
Elements deleted by the associated transaction.
"""

def get_update(self) -> YDocUpdate:
"""
Returns:
Encoded payload of all updates produced by the transaction.
"""

def encode_state_vector(doc: YDoc) -> EncodedStateVector:
"""
Expand Down

0 comments on commit 2aba86e

Please sign in to comment.