pycrdt 0.9.5__cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl → 0.12.45__cp311-cp311-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,20 +1,49 @@
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
7
+ from ._array import TypedArray as TypedArray
8
+ from ._awareness import Awareness as Awareness
9
+ from ._awareness import is_awareness_disconnect_message as is_awareness_disconnect_message
3
10
  from ._doc import Doc as Doc
11
+ from ._doc import TypedDoc as TypedDoc
4
12
  from ._map import Map as Map
5
13
  from ._map import MapEvent as MapEvent
14
+ from ._map import TypedMap as TypedMap
15
+ from ._provider import Channel as Channel
16
+ from ._provider import Provider as Provider
17
+ from ._pycrdt import DeleteSet as DeleteSet
18
+ from ._pycrdt import StackItem as StackItem
19
+ from ._pycrdt import SubdocsEvent as SubdocsEvent
6
20
  from ._pycrdt import Subscription as Subscription
7
21
  from ._pycrdt import TransactionEvent as TransactionEvent
22
+ from ._snapshot import Snapshot as Snapshot
23
+ from ._sticky_index import Assoc as Assoc
24
+ from ._sticky_index import StickyIndex as StickyIndex
8
25
  from ._sync import Decoder as Decoder
26
+ from ._sync import Encoder as Encoder
9
27
  from ._sync import YMessageType as YMessageType
10
28
  from ._sync import YSyncMessageType as YSyncMessageType
29
+ from ._sync import create_awareness_message as create_awareness_message
11
30
  from ._sync import create_sync_message as create_sync_message
12
31
  from ._sync import create_update_message as create_update_message
13
32
  from ._sync import handle_sync_message as handle_sync_message
14
33
  from ._sync import read_message as read_message
34
+ from ._sync import write_message as write_message
15
35
  from ._sync import write_var_uint as write_var_uint
16
36
  from ._text import Text as Text
17
37
  from ._text import TextEvent as TextEvent
38
+ from ._transaction import NewTransaction as NewTransaction
18
39
  from ._transaction import ReadTransaction as ReadTransaction
19
40
  from ._transaction import Transaction as Transaction
20
41
  from ._undo import UndoManager as UndoManager
42
+ from ._update import get_state as get_state
43
+ from ._update import get_update as get_update
44
+ from ._update import merge_updates as merge_updates
45
+ from ._version import __version__ as __version__
46
+ from ._xml import XmlElement as XmlElement
47
+ from ._xml import XmlEvent as XmlEvent
48
+ from ._xml import XmlFragment as XmlFragment
49
+ from ._xml import XmlText as XmlText
pycrdt/_array.py CHANGED
@@ -1,40 +1,57 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any
3
+ from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, cast, overload
4
4
 
5
- from ._base import BaseDoc, BaseEvent, BaseType, base_types, event_types
5
+ from ._base import BaseDoc, BaseEvent, BaseType, Sequence, Typed, base_types, event_types
6
6
  from ._pycrdt import Array as _Array
7
7
  from ._pycrdt import ArrayEvent as _ArrayEvent
8
+ from ._pycrdt import Subscription
8
9
 
9
- if TYPE_CHECKING: # pragma: no cover
10
+ if TYPE_CHECKING:
10
11
  from ._doc import Doc
11
12
 
13
+ T = TypeVar("T")
12
14
 
13
- class Array(BaseType):
14
- _prelim: list | None
15
+
16
+ class Array(Sequence, Generic[T]):
17
+ """
18
+ A collection used to store data in an indexed sequence structure, similar to a Python `list`.
19
+ """
20
+
21
+ _prelim: list[T] | None
15
22
  _integrated: _Array | None
16
23
 
17
24
  def __init__(
18
25
  self,
19
- init: list | None = None,
26
+ init: list[T] | None = None,
20
27
  *,
21
28
  _doc: Doc | None = None,
22
29
  _integrated: _Array | None = None,
23
30
  ) -> None:
31
+ """
32
+ Creates an array with an optional initial value:
33
+ ```py
34
+ array0 = Array()
35
+ array1 = Array(["foo", 3, array0])
36
+ ```
37
+
38
+ Args:
39
+ init: The list from which to initialize the array.
40
+ """
24
41
  super().__init__(
25
42
  init=init,
26
43
  _doc=_doc,
27
44
  _integrated=_integrated,
28
45
  )
29
46
 
30
- def _init(self, value: list[Any] | None) -> None:
47
+ def _init(self, value: list[T] | None) -> None:
31
48
  if value is None:
32
49
  return
33
50
  with self.doc.transaction():
34
51
  for i, v in enumerate(value):
35
52
  self._set(i, v)
36
53
 
37
- def _set(self, index: int, value: Any) -> None:
54
+ def _set(self, index: int, value: T) -> None:
38
55
  with self.doc.transaction() as txn:
39
56
  self._forbid_read_transaction(txn)
40
57
  if isinstance(value, BaseDoc):
@@ -49,27 +66,70 @@ class Array(BaseType):
49
66
  self.integrated.insert(txn._txn, index, value)
50
67
 
51
68
  def _get_or_insert(self, name: str, doc: Doc) -> _Array:
52
- return doc._doc.get_or_insert_array(name)
69
+ assert doc._txn is not None
70
+ assert doc._txn._txn is not None
71
+ return doc._doc.get_or_insert_array(doc._txn._txn, name)
53
72
 
54
73
  def __len__(self) -> int:
74
+ """
75
+ ```py
76
+ Doc()["array"] = array = Array([2, 3, 0])
77
+ assert len(array) == 3
78
+ ```
79
+
80
+ Returns:
81
+ The length of the array.
82
+ """
55
83
  with self.doc.transaction() as txn:
56
84
  return self.integrated.len(txn._txn)
57
85
 
58
- def append(self, value: Any) -> None:
86
+ def append(self, value: T) -> None:
87
+ """
88
+ Appends an item to the array.
89
+
90
+ Args:
91
+ value: The item to append to the array.
92
+ """
59
93
  with self.doc.transaction():
60
94
  self += [value]
61
95
 
62
- def extend(self, value: list[Any]) -> None:
96
+ def extend(self, value: list[T]) -> None:
97
+ """
98
+ Extends the array with a list of items.
99
+
100
+ Args:
101
+ value: The items that will extend the array.
102
+ """
63
103
  with self.doc.transaction():
64
104
  self += value
65
105
 
66
106
  def clear(self) -> None:
107
+ """
108
+ Removes all items from the array.
109
+ """
67
110
  del self[:]
68
111
 
69
- def insert(self, index, object) -> None:
112
+ def insert(self, index: int, object: T) -> None:
113
+ """
114
+ Inserts an item at a given index in the array.
115
+
116
+ Args:
117
+ index: The index where to insert the item.
118
+ object: The item to insert in the array.
119
+ """
70
120
  self[index:index] = [object]
71
121
 
72
- def pop(self, index: int = -1) -> Any:
122
+ def pop(self, index: int = -1) -> T:
123
+ """
124
+ Removes the item at the given index from the array, and returns it.
125
+ If no index is passed, removes and returns the last item.
126
+
127
+ Args:
128
+ index: The optional index of the item to pop.
129
+
130
+ Returns:
131
+ The item at the given index, or the last item.
132
+ """
73
133
  with self.doc.transaction():
74
134
  index = self._check_index(index)
75
135
  res = self[index]
@@ -79,24 +139,80 @@ class Array(BaseType):
79
139
  return res
80
140
 
81
141
  def move(self, source_index: int, destination_index: int) -> None:
142
+ """
143
+ Moves an item in the array from a source index to a destination index.
144
+
145
+ Args:
146
+ source_index: The index of the item to move.
147
+ destination_index: The index where the item will be inserted.
148
+ """
82
149
  with self.doc.transaction() as txn:
83
150
  self._forbid_read_transaction(txn)
84
151
  source_index = self._check_index(source_index)
85
152
  destination_index = self._check_index(destination_index)
86
153
  self.integrated.move_to(txn._txn, source_index, destination_index)
87
154
 
88
- def __add__(self, value: list[Any]) -> Array:
155
+ def __add__(self, value: list[T]) -> Array[T]:
156
+ """
157
+ Extends the array with a list of items:
158
+ ```py
159
+ Doc()["array"] = array = Array(["foo"])
160
+ array += ["bar", "baz"]
161
+ assert array.to_py() == ["foo", "bar", "baz"]
162
+ ```
163
+
164
+ Args:
165
+ value: The items that will extend the array.
166
+
167
+ Returns:
168
+ The extended array.
169
+ """
89
170
  with self.doc.transaction():
90
171
  length = len(self)
91
172
  self[length:length] = value
92
173
  return self
93
174
 
94
- def __radd__(self, value: list[Any]) -> Array:
175
+ def __radd__(self, value: list[T]) -> Array[T]:
176
+ """
177
+ Prepends a list of items to the array:
178
+ ```py
179
+ Doc()["array"] = array = Array(["bar", "baz"])
180
+ array = ["foo"] + array
181
+ assert array.to_py() == ["foo", "bar", "baz"]
182
+ ```
183
+
184
+ Args:
185
+ value: The list of items to prepend.
186
+
187
+ Returns:
188
+ The prepended array.
189
+ """
95
190
  with self.doc.transaction():
96
191
  self[0:0] = value
97
192
  return self
98
193
 
99
- def __setitem__(self, key: int | slice, value: Any | list[Any]) -> None:
194
+ @overload
195
+ def __setitem__(self, key: int, value: T) -> None: ...
196
+
197
+ @overload
198
+ def __setitem__(self, key: slice, value: list[T]) -> None: ...
199
+
200
+ def __setitem__(self, key, value):
201
+ """
202
+ Replaces the item at the given index with a new item:
203
+ ```py
204
+ Doc()["array"] = array = Array(["foo", "bar"])
205
+ array[1] = "baz"
206
+ assert array.to_py() == ["foo", "baz"]
207
+ ```
208
+
209
+ Args:
210
+ key: The index of the item to replace.
211
+ value: The new item to set.
212
+
213
+ Raises:
214
+ RuntimeError: Index must be of type integer.
215
+ """
100
216
  with self.doc.transaction():
101
217
  if isinstance(key, int):
102
218
  key = self._check_index(key)
@@ -125,6 +241,20 @@ class Array(BaseType):
125
241
  return idx
126
242
 
127
243
  def __delitem__(self, key: int | slice) -> None:
244
+ """
245
+ Removes the item at the given index from the array:
246
+ ```py
247
+ Doc()["array"] = array = Array(["foo", "bar", "baz"])
248
+ del array[2]
249
+ assert array.to_py() == ["foo", "bar"]
250
+ ```
251
+
252
+ Args:
253
+ key: The index of the item to remove.
254
+
255
+ Raises:
256
+ RuntimeError: Array indices must be integers or slices.
257
+ """
128
258
  with self.doc.transaction() as txn:
129
259
  self._forbid_read_transaction(txn)
130
260
  if isinstance(key, int):
@@ -148,10 +278,26 @@ class Array(BaseType):
148
278
  self.integrated.remove_range(txn._txn, i, n)
149
279
  else:
150
280
  raise TypeError(
151
- f"array indices must be integers or slices, not {type(key).__name__}"
281
+ f"Array indices must be integers or slices, not {type(key).__name__}"
152
282
  )
153
283
 
154
- def __getitem__(self, key: int) -> BaseType:
284
+ @overload
285
+ def __getitem__(self, key: int) -> T: ...
286
+
287
+ @overload
288
+ def __getitem__(self, key: slice) -> list[T]: ...
289
+
290
+ def __getitem__(self, key):
291
+ """
292
+ Gets the item at the given index:
293
+ ```py
294
+ Doc()["array"] = array = Array(["foo", "bar", "baz"])
295
+ assert array[1] == "bar"
296
+ ```
297
+
298
+ Returns:
299
+ The item at the given index.
300
+ """
155
301
  with self.doc.transaction() as txn:
156
302
  if isinstance(key, int):
157
303
  key = self._check_index(key)
@@ -162,30 +308,87 @@ class Array(BaseType):
162
308
  step = 1 if key.step is None else key.step
163
309
  return [self[i] for i in range(i0, i1, step)]
164
310
 
165
- def __iter__(self):
311
+ def __iter__(self) -> ArrayIterator:
312
+ """
313
+ ```py
314
+ Doc()["array"] = array = Array(["foo", "foo"])
315
+ for value in array:
316
+ assert value == "foo"
317
+ ```
318
+ Returns:
319
+ An iterable over the items of the array.
320
+ """
166
321
  return ArrayIterator(self)
167
322
 
168
- def __contains__(self, item: Any) -> bool:
169
- return item in list(self)
323
+ def __contains__(self, item: T) -> bool:
324
+ """
325
+ Checks if the given item is in the array:
326
+ ```py
327
+ Doc()["array"] = array = Array(["foo", "bar"])
328
+ assert "baz" not in array
329
+ ```
330
+
331
+ Args:
332
+ item: The item to look for in the array.
333
+
334
+ Returns:
335
+ True if the item was found.
336
+ """
337
+ return item in [value for value in self]
170
338
 
171
339
  def __str__(self) -> str:
340
+ """
341
+ ```py
342
+ Doc()["array"] = array = Array([2, 3, 0])
343
+ assert str(array) == "[2,3,0]"
344
+ ```
345
+
346
+ Returns:
347
+ The string representation of the array.
348
+ """
172
349
  with self.doc.transaction() as txn:
173
350
  return self.integrated.to_json(txn._txn)
174
351
 
175
- def to_py(self) -> list | None:
352
+ def to_py(self) -> list[T] | None:
353
+ """
354
+ Recursively converts the array's items to Python objects, and
355
+ returns them in a list. If the array was not yet inserted in a document,
356
+ returns `None` if the array was not initialized.
357
+
358
+ Returns:
359
+ The array recursively converted to Python objects, or `None`.
360
+ """
176
361
  if self._integrated is None:
177
362
  py = self._prelim
178
363
  if py is None:
179
364
  return None
180
365
  else:
181
- py = list(self)
366
+ py = [value for value in self]
182
367
  for idx, val in enumerate(py):
183
368
  if isinstance(val, BaseType):
184
369
  py[idx] = val.to_py()
185
370
  return py
186
371
 
372
+ def observe(self, callback: Callable[[ArrayEvent], None]) -> Subscription:
373
+ """
374
+ Subscribes a callback to be called with the array event.
375
+
376
+ Args:
377
+ callback: The callback to call with the [ArrayEvent][pycrdt.ArrayEvent].
378
+ """
379
+ return super().observe(cast(Callable[[BaseEvent], None], callback))
380
+
187
381
 
188
382
  class ArrayEvent(BaseEvent):
383
+ """
384
+ An array change event.
385
+
386
+ Attributes:
387
+ target (Array): The changed array.
388
+ delta (list[dict[str, Any]]): A list of items describing the changes.
389
+ path (list[int | str]): A list with the indices pointing to the array that was changed.
390
+ """
391
+
189
392
  __slots__ = "target", "delta", "path"
190
393
 
191
394
 
@@ -195,7 +398,10 @@ class ArrayIterator:
195
398
  self.length = len(array)
196
399
  self.idx = 0
197
400
 
198
- def __next__(self):
401
+ def __iter__(self) -> ArrayIterator:
402
+ return self # pragma: nocover
403
+
404
+ def __next__(self) -> Any:
199
405
  if self.idx == self.length:
200
406
  raise StopIteration
201
407
 
@@ -204,5 +410,76 @@ class ArrayIterator:
204
410
  return res
205
411
 
206
412
 
413
+ class TypedArray(Typed, Generic[T]):
414
+ """
415
+ A container for an [Array][pycrdt.Array.__init__] where values have types that can be
416
+ other typed containers, e.g. a [TypedMap][pycrdt.TypedMap]. The subclass of `TypedArray[T]`
417
+ must have a special `type: T` annotation where `T` is the same type.
418
+ The underlying `Array` can be accessed with the special `_` attribute.
419
+
420
+ ```py
421
+ from pycrdt import Array, TypedArray, TypedDoc, TypedMap
422
+
423
+ class MyMap(TypedMap):
424
+ name: str
425
+ toggle: bool
426
+ nested: Array[bool]
427
+
428
+ class MyArray(TypedArray[MyMap]):
429
+ type: MyMap
430
+
431
+ class MyDoc(TypedDoc):
432
+ array0: MyArray
433
+
434
+ doc = MyDoc()
435
+
436
+ map0 = MyMap()
437
+ doc.array0.append(map0)
438
+ map0.name = "foo"
439
+ map0.toggle = True
440
+ map0.nested = Array([True, False])
441
+
442
+ print(doc.array0._.to_py())
443
+ # [{'name': 'foo', 'toggle': True, 'nested': [True, False]}]
444
+ print(doc.array0[0].name)
445
+ # foo
446
+ print(doc.array0[0].toggle)
447
+ # True
448
+ print(doc.array0[0].nested.to_py())
449
+ # [True, False]
450
+ ```
451
+ """
452
+
453
+ type: T
454
+ _: Array
455
+
456
+ def __init__(self, array: TypedArray | Array | None = None) -> None:
457
+ super().__init__()
458
+ if array is None:
459
+ array = Array()
460
+ elif isinstance(array, TypedArray):
461
+ array = array._
462
+ self._ = array
463
+ self.__dict__["type"] = self.__dict__["annotations"]["type"]
464
+
465
+ def __getitem__(self, key: int) -> T:
466
+ return self.__dict__["type"](self._[key])
467
+
468
+ def __setitem__(self, key: int, value: T) -> None:
469
+ item = value._ if isinstance(value, Typed) else value
470
+ self._[key] = item
471
+
472
+ def append(self, value: T) -> None:
473
+ item = value._ if isinstance(value, Typed) else value
474
+ self._.append(item)
475
+
476
+ def extend(self, value: list[T]) -> None:
477
+ items = [item._ if isinstance(item, Typed) else item for item in value]
478
+ self._.extend(items)
479
+
480
+ def __len__(self) -> int:
481
+ return len(self._)
482
+
483
+
207
484
  base_types[_Array] = Array
208
485
  event_types[_ArrayEvent] = ArrayEvent