pycrdt 0.12.35__cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl → 0.12.45__cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pycrdt might be problematic. Click here for more details.
- pycrdt/__init__.py +5 -0
- pycrdt/_base.py +22 -6
- pycrdt/_doc.py +96 -9
- pycrdt/_provider.py +2 -4
- pycrdt/_pycrdt.cpython-313-powerpc64le-linux-gnu.so +0 -0
- pycrdt/_pycrdt.pyi +66 -5
- pycrdt/_transaction.py +46 -15
- pycrdt/_undo.py +11 -1
- pycrdt/_version.py +1 -7
- pycrdt/_xml.py +1 -3
- {pycrdt-0.12.35.dist-info → pycrdt-0.12.45.dist-info}/METADATA +4 -4
- pycrdt-0.12.45.dist-info/RECORD +23 -0
- pycrdt-0.12.45.dist-info/WHEEL +5 -0
- pycrdt-0.12.35.dist-info/RECORD +0 -23
- pycrdt-0.12.35.dist-info/WHEEL +0 -4
- {pycrdt-0.12.35.dist-info → pycrdt-0.12.45.dist-info}/licenses/LICENSE +0 -0
pycrdt/__init__.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from pkgutil import extend_path
|
|
2
|
+
|
|
3
|
+
__path__ = extend_path(__path__, __name__)
|
|
4
|
+
|
|
1
5
|
from ._array import Array as Array
|
|
2
6
|
from ._array import ArrayEvent as ArrayEvent
|
|
3
7
|
from ._array import TypedArray as TypedArray
|
|
@@ -10,6 +14,7 @@ from ._map import MapEvent as MapEvent
|
|
|
10
14
|
from ._map import TypedMap as TypedMap
|
|
11
15
|
from ._provider import Channel as Channel
|
|
12
16
|
from ._provider import Provider as Provider
|
|
17
|
+
from ._pycrdt import DeleteSet as DeleteSet
|
|
13
18
|
from ._pycrdt import StackItem as StackItem
|
|
14
19
|
from ._pycrdt import SubdocsEvent as SubdocsEvent
|
|
15
20
|
from ._pycrdt import Subscription as Subscription
|
pycrdt/_base.py
CHANGED
|
@@ -4,6 +4,7 @@ import threading
|
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from functools import lru_cache, partial
|
|
6
6
|
from inspect import signature
|
|
7
|
+
from types import UnionType
|
|
7
8
|
from typing import (
|
|
8
9
|
TYPE_CHECKING,
|
|
9
10
|
Any,
|
|
@@ -12,12 +13,15 @@ from typing import (
|
|
|
12
13
|
Type,
|
|
13
14
|
Union,
|
|
14
15
|
cast,
|
|
16
|
+
get_args,
|
|
17
|
+
get_origin,
|
|
15
18
|
get_type_hints,
|
|
16
19
|
overload,
|
|
17
20
|
)
|
|
18
21
|
|
|
19
22
|
import anyio
|
|
20
23
|
from anyio import BrokenResourceError, create_memory_object_stream
|
|
24
|
+
from anyio.abc import TaskGroup
|
|
21
25
|
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
22
26
|
|
|
23
27
|
from ._pycrdt import Doc as _Doc
|
|
@@ -43,12 +47,14 @@ class BaseDoc:
|
|
|
43
47
|
_doc: _Doc
|
|
44
48
|
_twin_doc: BaseDoc | None
|
|
45
49
|
_txn: Transaction | None
|
|
50
|
+
_exceptions: list[Exception]
|
|
46
51
|
_txn_lock: threading.Lock
|
|
47
52
|
_txn_async_lock: anyio.Lock
|
|
48
53
|
_allow_multithreading: bool
|
|
49
54
|
_Model: Any
|
|
50
55
|
_subscriptions: list[Subscription]
|
|
51
56
|
_origins: dict[int, Any]
|
|
57
|
+
_task_group: TaskGroup | None
|
|
52
58
|
|
|
53
59
|
def __init__(
|
|
54
60
|
self,
|
|
@@ -65,12 +71,14 @@ class BaseDoc:
|
|
|
65
71
|
doc = _Doc(client_id, skip_gc)
|
|
66
72
|
self._doc = doc
|
|
67
73
|
self._txn = None
|
|
74
|
+
self._exceptions = []
|
|
68
75
|
self._txn_lock = threading.Lock()
|
|
69
76
|
self._txn_async_lock = anyio.Lock()
|
|
70
77
|
self._Model = Model
|
|
71
78
|
self._subscriptions = []
|
|
72
79
|
self._origins = {}
|
|
73
80
|
self._allow_multithreading = allow_multithreading
|
|
81
|
+
self._task_group = None
|
|
74
82
|
|
|
75
83
|
|
|
76
84
|
class BaseType(ABC):
|
|
@@ -296,7 +304,10 @@ def observe_callback(
|
|
|
296
304
|
_event = event_types[type(event)](event, doc)
|
|
297
305
|
with doc._read_transaction(event.transaction) as txn:
|
|
298
306
|
params = (_event, txn)
|
|
299
|
-
|
|
307
|
+
try:
|
|
308
|
+
callback(*params[:param_nb]) # type: ignore[arg-type]
|
|
309
|
+
except Exception as exc:
|
|
310
|
+
doc._exceptions.append(exc)
|
|
300
311
|
|
|
301
312
|
|
|
302
313
|
def observe_deep_callback(
|
|
@@ -309,7 +320,10 @@ def observe_deep_callback(
|
|
|
309
320
|
events[idx] = event_types[type(event)](event, doc)
|
|
310
321
|
with doc._read_transaction(event.transaction) as txn:
|
|
311
322
|
params = (events, txn)
|
|
312
|
-
|
|
323
|
+
try:
|
|
324
|
+
callback(*params[:param_nb]) # type: ignore[arg-type]
|
|
325
|
+
except Exception as exc:
|
|
326
|
+
doc._exceptions.append(exc)
|
|
313
327
|
|
|
314
328
|
|
|
315
329
|
class BaseEvent:
|
|
@@ -385,10 +399,12 @@ class Typed:
|
|
|
385
399
|
if key not in annotations:
|
|
386
400
|
raise AttributeError(f'"{type(self).mro()[0]}" has no attribute "{key}"')
|
|
387
401
|
expected_type = annotations[key]
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
402
|
+
origin = get_origin(expected_type)
|
|
403
|
+
if origin in (Union, UnionType):
|
|
404
|
+
expected_types = get_args(expected_type)
|
|
405
|
+
elif origin is not None:
|
|
406
|
+
expected_type = origin
|
|
407
|
+
expected_types = (expected_type,)
|
|
392
408
|
else:
|
|
393
409
|
expected_types = (expected_type,)
|
|
394
410
|
if type(value) not in expected_types:
|
pycrdt/_doc.py
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from functools import partial
|
|
4
|
-
from
|
|
4
|
+
from inspect import iscoroutinefunction
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Awaitable,
|
|
8
|
+
Callable,
|
|
9
|
+
Generic,
|
|
10
|
+
Iterable,
|
|
11
|
+
Literal,
|
|
12
|
+
Type,
|
|
13
|
+
TypeVar,
|
|
14
|
+
Union,
|
|
15
|
+
cast,
|
|
16
|
+
overload,
|
|
17
|
+
)
|
|
5
18
|
|
|
6
19
|
from anyio import BrokenResourceError, create_memory_object_stream
|
|
7
20
|
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
@@ -14,6 +27,9 @@ from ._snapshot import Snapshot
|
|
|
14
27
|
from ._transaction import NewTransaction, ReadTransaction, Transaction
|
|
15
28
|
|
|
16
29
|
T = TypeVar("T", bound=BaseType)
|
|
30
|
+
TransactionOrSubdocsEvent = TypeVar(
|
|
31
|
+
"TransactionOrSubdocsEvent", bound=TransactionEvent | SubdocsEvent
|
|
32
|
+
)
|
|
17
33
|
|
|
18
34
|
|
|
19
35
|
class Doc(BaseDoc, Generic[T]):
|
|
@@ -45,7 +61,11 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
45
61
|
allow_multithreading: Whether to allow the document to be used in different threads.
|
|
46
62
|
"""
|
|
47
63
|
super().__init__(
|
|
48
|
-
client_id=client_id,
|
|
64
|
+
client_id=client_id,
|
|
65
|
+
skip_gc=skip_gc,
|
|
66
|
+
doc=doc,
|
|
67
|
+
Model=Model,
|
|
68
|
+
allow_multithreading=allow_multithreading,
|
|
49
69
|
)
|
|
50
70
|
for k, v in init.items():
|
|
51
71
|
self[k] = v
|
|
@@ -144,7 +164,9 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
144
164
|
Returns:
|
|
145
165
|
The current document state.
|
|
146
166
|
"""
|
|
147
|
-
|
|
167
|
+
with self.transaction() as txn:
|
|
168
|
+
assert txn._txn is not None
|
|
169
|
+
return self._doc.get_state(txn._txn)
|
|
148
170
|
|
|
149
171
|
def get_update(self, state: bytes | None = None) -> bytes:
|
|
150
172
|
"""
|
|
@@ -156,7 +178,9 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
156
178
|
"""
|
|
157
179
|
if state is None:
|
|
158
180
|
state = b"\x00"
|
|
159
|
-
|
|
181
|
+
with self.transaction() as txn:
|
|
182
|
+
assert txn._txn is not None
|
|
183
|
+
return self._doc.get_update(txn._txn, state)
|
|
160
184
|
|
|
161
185
|
def apply_update(self, update: bytes) -> None:
|
|
162
186
|
"""
|
|
@@ -284,21 +308,44 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
284
308
|
for key, val in self._doc.roots(txn._txn).items()
|
|
285
309
|
}
|
|
286
310
|
|
|
287
|
-
def observe(
|
|
311
|
+
def observe(
|
|
312
|
+
self,
|
|
313
|
+
callback: Callable[[TransactionEvent], None]
|
|
314
|
+
| Callable[[TransactionEvent], Awaitable[None]],
|
|
315
|
+
) -> Subscription:
|
|
288
316
|
"""
|
|
289
317
|
Subscribes a callback to be called with the document change event.
|
|
290
318
|
|
|
291
319
|
Args:
|
|
292
320
|
callback: The callback to call with the [TransactionEvent][pycrdt.TransactionEvent].
|
|
321
|
+
If the callback is async, async transactions must be used.
|
|
293
322
|
|
|
294
323
|
Returns:
|
|
295
324
|
The subscription that can be used to [unobserve()][pycrdt.Doc.unobserve].
|
|
296
325
|
"""
|
|
297
|
-
|
|
326
|
+
if iscoroutinefunction(callback):
|
|
327
|
+
cb = self._async_callback_to_sync(callback)
|
|
328
|
+
else:
|
|
329
|
+
cb = partial(observe_callback, cast(Callable[[TransactionEvent], None], callback), self)
|
|
330
|
+
subscription = self._doc.observe(cb)
|
|
298
331
|
self._subscriptions.append(subscription)
|
|
299
332
|
return subscription
|
|
300
333
|
|
|
301
|
-
def
|
|
334
|
+
def _async_callback_to_sync(
|
|
335
|
+
self,
|
|
336
|
+
async_callback: Callable[[TransactionOrSubdocsEvent], Awaitable[None]],
|
|
337
|
+
) -> Callable[[TransactionOrSubdocsEvent], None]:
|
|
338
|
+
def callback(event: TransactionOrSubdocsEvent) -> None:
|
|
339
|
+
if self._task_group is None:
|
|
340
|
+
raise RuntimeError("Async callback in non-async transaction")
|
|
341
|
+
self._task_group.start_soon(async_callback, event)
|
|
342
|
+
|
|
343
|
+
return callback
|
|
344
|
+
|
|
345
|
+
def observe_subdocs(
|
|
346
|
+
self,
|
|
347
|
+
callback: Callable[[SubdocsEvent], None] | Callable[[SubdocsEvent], Awaitable[None]],
|
|
348
|
+
) -> Subscription:
|
|
302
349
|
"""
|
|
303
350
|
Subscribes a callback to be called with the document subdoc change event.
|
|
304
351
|
|
|
@@ -308,7 +355,11 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
308
355
|
Returns:
|
|
309
356
|
The subscription that can be used to [unobserve()][pycrdt.Doc.unobserve].
|
|
310
357
|
"""
|
|
311
|
-
|
|
358
|
+
if iscoroutinefunction(callback):
|
|
359
|
+
cb = self._async_callback_to_sync(callback)
|
|
360
|
+
else:
|
|
361
|
+
cb = partial(observe_callback, cast(Callable[[SubdocsEvent], None], callback), self)
|
|
362
|
+
subscription = self._doc.observe_subdocs(cb)
|
|
312
363
|
self._subscriptions.append(subscription)
|
|
313
364
|
return subscription
|
|
314
365
|
|
|
@@ -327,6 +378,7 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
327
378
|
self,
|
|
328
379
|
subdocs: Literal[False] = False,
|
|
329
380
|
max_buffer_size: float = float("inf"),
|
|
381
|
+
async_transactions: bool = False,
|
|
330
382
|
) -> MemoryObjectReceiveStream[TransactionEvent]: ...
|
|
331
383
|
|
|
332
384
|
@overload
|
|
@@ -334,12 +386,14 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
334
386
|
self,
|
|
335
387
|
subdocs: Literal[True] = True,
|
|
336
388
|
max_buffer_size: float = float("inf"),
|
|
389
|
+
async_transactions: bool = False,
|
|
337
390
|
) -> MemoryObjectReceiveStream[list[SubdocsEvent]]: ...
|
|
338
391
|
|
|
339
392
|
def events(
|
|
340
393
|
self,
|
|
341
394
|
subdocs: bool = False,
|
|
342
395
|
max_buffer_size: float = float("inf"),
|
|
396
|
+
async_transactions: bool = False,
|
|
343
397
|
):
|
|
344
398
|
"""
|
|
345
399
|
Allows to asynchronously iterate over the document events, without using a callback.
|
|
@@ -360,13 +414,21 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
360
414
|
subdocs: Whether to iterate over the [SubdocsEvent][pycrdt.SubdocsEvent] events
|
|
361
415
|
(default is [TransactionEvent][pycrdt.TransactionEvent]).
|
|
362
416
|
max_buffer_size: Maximum number of events that can be buffered.
|
|
417
|
+
async_transactions: Whether async transactions are used for this document,
|
|
418
|
+
in which case iterating over the events can put back-pressure on the
|
|
419
|
+
transactions (don't use an infinite `max_buffer_size` in this case).
|
|
363
420
|
|
|
364
421
|
Returns:
|
|
365
422
|
An async iterator over the document events.
|
|
366
423
|
"""
|
|
367
424
|
observe = self.observe_subdocs if subdocs else self.observe
|
|
368
425
|
if not self._send_streams[subdocs]:
|
|
369
|
-
|
|
426
|
+
if async_transactions:
|
|
427
|
+
self._event_subscription[subdocs] = observe(
|
|
428
|
+
partial(self._async_send_event, subdocs)
|
|
429
|
+
)
|
|
430
|
+
else:
|
|
431
|
+
self._event_subscription[subdocs] = observe(partial(self._send_event, subdocs))
|
|
370
432
|
send_stream, receive_stream = create_memory_object_stream[
|
|
371
433
|
Union[TransactionEvent, SubdocsEvent]
|
|
372
434
|
](max_buffer_size=max_buffer_size)
|
|
@@ -387,6 +449,20 @@ class Doc(BaseDoc, Generic[T]):
|
|
|
387
449
|
if not send_streams:
|
|
388
450
|
self.unobserve(self._event_subscription[subdocs])
|
|
389
451
|
|
|
452
|
+
async def _async_send_event(self, subdocs: bool, event: TransactionEvent | SubdocsEvent):
|
|
453
|
+
to_remove: list[MemoryObjectSendStream[TransactionEvent | SubdocsEvent]] = []
|
|
454
|
+
send_streams = self._send_streams[subdocs]
|
|
455
|
+
for send_stream in send_streams:
|
|
456
|
+
try:
|
|
457
|
+
await send_stream.send(event)
|
|
458
|
+
except BrokenResourceError:
|
|
459
|
+
to_remove.append(send_stream)
|
|
460
|
+
for send_stream in to_remove:
|
|
461
|
+
send_stream.close()
|
|
462
|
+
send_streams.remove(send_stream)
|
|
463
|
+
if not send_streams:
|
|
464
|
+
self.unobserve(self._event_subscription[subdocs])
|
|
465
|
+
|
|
390
466
|
|
|
391
467
|
class TypedDoc(Typed):
|
|
392
468
|
"""
|
|
@@ -427,4 +503,15 @@ class TypedDoc(Typed):
|
|
|
427
503
|
doc[name] = root_type
|
|
428
504
|
|
|
429
505
|
|
|
506
|
+
def observe_callback(
|
|
507
|
+
callback: Callable[[TransactionEvent], None] | Callable[[SubdocsEvent], None],
|
|
508
|
+
doc: Doc,
|
|
509
|
+
event: Any,
|
|
510
|
+
) -> None:
|
|
511
|
+
try:
|
|
512
|
+
callback(event)
|
|
513
|
+
except Exception as exc:
|
|
514
|
+
doc._exceptions.append(exc)
|
|
515
|
+
|
|
516
|
+
|
|
430
517
|
base_types[_Doc] = Doc
|
pycrdt/_provider.py
CHANGED
|
@@ -141,10 +141,8 @@ class Provider:
|
|
|
141
141
|
return self
|
|
142
142
|
|
|
143
143
|
async def __aexit__(self, exc_type, exc_value, exc_tb):
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
finally:
|
|
147
|
-
return await self._exit_stack.__aexit__(exc_type, exc_value, exc_tb)
|
|
144
|
+
await self.stop()
|
|
145
|
+
return await self._exit_stack.__aexit__(exc_type, exc_value, exc_tb)
|
|
148
146
|
|
|
149
147
|
@asynccontextmanager
|
|
150
148
|
async def _get_or_create_task_group(self) -> AsyncIterator[TaskGroup]:
|
|
Binary file
|
pycrdt/_pycrdt.pyi
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Callable, Iterator
|
|
1
|
+
from typing import Any, Callable, Generic, Iterator, TypeVar
|
|
2
2
|
|
|
3
3
|
class Snapshot:
|
|
4
4
|
"""A snapshot of a document's state at a given point in time."""
|
|
@@ -49,10 +49,10 @@ class Doc:
|
|
|
49
49
|
def get_or_insert_xml_fragment(self, txn: Transaction, name: str) -> XmlFragment:
|
|
50
50
|
"""Create an XML fragment root type on this document, or get an existing one."""
|
|
51
51
|
|
|
52
|
-
def get_state(self) -> bytes:
|
|
52
|
+
def get_state(self, txn: Transaction) -> bytes:
|
|
53
53
|
"""Get the current document state."""
|
|
54
54
|
|
|
55
|
-
def get_update(self, state: bytes) -> bytes:
|
|
55
|
+
def get_update(self, txn: Transaction, state: bytes) -> bytes:
|
|
56
56
|
"""Get the update from the given state to the current state."""
|
|
57
57
|
|
|
58
58
|
def apply_update(self, txn: Transaction, update: bytes) -> None:
|
|
@@ -363,7 +363,14 @@ class XmlText:
|
|
|
363
363
|
class UndoManager:
|
|
364
364
|
"""Undo manager."""
|
|
365
365
|
|
|
366
|
-
def __init__(
|
|
366
|
+
def __init__(
|
|
367
|
+
self,
|
|
368
|
+
doc: Doc,
|
|
369
|
+
capture_timeout_millis: int,
|
|
370
|
+
timestamp: Callable[[], int],
|
|
371
|
+
undo_stack: list[StackItem] | None = None,
|
|
372
|
+
redo_stack: list[StackItem] | None = None,
|
|
373
|
+
) -> None:
|
|
367
374
|
"""Creates an undo manager."""
|
|
368
375
|
|
|
369
376
|
def expand_scope(self, scope: Text | Array | Map) -> None:
|
|
@@ -396,11 +403,65 @@ class UndoManager:
|
|
|
396
403
|
def redo_stack(self) -> list[StackItem]:
|
|
397
404
|
"""Returns the undo manager's redo stack."""
|
|
398
405
|
|
|
399
|
-
class
|
|
406
|
+
class DeleteSet:
|
|
407
|
+
"""A set of deletions in a CRDT document."""
|
|
408
|
+
|
|
409
|
+
def __init__(self) -> None:
|
|
410
|
+
"""Create a new empty DeleteSet."""
|
|
411
|
+
|
|
412
|
+
def encode(self) -> bytes:
|
|
413
|
+
"""Encode the DeleteSet to bytes."""
|
|
414
|
+
|
|
415
|
+
@staticmethod
|
|
416
|
+
def decode(data: bytes) -> DeleteSet:
|
|
417
|
+
"""Decode a DeleteSet from bytes."""
|
|
418
|
+
|
|
419
|
+
MetaT = TypeVar("MetaT")
|
|
420
|
+
|
|
421
|
+
class StackItem(Generic[MetaT]):
|
|
400
422
|
"""A unit of work for the [UndoManager][pycrdt.UndoManager], consisting of
|
|
401
423
|
compressed information about all updates and deletions tracked by it.
|
|
402
424
|
"""
|
|
403
425
|
|
|
426
|
+
def __init__(
|
|
427
|
+
self, deletions: DeleteSet, insertions: DeleteSet, meta: MetaT | None = None
|
|
428
|
+
) -> None:
|
|
429
|
+
"""Create a new StackItem.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
deletions: The DeleteSet of deletions.
|
|
433
|
+
insertions: The DeleteSet of insertions.
|
|
434
|
+
meta: Optional metadata (can be any Python object).
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def deletions(self) -> DeleteSet:
|
|
439
|
+
"""Get the deletions DeleteSet."""
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def insertions(self) -> DeleteSet:
|
|
443
|
+
"""Get the insertions DeleteSet."""
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def meta(self) -> MetaT | None:
|
|
447
|
+
"""Custom metadata. Can be any Python object."""
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def merge(
|
|
451
|
+
a: "StackItem[MetaT]",
|
|
452
|
+
b: "StackItem[MetaT]",
|
|
453
|
+
merge_meta: Callable[[MetaT | None, MetaT | None], Any] | None = None,
|
|
454
|
+
) -> "StackItem[Any]":
|
|
455
|
+
"""Merge two StackItems into one containing the union of their deletions and insertions.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
a: First StackItem to merge.
|
|
459
|
+
b: Second StackItem to merge.
|
|
460
|
+
merge_meta: Optional function to handle metadata conflicts.
|
|
461
|
+
Takes (meta_a, meta_b) and returns the merged metadata.
|
|
462
|
+
If None, keeps the first item's metadata.
|
|
463
|
+
"""
|
|
464
|
+
|
|
404
465
|
class StickyIndex:
|
|
405
466
|
def get_offset(self, txn: Transaction) -> int: ...
|
|
406
467
|
def encode(self) -> bytes: ...
|
pycrdt/_transaction.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import sys
|
|
3
4
|
from functools import partial
|
|
4
5
|
from types import TracebackType
|
|
5
6
|
from typing import TYPE_CHECKING, Any
|
|
6
7
|
|
|
7
|
-
from anyio import to_thread
|
|
8
|
+
from anyio import create_task_group, to_thread
|
|
8
9
|
|
|
9
10
|
from ._pycrdt import Transaction as _Transaction
|
|
10
11
|
|
|
12
|
+
if sys.version_info < (3, 11):
|
|
13
|
+
from exceptiongroup import ExceptionGroup # pragma: no cover
|
|
14
|
+
|
|
11
15
|
if TYPE_CHECKING:
|
|
12
16
|
from ._doc import Doc
|
|
13
17
|
|
|
@@ -73,16 +77,42 @@ class Transaction:
|
|
|
73
77
|
# since nested transactions reuse the root transaction
|
|
74
78
|
if self._leases == 0:
|
|
75
79
|
assert self._txn is not None
|
|
76
|
-
|
|
77
|
-
self
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
self._doc.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
try:
|
|
81
|
+
if not isinstance(self, ReadTransaction):
|
|
82
|
+
self._txn.commit()
|
|
83
|
+
origin_hash = self._txn.origin()
|
|
84
|
+
if origin_hash is not None:
|
|
85
|
+
del self._doc._origins[origin_hash]
|
|
86
|
+
if self._doc._allow_multithreading:
|
|
87
|
+
self._doc._txn_lock.release()
|
|
88
|
+
if self._doc._exceptions:
|
|
89
|
+
exceptions = tuple(self._doc._exceptions)
|
|
90
|
+
self._doc._exceptions.clear()
|
|
91
|
+
raise ExceptionGroup("Observer callback error", exceptions)
|
|
92
|
+
finally:
|
|
93
|
+
self._txn.drop()
|
|
94
|
+
self._txn = None
|
|
95
|
+
self._doc._txn = None
|
|
96
|
+
|
|
97
|
+
async def __aenter__(self, _acquire_transaction: bool = True) -> Transaction:
|
|
98
|
+
if self._leases > 0 and self._doc._task_group is None:
|
|
99
|
+
raise RuntimeError("Already in a non-async transaction")
|
|
100
|
+
self._doc._task_group = await create_task_group().__aenter__()
|
|
101
|
+
return self.__enter__(_acquire_transaction)
|
|
102
|
+
|
|
103
|
+
async def __aexit__(
|
|
104
|
+
self,
|
|
105
|
+
exc_type: type[BaseException] | None,
|
|
106
|
+
exc_val: BaseException | None,
|
|
107
|
+
exc_tb: TracebackType | None,
|
|
108
|
+
) -> bool | None:
|
|
109
|
+
self.__exit__(exc_type, exc_val, exc_tb)
|
|
110
|
+
if self._leases == 0:
|
|
111
|
+
assert self._doc._task_group is not None
|
|
112
|
+
res = await self._doc._task_group.__aexit__(exc_type, exc_val, exc_tb)
|
|
113
|
+
self._doc._task_group = None
|
|
114
|
+
return res
|
|
115
|
+
return None
|
|
86
116
|
|
|
87
117
|
@property
|
|
88
118
|
def origin(self) -> Any:
|
|
@@ -116,7 +146,7 @@ class NewTransaction(Transaction):
|
|
|
116
146
|
```
|
|
117
147
|
"""
|
|
118
148
|
|
|
119
|
-
async def __aenter__(self) -> Transaction:
|
|
149
|
+
async def __aenter__(self) -> Transaction: # type: ignore[override]
|
|
120
150
|
if self._doc._allow_multithreading:
|
|
121
151
|
if not await to_thread.run_sync(
|
|
122
152
|
partial(self._doc._txn_lock.acquire, timeout=self._timeout), abandon_on_cancel=True
|
|
@@ -124,17 +154,18 @@ class NewTransaction(Transaction):
|
|
|
124
154
|
raise TimeoutError("Could not acquire transaction")
|
|
125
155
|
else:
|
|
126
156
|
await self._doc._txn_async_lock.acquire()
|
|
127
|
-
return super().
|
|
157
|
+
return await super().__aenter__(_acquire_transaction=False)
|
|
128
158
|
|
|
129
159
|
async def __aexit__(
|
|
130
160
|
self,
|
|
131
161
|
exc_type: type[BaseException] | None,
|
|
132
162
|
exc_val: BaseException | None,
|
|
133
163
|
exc_tb: TracebackType | None,
|
|
134
|
-
) -> None:
|
|
135
|
-
super().
|
|
164
|
+
) -> bool | None:
|
|
165
|
+
res = await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
136
166
|
if not self._doc._allow_multithreading:
|
|
137
167
|
self._doc._txn_async_lock.release()
|
|
168
|
+
return res
|
|
138
169
|
|
|
139
170
|
|
|
140
171
|
class ReadTransaction(Transaction):
|
pycrdt/_undo.py
CHANGED
|
@@ -37,6 +37,8 @@ class UndoManager:
|
|
|
37
37
|
scopes: list[BaseType] = [],
|
|
38
38
|
capture_timeout_millis: int = 500,
|
|
39
39
|
timestamp: Callable[[], int] = timestamp,
|
|
40
|
+
undo_stack: list[StackItem] | None = None,
|
|
41
|
+
redo_stack: list[StackItem] | None = None,
|
|
40
42
|
) -> None:
|
|
41
43
|
"""
|
|
42
44
|
Args:
|
|
@@ -44,6 +46,8 @@ class UndoManager:
|
|
|
44
46
|
scopes: A list of shared types the undo manager will work with.
|
|
45
47
|
capture_timeout_millis: A time interval for grouping changes that will be undone/redone.
|
|
46
48
|
timestamp: A function that returns a timestamp as an integer number of milli-seconds.
|
|
49
|
+
undo_stack: Pre-filled undo stack items.
|
|
50
|
+
redo_stack: Pre-filled redo stack items.
|
|
47
51
|
|
|
48
52
|
Raises:
|
|
49
53
|
RuntimeError: UndoManager must be created with doc or scopes.
|
|
@@ -54,7 +58,13 @@ class UndoManager:
|
|
|
54
58
|
doc = scopes[0].doc
|
|
55
59
|
elif scopes:
|
|
56
60
|
raise RuntimeError("UndoManager must be created with doc or scopes")
|
|
57
|
-
self._undo_manager = _UndoManager(
|
|
61
|
+
self._undo_manager = _UndoManager(
|
|
62
|
+
doc._doc,
|
|
63
|
+
capture_timeout_millis,
|
|
64
|
+
timestamp,
|
|
65
|
+
undo_stack or [],
|
|
66
|
+
redo_stack or [],
|
|
67
|
+
)
|
|
58
68
|
for scope in scopes:
|
|
59
69
|
self.expand_scope(scope)
|
|
60
70
|
|
pycrdt/_version.py
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
from importlib.metadata import PackageNotFoundError, version
|
|
3
|
-
except ImportError: # pragma: no cover
|
|
4
|
-
from importlib_metadata import ( # type: ignore[assignment, import-not-found, no-redef]
|
|
5
|
-
PackageNotFoundError,
|
|
6
|
-
version,
|
|
7
|
-
)
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
2
|
|
|
9
3
|
try:
|
|
10
4
|
__version__ = version("pycrdt")
|
pycrdt/_xml.py
CHANGED
|
@@ -269,9 +269,7 @@ class XmlText(_XmlTraitMixin):
|
|
|
269
269
|
self._do_and_integrate("insert", value, txn._txn, index, _attrs)
|
|
270
270
|
else:
|
|
271
271
|
# primitive type
|
|
272
|
-
self.integrated.insert_embed(
|
|
273
|
-
txn._txn, index, value, _attrs
|
|
274
|
-
)
|
|
272
|
+
self.integrated.insert_embed(txn._txn, index, value, _attrs)
|
|
275
273
|
|
|
276
274
|
def format(self, start: int, stop: int, attrs: dict[str, Any]) -> None:
|
|
277
275
|
"""
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycrdt
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.45
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
7
7
|
Classifier: Programming Language :: Python
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
9
8
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
13
|
Classifier: Programming Language :: Rust
|
|
14
14
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
15
15
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
16
16
|
Requires-Dist: anyio>=4.4.0,<5.0.0
|
|
17
|
-
Requires-Dist: importlib-metadata>=3.6 ; python_full_version < '3.10'
|
|
18
17
|
Requires-Dist: typing-extensions>=4.14.0 ; python_full_version < '3.11'
|
|
18
|
+
Requires-Dist: exceptiongroup ; python_full_version < '3.11'
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Summary: Python bindings for Yrs
|
|
21
21
|
Keywords: crdt
|
|
22
22
|
Author-email: David Brochart <david.brochart@gmail.com>
|
|
23
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
24
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
25
25
|
Project-URL: Homepage, https://github.com/y-crdt/pycrdt
|
|
26
26
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
pycrdt/__init__.py,sha256=OPwvLxHB4jU6QfLhzJgDxX3R9j2oXZrTecGxfnK0msA,2227
|
|
2
|
+
pycrdt/_array.py,sha256=sWGBro3o279Cw1_EOTlEWFe51Ymo66Nt-gLPV2cY0p0,14614
|
|
3
|
+
pycrdt/_awareness.py,sha256=1F8olK8EgF6ea2VzeDwHjnoy090cAYOjakwA4yLxWH0,10904
|
|
4
|
+
pycrdt/_base.py,sha256=pXRfzzQkbERczfE6zajduo3LO2myCQAYwxqfyRAHulI,13316
|
|
5
|
+
pycrdt/_doc.py,sha256=AMCeuJLxgTmX1dRWh2aHL_vt31bDxHO5B9TxxQz475s,17282
|
|
6
|
+
pycrdt/_map.py,sha256=N0rBhDfcMmzPfmzZyWP0lJdprGQhjg086-1OlApwEQw,10595
|
|
7
|
+
pycrdt/_provider.py,sha256=RigUnf6S6AWuxxZ7I-oDZYZbjnBZYTMD0It7ADBxsSI,5295
|
|
8
|
+
pycrdt/_pycrdt.cpython-313-powerpc64le-linux-gnu.so,sha256=t01F4Ql88P7XPAkmOaCg9tdCx1h70JvjMGSLoZVanxc,3219184
|
|
9
|
+
pycrdt/_pycrdt.pyi,sha256=MD5A5DOz30dHJuTs_7ooK2svpR_uBnhAVUt9xDznRXQ,17995
|
|
10
|
+
pycrdt/_snapshot.py,sha256=3Yof2MsnKs4T3OlCnBJlxjuvduRViHbtLfN_m65FXqk,1366
|
|
11
|
+
pycrdt/_sticky_index.py,sha256=3-KoFY3fCHTrQrHHtPJkDjTOCX3AGK9fnZNnOg0qqvE,4467
|
|
12
|
+
pycrdt/_sync.py,sha256=-UQaY1M0_g5WKzwHg06FotYdD4KmH5OXyVlHsFYDqjI,7801
|
|
13
|
+
pycrdt/_text.py,sha256=7Z3mGalJcN5q4nbc4cnfr4ISOxTwGjhB5sXsQMgOaE0,10402
|
|
14
|
+
pycrdt/_transaction.py,sha256=6mWwy-7514leTtN02JqNW1hN8O7f0cifx5brACwgGgI,5801
|
|
15
|
+
pycrdt/_undo.py,sha256=-5tu_IKXn1FdUy6Y_heBzV5Z3uu-5wj09QTWlE_LnM0,4390
|
|
16
|
+
pycrdt/_update.py,sha256=VLqfKIab0qlRrHTgWNWjGiULzEC4Zjt-EtN1oltaoJk,1158
|
|
17
|
+
pycrdt/_version.py,sha256=-6olC6rrn13JKphGwX4vnzXh3q4oCrxxJR8STERSBIw,184
|
|
18
|
+
pycrdt/_xml.py,sha256=D7tewpsOD4UYT6yj2J-QRfJY2o_KjIWLXIWQh1WGZkI,18830
|
|
19
|
+
pycrdt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
pycrdt-0.12.45.dist-info/METADATA,sha256=XNWYfFEVi2C71V1SasbH3KJiQY0TYS9jmtxshvVYT7o,1591
|
|
21
|
+
pycrdt-0.12.45.dist-info/WHEEL,sha256=bXGMTa4RzDiUNC4ZGkabASlbeaOQQMA-eyfwmwikwto,149
|
|
22
|
+
pycrdt-0.12.45.dist-info/licenses/LICENSE,sha256=Sesm0tdTWXYb_Om3U3LUusWWhxEyBNO19HXj8nLklfc,1081
|
|
23
|
+
pycrdt-0.12.45.dist-info/RECORD,,
|
pycrdt-0.12.35.dist-info/RECORD
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
pycrdt-0.12.35.dist-info/METADATA,sha256=ERpYiTH08KiZjcHmbXtiZ2xLkKQ8V6gAHG9BOSIAj6A,1598
|
|
2
|
-
pycrdt-0.12.35.dist-info/WHEEL,sha256=pRcitBs64vnjhhxH8reDexDsugt6T62SWxFe-gRXupU,131
|
|
3
|
-
pycrdt-0.12.35.dist-info/licenses/LICENSE,sha256=Sesm0tdTWXYb_Om3U3LUusWWhxEyBNO19HXj8nLklfc,1081
|
|
4
|
-
pycrdt/__init__.py,sha256=uGg4-TDp_XHh-RM17bWVk_V50Oh4n10MXTS91RLz3cw,2106
|
|
5
|
-
pycrdt/_array.py,sha256=sWGBro3o279Cw1_EOTlEWFe51Ymo66Nt-gLPV2cY0p0,14614
|
|
6
|
-
pycrdt/_awareness.py,sha256=1F8olK8EgF6ea2VzeDwHjnoy090cAYOjakwA4yLxWH0,10904
|
|
7
|
-
pycrdt/_base.py,sha256=q6DNaapYHwzggpEgcEd9t12zI_4rDOqPDKr8XGCYF4Q,12859
|
|
8
|
-
pycrdt/_doc.py,sha256=UrwnqYg_L1ldNTJCXoTY8-P8gnLRpxraR7I1YNYzQm4,14299
|
|
9
|
-
pycrdt/_map.py,sha256=N0rBhDfcMmzPfmzZyWP0lJdprGQhjg086-1OlApwEQw,10595
|
|
10
|
-
pycrdt/_provider.py,sha256=9Jsb7EGjVK1uZ65FfzFypT-eMU_QHnfm_SORfTDSDSU,5333
|
|
11
|
-
pycrdt/_pycrdt.cpython-313-powerpc64le-linux-gnu.so,sha256=P0FkuMPuqrYWYxjdyv3AuYmwwbWkKLk2RLI2WhCy2Xc,3265128
|
|
12
|
-
pycrdt/_pycrdt.pyi,sha256=g8MA2J99XYNulj-YbXgFvngYH23lMAQFUByZLoovXKk,16164
|
|
13
|
-
pycrdt/_snapshot.py,sha256=3Yof2MsnKs4T3OlCnBJlxjuvduRViHbtLfN_m65FXqk,1366
|
|
14
|
-
pycrdt/_sticky_index.py,sha256=3-KoFY3fCHTrQrHHtPJkDjTOCX3AGK9fnZNnOg0qqvE,4467
|
|
15
|
-
pycrdt/_sync.py,sha256=-UQaY1M0_g5WKzwHg06FotYdD4KmH5OXyVlHsFYDqjI,7801
|
|
16
|
-
pycrdt/_text.py,sha256=7Z3mGalJcN5q4nbc4cnfr4ISOxTwGjhB5sXsQMgOaE0,10402
|
|
17
|
-
pycrdt/_transaction.py,sha256=9YfpbZRL9AdxUcWHKsPIxytxVaVSNfF2GEr_3b3DmNc,4477
|
|
18
|
-
pycrdt/_undo.py,sha256=NYvyngRDFtOnWpCS4M5AGwKNYXjgJQMl2fDsG8iZ6oU,4075
|
|
19
|
-
pycrdt/_update.py,sha256=VLqfKIab0qlRrHTgWNWjGiULzEC4Zjt-EtN1oltaoJk,1158
|
|
20
|
-
pycrdt/_version.py,sha256=BO8Bo1LgU5k23oxZQKvoPxnQ2RiKSzM3r_FH1JBBXT4,379
|
|
21
|
-
pycrdt/_xml.py,sha256=jHRpLpC07oH4KDY2RskGQ7HTcMqO-TFQj56xbnnwejM,18870
|
|
22
|
-
pycrdt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
pycrdt-0.12.35.dist-info/RECORD,,
|
pycrdt-0.12.35.dist-info/WHEEL
DELETED
|
File without changes
|