Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

After Transaction Update #73

Merged
merged 1 commit into from
Jul 11, 2022
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
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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