kurrentdbclient 0.3__py3-none-any.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.
Files changed (49) hide show
  1. kurrentdbclient/__init__.py +49 -0
  2. kurrentdbclient/asyncio_client.py +1662 -0
  3. kurrentdbclient/client.py +1914 -0
  4. kurrentdbclient/common.py +535 -0
  5. kurrentdbclient/connection.py +107 -0
  6. kurrentdbclient/connection_spec.py +371 -0
  7. kurrentdbclient/events.py +141 -0
  8. kurrentdbclient/exceptions.py +239 -0
  9. kurrentdbclient/gossip.py +104 -0
  10. kurrentdbclient/instrumentation/__init__.py +0 -0
  11. kurrentdbclient/instrumentation/opentelemetry/__init__.py +185 -0
  12. kurrentdbclient/instrumentation/opentelemetry/attributes.py +20 -0
  13. kurrentdbclient/instrumentation/opentelemetry/grpc.py +165 -0
  14. kurrentdbclient/instrumentation/opentelemetry/package.py +2 -0
  15. kurrentdbclient/instrumentation/opentelemetry/spanners.py +1097 -0
  16. kurrentdbclient/instrumentation/opentelemetry/utils.py +199 -0
  17. kurrentdbclient/instrumentation/opentelemetry/version.py +2 -0
  18. kurrentdbclient/persistent.py +1982 -0
  19. kurrentdbclient/projections.py +735 -0
  20. kurrentdbclient/protos/Grpc/cluster_pb2.py +92 -0
  21. kurrentdbclient/protos/Grpc/cluster_pb2.pyi +765 -0
  22. kurrentdbclient/protos/Grpc/cluster_pb2_grpc.py +514 -0
  23. kurrentdbclient/protos/Grpc/code_pb2.py +37 -0
  24. kurrentdbclient/protos/Grpc/code_pb2.pyi +357 -0
  25. kurrentdbclient/protos/Grpc/code_pb2_grpc.py +24 -0
  26. kurrentdbclient/protos/Grpc/gossip_pb2.py +46 -0
  27. kurrentdbclient/protos/Grpc/gossip_pb2.pyi +126 -0
  28. kurrentdbclient/protos/Grpc/gossip_pb2_grpc.py +98 -0
  29. kurrentdbclient/protos/Grpc/persistent_pb2.py +140 -0
  30. kurrentdbclient/protos/Grpc/persistent_pb2.pyi +1135 -0
  31. kurrentdbclient/protos/Grpc/persistent_pb2_grpc.py +399 -0
  32. kurrentdbclient/protos/Grpc/projections_pb2.py +99 -0
  33. kurrentdbclient/protos/Grpc/projections_pb2.pyi +558 -0
  34. kurrentdbclient/protos/Grpc/projections_pb2_grpc.py +485 -0
  35. kurrentdbclient/protos/Grpc/shared_pb2.py +62 -0
  36. kurrentdbclient/protos/Grpc/shared_pb2.pyi +218 -0
  37. kurrentdbclient/protos/Grpc/shared_pb2_grpc.py +24 -0
  38. kurrentdbclient/protos/Grpc/status_pb2.py +39 -0
  39. kurrentdbclient/protos/Grpc/status_pb2.pyi +67 -0
  40. kurrentdbclient/protos/Grpc/status_pb2_grpc.py +24 -0
  41. kurrentdbclient/protos/Grpc/streams_pb2.py +132 -0
  42. kurrentdbclient/protos/Grpc/streams_pb2.pyi +1038 -0
  43. kurrentdbclient/protos/Grpc/streams_pb2_grpc.py +269 -0
  44. kurrentdbclient/py.typed +0 -0
  45. kurrentdbclient/streams.py +1400 -0
  46. kurrentdbclient-0.3.dist-info/LICENSE +29 -0
  47. kurrentdbclient-0.3.dist-info/METADATA +3769 -0
  48. kurrentdbclient-0.3.dist-info/RECORD +49 -0
  49. kurrentdbclient-0.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,1097 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import annotations
3
+
4
+ import inspect
5
+ import json
6
+ import sys
7
+ from typing import (
8
+ Any,
9
+ Callable,
10
+ Dict,
11
+ Generic,
12
+ Iterable,
13
+ Literal,
14
+ Optional,
15
+ Protocol,
16
+ Sequence,
17
+ Tuple,
18
+ TypeVar,
19
+ Union,
20
+ cast,
21
+ overload,
22
+ )
23
+ from uuid import UUID
24
+
25
+ import grpc
26
+ from opentelemetry.context import Context
27
+ from opentelemetry.trace import (
28
+ NonRecordingSpan,
29
+ Span,
30
+ SpanContext,
31
+ SpanKind,
32
+ StatusCode,
33
+ TraceFlags,
34
+ Tracer,
35
+ set_span_in_context,
36
+ )
37
+ from opentelemetry.util.types import AttributeValue
38
+ from typing_extensions import Self
39
+
40
+ from kurrentdbclient import (
41
+ AsyncKurrentDBClient,
42
+ AsyncReadResponse,
43
+ KurrentDBClient,
44
+ NewEvent,
45
+ ReadResponse,
46
+ RecordedEvent,
47
+ StreamState,
48
+ )
49
+ from kurrentdbclient.client import BaseKurrentDBClient
50
+ from kurrentdbclient.common import (
51
+ AbstractAsyncCatchupSubscription,
52
+ AbstractAsyncPersistentSubscription,
53
+ AbstractAsyncReadResponse,
54
+ AbstractCatchupSubscription,
55
+ AbstractPersistentSubscription,
56
+ AbstractReadResponse,
57
+ AsyncRecordedEventIterator,
58
+ AsyncRecordedEventSubscription,
59
+ RecordedEventIterator,
60
+ RecordedEventSubscription,
61
+ )
62
+ from kurrentdbclient.connection_spec import URI_SCHEMES_DISCOVER
63
+ from kurrentdbclient.instrumentation.opentelemetry.attributes import Attributes
64
+ from kurrentdbclient.instrumentation.opentelemetry.utils import (
65
+ AsyncSpannerResponse,
66
+ OverloadedSpannerResponse,
67
+ SpannerResponse,
68
+ _set_span_error,
69
+ _set_span_ok,
70
+ _start_span,
71
+ )
72
+
73
+ STREAMS_APPEND = "streams.append"
74
+ STREAMS_SUBSCRIBE = "streams.subscribe"
75
+ SPAN_NAMES_BY_CLIENT_METHOD = {
76
+ KurrentDBClient.append_to_stream.__qualname__: STREAMS_APPEND,
77
+ KurrentDBClient.subscribe_to_all.__qualname__: STREAMS_SUBSCRIBE,
78
+ KurrentDBClient.subscribe_to_stream.__qualname__: STREAMS_SUBSCRIBE,
79
+ KurrentDBClient.read_subscription_to_all.__qualname__: STREAMS_SUBSCRIBE,
80
+ KurrentDBClient.read_subscription_to_stream.__qualname__: STREAMS_SUBSCRIBE,
81
+ AsyncKurrentDBClient.append_to_stream.__qualname__: STREAMS_APPEND,
82
+ AsyncKurrentDBClient.subscribe_to_all.__qualname__: STREAMS_SUBSCRIBE,
83
+ AsyncKurrentDBClient.subscribe_to_stream.__qualname__: STREAMS_SUBSCRIBE,
84
+ AsyncKurrentDBClient.read_subscription_to_all.__qualname__: STREAMS_SUBSCRIBE,
85
+ AsyncKurrentDBClient.read_subscription_to_stream.__qualname__: STREAMS_SUBSCRIBE,
86
+ }
87
+ SPAN_KINDS_BY_CLIENT_METHOD = {
88
+ KurrentDBClient.append_to_stream.__qualname__: SpanKind.PRODUCER,
89
+ KurrentDBClient.subscribe_to_all.__qualname__: SpanKind.CONSUMER,
90
+ KurrentDBClient.subscribe_to_stream.__qualname__: SpanKind.CONSUMER,
91
+ KurrentDBClient.read_subscription_to_all.__qualname__: SpanKind.CONSUMER,
92
+ KurrentDBClient.read_subscription_to_stream.__qualname__: SpanKind.CONSUMER,
93
+ AsyncKurrentDBClient.append_to_stream.__qualname__: SpanKind.PRODUCER,
94
+ AsyncKurrentDBClient.subscribe_to_all.__qualname__: SpanKind.CONSUMER,
95
+ AsyncKurrentDBClient.subscribe_to_stream.__qualname__: SpanKind.CONSUMER,
96
+ AsyncKurrentDBClient.read_subscription_to_all.__qualname__: SpanKind.CONSUMER,
97
+ AsyncKurrentDBClient.read_subscription_to_stream.__qualname__: SpanKind.CONSUMER,
98
+ }
99
+
100
+
101
+ def _get_span_kind(func: Callable[..., Any]) -> SpanKind:
102
+ return SPAN_KINDS_BY_CLIENT_METHOD.get(func.__qualname__, SpanKind.CLIENT)
103
+
104
+
105
+ def _get_span_name(func: Callable[..., Any]) -> str:
106
+ return SPAN_NAMES_BY_CLIENT_METHOD.get(func.__qualname__, func.__qualname__)
107
+
108
+
109
+ def _get_span_name_and_kind(func: Callable[..., Any]) -> Tuple[str, SpanKind]:
110
+ return _get_span_name(func), _get_span_kind(func)
111
+
112
+
113
+ class GetStreamMethod(Protocol):
114
+ def __call__(
115
+ self,
116
+ /,
117
+ stream_name: str,
118
+ *,
119
+ stream_position: Optional[int] = None,
120
+ backwards: bool = False,
121
+ resolve_links: bool = False,
122
+ limit: int = sys.maxsize,
123
+ timeout: Optional[float] = None,
124
+ credentials: Optional[grpc.CallCredentials] = None,
125
+ ) -> Sequence[RecordedEvent]:
126
+ pass # pragma: no cover
127
+
128
+
129
+ class AsyncGetStreamMethod(Protocol):
130
+ async def __call__(
131
+ self,
132
+ /,
133
+ stream_name: str,
134
+ *,
135
+ stream_position: Optional[int] = None,
136
+ backwards: bool = False,
137
+ resolve_links: bool = False,
138
+ limit: int = sys.maxsize,
139
+ timeout: Optional[float] = None,
140
+ credentials: Optional[grpc.CallCredentials] = None,
141
+ ) -> Sequence[RecordedEvent]:
142
+ pass # pragma: no cover
143
+
144
+
145
+ @overload
146
+ def span_get_stream(
147
+ tracer: Tracer,
148
+ instance: BaseKurrentDBClient,
149
+ spanned_func: AsyncGetStreamMethod,
150
+ /,
151
+ stream_name: str,
152
+ *,
153
+ stream_position: Optional[int] = None,
154
+ backwards: bool = False,
155
+ resolve_links: bool = False,
156
+ limit: int = sys.maxsize,
157
+ timeout: Optional[float] = None,
158
+ credentials: Optional[grpc.CallCredentials] = None,
159
+ ) -> AsyncSpannerResponse[Sequence[RecordedEvent]]:
160
+ pass # pragma: no cover
161
+
162
+
163
+ @overload
164
+ def span_get_stream(
165
+ tracer: Tracer,
166
+ instance: BaseKurrentDBClient,
167
+ spanned_func: GetStreamMethod,
168
+ /,
169
+ stream_name: str,
170
+ *,
171
+ stream_position: Optional[int] = None,
172
+ backwards: bool = False,
173
+ resolve_links: bool = False,
174
+ limit: int = sys.maxsize,
175
+ timeout: Optional[float] = None,
176
+ credentials: Optional[grpc.CallCredentials] = None,
177
+ ) -> SpannerResponse[Sequence[RecordedEvent]]:
178
+ pass # pragma: no cover
179
+
180
+
181
+ def span_get_stream(
182
+ tracer: Tracer,
183
+ instance: BaseKurrentDBClient,
184
+ spanned_func: Union[GetStreamMethod, AsyncGetStreamMethod],
185
+ /,
186
+ stream_name: str,
187
+ *,
188
+ stream_position: Optional[int] = None,
189
+ backwards: bool = False,
190
+ resolve_links: bool = False,
191
+ limit: int = sys.maxsize,
192
+ timeout: Optional[float] = None,
193
+ credentials: Optional[grpc.CallCredentials] = None,
194
+ ) -> OverloadedSpannerResponse[Sequence[RecordedEvent], Sequence[RecordedEvent]]:
195
+ span_name, span_kind = _get_span_name_and_kind(spanned_func)
196
+
197
+ with _start_span(tracer, span_name, span_kind) as span:
198
+ _enrich_span(
199
+ span=span,
200
+ client=instance,
201
+ db_operation_name=span_name,
202
+ stream_name=stream_name,
203
+ )
204
+ try:
205
+ yield spanned_func(
206
+ stream_name,
207
+ stream_position=stream_position,
208
+ backwards=backwards,
209
+ resolve_links=resolve_links,
210
+ limit=limit,
211
+ timeout=timeout,
212
+ credentials=credentials,
213
+ )
214
+ except Exception as e:
215
+ _set_span_error(span, e)
216
+ raise
217
+ else:
218
+ _set_span_ok(span)
219
+
220
+
221
+ class ReadStreamMethod(Protocol):
222
+ def __call__(
223
+ self,
224
+ /,
225
+ stream_name: str,
226
+ *args: Any,
227
+ **kwargs: Any,
228
+ ) -> AbstractReadResponse:
229
+ pass # pragma: no cover
230
+
231
+
232
+ class AsyncReadStreamMethod(Protocol):
233
+ async def __call__(
234
+ self,
235
+ /,
236
+ stream_name: str,
237
+ *args: Any,
238
+ **kwargs: Any,
239
+ ) -> AsyncReadResponse:
240
+ pass # pragma: no cover
241
+
242
+
243
+ @overload
244
+ def span_read_stream(
245
+ tracer: Tracer,
246
+ instance: BaseKurrentDBClient,
247
+ spanned_func: AsyncReadStreamMethod,
248
+ /,
249
+ stream_name: str,
250
+ *args: Any,
251
+ **kwargs: Any,
252
+ ) -> AsyncSpannerResponse[AsyncReadResponse]:
253
+ pass # pragma: no cover
254
+
255
+
256
+ @overload
257
+ def span_read_stream(
258
+ tracer: Tracer,
259
+ instance: BaseKurrentDBClient,
260
+ spanned_func: ReadStreamMethod,
261
+ /,
262
+ stream_name: str,
263
+ *args: Any,
264
+ **kwargs: Any,
265
+ ) -> SpannerResponse[AbstractReadResponse]:
266
+ pass # pragma: no cover
267
+
268
+
269
+ def span_read_stream(
270
+ tracer: Tracer,
271
+ instance: BaseKurrentDBClient,
272
+ spanned_func: Union[ReadStreamMethod, AsyncReadStreamMethod],
273
+ /,
274
+ stream_name: str,
275
+ *args: Any,
276
+ **kwargs: Any,
277
+ ) -> OverloadedSpannerResponse[AbstractReadResponse, AsyncReadResponse]:
278
+ span_name, span_kind = _get_span_name_and_kind(spanned_func)
279
+ with _start_span(tracer, span_name, span_kind) as span:
280
+
281
+ _enrich_span(
282
+ span=span,
283
+ client=instance,
284
+ db_operation_name=span_name,
285
+ stream_name=stream_name,
286
+ )
287
+ try:
288
+ response = spanned_func(stream_name, *args, **kwargs)
289
+ if inspect.iscoroutine(response):
290
+
291
+ async def wrap_response() -> AsyncReadResponse:
292
+ return cast(
293
+ AsyncReadResponse,
294
+ TracedAsyncReadResponse(
295
+ client=instance,
296
+ response=await response,
297
+ tracer=tracer,
298
+ span_name=span_name,
299
+ span_kind=span_kind,
300
+ ),
301
+ )
302
+
303
+ yield wrap_response()
304
+ else:
305
+ # Because TypeGuard doesn't do type narrowing in negative case.
306
+ assert isinstance(response, ReadResponse)
307
+
308
+ yield TracedReadResponse(
309
+ client=instance,
310
+ response=response,
311
+ tracer=tracer,
312
+ span_name=span_name,
313
+ span_kind=span_kind,
314
+ )
315
+ except Exception as e:
316
+ _set_span_error(span, e)
317
+ raise
318
+ else:
319
+ _set_span_ok(span)
320
+
321
+
322
+ class AppendToStreamMethod(Protocol):
323
+ def __call__(
324
+ self,
325
+ /,
326
+ stream_name: str,
327
+ *,
328
+ current_version: Union[int, StreamState],
329
+ events: Union[NewEvent, Iterable[NewEvent]],
330
+ timeout: Optional[float] = None,
331
+ credentials: Optional[grpc.CallCredentials] = None,
332
+ ) -> int:
333
+ pass # pragma: no cover
334
+
335
+
336
+ class AsyncAppendToStreamMethod(Protocol):
337
+ async def __call__(
338
+ self,
339
+ /,
340
+ stream_name: str,
341
+ *,
342
+ current_version: Union[int, StreamState],
343
+ events: Union[NewEvent, Iterable[NewEvent]],
344
+ timeout: Optional[float] = None,
345
+ credentials: Optional[grpc.CallCredentials] = None,
346
+ ) -> int:
347
+ pass # pragma: no cover
348
+
349
+
350
+ @overload
351
+ def span_append_to_stream(
352
+ tracer: Tracer,
353
+ instance: BaseKurrentDBClient,
354
+ spanned_func: AsyncAppendToStreamMethod,
355
+ /,
356
+ stream_name: str,
357
+ *,
358
+ current_version: Union[int, StreamState],
359
+ events: Union[NewEvent, Iterable[NewEvent]],
360
+ timeout: Optional[float] = None,
361
+ credentials: Optional[grpc.CallCredentials] = None,
362
+ ) -> AsyncSpannerResponse[int]:
363
+ pass # pragma: no cover
364
+
365
+
366
+ @overload
367
+ def span_append_to_stream(
368
+ tracer: Tracer,
369
+ instance: BaseKurrentDBClient,
370
+ spanned_func: AppendToStreamMethod,
371
+ /,
372
+ stream_name: str,
373
+ *,
374
+ current_version: Union[int, StreamState],
375
+ events: Union[NewEvent, Iterable[NewEvent]],
376
+ timeout: Optional[float] = None,
377
+ credentials: Optional[grpc.CallCredentials] = None,
378
+ ) -> SpannerResponse[int]:
379
+ pass # pragma: no cover
380
+
381
+
382
+ def span_append_to_stream(
383
+ tracer: Tracer,
384
+ instance: BaseKurrentDBClient,
385
+ spanned_func: Union[AppendToStreamMethod, AsyncAppendToStreamMethod],
386
+ /,
387
+ stream_name: str,
388
+ *,
389
+ current_version: Union[int, StreamState],
390
+ events: Union[NewEvent, Iterable[NewEvent]],
391
+ timeout: Optional[float] = None,
392
+ credentials: Optional[grpc.CallCredentials] = None,
393
+ ) -> OverloadedSpannerResponse[int, int]:
394
+
395
+ span_name, span_kind = _get_span_name_and_kind(spanned_func)
396
+
397
+ with _start_span(tracer, span_name, span_kind) as span:
398
+ try:
399
+ _enrich_span(
400
+ span=span,
401
+ client=instance,
402
+ db_operation_name=span_name,
403
+ stream_name=stream_name,
404
+ )
405
+ events = _set_context_in_events(span.get_span_context(), events)
406
+ yield spanned_func(
407
+ stream_name,
408
+ current_version=current_version,
409
+ events=events,
410
+ timeout=timeout,
411
+ credentials=credentials,
412
+ )
413
+ except Exception as e:
414
+ _set_span_error(span, e)
415
+ raise
416
+ else:
417
+ _set_span_ok(span)
418
+
419
+
420
+ class CatchupSubscriptionMethod(Protocol):
421
+ def __call__(
422
+ self,
423
+ /,
424
+ *args: Any,
425
+ **kwargs: Any,
426
+ ) -> AbstractCatchupSubscription:
427
+ pass # pragma: no cover
428
+
429
+
430
+ class AsyncCatchupSubscriptionMethod(Protocol):
431
+ async def __call__(
432
+ self,
433
+ /,
434
+ *args: Any,
435
+ **kwargs: Any,
436
+ ) -> AbstractAsyncCatchupSubscription:
437
+ pass # pragma: no cover
438
+
439
+
440
+ @overload
441
+ def span_catchup_subscription(
442
+ tracer: Tracer,
443
+ instance: BaseKurrentDBClient,
444
+ spanned_func: AsyncCatchupSubscriptionMethod,
445
+ /,
446
+ *args: Any,
447
+ **kwargs: Any,
448
+ ) -> AsyncSpannerResponse[AbstractAsyncCatchupSubscription]:
449
+ pass # pragma: no cover
450
+
451
+
452
+ @overload
453
+ def span_catchup_subscription(
454
+ tracer: Tracer,
455
+ instance: BaseKurrentDBClient,
456
+ spanned_func: CatchupSubscriptionMethod,
457
+ /,
458
+ *args: Any,
459
+ **kwargs: Any,
460
+ ) -> SpannerResponse[AbstractCatchupSubscription]:
461
+ pass # pragma: no cover
462
+
463
+
464
+ def span_catchup_subscription(
465
+ tracer: Tracer,
466
+ instance: BaseKurrentDBClient,
467
+ spanned_func: Union[CatchupSubscriptionMethod, AsyncCatchupSubscriptionMethod],
468
+ /,
469
+ *args: Any,
470
+ **kwargs: Any,
471
+ ) -> OverloadedSpannerResponse[
472
+ AbstractCatchupSubscription, AbstractAsyncCatchupSubscription
473
+ ]:
474
+ span_name, span_kind = _get_span_name_and_kind(spanned_func)
475
+ try:
476
+ response = spanned_func(*args, **kwargs)
477
+ if inspect.isawaitable(response):
478
+
479
+ async def wrap_response() -> AbstractAsyncCatchupSubscription:
480
+ return TracedAsyncCatchupSubscription(
481
+ client=instance,
482
+ response=await response,
483
+ tracer=tracer,
484
+ span_name=span_name,
485
+ span_kind=span_kind,
486
+ )
487
+
488
+ yield wrap_response()
489
+ else:
490
+ # Because TypeGuard doesn't do type narrowing in negative case.
491
+ assert isinstance(response, AbstractCatchupSubscription)
492
+
493
+ yield TracedCatchupSubscription(
494
+ client=instance,
495
+ response=response,
496
+ tracer=tracer,
497
+ span_name=span_name,
498
+ span_kind=span_kind,
499
+ )
500
+ except Exception as e:
501
+ with _start_span(tracer, span_name, span_kind) as span:
502
+ _enrich_span(
503
+ span=span,
504
+ client=instance,
505
+ db_operation_name=span_name,
506
+ )
507
+ _set_span_error(span, e)
508
+ raise
509
+
510
+
511
+ class ReadPersistentSubscriptionMethod(Protocol):
512
+ def __call__(
513
+ self,
514
+ /,
515
+ *args: Any,
516
+ **kwargs: Any,
517
+ ) -> AbstractPersistentSubscription:
518
+ pass # pragma: no cover
519
+
520
+
521
+ class AsyncReadPersistentSubscriptionMethod(Protocol):
522
+ async def __call__(
523
+ self,
524
+ /,
525
+ *args: Any,
526
+ **kwargs: Any,
527
+ ) -> AbstractAsyncPersistentSubscription:
528
+ pass # pragma: no cover
529
+
530
+
531
+ @overload
532
+ def span_persistent_subscription(
533
+ tracer: Tracer,
534
+ instance: BaseKurrentDBClient,
535
+ spanned_func: AsyncReadPersistentSubscriptionMethod,
536
+ /,
537
+ *args: Any,
538
+ **kwargs: Any,
539
+ ) -> AsyncSpannerResponse[AbstractAsyncPersistentSubscription]:
540
+ pass # pragma: no cover
541
+
542
+
543
+ @overload
544
+ def span_persistent_subscription(
545
+ tracer: Tracer,
546
+ instance: BaseKurrentDBClient,
547
+ spanned_func: ReadPersistentSubscriptionMethod,
548
+ /,
549
+ *args: Any,
550
+ **kwargs: Any,
551
+ ) -> SpannerResponse[AbstractPersistentSubscription]:
552
+ pass # pragma: no cover
553
+
554
+
555
+ def span_persistent_subscription(
556
+ tracer: Tracer,
557
+ instance: BaseKurrentDBClient,
558
+ spanned_func: Union[
559
+ ReadPersistentSubscriptionMethod, AsyncReadPersistentSubscriptionMethod
560
+ ],
561
+ /,
562
+ *args: Any,
563
+ **kwargs: Any,
564
+ ) -> OverloadedSpannerResponse[
565
+ AbstractPersistentSubscription, AbstractAsyncPersistentSubscription
566
+ ]:
567
+ span_name, span_kind = _get_span_name_and_kind(spanned_func)
568
+ try:
569
+ response = spanned_func(*args, **kwargs)
570
+ if inspect.isawaitable(response):
571
+
572
+ async def wrap_response() -> AbstractAsyncPersistentSubscription:
573
+ return TracedAsyncPersistentSubscription(
574
+ client=instance,
575
+ response=await response,
576
+ tracer=tracer,
577
+ span_name=span_name,
578
+ span_kind=span_kind,
579
+ )
580
+
581
+ yield wrap_response()
582
+
583
+ else:
584
+ # Because TypeGuard doesn't do type narrowing in negative case.
585
+ assert isinstance(response, AbstractPersistentSubscription)
586
+
587
+ yield TracedPersistentSubscription(
588
+ client=instance,
589
+ response=response,
590
+ tracer=tracer,
591
+ span_name=span_name,
592
+ span_kind=span_kind,
593
+ )
594
+
595
+ except Exception as e:
596
+ with _start_span(tracer, span_name, span_kind) as span:
597
+ _enrich_span(
598
+ span=span,
599
+ client=instance,
600
+ db_operation_name=span_name,
601
+ )
602
+ _set_span_error(span, e)
603
+ raise
604
+
605
+
606
+ TRecordedEventIterator = TypeVar("TRecordedEventIterator", bound=RecordedEventIterator)
607
+
608
+ TRecordedEventSubscription = TypeVar(
609
+ "TRecordedEventSubscription", bound=RecordedEventSubscription
610
+ )
611
+
612
+
613
+ class TracedRecordedEventIterator(
614
+ RecordedEventIterator, Generic[TRecordedEventIterator]
615
+ ):
616
+ def __init__(
617
+ self,
618
+ *,
619
+ client: BaseKurrentDBClient,
620
+ response: TRecordedEventIterator,
621
+ tracer: Tracer,
622
+ span_name: str,
623
+ span_kind: SpanKind,
624
+ ) -> None:
625
+ self.client = client
626
+ self.response = response
627
+ self.tracer = tracer
628
+ self.span_name = span_name
629
+ self.span_kind = span_kind
630
+ self._current_span: Optional[Span] = None
631
+
632
+ # self.iterator_span: Optional[Span] = None
633
+ # self.iterator_context: Optional[Context] = None
634
+
635
+ # with _start_span(self.tracer, "ReadResponse", end_on_exit=False) as span:
636
+ # self.iterator_span = span
637
+ # _enrich_span(
638
+ # span=self.iterator_span,
639
+ # client=self.client,
640
+ # )
641
+ # self.iterator_context = set_span_in_context(self.iterator_span, Context())
642
+
643
+ def __next__(self) -> RecordedEvent:
644
+ span_name, span_kind = _get_span_name_and_kind(self.response.__next__)
645
+
646
+ with _start_span(
647
+ self.tracer,
648
+ span_name,
649
+ span_kind,
650
+ # context=self.iterator_context,
651
+ end_on_exit=False,
652
+ ) as span:
653
+ self._current_span = span
654
+ try:
655
+ recorded_event = next(self.response)
656
+ except StopIteration:
657
+ _enrich_span(
658
+ span=span,
659
+ client=self.client,
660
+ db_operation_name=span_name,
661
+ )
662
+ _set_span_ok(span)
663
+ raise
664
+ except Exception as e:
665
+ _enrich_span(
666
+ span=span,
667
+ client=self.client,
668
+ db_operation_name=span_name,
669
+ )
670
+ _set_span_error(span, e)
671
+ # if self.iterator_span is not None:
672
+ # _set_span_error(self.iterator_span, e)
673
+ # self.iterator_span.end()
674
+ raise
675
+ else:
676
+ _enrich_span(
677
+ span=span,
678
+ client=self.client,
679
+ db_operation_name=span_name,
680
+ stream_name=recorded_event.stream_name,
681
+ event_id=str(recorded_event.id),
682
+ event_type=recorded_event.type,
683
+ )
684
+ _set_span_ok(span)
685
+ return recorded_event
686
+ finally:
687
+ span.end()
688
+ self._current_span = None
689
+
690
+ def stop(self) -> None:
691
+ self.response.stop()
692
+
693
+ def __enter__(self) -> Self:
694
+ self.response.__enter__()
695
+ return self
696
+
697
+ def __exit__(self, *args: Any, **kwargs: Any) -> None:
698
+ return self.response.__exit__(*args, **kwargs)
699
+
700
+ def __del__(self) -> None:
701
+ current_span = self._current_span
702
+ if current_span and current_span.is_recording(): # pragma: no cover
703
+ _set_span_ok(current_span)
704
+ current_span.end()
705
+ # iterator_span = self.iterator_span
706
+ # if iterator_span and iterator_span.is_recording():
707
+ # _set_span_ok(iterator_span)
708
+ # iterator_span.end()
709
+
710
+
711
+ class TracedReadResponse(
712
+ TracedRecordedEventIterator[AbstractReadResponse], AbstractReadResponse
713
+ ):
714
+ pass
715
+
716
+
717
+ class TracedRecordedEventSubscription(
718
+ TracedRecordedEventIterator[TRecordedEventSubscription]
719
+ ):
720
+ def __next__(self) -> RecordedEvent:
721
+ try:
722
+ recorded_event = next(self.response)
723
+ except StopIteration:
724
+ raise
725
+ except Exception as e:
726
+ with _start_span(self.tracer, self.span_name, self.span_kind) as span:
727
+ self._enrich_span(
728
+ span=span,
729
+ )
730
+ _set_span_error(span, e)
731
+ raise
732
+ else:
733
+ context = _extract_context_from_event(recorded_event)
734
+
735
+ if context is not None:
736
+ with _start_span(
737
+ self.tracer, self.span_name, self.span_kind, context=context
738
+ ) as span:
739
+ self._enrich_span(
740
+ span=span,
741
+ stream_name=recorded_event.stream_name,
742
+ event_id=str(recorded_event.id),
743
+ event_type=recorded_event.type,
744
+ )
745
+
746
+ span.set_status(StatusCode.OK)
747
+
748
+ return recorded_event
749
+ else:
750
+ return recorded_event
751
+
752
+ def _enrich_span(
753
+ self,
754
+ *,
755
+ span: Span,
756
+ stream_name: Optional[str] = None,
757
+ event_id: Optional[str] = None,
758
+ event_type: Optional[str] = None,
759
+ ) -> None:
760
+ _enrich_span(
761
+ span=span,
762
+ client=self.client,
763
+ db_operation_name=self.span_name,
764
+ stream_name=stream_name,
765
+ subscription_id=self.subscription_id,
766
+ event_id=event_id,
767
+ event_type=event_type,
768
+ )
769
+
770
+ @property
771
+ def subscription_id(self) -> str:
772
+ return self.response.subscription_id
773
+
774
+
775
+ class TracedCatchupSubscription(
776
+ TracedRecordedEventSubscription[AbstractCatchupSubscription],
777
+ AbstractCatchupSubscription,
778
+ ):
779
+ pass
780
+
781
+
782
+ class TracedPersistentSubscription(
783
+ TracedRecordedEventSubscription[AbstractPersistentSubscription],
784
+ AbstractPersistentSubscription,
785
+ ):
786
+ def ack(self, item: Union[UUID, RecordedEvent]) -> None:
787
+ self.response.ack(item)
788
+
789
+ def nack(
790
+ self,
791
+ item: Union[UUID, RecordedEvent],
792
+ action: Literal["unknown", "park", "retry", "skip", "stop"],
793
+ ) -> None:
794
+ self.response.nack(item, action)
795
+
796
+
797
+ TAsyncRecordedEventIterator = TypeVar(
798
+ "TAsyncRecordedEventIterator", bound=AsyncRecordedEventIterator
799
+ )
800
+
801
+ TAsyncRecordedEventSubscription = TypeVar(
802
+ "TAsyncRecordedEventSubscription", bound=AsyncRecordedEventSubscription
803
+ )
804
+
805
+
806
+ class TracedAsyncRecordedEventIterator(
807
+ AsyncRecordedEventIterator, Generic[TAsyncRecordedEventIterator]
808
+ ):
809
+ def __init__(
810
+ self,
811
+ *,
812
+ client: BaseKurrentDBClient,
813
+ response: TAsyncRecordedEventIterator,
814
+ tracer: Tracer,
815
+ span_name: str,
816
+ span_kind: SpanKind,
817
+ ) -> None:
818
+ self.client = client
819
+ self.response = response
820
+ self.tracer = tracer
821
+ self.span_name = span_name
822
+ self.span_kind = span_kind
823
+ self._current_span: Optional[Span] = None
824
+
825
+ async def __anext__(self) -> RecordedEvent:
826
+ span_name = _get_span_name(self.response.__anext__)
827
+ span_kind = _get_span_kind(self.response.__anext__)
828
+
829
+ with _start_span(
830
+ self.tracer,
831
+ span_name,
832
+ span_kind,
833
+ end_on_exit=False,
834
+ ) as span:
835
+ self._current_span = span
836
+ try:
837
+ recorded_event = await self.response.__anext__()
838
+ except StopAsyncIteration:
839
+ _enrich_span(
840
+ span=span,
841
+ client=self.client,
842
+ db_operation_name=span_name,
843
+ )
844
+ _set_span_ok(span)
845
+ raise
846
+ except Exception as e:
847
+ _enrich_span(
848
+ span=span,
849
+ client=self.client,
850
+ db_operation_name=span_name,
851
+ )
852
+ _set_span_error(span, e)
853
+ raise
854
+ else:
855
+ _enrich_span(
856
+ span=span,
857
+ client=self.client,
858
+ db_operation_name=span_name,
859
+ stream_name=recorded_event.stream_name,
860
+ event_id=str(recorded_event.id),
861
+ event_type=recorded_event.type,
862
+ )
863
+ _set_span_ok(span)
864
+ return recorded_event
865
+ finally:
866
+ span.end()
867
+ self._current_span = None
868
+
869
+ async def stop(self) -> None:
870
+ await self.response.stop()
871
+
872
+ async def __aenter__(self) -> Self:
873
+ await self.response.__aenter__()
874
+ return self
875
+
876
+ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
877
+ return await self.response.__aexit__(*args, **kwargs)
878
+
879
+ def _set_iter_error_for_testing(self) -> None:
880
+ self.response._set_iter_error_for_testing()
881
+
882
+ def __del__(self) -> None:
883
+ current_span = self._current_span
884
+ if current_span and current_span.is_recording(): # pragma: no cover
885
+ _set_span_ok(current_span)
886
+ current_span.end()
887
+
888
+
889
+ class TracedAsyncReadResponse(
890
+ TracedAsyncRecordedEventIterator[AsyncReadResponse],
891
+ AbstractAsyncReadResponse,
892
+ ):
893
+ pass
894
+
895
+
896
+ class TracedAsyncRecordedEventSubscription(
897
+ TracedAsyncRecordedEventIterator[TAsyncRecordedEventSubscription]
898
+ ):
899
+ async def __anext__(self) -> RecordedEvent:
900
+ try:
901
+ recorded_event = await self.response.__anext__()
902
+ except StopAsyncIteration:
903
+ raise
904
+ except Exception as e:
905
+ with _start_span(self.tracer, self.span_name, self.span_kind) as span:
906
+ self._enrich_span(
907
+ span=span,
908
+ )
909
+ _set_span_error(span, e)
910
+ raise
911
+ else:
912
+ context = _extract_context_from_event(recorded_event)
913
+
914
+ if context is not None:
915
+ with _start_span(
916
+ self.tracer, self.span_name, self.span_kind, context=context
917
+ ) as span:
918
+ self._enrich_span(
919
+ span=span,
920
+ stream_name=recorded_event.stream_name,
921
+ event_id=str(recorded_event.id),
922
+ event_type=recorded_event.type,
923
+ )
924
+ _set_span_ok(span)
925
+ return recorded_event
926
+ else:
927
+ return recorded_event
928
+
929
+ def _enrich_span(
930
+ self,
931
+ *,
932
+ span: Span,
933
+ stream_name: Optional[str] = None,
934
+ event_id: Optional[str] = None,
935
+ event_type: Optional[str] = None,
936
+ ) -> None:
937
+ _enrich_span(
938
+ span=span,
939
+ client=self.client,
940
+ db_operation_name=self.span_name,
941
+ stream_name=stream_name,
942
+ subscription_id=self.subscription_id,
943
+ event_id=event_id,
944
+ event_type=event_type,
945
+ )
946
+
947
+ @property
948
+ def subscription_id(self) -> str:
949
+ return self.response.subscription_id
950
+
951
+
952
+ class TracedAsyncCatchupSubscription(
953
+ TracedAsyncRecordedEventSubscription[AbstractAsyncCatchupSubscription],
954
+ AbstractAsyncCatchupSubscription,
955
+ ):
956
+ pass
957
+
958
+
959
+ class TracedAsyncPersistentSubscription(
960
+ TracedAsyncRecordedEventSubscription[AbstractAsyncPersistentSubscription],
961
+ AbstractAsyncPersistentSubscription,
962
+ ):
963
+ async def ack(self, item: Union[UUID, RecordedEvent]) -> None:
964
+ await self.response.ack(item)
965
+
966
+ async def nack(
967
+ self,
968
+ item: Union[UUID, RecordedEvent],
969
+ action: Literal["unknown", "park", "retry", "skip", "stop"],
970
+ ) -> None:
971
+ await self.response.nack(item, action)
972
+
973
+
974
+ def _enrich_span(
975
+ *,
976
+ span: Span,
977
+ client: BaseKurrentDBClient,
978
+ db_operation_name: Optional[str] = None,
979
+ stream_name: Optional[str] = None,
980
+ subscription_id: Optional[str] = None,
981
+ event_id: Optional[str] = None,
982
+ event_type: Optional[str] = None,
983
+ ) -> None:
984
+ if span.is_recording():
985
+
986
+ # Gather attributes.
987
+ attributes: Dict[str, AttributeValue] = {}
988
+
989
+ # Gather db attributes.
990
+ if db_operation_name is not None:
991
+ attributes[Attributes.DB_OPERATION] = db_operation_name
992
+ attributes[Attributes.DB_SYSTEM] = "kurrentdb"
993
+ # Todo: Username from credentials passed in as arg (not just from URI).
994
+ attributes[Attributes.DB_USER] = _extract_db_user(client)
995
+
996
+ # Gather kurrentdb attributes.
997
+ if event_id is not None:
998
+ attributes[Attributes.EVENTSTOREDB_EVENT_ID] = str(event_id)
999
+ if event_type is not None:
1000
+ attributes[Attributes.EVENTSTOREDB_EVENT_TYPE] = event_type
1001
+ if stream_name is not None:
1002
+ attributes[Attributes.EVENTSTOREDB_STREAM] = stream_name
1003
+ if subscription_id is not None:
1004
+ attributes[Attributes.EVENTSTOREDB_SUBSCRIPTION_ID] = subscription_id
1005
+
1006
+ # Gather server attributes.
1007
+ server_address, server_port = _extract_server_address_and_port(client)
1008
+ attributes[Attributes.SERVER_ADDRESS] = server_address
1009
+ attributes[Attributes.SERVER_PORT] = server_port
1010
+
1011
+ # Set attributes on span.
1012
+ span.set_attributes(attributes)
1013
+
1014
+
1015
+ def _extract_db_user(client: BaseKurrentDBClient) -> str:
1016
+ return client.connection_spec.username or ""
1017
+
1018
+
1019
+ def _extract_server_address_and_port(client: BaseKurrentDBClient) -> Tuple[str, str]:
1020
+ # For "quality of life" of readers of observability platforms, try to
1021
+ # maintain a constant server address (when using esdb+discover with
1022
+ # one target only).
1023
+ if (
1024
+ client.connection_spec.scheme in URI_SCHEMES_DISCOVER
1025
+ and len(client.connection_spec.targets) == 1
1026
+ ):
1027
+ # Signal server address as the DNS cluster name ("quality of life").
1028
+ server_address, server_port = client.connection_spec.targets[0].split(":")
1029
+ else:
1030
+ # Signal server address as the current connection address.
1031
+ server_address, server_port = client.connection_target.split(":")
1032
+ return server_address, server_port
1033
+
1034
+
1035
+ METADATA_TRACE_ID = "$traceId"
1036
+ METADATA_SPAN_ID = "$spanId"
1037
+
1038
+
1039
+ def _set_context_in_events(
1040
+ context: SpanContext, events: Union[NewEvent, Iterable[NewEvent]]
1041
+ ) -> Sequence[NewEvent]:
1042
+ # Kind of propagate OpenTelemetry context in "standard KurrentDB" style.
1043
+ reconstructed_events = []
1044
+ if isinstance(events, NewEvent):
1045
+ events = [events]
1046
+ for event in events:
1047
+ if event.content_type == "application/json":
1048
+ try:
1049
+ d = json.loads((event.metadata or b"{}").decode("utf8"))
1050
+ d[METADATA_SPAN_ID] = _int_to_hex(context.span_id)
1051
+ d[METADATA_TRACE_ID] = _int_to_hex(context.trace_id)
1052
+ metadata = json.dumps(d).encode("utf8")
1053
+ except Exception:
1054
+ pass
1055
+ else:
1056
+ event = NewEvent(
1057
+ id=event.id,
1058
+ type=event.type,
1059
+ data=event.data,
1060
+ content_type=event.content_type,
1061
+ metadata=metadata,
1062
+ )
1063
+ reconstructed_events.append(event)
1064
+ return reconstructed_events
1065
+
1066
+
1067
+ def _extract_context_from_event(
1068
+ recorded_event: Union[NewEvent, RecordedEvent],
1069
+ ) -> Optional[Context]:
1070
+ # Extract propagated OpenTelemetry context using "standard KurrentDB" style.
1071
+ try:
1072
+ m = json.loads(recorded_event.metadata.decode("utf8"))
1073
+ parent_span_id = _hex_to_int(m[METADATA_SPAN_ID])
1074
+ trace_id = _hex_to_int(m[METADATA_TRACE_ID])
1075
+ except Exception:
1076
+ context: Optional[Context] = None
1077
+ else:
1078
+ trace_flags = TraceFlags(TraceFlags.SAMPLED)
1079
+ span_context = SpanContext(
1080
+ trace_id=trace_id,
1081
+ span_id=parent_span_id,
1082
+ is_remote=True,
1083
+ trace_flags=trace_flags,
1084
+ )
1085
+ context = set_span_in_context(
1086
+ NonRecordingSpan(span_context),
1087
+ Context(),
1088
+ )
1089
+ return context
1090
+
1091
+
1092
+ def _int_to_hex(i: int) -> str:
1093
+ return f"{i:#x}"
1094
+
1095
+
1096
+ def _hex_to_int(hex_string: str) -> int:
1097
+ return int(hex_string, 16)