Skip to content

Commit

Permalink
Add ReadTransaction class and check transaction type on operation
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Nov 30, 2023
1 parent 3ee16d4 commit 97d52ee
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 36 deletions.
2 changes: 2 additions & 0 deletions python/pycrdt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
from .map import MapEvent as MapEvent
from .text import Text as Text
from .text import TextEvent as TextEvent
from .transaction import ReadTransaction as ReadTransaction
from .transaction import Transaction as Transaction
32 changes: 23 additions & 9 deletions python/pycrdt/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ._pycrdt import Array as _Array
from ._pycrdt import ArrayEvent as _ArrayEvent
from .base import BaseDoc, BaseEvent, BaseType, base_types, event_types
from .transaction import ReadTransaction

if TYPE_CHECKING:
from .doc import Doc
Expand Down Expand Up @@ -37,22 +38,27 @@ def _init(self, value: list[Any] | None) -> None:

def _set(self, index: int, value: Any) -> None:
with self.doc.transaction() as txn:
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
if isinstance(value, BaseDoc):
# subdoc
self.integrated.insert_doc(txn, index, value._doc)
self.integrated.insert_doc(txn._txn, index, value._doc)
elif isinstance(value, BaseType):
# shared type
self._do_and_integrate("insert", value, txn, index)
assert txn._txn is not None
self._do_and_integrate("insert", value, txn._txn, index)
else:
# primitive type
self.integrated.insert(txn, index, value)
self.integrated.insert(txn._txn, index, value)

def _get_or_insert(self, name: str, doc: Doc) -> _Array:
return doc._doc.get_or_insert_array(name)

def __len__(self) -> int:
with self.doc.transaction() as txn:
return self.integrated.len(txn)
return self.integrated.len(txn._txn)

def append(self, value: Any) -> None:
with self.doc.transaction():
Expand All @@ -76,7 +82,11 @@ def pop(self, index: int = -1) -> Any:

def move(self, source_index: int, destination_index: int) -> None:
with self.doc.transaction() as txn:
self.integrated.move_to(txn, source_index, destination_index)
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
self.integrated.move_to(txn._txn, source_index, destination_index)

def __add__(self, value: list[Any]) -> Array:
with self.doc.transaction():
Expand Down Expand Up @@ -113,13 +123,17 @@ def __setitem__(self, key: int | slice, value: Any | list[Any]) -> None:

def __delitem__(self, key: int | slice) -> None:
with self.doc.transaction() as txn:
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
if isinstance(key, int):
length = len(self)
if length == 0:
raise IndexError("Array index out of range")
if key < 0:
key += length
self.integrated.remove_range(txn, key, 1)
self.integrated.remove_range(txn._txn, key, 1)
elif isinstance(key, slice):
if key.step is not None:
raise RuntimeError("Step not supported")
Expand All @@ -135,7 +149,7 @@ def __delitem__(self, key: int | slice) -> None:
raise RuntimeError("Negative stop not supported")
else:
n = key.stop - i
self.integrated.remove_range(txn, i, n)
self.integrated.remove_range(txn._txn, i, n)
else:
raise RuntimeError(f"Index not supported: {key}")

Expand All @@ -147,7 +161,7 @@ def __getitem__(self, key: int) -> BaseType:
raise IndexError("Array index out of range")
if key < 0:
key += length
return self._maybe_as_type_or_doc(self.integrated.get(txn, key))
return self._maybe_as_type_or_doc(self.integrated.get(txn._txn, key))
elif isinstance(key, slice):
i0 = 0 if key.start is None else key.start
i1 = len(self) if key.stop is None else key.stop
Expand All @@ -159,7 +173,7 @@ def __iter__(self):

def __str__(self) -> str:
with self.doc.transaction() as txn:
return self.integrated.to_json(txn)
return self.integrated.to_json(txn._txn)

def observe(self, callback: Callable[[Any], None]) -> str:
_callback = partial(observe_callback, callback, self.doc)
Expand Down
8 changes: 4 additions & 4 deletions python/pycrdt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from ._pycrdt import Doc as _Doc
from ._pycrdt import Transaction as _Transaction
from .transaction import Transaction
from .transaction import ReadTransaction, Transaction

if TYPE_CHECKING:
from .doc import Doc
Expand Down Expand Up @@ -64,12 +64,12 @@ def _get_or_insert(self, name: str, doc: Doc) -> Any:
def _init(self, value: Any | None) -> None:
...

def _current_transaction(self) -> _Transaction:
def _current_transaction(self) -> Transaction:
if self._doc is None:
raise RuntimeError("Not associated with a document")
if self._doc._txn is None:
raise RuntimeError("No current transaction")
res = cast(_Transaction, self._doc._txn._txn)
res = cast(Transaction, self._doc._txn)
return res

def _integrate(self, doc: Doc, integrated: Any) -> Any:
Expand Down Expand Up @@ -164,5 +164,5 @@ def process_event(value: Any, doc: Doc, txn) -> Any:
else:
base_type = cast(Type[BaseType], base_types[val_type])
value = base_type(_integrated=value, _doc=doc)
doc._txn = Transaction(doc=doc, _txn=txn)
doc._txn = ReadTransaction(doc=doc, _txn=txn)
return value
37 changes: 25 additions & 12 deletions python/pycrdt/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ._pycrdt import Map as _Map
from ._pycrdt import MapEvent as _MapEvent
from .base import BaseDoc, BaseEvent, BaseType, base_types, event_types
from .transaction import ReadTransaction

if TYPE_CHECKING:
from .doc import Doc
Expand Down Expand Up @@ -37,45 +38,57 @@ def _init(self, value: dict[str, Any] | None) -> None:

def _set(self, key: str, value: Any) -> None:
with self.doc.transaction() as txn:
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
if isinstance(value, BaseDoc):
# subdoc
self.integrated.insert_doc(txn, key, value._doc)
self.integrated.insert_doc(txn._txn, key, value._doc)
elif isinstance(value, BaseType):
# shared type
self._do_and_integrate("insert", value, txn, key)
assert txn._txn is not None
self._do_and_integrate("insert", value, txn._txn, key)
else:
# primitive type
self.integrated.insert(txn, key, value)
self.integrated.insert(txn._txn, key, value)

def _get_or_insert(self, name: str, doc: Doc) -> _Map:
return doc._doc.get_or_insert_map(name)

def __len__(self) -> int:
with self.doc.transaction() as txn:
return self.integrated.len(txn)
return self.integrated.len(txn._txn)

def __str__(self) -> str:
with self.doc.transaction() as txn:
return self.integrated.to_json(txn)
return self.integrated.to_json(txn._txn)

def __delitem__(self, key: str) -> None:
if not isinstance(key, str):
raise RuntimeError("Key must be of type string")
txn = self._current_transaction()
self.integrated.remove(txn, key)
with self.doc.transaction() as txn:
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
self.integrated.remove(txn._txn, key)

def __getitem__(self, key: str) -> Any:
with self.doc.transaction() as txn:
if not isinstance(key, str):
raise RuntimeError("Key must be of type string")
return self._maybe_as_type_or_doc(self.integrated.get(txn, key))
return self._maybe_as_type_or_doc(self.integrated.get(txn._txn, key))

def __setitem__(self, key: str, value: Any) -> None:
if not isinstance(key, str):
raise RuntimeError("Key must be of type string")
with self.doc.transaction():
self._set(key, value)

def __iter__(self):
return self.keys()

def get(self, key: str, default_value: Any | None = None) -> Any | None:
with self.doc.transaction():
if key in self.keys():
Expand All @@ -96,21 +109,21 @@ def pop(self, key: str, default_value: Any | None = None) -> Any:

def keys(self):
with self.doc.transaction() as txn:
return iter(self.integrated.keys(txn))
return iter(self.integrated.keys(txn._txn))

def values(self):
with self.doc.transaction() as txn:
for k in self.integrated.keys(txn):
for k in self.integrated.keys(txn._txn):
yield self[k]

def items(self):
with self.doc.transaction() as txn:
for k in self.integrated.keys(txn):
for k in self.integrated.keys(txn._txn):
yield k, self[k]

def clear(self) -> None:
with self.doc.transaction() as txn:
for k in self.integrated.keys(txn):
for k in self.integrated.keys(txn._txn):
del self[k]

def update(self, value: dict[str, Any]) -> None:
Expand Down
30 changes: 21 additions & 9 deletions python/pycrdt/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ._pycrdt import Text as _Text
from ._pycrdt import TextEvent as _TextEvent
from .base import BaseEvent, BaseType, base_types, event_types
from .transaction import ReadTransaction

if TYPE_CHECKING:
from .doc import Doc
Expand All @@ -32,29 +33,36 @@ def _init(self, value: str | None) -> None:
if value is None:
return
with self.doc.transaction() as txn:
self.integrated.insert(txn, 0, value)
self.integrated.insert(txn._txn, 0, value)

def _get_or_insert(self, name: str, doc: Doc) -> _Text:
return doc._doc.get_or_insert_text(name)

def __len__(self) -> int:
with self.doc.transaction() as txn:
return self.integrated.len(txn)
return self.integrated.len(txn._txn)

def __str__(self) -> str:
with self.doc.transaction():
txn = self._current_transaction()
return self.integrated.get_string(txn)
with self.doc.transaction() as txn:
return self.integrated.get_string(txn._txn)

def __iadd__(self, value: str) -> Text:
with self.doc.transaction() as txn:
self.integrated.insert(txn, len(self), value)
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
self.integrated.insert(txn._txn, len(self), value)
return self

def __delitem__(self, key: int | slice) -> None:
with self.doc.transaction() as txn:
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
if isinstance(key, int):
self.integrated.remove_range(txn, key, 1)
self.integrated.remove_range(txn._txn, key, 1)
elif isinstance(key, slice):
if key.step is not None:
raise RuntimeError("Step not supported")
Expand All @@ -70,12 +78,16 @@ def __delitem__(self, key: int | slice) -> None:
raise RuntimeError("Negative stop not supported")
else:
n = key.stop - i
self.integrated.remove_range(txn, i, n)
self.integrated.remove_range(txn._txn, i, n)
else:
raise RuntimeError(f"Index not supported: {key}")

def __setitem__(self, key: int | slice, value: str) -> None:
with self.doc.transaction() as txn:
if isinstance(txn, ReadTransaction):
raise RuntimeError(
"Read-only transaction cannot be used to modify document structure"
)
if isinstance(key, int):
raise RuntimeError("Single item assignment not supported")
elif isinstance(key, slice):
Expand All @@ -85,7 +97,7 @@ def __setitem__(self, key: int | slice, value: str) -> None:
raise RuntimeError("Start and stop should be equal")
if len(self) <= key.start < 0:
raise RuntimeError("Index out of range")
self.integrated.insert(txn, key.start, value)
self.integrated.insert(txn._txn, key.start, value)
else:
raise RuntimeError(f"Index not supported: {key}")

Expand Down
8 changes: 6 additions & 2 deletions python/pycrdt/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ def __init__(self, doc: Doc, _txn: _Transaction | None = None) -> None:
self._txn = _txn
self._nb = 0

def __enter__(self) -> _Transaction:
def __enter__(self) -> Transaction:
self._nb += 1
if self._txn is None:
self._txn = self._doc._doc.create_transaction()
self._doc._txn = self
return self._txn
return self

def __exit__(self, exc_type, exc_value, exc_tb) -> None:
self._nb -= 1
Expand All @@ -36,3 +36,7 @@ def __exit__(self, exc_type, exc_value, exc_tb) -> None:
self._txn.drop()
self._txn = None
self._doc._txn = None


class ReadTransaction(Transaction):
pass

0 comments on commit 97d52ee

Please sign in to comment.