diff --git a/src/shared_types.rs b/src/shared_types.rs index 92ab091..dff538f 100644 --- a/src/shared_types.rs +++ b/src/shared_types.rs @@ -4,11 +4,42 @@ use crate::{ y_text::YText, y_xml::{YXmlElement, YXmlText}, }; -use pyo3::prelude::*; +use pyo3::create_exception; +use pyo3::{exceptions::PyException, prelude::*}; use std::convert::TryFrom; -use yrs::types::TYPE_REFS_XML_ELEMENT; use yrs::types::TYPE_REFS_XML_TEXT; use yrs::types::{TypeRefs, TYPE_REFS_ARRAY, TYPE_REFS_MAP, TYPE_REFS_TEXT}; +use yrs::{types::TYPE_REFS_XML_ELEMENT, SubscriptionId}; + +// Common errors +create_exception!(y_py, PreliminaryObservationException, PyException, "Occurs when an observer is attached to a Y type that is not integrated into a YDoc. Y types can only be observed once they have been added to a YDoc."); + +/// Creates a default error with a common message string for throwing a `PyErr`. +pub(crate) trait DefaultPyErr { + /// Creates a new instance of the error with a default message. + fn default_message() -> PyErr; +} + +impl DefaultPyErr for PreliminaryObservationException { + fn default_message() -> PyErr { + PreliminaryObservationException::new_err( + "Cannot observe a preliminary type. Must be added to a YDoc first", + ) + } +} + +#[pyclass] +#[derive(Clone, Copy)] +pub struct ShallowSubscription(pub SubscriptionId); +#[pyclass] +#[derive(Clone, Copy)] +pub struct DeepSubscription(pub SubscriptionId); + +#[derive(FromPyObject)] +pub enum SubId { + Shallow(ShallowSubscription), + Deep(DeepSubscription), +} #[derive(Clone)] pub enum SharedType { diff --git a/src/type_conversions.rs b/src/type_conversions.rs index f471190..38c5b04 100644 --- a/src/type_conversions.rs +++ b/src/type_conversions.rs @@ -1,17 +1,24 @@ use lib0::any::Any; use pyo3::prelude::*; use pyo3::types as pytypes; +use pyo3::types::PyList; use std::collections::HashMap; use std::convert::TryFrom; use std::ops::Deref; use yrs::block::{ItemContent, Prelim}; +use yrs::types::Events; use yrs::types::{Attrs, Branch, BranchPtr, Change, Delta, EntryChange, Value}; use yrs::{Array, Map, Text, Transaction}; use crate::shared_types::{Shared, SharedType}; use crate::y_array::YArray; +use crate::y_array::YArrayEvent; use crate::y_map::YMap; +use crate::y_map::YMapEvent; use crate::y_text::YText; +use crate::y_text::YTextEvent; +use crate::y_xml::YXmlEvent; +use crate::y_xml::YXmlTextEvent; use crate::y_xml::{YXmlElement, YXmlText}; pub trait ToPython { @@ -324,6 +331,19 @@ impl ToPython for Value { } } +pub(crate) fn events_into_py(txn: &Transaction, events: &Events) -> PyObject { + Python::with_gil(|py| { + let py_events = events.iter().map(|event| match event { + yrs::types::Event::Text(e_txt) => YTextEvent::new(e_txt, txn).into_py(py), + yrs::types::Event::Array(e_arr) => YArrayEvent::new(e_arr, txn).into_py(py), + yrs::types::Event::Map(e_map) => YMapEvent::new(e_map, txn).into_py(py), + yrs::types::Event::XmlElement(e_xml) => YXmlEvent::new(e_xml, txn).into_py(py), + yrs::types::Event::XmlText(e_xml) => YXmlTextEvent::new(e_xml, txn).into_py(py), + }); + PyList::new(py, py_events).into() + }) +} + pub struct PyValueWrapper(pub PyObject); impl Prelim for PyValueWrapper { diff --git a/src/y_array.rs b/src/y_array.rs index 0436f63..108d9a3 100644 --- a/src/y_array.rs +++ b/src/y_array.rs @@ -2,15 +2,19 @@ use std::convert::TryInto; use std::mem::ManuallyDrop; use std::ops::DerefMut; -use crate::type_conversions::insert_at; +use crate::shared_types::{ + DeepSubscription, DefaultPyErr, PreliminaryObservationException, ShallowSubscription, SubId, +}; +use crate::type_conversions::{events_into_py, insert_at}; use crate::y_transaction::YTransaction; use super::shared_types::SharedType; use crate::type_conversions::ToPython; -use pyo3::exceptions::{PyIndexError, PyTypeError}; +use pyo3::exceptions::PyIndexError; use pyo3::prelude::*; use pyo3::types::{PyList, PySlice, PySliceIndices}; use yrs::types::array::{ArrayEvent, ArrayIter}; +use yrs::types::DeepObservable; use yrs::{Array, SubscriptionId, Transaction}; /// A collection used to store data in an indexed sequence structure. This type is internally @@ -186,35 +190,52 @@ impl YArray { /// Subscribes to all operations happening over this instance of `YArray`. All changes are /// batched and eventually triggered during transaction commit phase. /// Returns a `SubscriptionId` which can be used to cancel the callback with `unobserve`. - pub fn observe(&mut self, f: PyObject) -> PyResult { + pub fn observe(&mut self, f: PyObject) -> PyResult { match &mut self.0 { SharedType::Integrated(array) => { - let subscription = array.observe(move |txn, e| { - Python::with_gil(|py| { - let event = YArrayEvent::new(e, txn); - if let Err(err) = f.call1(py, (event,)) { - err.restore(py) - } + let sub: SubscriptionId = array + .observe(move |txn, e| { + Python::with_gil(|py| { + let event = YArrayEvent::new(e, txn); + if let Err(err) = f.call1(py, (event,)) { + err.restore(py) + } + }) }) - }); - Ok(subscription.into()) + .into(); + Ok(ShallowSubscription(sub)) } - SharedType::Prelim(_) => Err(PyTypeError::new_err( - "Cannot observe a preliminary type. Must be added to a YDoc first", - )), + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), + } + } + /// Observes YArray events and events of all child elements. + pub fn observe_deep(&mut self, f: PyObject) -> PyResult { + match &mut self.0 { + SharedType::Integrated(array) => { + let sub: SubscriptionId = array + .observe_deep(move |txn, events| { + Python::with_gil(|py| { + let events = events_into_py(txn, events); + if let Err(err) = f.call1(py, (events,)) { + err.restore(py) + } + }) + }) + .into(); + Ok(DeepSubscription(sub)) + } + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), } } /// Cancels the callback of an observer using the Subscription ID returned from the `observe` method. - pub fn unobserve(&mut self, subscription_id: SubscriptionId) -> PyResult<()> { + pub fn unobserve(&mut self, subscription_id: SubId) -> PyResult<()> { match &mut self.0 { - SharedType::Integrated(v) => { - v.unobserve(subscription_id); - Ok(()) - } - SharedType::Prelim(_) => Err(PyTypeError::new_err( - "Cannot call unobserve on a preliminary type. Must be added to a YDoc first", - )), + SharedType::Integrated(arr) => Ok(match subscription_id { + SubId::Shallow(ShallowSubscription(id)) => arr.unobserve(id), + SubId::Deep(DeepSubscription(id)) => arr.unobserve_deep(id), + }), + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), } } } @@ -356,7 +377,7 @@ pub struct YArrayEvent { } impl YArrayEvent { - fn new(event: &ArrayEvent, txn: &Transaction) -> Self { + pub fn new(event: &ArrayEvent, txn: &Transaction) -> Self { let inner = event as *const ArrayEvent; let txn = txn as *const Transaction; YArrayEvent { diff --git a/src/y_map.rs b/src/y_map.rs index 06260a3..70ee596 100644 --- a/src/y_map.rs +++ b/src/y_map.rs @@ -5,10 +5,14 @@ use std::collections::HashMap; use std::mem::ManuallyDrop; use std::ops::DerefMut; use yrs::types::map::{MapEvent, MapIter}; +use yrs::types::DeepObservable; use yrs::{Map, SubscriptionId, Transaction}; -use crate::shared_types::SharedType; -use crate::type_conversions::{PyValueWrapper, ToPython}; +use crate::shared_types::{ + DeepSubscription, DefaultPyErr, PreliminaryObservationException, ShallowSubscription, + SharedType, SubId, +}; +use crate::type_conversions::{events_into_py, PyValueWrapper, ToPython}; use crate::y_transaction::YTransaction; /// Collection used to store key-value entries in an unordered manner. Keys are always represented @@ -205,33 +209,51 @@ impl YMap { YMapKeyIterator(self.items()) } - pub fn observe(&mut self, f: PyObject) -> PyResult { + pub fn observe(&mut self, f: PyObject) -> PyResult { match &mut self.0 { - SharedType::Integrated(v) => Ok(v - .observe(move |txn, e| { - Python::with_gil(|py| { - let e = YMapEvent::new(e, txn); - if let Err(err) = f.call1(py, (e,)) { - err.restore(py) - } + SharedType::Integrated(v) => { + let sub_id: SubscriptionId = v + .observe(move |txn, e| { + Python::with_gil(|py| { + let e = YMapEvent::new(e, txn); + if let Err(err) = f.call1(py, (e,)) { + err.restore(py) + } + }) }) - }) - .into()), - SharedType::Prelim(_) => Err(PyTypeError::new_err( - "Cannot observe a preliminary type. Must be added to a YDoc first", - )), + .into(); + Ok(ShallowSubscription(sub_id)) + } + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), } } - /// Cancels the observer callback associated with the `subscripton_id`. - pub fn unobserve(&mut self, subscription_id: SubscriptionId) -> PyResult<()> { + + pub fn observe_deep(&mut self, f: PyObject) -> PyResult { match &mut self.0 { SharedType::Integrated(map) => { - map.unobserve(subscription_id); - Ok(()) + let sub: SubscriptionId = map + .observe_deep(move |txn, events| { + Python::with_gil(|py| { + let events = events_into_py(txn, events); + if let Err(err) = f.call1(py, (events,)) { + err.restore(py) + } + }) + }) + .into(); + Ok(DeepSubscription(sub)) } - SharedType::Prelim(_) => Err(PyTypeError::new_err( - "Cannot unobserve a preliminary type. Must be added to a YDoc first", - )), + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), + } + } + /// Cancels the observer callback associated with the `subscripton_id`. + pub fn unobserve(&mut self, subscription_id: SubId) -> PyResult<()> { + match &mut self.0 { + SharedType::Integrated(map) => Ok(match subscription_id { + SubId::Shallow(ShallowSubscription(id)) => map.unobserve(id), + SubId::Deep(DeepSubscription(id)) => map.unobserve_deep(id), + }), + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), } } } @@ -296,7 +318,7 @@ pub struct YMapEvent { } impl YMapEvent { - fn new(event: &MapEvent, txn: &Transaction) -> Self { + pub fn new(event: &MapEvent, txn: &Transaction) -> Self { let inner = event as *const MapEvent; let txn = txn as *const Transaction; YMapEvent { diff --git a/src/y_text.rs b/src/y_text.rs index b78bd1e..a5c3ac3 100644 --- a/src/y_text.rs +++ b/src/y_text.rs @@ -1,18 +1,20 @@ -use std::collections::HashMap; -use std::rc::Rc; - +use crate::shared_types::{ + DeepSubscription, DefaultPyErr, PreliminaryObservationException, ShallowSubscription, + SharedType, SubId, +}; +use crate::type_conversions::py_into_any; +use crate::type_conversions::{events_into_py, ToPython}; +use crate::y_transaction::YTransaction; use lib0::any::Any; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::types::PyList; +use std::collections::HashMap; +use std::rc::Rc; use yrs::types::text::TextEvent; use yrs::types::Attrs; -use yrs::{SubscriptionId, Text, Transaction}; - -use crate::shared_types::SharedType; -use crate::type_conversions::py_into_any; -use crate::type_conversions::ToPython; -use crate::y_transaction::YTransaction; +use yrs::types::DeepObservable; +use yrs::{Text, Transaction}; /// A shared data type used for collaborative text editing. It enables multiple users to add and /// remove chunks of text in efficient manner. This type is internally represented as a mutable @@ -193,33 +195,53 @@ impl YText { } } - pub fn observe(&mut self, f: PyObject) -> PyResult { + /// Observes updates from the `YText` instance. + pub fn observe(&mut self, f: PyObject) -> PyResult { match &mut self.0 { - SharedType::Integrated(v) => Ok(v - .observe(move |txn, e| { - Python::with_gil(|py| { - let e = YTextEvent::new(e, txn); - if let Err(err) = f.call1(py, (e,)) { - err.restore(py) - } - }); - }) - .into()), - SharedType::Prelim(_) => Err(PyTypeError::new_err( - "Cannot observe a preliminary type. Must be added to a YDoc first", - )), + SharedType::Integrated(text) => { + let sub_id = text + .observe(move |txn, e| { + Python::with_gil(|py| { + let e = YTextEvent::new(e, txn); + if let Err(err) = f.call1(py, (e,)) { + err.restore(py) + } + }); + }) + .into(); + Ok(ShallowSubscription(sub_id)) + } + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), } } - /// Cancels the observer callback associated with the `subscripton_id`. - pub fn unobserve(&mut self, subscription_id: SubscriptionId) -> PyResult<()> { + + /// Observes updates from the `YText` instance and all of its nested children. + pub fn observe_deep(&mut self, f: PyObject) -> PyResult { match &mut self.0 { SharedType::Integrated(text) => { - text.unobserve(subscription_id); - Ok(()) + let sub = text + .observe_deep(move |txn, events| { + Python::with_gil(|py| { + let events = events_into_py(txn, events); + if let Err(err) = f.call1(py, (events,)) { + err.restore(py) + } + }) + }) + .into(); + Ok(DeepSubscription(sub)) } - SharedType::Prelim(_) => Err(PyTypeError::new_err( - "Cannot unobserve a preliminary type. Must be added to a YDoc first", - )), + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), + } + } + /// Cancels the observer callback associated with the `subscripton_id`. + pub fn unobserve(&mut self, subscription_id: SubId) -> PyResult<()> { + match &mut self.0 { + SharedType::Integrated(text) => Ok(match subscription_id { + SubId::Shallow(ShallowSubscription(id)) => text.unobserve(id), + SubId::Deep(DeepSubscription(id)) => text.unobserve_deep(id), + }), + SharedType::Prelim(_) => Err(PreliminaryObservationException::default_message()), } } } @@ -253,7 +275,7 @@ pub struct YTextEvent { } impl YTextEvent { - fn new(event: &TextEvent, txn: &Transaction) -> Self { + pub fn new(event: &TextEvent, txn: &Transaction) -> Self { let inner = event as *const TextEvent; let txn = txn as *const Transaction; YTextEvent { diff --git a/src/y_xml.rs b/src/y_xml.rs index f09f342..1651358 100644 --- a/src/y_xml.rs +++ b/src/y_xml.rs @@ -1,16 +1,18 @@ +use crate::shared_types::SubId; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; use std::mem::ManuallyDrop; use std::ops::Deref; use yrs::types::xml::{Attributes, TreeWalker, XmlEvent, XmlTextEvent}; -use yrs::types::{EntryChange, Path, PathSegment}; +use yrs::types::{DeepObservable, EntryChange, Path, PathSegment}; use yrs::SubscriptionId; use yrs::Transaction; use yrs::Xml; use yrs::XmlElement; use yrs::XmlText; -use crate::type_conversions::ToPython; +use crate::shared_types::{DeepSubscription, ShallowSubscription}; +use crate::type_conversions::{events_into_py, ToPython}; use crate::y_transaction::YTransaction; /// XML element data type. It represents an XML node, which can contain key-value attributes @@ -165,8 +167,9 @@ impl YXmlElement { /// Subscribes to all operations happening over this instance of `YXmlElement`. All changes are /// batched and eventually triggered during transaction commit phase. /// Returns an `SubscriptionId` which, can be used to unsubscribe the observer. - pub fn observe(&mut self, f: PyObject) -> SubscriptionId { - self.0 + pub fn observe(&mut self, f: PyObject) -> ShallowSubscription { + let sub_id = self + .0 .observe(move |txn, e| { Python::with_gil(|py| { let event = YXmlEvent::new(e, txn); @@ -175,11 +178,35 @@ impl YXmlElement { } }) }) - .into() + .into(); + + ShallowSubscription(sub_id) } + + /// Subscribes to all operations happening over this instance of `YXmlElement` and all of its children. + /// All changes are batched and eventually triggered during transaction commit phase. + /// Returns an `SubscriptionId` which, can be used to unsubscribe the observer. + pub fn observe_deep(&mut self, f: PyObject) -> DeepSubscription { + let sub_id = self + .0 + .observe_deep(move |txn, events| { + Python::with_gil(|py| { + let events = events_into_py(txn, events); + if let Err(err) = f.call1(py, (events,)) { + err.restore(py) + } + }) + }) + .into(); + DeepSubscription(sub_id) + } + /// Cancels the observer callback associated with the `subscripton_id`. - pub fn unobserve(&mut self, subscription_id: SubscriptionId) { - self.0.unobserve(subscription_id); + pub fn unobserve(&mut self, subscription_id: SubId) { + match subscription_id { + SubId::Shallow(ShallowSubscription(id)) => self.0.unobserve(id), + SubId::Deep(DeepSubscription(id)) => self.0.unobserve_deep(id), + } } } @@ -300,8 +327,9 @@ impl YXmlText { /// Subscribes to all operations happening over this instance of `YXmlText`. All changes are /// batched and eventually triggered during transaction commit phase. /// Returns an `SubscriptionId` which, which can be used to unsubscribe the callback function. - pub fn observe(&mut self, f: PyObject) -> SubscriptionId { - self.0 + pub fn observe(&mut self, f: PyObject) -> ShallowSubscription { + let sub_id: SubscriptionId = self + .0 .observe(move |txn, e| { Python::with_gil(|py| { let e = YXmlTextEvent::new(e, txn); @@ -310,12 +338,34 @@ impl YXmlText { } }) }) - .into() + .into(); + ShallowSubscription(sub_id) + } + + /// Subscribes to all operations happening over this instance of `YXmlText` and its child elements. All changes are + /// batched and eventually triggered during transaction commit phase. + /// Returns an `SubscriptionId` which, which can be used to unsubscribe the callback function. + pub fn observe_deep(&mut self, f: PyObject) -> DeepSubscription { + let sub_id: SubscriptionId = self + .0 + .observe_deep(move |txn, events| { + Python::with_gil(|py| { + let e = events_into_py(txn, events); + if let Err(err) = f.call1(py, (e,)) { + err.restore(py) + } + }) + }) + .into(); + DeepSubscription(sub_id) } /// Cancels the observer callback associated with the `subscripton_id`. - pub fn unobserve(&mut self, subscription_id: SubscriptionId) { - self.0.unobserve(subscription_id); + pub fn unobserve(&mut self, subscription_id: SubId) { + match subscription_id { + SubId::Shallow(ShallowSubscription(id)) => self.0.unobserve(id), + SubId::Deep(DeepSubscription(id)) => self.0.unobserve_deep(id), + } } } @@ -371,7 +421,7 @@ pub struct YXmlEvent { keys: Option, } impl YXmlEvent { - fn new(event: &XmlEvent, txn: &Transaction) -> Self { + pub fn new(event: &XmlEvent, txn: &Transaction) -> Self { let inner = event as *const XmlEvent; let txn = txn as *const Transaction; YXmlEvent { @@ -470,7 +520,7 @@ pub struct YXmlTextEvent { } impl YXmlTextEvent { - fn new(event: &XmlTextEvent, txn: &Transaction) -> Self { + pub fn new(event: &XmlTextEvent, txn: &Transaction) -> Self { let inner = event as *const XmlTextEvent; let txn = txn as *const Transaction; YXmlTextEvent { diff --git a/tests/test_y_array.py b/tests/test_y_array.py index 6f73212..b6d46a2 100644 --- a/tests/test_y_array.py +++ b/tests/test_y_array.py @@ -180,7 +180,7 @@ def callback(e): target = None delta = None - # insert item in the middle + # insert item in the middle with d1.begin_transaction() as txn: x.insert(txn, 1, [5]) assert target.to_json() == x.to_json() @@ -197,3 +197,34 @@ def callback(e): assert target == None assert delta == None + + +def test_deep_observe(): + """ + Ensure that changes to elements inside the array trigger a callback. + """ + ydoc = YDoc() + container = ydoc.get_array("container") + yarray = YArray([1, 2]) + with ydoc.begin_transaction() as txn: + container.push(txn, [yarray]) + + events = None + + def callback(e: list): + nonlocal events + events = e + + sub = container.observe_deep(callback) + with ydoc.begin_transaction() as txn: + container[0].push(txn, [3]) + + assert events != None + + # Ensure that observer unsubscribes + events = None + container.unobserve(sub) + with ydoc.begin_transaction() as txn: + container[0].push(txn, [4]) + + assert events == None diff --git a/tests/test_y_map.py b/tests/test_y_map.py index a9d9788..7ae0b14 100644 --- a/tests/test_y_map.py +++ b/tests/test_y_map.py @@ -154,3 +154,31 @@ def callback(e): x.set(txn, "key1", [6]) assert target == None assert entries == None + + +def test_deep_observe(): + """ + Ensure that changes to elements inside the array trigger a callback. + """ + doc = Y.YDoc() + container = doc.get_map("container") + inner_map = Y.YMap({"key": "initial"}) + with doc.begin_transaction() as txn: + container.set(txn, "inner", inner_map) + + events = None + + def callback(e: list): + nonlocal events + events = e + + sub = container.observe_deep(callback) + with doc.begin_transaction() as txn: + container["inner"].set(txn, "addition", 1) + + events = None + container.unobserve(sub) + with doc.begin_transaction() as txn: + container["inner"].set(txn, "don't show up", 1) + + assert events is None diff --git a/tests/test_y_text.py b/tests/test_y_text.py index 774a23e..0e0083d 100644 --- a/tests/test_y_text.py +++ b/tests/test_y_text.py @@ -195,3 +195,33 @@ def callback(e): assert delta == [{"retain": 4}, {"retain": 3, "attributes": {"bold": True}}] text.unobserve(sub) + + +def test_deep_observe(): + d = Y.YDoc() + text = d.get_text("text") + nested = Y.YMap({"bold": True}) + with d.begin_transaction() as txn: + text.push(txn, "Hello") + events = None + + def callback(e): + nonlocal events + events = e + + sub = text.observe_deep(callback) + + with d.begin_transaction() as txn: + # Currently, Yrs does not support deep observe on embedded values. + # Deep observe will pick up the same events as shallow observe. + text.push(txn, " World") + + assert events is not None and len(events) == 1 + + # verify that the subscription drops + events = None + text.unobserve(sub) + with d.begin_transaction() as txn: + nested.delete(txn, "new_attr") + + assert events is None diff --git a/tests/test_y_xml.py b/tests/test_y_xml.py index 3f3ce2d..2d68ff7 100644 --- a/tests/test_y_xml.py +++ b/tests/test_y_xml.py @@ -254,3 +254,22 @@ def callback(e): assert target == None assert nodes == None assert attributes == None + + +def test_deep_observe(): + ydoc = Y.YDoc() + container = ydoc.get_xml_element("container") + with ydoc.begin_transaction() as txn: + text = container.insert_xml_text(txn, 0) + + events = None + + def callback(e: list): + nonlocal events + events = e + + sub = container.observe_deep(callback) + with ydoc.begin_transaction() as txn: + container.first_child.push(txn, "nested") + + assert events != None diff --git a/y_py.pyi b/y_py.pyi index c62fa0d..ac5ee51 100644 --- a/y_py.pyi +++ b/y_py.pyi @@ -12,7 +12,13 @@ from typing import ( Dict, ) -SubscriptionId = int +class SubscriptionId: + """ + Tracks an observer callback. Pass this to the `unobserve` method to cancel + its associated callback. + """ + +Event = Union[YTextEvent, YArrayEvent, YMapEvent, YXmlTextEvent, YXmlElementEvent] class YDoc: """ @@ -424,6 +430,17 @@ class YText: Returns: A reference to the callback subscription. """ + def observe_deep(self, f: Callable[[List[Event]]]) -> SubscriptionId: + """ + Assigns a callback function to listen to the updates of the YText instance and those of its nested attributes. + Currently, this listens to the same events as YText.observe, but in the future this will also listen to + the events of embedded values. + + Args: + f: Callback function that runs when the text object or its nested attributes receive an update. + Returns: + A reference to the callback subscription. + """ def unobserve(self, subscription_id: SubscriptionId): """ Cancels the observer callback associated with the `subscripton_id`. @@ -527,6 +544,15 @@ class YArray: Returns: An identifier associated with the callback subscription. """ + def observe_deep(self, f: Callable[[List[Event]]]) -> SubscriptionId: + """ + Assigns a callback function to listen to the aggregated updates of the YArray and its child elements. + + Args: + f: Callback function that runs when the array object or components receive an update. + Returns: + An identifier associated with the callback subscription. + """ def unobserve(self, subscription_id: SubscriptionId): """ Cancels the observer callback associated with the `subscripton_id`. @@ -643,6 +669,15 @@ class YMap: Returns: A reference to the callback subscription. Delete this observer in order to erase the associated callback function. """ + def observe_deep(self, f: Callable[[List[Event]]]) -> SubscriptionId: + """ + Assigns a callback function to listen to YMap and child element updates. + + Args: + f: Callback function that runs when the map object or any of its tracked elements receive an update. + Returns: + A reference to the callback subscription. Delete this observer in order to erase the associated callback function. + """ def unobserve(self, subscription_id: SubscriptionId): """ Cancels the observer callback associated with the `subscripton_id`. @@ -777,6 +812,16 @@ class YXmlElement: Returns: A `SubscriptionId` that can be used to cancel the observer callback. """ + def observe_deep(self, f: Callable[[List[Event]]]) -> SubscriptionId: + """ + Subscribes to all operations happening over this instance of `YXmlElement` and its children. All changes are + batched and eventually triggered during transaction commit phase. + + Args: + f: A callback function that receives update events from the Xml element and its children. + Returns: + A `SubscriptionId` that can be used to cancel the observer callback. + """ def unobserve(self, subscription_id: SubscriptionId): """ Cancels the observer callback associated with the `subscripton_id`. @@ -845,6 +890,18 @@ class YXmlText: Args: f: A callback function that receives update events. + deep: Determines whether observer is triggered by changes to elements in the YXmlText. + Returns: + A `SubscriptionId` that can be used to cancel the observer callback. + """ + def observe_deep(self, f: Callable[[List[Event]]]) -> SubscriptionId: + """ + Subscribes to all operations happening over this instance of `YXmlText` and its children. All changes are + batched and eventually triggered during transaction commit phase. + + Args: + f: A callback function that receives update events of this element and its descendants. + deep: Determines whether observer is triggered by changes to elements in the YXmlText. Returns: A `SubscriptionId` that can be used to cancel the observer callback. """