kurrentdbclient 1.2.3__tar.gz → 1.3.0__tar.gz

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.
Files changed (74) hide show
  1. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/PKG-INFO +3 -3
  2. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/README.md +1 -1
  3. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/__init__.py +5 -1
  4. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/asyncio_client.py +70 -2
  5. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/client.py +71 -3
  6. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/common.py +38 -2
  7. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/events.py +26 -0
  8. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/exceptions.py +43 -4
  9. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/projections.py +27 -6
  10. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/google/rpc/code_pb2.pyi +12 -12
  11. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/google/rpc/code_pb2_grpc.py +2 -2
  12. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/kurrent/rpc/errors_pb2.pyi +74 -61
  13. {kurrentdbclient-1.2.3/kurrentdbclient/protos/v2/streams → kurrentdbclient-1.3.0/kurrentdbclient/protos/kurrent/rpc}/errors_pb2_grpc.py +2 -2
  14. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/kurrent/rpc/rpc_pb2.pyi +33 -23
  15. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/kurrent/rpc/rpc_pb2_grpc.py +2 -2
  16. kurrentdbclient-1.3.0/kurrentdbclient/protos/v1/cluster_pb2.pyi +855 -0
  17. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/cluster_pb2_grpc.py +2 -2
  18. kurrentdbclient-1.3.0/kurrentdbclient/protos/v1/gossip_pb2.pyi +137 -0
  19. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/gossip_pb2_grpc.py +2 -2
  20. kurrentdbclient-1.3.0/kurrentdbclient/protos/v1/persistent_pb2.pyi +1333 -0
  21. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/persistent_pb2_grpc.py +2 -2
  22. kurrentdbclient-1.3.0/kurrentdbclient/protos/v1/projections_pb2.py +101 -0
  23. kurrentdbclient-1.3.0/kurrentdbclient/protos/v1/projections_pb2.pyi +688 -0
  24. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/projections_pb2_grpc.py +2 -2
  25. kurrentdbclient-1.3.0/kurrentdbclient/protos/v1/shared_pb2.pyi +282 -0
  26. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/shared_pb2_grpc.py +2 -2
  27. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/status_pb2.pyi +32 -23
  28. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/status_pb2_grpc.py +2 -2
  29. kurrentdbclient-1.3.0/kurrentdbclient/protos/v1/streams_pb2.pyi +1307 -0
  30. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/streams_pb2_grpc.py +2 -2
  31. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v2/streams/errors_pb2.py +11 -3
  32. kurrentdbclient-1.3.0/kurrentdbclient/protos/v2/streams/errors_pb2.pyi +586 -0
  33. kurrentdbclient-1.3.0/kurrentdbclient/protos/v2/streams/errors_pb2_grpc.py +24 -0
  34. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v2/streams/streams_pb2.py +20 -10
  35. kurrentdbclient-1.3.0/kurrentdbclient/protos/v2/streams/streams_pb2.pyi +498 -0
  36. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v2/streams/streams_pb2_grpc.py +65 -2
  37. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/streams.py +47 -8
  38. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/v2streams.py +157 -50
  39. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/pyproject.toml +2 -2
  40. kurrentdbclient-1.2.3/kurrentdbclient/.DS_Store +0 -0
  41. kurrentdbclient-1.2.3/kurrentdbclient/protos/kurrent/rpc/errors_pb2_grpc.py +0 -24
  42. kurrentdbclient-1.2.3/kurrentdbclient/protos/v1/cluster_pb2.pyi +0 -765
  43. kurrentdbclient-1.2.3/kurrentdbclient/protos/v1/gossip_pb2.pyi +0 -126
  44. kurrentdbclient-1.2.3/kurrentdbclient/protos/v1/persistent_pb2.pyi +0 -1135
  45. kurrentdbclient-1.2.3/kurrentdbclient/protos/v1/projections_pb2.py +0 -99
  46. kurrentdbclient-1.2.3/kurrentdbclient/protos/v1/projections_pb2.pyi +0 -558
  47. kurrentdbclient-1.2.3/kurrentdbclient/protos/v1/shared_pb2.pyi +0 -218
  48. kurrentdbclient-1.2.3/kurrentdbclient/protos/v1/streams_pb2.pyi +0 -1123
  49. kurrentdbclient-1.2.3/kurrentdbclient/protos/v2/streams/errors_pb2.pyi +0 -415
  50. kurrentdbclient-1.2.3/kurrentdbclient/protos/v2/streams/streams_pb2.pyi +0 -296
  51. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/LICENSE +0 -0
  52. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/connection.py +0 -0
  53. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/connection_spec.py +0 -0
  54. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/gossip.py +0 -0
  55. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/__init__.py +0 -0
  56. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/opentelemetry/__init__.py +0 -0
  57. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/opentelemetry/attributes.py +0 -0
  58. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/opentelemetry/grpc.py +0 -0
  59. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/opentelemetry/package.py +0 -0
  60. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/opentelemetry/spanners.py +0 -0
  61. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/opentelemetry/utils.py +0 -0
  62. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/instrumentation/opentelemetry/version.py +0 -0
  63. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/persistent.py +0 -0
  64. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/google/rpc/code_pb2.py +0 -0
  65. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/kurrent/rpc/errors_pb2.py +0 -0
  66. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/kurrent/rpc/rpc_pb2.py +0 -0
  67. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/cluster_pb2.py +0 -0
  68. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/gossip_pb2.py +0 -0
  69. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/persistent_pb2.py +0 -0
  70. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/shared_pb2.py +0 -0
  71. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/status_pb2.py +0 -0
  72. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/protos/v1/streams_pb2.py +0 -0
  73. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/py.typed +0 -0
  74. {kurrentdbclient-1.2.3 → kurrentdbclient-1.3.0}/kurrentdbclient/unpack_error_status.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kurrentdbclient
3
- Version: 1.2.3
3
+ Version: 1.3.0
4
4
  Summary: Python gRPC Client for KurrentDB
5
5
  License: BSD-3-Clause
6
6
  License-File: LICENSE
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python
19
19
  Provides-Extra: opentelemetry
20
20
  Requires-Dist: googleapis-common-protos
21
21
  Requires-Dist: grpcio-status
22
- Requires-Dist: grpcio[protobuf] (>=1.75.1,<2.0)
22
+ Requires-Dist: grpcio[protobuf] (>=1.80.0,<2.0)
23
23
  Requires-Dist: opentelemetry-api (>=1.28.0,<2.0) ; extra == "opentelemetry"
24
24
  Requires-Dist: opentelemetry-instrumentation (>=0.62b1) ; extra == "opentelemetry"
25
25
  Requires-Dist: opentelemetry-semantic-conventions (>=0.62b1) ; extra == "opentelemetry"
@@ -83,7 +83,7 @@ https://github.com/pyeventsourcing/eventsourcing-kurrentdb) package.
83
83
  * [Query string](#query-string)
84
84
  * [Examples](#examples)
85
85
  * [Event objects](#event-objects)
86
- * [New events](#new-events)
86
+ * [New events](#the-newevent-class)
87
87
  * [Recorded events](#recorded-events)
88
88
  * [Streams](#streams)
89
89
  * [Append events](#append-events)
@@ -53,7 +53,7 @@ https://github.com/pyeventsourcing/eventsourcing-kurrentdb) package.
53
53
  * [Query string](#query-string)
54
54
  * [Examples](#examples)
55
55
  * [Event objects](#event-objects)
56
- * [New events](#new-events)
56
+ * [New events](#the-newevent-class)
57
57
  * [Recorded events](#recorded-events)
58
58
  * [Streams](#streams)
59
59
  * [Append events](#append-events)
@@ -12,8 +12,10 @@ from kurrentdbclient.events import (
12
12
  FellBehind,
13
13
  NewEvent,
14
14
  NewEvents,
15
+ NewRecord,
15
16
  RecordedEvent,
16
17
  StreamState,
18
+ StreamStateCheck,
17
19
  )
18
20
  from kurrentdbclient.persistent import (
19
21
  AsyncPersistentSubscription,
@@ -26,7 +28,7 @@ from kurrentdbclient.streams import (
26
28
  ReadResponse,
27
29
  )
28
30
 
29
- __version__ = "1.2.3"
31
+ __version__ = "1.3.0"
30
32
 
31
33
  __all__ = [
32
34
  "DEFAULT_EXCLUDE_FILTER",
@@ -44,8 +46,10 @@ __all__ = [
44
46
  "KurrentDBClient",
45
47
  "NewEvent",
46
48
  "NewEvents",
49
+ "NewRecord",
47
50
  "RecordedEvent",
48
51
  "ReadResponse",
49
52
  "StreamState",
53
+ "StreamStateCheck",
50
54
  "PersistentSubscription",
51
55
  ]
@@ -45,7 +45,14 @@ from kurrentdbclient.connection_spec import (
45
45
  URI_SCHEMES_NON_DISCOVER,
46
46
  grpc_target,
47
47
  )
48
- from kurrentdbclient.events import NewEvent, NewEvents, RecordedEvent, StreamState
48
+ from kurrentdbclient.events import (
49
+ NewEvent,
50
+ NewEvents,
51
+ NewRecord,
52
+ RecordedEvent,
53
+ StreamState,
54
+ StreamStateCheck,
55
+ )
49
56
  from kurrentdbclient.exceptions import (
50
57
  DeadlineExceededError,
51
58
  DiscoveryFailedError,
@@ -62,6 +69,7 @@ if TYPE_CHECKING:
62
69
  from kurrentdbclient.persistent import ConsumerStrategy, SubscriptionInfo
63
70
  from kurrentdbclient.projections import ProjectionState, ProjectionStatistics
64
71
  from kurrentdbclient.streams import AsyncReadResponse
72
+ from kurrentdbclient.v2streams import AppendRecordsResult
65
73
 
66
74
  _TCallable = TypeVar("_TCallable", bound=Callable[..., Any])
67
75
 
@@ -331,6 +339,33 @@ class AsyncKurrentDBClient(BaseKurrentDBClient):
331
339
  credentials=credentials or self._call_credentials,
332
340
  )
333
341
 
342
+ @retrygrpc
343
+ @autoreconnect
344
+ async def append_records(
345
+ self,
346
+ /,
347
+ records: NewRecord | Iterable[NewRecord],
348
+ checks: StreamStateCheck | Iterable[StreamStateCheck] | None = None,
349
+ *,
350
+ timeout: float | None = None,
351
+ credentials: grpc.CallCredentials | None = None,
352
+ ) -> AppendRecordsResult: # pragma: <26.1 no cover
353
+ """
354
+ Appends new events to one or many streams.
355
+ """
356
+ timeout = timeout if timeout is not None else self._default_deadline
357
+ if isinstance(records, NewRecord):
358
+ records = [records]
359
+ if isinstance(checks, StreamStateCheck):
360
+ checks = [checks]
361
+ return await self.connection.v2streams.append_records(
362
+ records=records,
363
+ checks=checks,
364
+ timeout=timeout,
365
+ metadata=self._call_metadata,
366
+ credentials=credentials or self._call_credentials,
367
+ )
368
+
334
369
  @retrygrpc
335
370
  @autoreconnect
336
371
  async def read_all(
@@ -1485,6 +1520,37 @@ class AsyncKurrentDBClient(BaseKurrentDBClient):
1485
1520
  credentials=credentials or self._call_credentials,
1486
1521
  )
1487
1522
 
1523
+ @overload
1524
+ async def create_projection(
1525
+ self,
1526
+ *,
1527
+ name: str,
1528
+ query: str,
1529
+ engine_version: Literal["v1"] = "v1",
1530
+ emit_enabled: bool = False,
1531
+ track_emitted_streams: bool = False,
1532
+ timeout: float | None = None,
1533
+ credentials: grpc.CallCredentials | None = None,
1534
+ ) -> None:
1535
+ """
1536
+ Creates a projection using the v1 engine.
1537
+ """
1538
+
1539
+ @overload
1540
+ async def create_projection(
1541
+ self,
1542
+ *,
1543
+ name: str,
1544
+ query: str,
1545
+ engine_version: Literal["v2"],
1546
+ emit_enabled: bool = False,
1547
+ timeout: float | None = None,
1548
+ credentials: grpc.CallCredentials | None = None,
1549
+ ) -> None:
1550
+ """
1551
+ Creates a projection using the v2 engine.
1552
+ """
1553
+
1488
1554
  @retrygrpc
1489
1555
  @autoreconnect
1490
1556
  async def create_projection(
@@ -1492,6 +1558,7 @@ class AsyncKurrentDBClient(BaseKurrentDBClient):
1492
1558
  *,
1493
1559
  name: str,
1494
1560
  query: str,
1561
+ engine_version: Literal["v1", "v2"] = "v1",
1495
1562
  emit_enabled: bool = False,
1496
1563
  track_emitted_streams: bool = False,
1497
1564
  timeout: float | None = None,
@@ -1503,8 +1570,9 @@ class AsyncKurrentDBClient(BaseKurrentDBClient):
1503
1570
  timeout = timeout if timeout is not None else self._default_deadline
1504
1571
 
1505
1572
  await self.connection.projections.create(
1506
- query=query,
1507
1573
  name=name,
1574
+ query=query,
1575
+ engine_version=engine_version,
1508
1576
  emit_enabled=emit_enabled,
1509
1577
  track_emitted_streams=track_emitted_streams,
1510
1578
  timeout=timeout,
@@ -52,7 +52,15 @@ from kurrentdbclient.connection_spec import (
52
52
  ConnectionSpec,
53
53
  grpc_target,
54
54
  )
55
- from kurrentdbclient.events import NewEvent, NewEvents, RecordedEvent, StreamState
55
+ from kurrentdbclient.events import (
56
+ ConsistencyCheck,
57
+ NewEvent,
58
+ NewEvents,
59
+ NewRecord,
60
+ RecordedEvent,
61
+ StreamState,
62
+ StreamStateCheck,
63
+ )
56
64
  from kurrentdbclient.exceptions import (
57
65
  DiscoveryFailedError,
58
66
  FollowerNotFoundError,
@@ -85,7 +93,7 @@ if TYPE_CHECKING:
85
93
  ProjectionStatistics,
86
94
  )
87
95
  from kurrentdbclient.streams import StreamsService
88
- from kurrentdbclient.v2streams import V2StreamsService
96
+ from kurrentdbclient.v2streams import AppendRecordsResult, V2StreamsService
89
97
 
90
98
  # Matches the 'type' of "system" events.
91
99
  KDB_SYSTEM_EVENTS_REGEX = r"\$.+"
@@ -578,6 +586,33 @@ class KurrentDBClient(BaseKurrentDBClient):
578
586
  credentials=credentials or self._call_credentials,
579
587
  )
580
588
 
589
+ @retrygrpc
590
+ @autoreconnect
591
+ def append_records(
592
+ self,
593
+ /,
594
+ records: NewRecord | Iterable[NewRecord],
595
+ checks: ConsistencyCheck | Iterable[ConsistencyCheck] | None = None,
596
+ *,
597
+ timeout: float | None = None,
598
+ credentials: grpc.CallCredentials | None = None,
599
+ ) -> AppendRecordsResult: # pragma: <26.1 no cover
600
+ """
601
+ Appends new events to one or many streams.
602
+ """
603
+ timeout = timeout if timeout is not None else self._default_deadline
604
+ if isinstance(records, NewRecord):
605
+ records = [records]
606
+ if isinstance(checks, StreamStateCheck):
607
+ checks = [checks]
608
+ return self.v2streams.append_records(
609
+ records=records,
610
+ checks=checks,
611
+ timeout=timeout,
612
+ metadata=self._call_metadata,
613
+ credentials=credentials or self._call_credentials,
614
+ )
615
+
581
616
  @retrygrpc
582
617
  @autoreconnect
583
618
  def delete_stream(
@@ -1803,6 +1838,37 @@ class KurrentDBClient(BaseKurrentDBClient):
1803
1838
  credentials=credentials or self._call_credentials,
1804
1839
  )
1805
1840
 
1841
+ @overload
1842
+ def create_projection(
1843
+ self,
1844
+ *,
1845
+ name: str,
1846
+ query: str,
1847
+ engine_version: Literal["v1"] = "v1",
1848
+ emit_enabled: bool = False,
1849
+ track_emitted_streams: bool = False,
1850
+ timeout: float | None = None,
1851
+ credentials: grpc.CallCredentials | None = None,
1852
+ ) -> None:
1853
+ """
1854
+ Creates a projection using the v1 engine.
1855
+ """
1856
+
1857
+ @overload
1858
+ def create_projection(
1859
+ self,
1860
+ *,
1861
+ name: str,
1862
+ query: str,
1863
+ engine_version: Literal["v2"],
1864
+ emit_enabled: bool = False,
1865
+ timeout: float | None = None,
1866
+ credentials: grpc.CallCredentials | None = None,
1867
+ ) -> None:
1868
+ """
1869
+ Creates a projection using the v2 engine.
1870
+ """
1871
+
1806
1872
  @retrygrpc
1807
1873
  @autoreconnect
1808
1874
  def create_projection(
@@ -1810,6 +1876,7 @@ class KurrentDBClient(BaseKurrentDBClient):
1810
1876
  *,
1811
1877
  name: str,
1812
1878
  query: str,
1879
+ engine_version: Literal["v1", "v2"] = "v1",
1813
1880
  emit_enabled: bool = False,
1814
1881
  track_emitted_streams: bool = False,
1815
1882
  timeout: float | None = None,
@@ -1821,8 +1888,9 @@ class KurrentDBClient(BaseKurrentDBClient):
1821
1888
  timeout = timeout if timeout is not None else self._default_deadline
1822
1889
 
1823
1890
  self.projections.create(
1824
- query=query,
1825
1891
  name=name,
1892
+ query=query,
1893
+ engine_version=engine_version,
1826
1894
  emit_enabled=emit_enabled,
1827
1895
  track_emitted_streams=track_emitted_streams,
1828
1896
  timeout=timeout,
@@ -7,7 +7,7 @@ import os
7
7
  import threading
8
8
  from abc import ABC, abstractmethod
9
9
  from base64 import b64encode
10
- from collections.abc import AsyncIterator, Iterator, Sequence
10
+ from collections.abc import AsyncIterator, Iterable, Iterator, Sequence
11
11
  from contextlib import AbstractAsyncContextManager, AbstractContextManager
12
12
  from typing import (
13
13
  TYPE_CHECKING,
@@ -31,6 +31,8 @@ from kurrentdbclient.exceptions import (
31
31
  AbortedByServerError,
32
32
  AlreadyExistsError,
33
33
  CancelledByClientError,
34
+ ConsistencyCheckFailure,
35
+ ConsistencyChecksFailedError,
34
36
  ConsumerTooSlowError,
35
37
  ExceptionThrownByHandlerError,
36
38
  FailedPreconditionError,
@@ -47,6 +49,7 @@ from kurrentdbclient.exceptions import (
47
49
  RecordMaxSizeExceededError,
48
50
  ServiceUnavailableError,
49
51
  SSLError,
52
+ StreamStateCheckFailure,
50
53
  StreamTombstonedError,
51
54
  TransactionMaxSizeExceededError,
52
55
  UnauthenticatedError,
@@ -239,7 +242,7 @@ def handle_rpc_error(e: grpc.RpcError) -> KurrentDBClientError: # noqa: PLR0911
239
242
  "actual", "current"
240
243
  ),
241
244
  stream_name=unpacked_detail.stream,
242
- current_version=unpacked_detail.actual_revision,
245
+ actual_version=unpacked_detail.actual_revision,
243
246
  expected_version=unpacked_detail.expected_revision,
244
247
  )
245
248
  if isinstance(
@@ -249,6 +252,16 @@ def handle_rpc_error(e: grpc.RpcError) -> KurrentDBClientError: # noqa: PLR0911
249
252
  return StreamTombstonedError(
250
253
  rich_status.message, stream_name=unpacked_detail.stream
251
254
  )
255
+ if isinstance(
256
+ unpacked_detail,
257
+ v2streams_errors_pb2.AppendConsistencyViolationErrorDetails,
258
+ ):
259
+ return ConsistencyChecksFailedError(
260
+ rich_status.message,
261
+ failures=_convert_append_consistency_violations(
262
+ unpacked_detail.violations
263
+ ),
264
+ )
252
265
  if status_msg.code == grpc.StatusCode.INVALID_ARGUMENT: # noqa: SIM102
253
266
  if isinstance(
254
267
  unpacked_detail,
@@ -381,6 +394,29 @@ def handle_rpc_error(e: grpc.RpcError) -> KurrentDBClientError: # noqa: PLR0911
381
394
  return GrpcError(str(e))
382
395
 
383
396
 
397
+ def _convert_append_consistency_violations(
398
+ violations: Iterable[v2streams_errors_pb2.ConsistencyViolation],
399
+ ) -> tuple[ConsistencyCheckFailure, ...]:
400
+ failures = []
401
+ for violation in violations:
402
+ which_type = violation.WhichOneof("type")
403
+ if which_type == "stream_state":
404
+ stream_state_violation = violation.stream_state
405
+ failures.append(
406
+ ConsistencyCheckFailure(
407
+ check_index=violation.check_index,
408
+ stream_state_failure=StreamStateCheckFailure(
409
+ stream_name=stream_state_violation.stream,
410
+ expected_state=stream_state_violation.expected_state,
411
+ actual_state=stream_state_violation.actual_state,
412
+ ),
413
+ )
414
+ )
415
+ else: # pragma: no cover
416
+ pass
417
+ return tuple(failures)
418
+
419
+
384
420
  class KurrentDBService(Generic[TGrpcStreamers]):
385
421
  def __init__(
386
422
  self,
@@ -47,6 +47,32 @@ class NewEvents:
47
47
  current_version: int | StreamState
48
48
 
49
49
 
50
+ @dataclass(frozen=True)
51
+ class NewRecord:
52
+ """
53
+ Encapsulates event data to be recorded in KurrentDB.
54
+ """
55
+
56
+ stream_name: str
57
+ type: str
58
+ data: bytes
59
+ metadata: bytes = b""
60
+ content_type: ContentType = "application/json"
61
+ id: UUID = field(default_factory=uuid4)
62
+
63
+ def __eq__(self, other: object) -> bool: # pragma: no cover
64
+ return isinstance(other, NewRecord) and self.id == other.id
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class StreamStateCheck:
69
+ stream_name: str
70
+ expected_state: StreamState | int
71
+
72
+
73
+ ConsistencyCheck = StreamStateCheck # ... | QueryPredicateCheck
74
+
75
+
50
76
  @dataclass(frozen=True)
51
77
  class RecordedEvent:
52
78
  """
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  from typing_extensions import deprecated
6
7
 
8
+ from kurrentdbclient.events import StreamState
9
+
7
10
  if TYPE_CHECKING:
8
11
  from typing import Any
9
12
  from uuid import UUID
@@ -104,15 +107,26 @@ class WrongCurrentVersionError(KurrentDBClientError):
104
107
  self,
105
108
  *args: Any,
106
109
  stream_name: str | None = None,
107
- current_version: int | None = None,
108
- expected_version: int | None = None,
110
+ actual_version: int | StreamState | None = None,
111
+ expected_version: int | StreamState | None = None,
109
112
  ) -> None:
110
113
  self.stream_name = stream_name
111
- self.current_version = current_version
112
- self.expected_version = expected_version
114
+ self.actual_version = MAPPING_INT_TO_STREAM_STATE.get(
115
+ actual_version, actual_version
116
+ )
117
+ self.expected_version = MAPPING_INT_TO_STREAM_STATE.get(
118
+ expected_version, expected_version
119
+ )
113
120
  super().__init__(*args)
114
121
 
115
122
 
123
+ MAPPING_INT_TO_STREAM_STATE: dict[int | StreamState | None, StreamState] = {
124
+ -1: StreamState.NO_STREAM,
125
+ -2: StreamState.ANY,
126
+ -4: StreamState.EXISTS,
127
+ }
128
+
129
+
116
130
  class AccessDeniedError(KurrentDBClientError):
117
131
  """
118
132
  Raised when access is denied by the server.
@@ -331,3 +345,28 @@ class InvalidCommitPositionError(KurrentDBClientError):
331
345
  """
332
346
  Raised when reading from an commit position that does not exist.
333
347
  """
348
+
349
+
350
+ class ConsistencyChecksFailedError(KurrentDBClientError):
351
+ """
352
+ Raised when appending records with a consistency check that fails.
353
+ """
354
+
355
+ def __init__(
356
+ self, *args: Any, failures: tuple[ConsistencyCheckFailure, ...]
357
+ ) -> None:
358
+ self.failures = failures
359
+ super().__init__(*args)
360
+
361
+
362
+ @dataclass(frozen=True)
363
+ class ConsistencyCheckFailure:
364
+ check_index: int
365
+ stream_state_failure: StreamStateCheckFailure | None
366
+
367
+
368
+ @dataclass(frozen=True)
369
+ class StreamStateCheckFailure:
370
+ stream_name: str
371
+ expected_state: int
372
+ actual_state: int
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any
4
+ from typing import TYPE_CHECKING, Any, Literal
5
5
 
6
6
  import grpc
7
7
  import grpc.aio
@@ -68,17 +68,34 @@ class BaseProjectionsService(KurrentDBService[TGrpcStreamers]):
68
68
  @staticmethod
69
69
  def _construct_create_req(
70
70
  *,
71
- query: str,
72
71
  name: str,
72
+ query: str,
73
+ engine_version: Literal["v1", "v2"],
73
74
  emit_enabled: bool,
74
75
  track_emitted_streams: bool,
75
76
  ) -> projections_pb2.CreateReq:
77
+ # Decide engine version.
78
+ if engine_version == "v1":
79
+ engine_version_option = 0
80
+ elif engine_version == "v2": # pragma: <26.1 no cover
81
+ engine_version_option = 1
82
+ else:
83
+ msg = (
84
+ f"Unsupported projection engine version "
85
+ f"(hint: use 'v1' or 'v2'): {engine_version}"
86
+ )
87
+ raise ValueError(msg)
88
+ # Defend server against invalid requests.
89
+ if engine_version == "v2" and track_emitted_streams: # pragma: <26.1 no cover
90
+ msg = "Tracking emitted streams is not supported with engine version 2."
91
+ raise ValueError(msg)
76
92
  options = projections_pb2.CreateReq.Options(
77
93
  continuous=projections_pb2.CreateReq.Options.Continuous(
78
94
  name=name,
79
95
  emit_enabled=emit_enabled,
80
96
  track_emitted_streams=track_emitted_streams,
81
97
  ),
98
+ engine_version=engine_version_option,
82
99
  query=query,
83
100
  )
84
101
  return projections_pb2.CreateReq(options=options)
@@ -261,8 +278,9 @@ class AsyncProjectionsService(BaseProjectionsService[AsyncGrpcStreamers]):
261
278
  async def create(
262
279
  self,
263
280
  *,
264
- query: str,
265
281
  name: str,
282
+ query: str,
283
+ engine_version: Literal["v1", "v2"],
266
284
  emit_enabled: bool,
267
285
  track_emitted_streams: bool,
268
286
  timeout: float | None = None,
@@ -270,8 +288,9 @@ class AsyncProjectionsService(BaseProjectionsService[AsyncGrpcStreamers]):
270
288
  credentials: grpc.CallCredentials | None = None,
271
289
  ) -> None:
272
290
  create_req = self._construct_create_req(
273
- query=query,
274
291
  name=name,
292
+ query=query,
293
+ engine_version=engine_version,
275
294
  emit_enabled=emit_enabled,
276
295
  track_emitted_streams=track_emitted_streams,
277
296
  )
@@ -541,8 +560,9 @@ class ProjectionsService(BaseProjectionsService[GrpcStreamers]):
541
560
  def create(
542
561
  self,
543
562
  *,
544
- query: str,
545
563
  name: str,
564
+ query: str,
565
+ engine_version: Literal["v1", "v2"] = "v1",
546
566
  emit_enabled: bool,
547
567
  track_emitted_streams: bool,
548
568
  timeout: float | None = None,
@@ -550,8 +570,9 @@ class ProjectionsService(BaseProjectionsService[GrpcStreamers]):
550
570
  credentials: grpc.CallCredentials | None = None,
551
571
  ) -> None:
552
572
  create_req = self._construct_create_req(
553
- query=query,
554
573
  name=name,
574
+ query=query,
575
+ engine_version=engine_version,
555
576
  emit_enabled=emit_enabled,
556
577
  track_emitted_streams=track_emitted_streams,
557
578
  )
@@ -16,25 +16,25 @@ See the License for the specific language governing permissions and
16
16
  limitations under the License.
17
17
  """
18
18
 
19
- import builtins
20
- import google.protobuf.descriptor
21
- import google.protobuf.internal.enum_type_wrapper
19
+ from google.protobuf import descriptor as _descriptor
20
+ from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
21
+ import builtins as _builtins
22
22
  import sys
23
- import typing
23
+ import typing as _typing
24
24
 
25
25
  if sys.version_info >= (3, 10):
26
- import typing as typing_extensions
26
+ from typing import TypeAlias as _TypeAlias
27
27
  else:
28
- import typing_extensions
28
+ from typing_extensions import TypeAlias as _TypeAlias
29
29
 
30
- DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
30
+ DESCRIPTOR: _descriptor.FileDescriptor
31
31
 
32
32
  class _Code:
33
- ValueType = typing.NewType("ValueType", builtins.int)
34
- V: typing_extensions.TypeAlias = ValueType
33
+ ValueType = _typing.NewType("ValueType", _builtins.int)
34
+ V: _TypeAlias = ValueType # noqa: Y015
35
35
 
36
- class _CodeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Code.ValueType], builtins.type):
37
- DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
36
+ class _CodeEnumTypeWrapper(_enum_type_wrapper._EnumTypeWrapper[_Code.ValueType], _builtins.type):
37
+ DESCRIPTOR: _descriptor.EnumDescriptor
38
38
  OK: _Code.ValueType # 0
39
39
  """Not an error; returned on success.
40
40
 
@@ -354,4 +354,4 @@ DATA_LOSS: Code.ValueType # 15
354
354
 
355
355
  HTTP Mapping: 500 Internal Server Error
356
356
  """
357
- global___Code = Code
357
+ Global___Code: _TypeAlias = Code # noqa: Y015
@@ -4,7 +4,7 @@ import grpc
4
4
  import warnings
5
5
 
6
6
 
7
- GRPC_GENERATED_VERSION = '1.75.1'
7
+ GRPC_GENERATED_VERSION = '1.80.0'
8
8
  GRPC_VERSION = grpc.__version__
9
9
  _version_not_supported = False
10
10
 
@@ -17,7 +17,7 @@ except ImportError:
17
17
  if _version_not_supported:
18
18
  raise RuntimeError(
19
19
  f'The grpc package installed is at version {GRPC_VERSION},'
20
- + f' but the generated code in kurrentdbclient/protos/google/rpc/code_pb2_grpc.py depends on'
20
+ + ' but the generated code in kurrentdbclient/protos/google/rpc/code_pb2_grpc.py depends on'
21
21
  + f' grpcio>={GRPC_GENERATED_VERSION}.'
22
22
  + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
23
23
  + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'