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 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
- callback(*params[:param_nb]) # type: ignore[arg-type]
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
- callback(*params[:param_nb]) # type: ignore[arg-type]
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
- if hasattr(expected_type, "__origin__"):
389
- expected_type = expected_type.__origin__
390
- if hasattr(expected_type, "__args__"):
391
- expected_types = expected_type.__args__
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 typing import Any, Callable, Generic, Iterable, Literal, Type, TypeVar, Union, cast, overload
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, skip_gc=skip_gc, doc=doc, Model=Model, allow_multithreading=allow_multithreading
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
- return self._doc.get_state()
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
- return self._doc.get_update(state)
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(self, callback: Callable[[TransactionEvent], None]) -> Subscription:
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
- subscription = self._doc.observe(callback)
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 observe_subdocs(self, callback: Callable[[SubdocsEvent], None]) -> Subscription:
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
- subscription = self._doc.observe_subdocs(callback)
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
- self._event_subscription[subdocs] = observe(partial(self._send_event, subdocs))
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
- try:
145
- await self.stop()
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]:
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__(self, doc: Doc, capture_timeout_millis, timestamp: Callable[[], int]) -> None:
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 StackItem:
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
- if not isinstance(self, ReadTransaction):
77
- self._txn.commit()
78
- origin_hash = self._txn.origin()
79
- if origin_hash is not None:
80
- del self._doc._origins[origin_hash]
81
- if self._doc._allow_multithreading:
82
- self._doc._txn_lock.release()
83
- self._txn.drop()
84
- self._txn = None
85
- self._doc._txn = None
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().__enter__(_acquire_transaction=False) # type: ignore[call-arg]
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().__exit__(exc_type, exc_val, exc_tb)
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(doc._doc, capture_timeout_millis, timestamp)
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
- try:
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.35
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.9
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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-manylinux_2_17_ppc64le
5
+ Tag: cp313-cp313-manylinux2014_ppc64le
@@ -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,,
@@ -1,4 +0,0 @@
1
- Wheel-Version: 1.0
2
- Generator: maturin (1.9.4)
3
- Root-Is-Purelib: false
4
- Tag: cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le