neuroglia-python 0.6.18__tar.gz → 0.6.20__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 (120) hide show
  1. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/PKG-INFO +1 -1
  2. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/pyproject.toml +1 -1
  3. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/event_sourcing/__init__.py +8 -6
  4. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/event_sourcing/event_sourcing_repository.py +4 -2
  5. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/event_sourcing/event_store/event_store.py +12 -4
  6. neuroglia_python-0.6.20/src/neuroglia/data/infrastructure/event_sourcing/patches.py +74 -0
  7. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/LICENSE +0 -0
  8. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/README.md +0 -0
  9. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/__init__.py +0 -0
  10. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/application/__init__.py +0 -0
  11. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/application/background_scheduler.py +0 -0
  12. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/coordination/__init__.py +0 -0
  13. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/coordination/abstractions.py +0 -0
  14. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/coordination/leader_election.py +0 -0
  15. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/coordination/redis_backend.py +0 -0
  16. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/core/__init__.py +0 -0
  17. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/core/module_loader.py +0 -0
  18. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/core/operation_result.py +0 -0
  19. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/core/problem_details.py +0 -0
  20. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/core/type_extensions.py +0 -0
  21. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/core/type_finder.py +0 -0
  22. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/core/type_registry.py +0 -0
  23. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/__init__.py +0 -0
  24. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/abstractions.py +0 -0
  25. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/exceptions.py +0 -0
  26. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/__init__.py +0 -0
  27. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/abstractions.py +0 -0
  28. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/event_sourcing/abstractions.py +0 -0
  29. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/event_sourcing/event_store/__init__.py +0 -0
  30. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +0 -0
  31. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/filesystem/__init__.py +0 -0
  32. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/filesystem/filesystem_repository.py +0 -0
  33. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/memory/__init__.py +0 -0
  34. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/memory/memory_repository.py +0 -0
  35. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/mongo/__init__.py +0 -0
  36. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/mongo/enhanced_mongo_repository.py +0 -0
  37. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/mongo/mongo_repository.py +0 -0
  38. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/mongo/motor_repository.py +0 -0
  39. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/mongo/serialization_helper.py +0 -0
  40. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/mongo/typed_mongo_query.py +0 -0
  41. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/resources/__init__.py +0 -0
  42. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/resources/in_memory_storage_backend.py +0 -0
  43. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/resources/postgres_resource_store.py +0 -0
  44. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/resources/redis_resource_store.py +0 -0
  45. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/resources/resource_repository.py +0 -0
  46. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/infrastructure/tracing_mixin.py +0 -0
  47. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/queries/generic/__init__.py +0 -0
  48. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/queries/generic/get_by_id_query.py +0 -0
  49. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/queries/generic/list_query.py +0 -0
  50. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/queryable.py +0 -0
  51. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/__init__.py +0 -0
  52. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/abstractions.py +0 -0
  53. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/controller.py +0 -0
  54. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/serializers/__init__.py +0 -0
  55. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/serializers/xml_serializer.py +0 -0
  56. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/serializers/yaml_serializer.py +0 -0
  57. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/state_machine.py +0 -0
  58. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/resources/watcher.py +0 -0
  59. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/data/unit_of_work.py +0 -0
  60. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/dependency_injection/__init__.py +0 -0
  61. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/dependency_injection/service_provider.py +0 -0
  62. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/dependency_injection/service_provider_backup.py +0 -0
  63. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/__init__.py +0 -0
  64. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/__init__.py +0 -0
  65. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/cloud_event.py +0 -0
  66. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/decorators.py +0 -0
  67. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/infrastructure/__init__.py +0 -0
  68. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_bus.py +0 -0
  69. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_ingestor.py +0 -0
  70. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_middleware.py +0 -0
  71. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_publisher.py +0 -0
  72. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/eventing/tracing.py +0 -0
  73. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/expressions/__init__.py +0 -0
  74. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/expressions/javascript_expression_translator.py +0 -0
  75. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/extensions/cqrs_metrics_extensions.py +0 -0
  76. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/extensions/mediator_extensions.py +0 -0
  77. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/extensions/state_persistence_extensions.py +0 -0
  78. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/hosting/__init__.py +0 -0
  79. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/hosting/abstractions.py +0 -0
  80. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/hosting/configuration/data_access_layer.py +0 -0
  81. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/hosting/web.py +0 -0
  82. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/integration/__init__.py +0 -0
  83. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/integration/cache_repository.py +0 -0
  84. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/integration/http_service_client.py +0 -0
  85. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/integration/models.py +0 -0
  86. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/logging/__init__.py +0 -0
  87. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/logging/logger.py +0 -0
  88. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mapping/__init__.py +0 -0
  89. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mapping/mapper.py +0 -0
  90. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/__init__.py +0 -0
  91. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/behaviors/domain_event_cloudevent_behavior.py +0 -0
  92. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/behaviors/domain_event_dispatching_middleware.py +0 -0
  93. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/mediator.py +0 -0
  94. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/metrics_middleware.py +0 -0
  95. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/pipeline_behavior.py +0 -0
  96. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/simple.py +0 -0
  97. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mediation/tracing_middleware.py +0 -0
  98. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mvc/__init__.py +0 -0
  99. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/mvc/controller_base.py +0 -0
  100. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/observability/__init__.py +0 -0
  101. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/observability/framework.py +0 -0
  102. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/observability/logging.py +0 -0
  103. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/observability/metrics.py +0 -0
  104. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/observability/otel_sdk.py +0 -0
  105. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/observability/settings.py +0 -0
  106. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/observability/tracing.py +0 -0
  107. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/py.typed +0 -0
  108. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/reactive/__init__.py +0 -0
  109. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/reactive/rx_async.py +0 -0
  110. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/serialization/__init__.py +0 -0
  111. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/serialization/abstractions.py +0 -0
  112. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/serialization/aggregate_serializer.py +0 -0
  113. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/serialization/json.py +0 -0
  114. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/utils/__init__.py +0 -0
  115. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/utils/camel_model.py +0 -0
  116. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/utils/case_conversion.py +0 -0
  117. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/validation/__init__.py +0 -0
  118. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/validation/business_rules.py +0 -0
  119. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/validation/exceptions.py +0 -0
  120. {neuroglia_python-0.6.18 → neuroglia_python-0.6.20}/src/neuroglia/validation/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neuroglia-python
3
- Version: 0.6.18
3
+ Version: 0.6.20
4
4
  Summary: Port from .NET to Python of the Neuroglia Framework
5
5
  License: Apache
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "neuroglia-python"
3
- version = "0.6.18"
3
+ version = "0.6.20"
4
4
  description = "Port from .NET to Python of the Neuroglia Framework"
5
5
  authors = ["Charles d'Avernas <charles.davernas@neuroglia.io>", "Bruno van de Werve <bvandewe@gmail.com>"]
6
6
  license = "Apache"
@@ -4,14 +4,16 @@ Event sourcing infrastructure for Neuroglia.
4
4
  Provides event store implementations and aggregate root support.
5
5
  """
6
6
 
7
+ # Apply runtime patches for third-party library bugs
8
+ from . import patches # noqa: F401
7
9
  from .abstractions import (
8
- EventStore,
9
- EventRecord,
10
+ Aggregator,
10
11
  EventDescriptor,
12
+ EventRecord,
13
+ EventStore,
14
+ EventStoreOptions,
11
15
  StreamDescriptor,
12
16
  StreamReadDirection,
13
- EventStoreOptions,
14
- Aggregator
15
17
  )
16
18
  from .event_sourcing_repository import EventSourcingRepository
17
19
 
@@ -19,9 +21,9 @@ __all__ = [
19
21
  "EventStore",
20
22
  "EventSourcingRepository",
21
23
  "EventRecord",
22
- "EventDescriptor",
24
+ "EventDescriptor",
23
25
  "StreamDescriptor",
24
26
  "StreamReadDirection",
25
27
  "EventStoreOptions",
26
28
  "Aggregator",
27
- ]
29
+ ]
@@ -127,7 +127,8 @@ class EventSourcingRepository(Generic[TAggregate, TKey], Repository[TAggregate,
127
127
  encoded_events = [self._encode_event(e) for e in events]
128
128
  await self._eventstore.append_async(stream_id, encoded_events)
129
129
  aggregate.state.state_version = events[-1].aggregate_version
130
- aggregate.clear_pending_events()
130
+ # DON'T clear pending events here - let base class do it after publishing!
131
+ # aggregate.clear_pending_events()
131
132
  return aggregate
132
133
 
133
134
  async def _do_update_async(self, aggregate: TAggregate) -> TAggregate:
@@ -139,7 +140,8 @@ class EventSourcingRepository(Generic[TAggregate, TKey], Repository[TAggregate,
139
140
  encoded_events = [self._encode_event(e) for e in events]
140
141
  await self._eventstore.append_async(stream_id, encoded_events, aggregate.state.state_version)
141
142
  aggregate.state.state_version = events[-1].aggregate_version
142
- aggregate.clear_pending_events()
143
+ # DON'T clear pending events here - let base class do it after publishing!
144
+ # aggregate.clear_pending_events()
143
145
  return aggregate
144
146
 
145
147
  async def _do_remove_async(self, id: TKey) -> None:
@@ -253,7 +253,9 @@ class ESEventStore(EventStore):
253
253
  logging.debug(f"Skipping tombstone event from stream: {e.stream_name}")
254
254
  # Acknowledge tombstone to continue processing
255
255
  if is_persistent:
256
- await subscription.ack(e.id)
256
+ # Use ack_id for resolved link events, fall back to id
257
+ ack_id = getattr(e, "ack_id", e.id)
258
+ await subscription.ack(ack_id)
257
259
  continue
258
260
 
259
261
  # Skip system event types (prefixed with $)
@@ -262,7 +264,9 @@ class ESEventStore(EventStore):
262
264
  logging.debug(f"Skipping system event type '{e.type}' from stream: {e.stream_name}")
263
265
  # Acknowledge system event to continue processing
264
266
  if is_persistent:
265
- await subscription.ack(e.id)
267
+ # Use ack_id for resolved link events, fall back to id
268
+ ack_id = getattr(e, "ack_id", e.id)
269
+ await subscription.ack(ack_id)
266
270
  continue
267
271
 
268
272
  try:
@@ -271,12 +275,16 @@ class ESEventStore(EventStore):
271
275
  logging.warning(f"Could not decode event with offset '{e.stream_position}' from stream '{e.stream_name}': {ex}")
272
276
  # Acknowledge failed decode to continue processing (don't park/retry invalid events)
273
277
  if is_persistent:
274
- await subscription.ack(e.id)
278
+ # Use ack_id for resolved link events, fall back to id
279
+ ack_id = getattr(e, "ack_id", e.id)
280
+ await subscription.ack(ack_id)
275
281
  continue
276
282
 
277
283
  # Convert to AckableEventRecord if subscription supports ack/nack
278
284
  if is_persistent:
279
- event_id = e.id
285
+ # Use ack_id for resolved link events (resolveLinktos=true), fall back to id
286
+ # This is critical for persistent subscriptions to category streams ($ce-*)
287
+ event_id = getattr(e, "ack_id", e.id)
280
288
 
281
289
  async def ack_delegate(eid=event_id, sub=subscription):
282
290
  """
@@ -0,0 +1,74 @@
1
+ """
2
+ Runtime patches for third-party library bugs affecting Neuroglia event sourcing.
3
+
4
+ This module contains monkey-patches that fix critical bugs in dependencies.
5
+ Patches are applied automatically when this module is imported.
6
+ """
7
+
8
+ import logging
9
+
10
+ log = logging.getLogger(__name__)
11
+
12
+
13
+ def patch_esdbclient_async_subscription_id():
14
+ """
15
+ Fix esdbclient bug: AsyncPersistentSubscription.init() doesn't propagate
16
+ subscription_id to _read_reqs, causing ACKs to be sent with empty subscription ID.
17
+
18
+ **Bug Description**:
19
+ The sync version (PersistentSubscription.__init__) has this line:
20
+ self._read_reqs.subscription_id = subscription_id.encode()
21
+
22
+ But the async version (AsyncPersistentSubscription.init) is missing it!
23
+ This causes ACKs to fail silently because EventStoreDB doesn't know which
24
+ subscription the ACK is for.
25
+
26
+ **Symptoms**:
27
+ - Events are redelivered every message_timeout seconds despite being processed
28
+ - Checkpoint never advances in EventStoreDB
29
+ - Events eventually get parked after maxRetryCount attempts
30
+ - Read models may process the same event multiple times
31
+
32
+ **Root Cause**:
33
+ BaseSubscriptionReadReqs.__init__() initializes subscription_id = b"" (empty bytes).
34
+ The sync version overwrites this with the correct value in __init__, but the async
35
+ version only sets self._subscription_id without propagating it to _read_reqs.
36
+
37
+ **Affected Versions**:
38
+ - esdbclient: 1.1.7 (and likely all earlier versions with async support)
39
+ - kurrentdbclient: Likely affected (same codebase, rebranded)
40
+
41
+ **Upstream Issue**:
42
+ Bug report to be filed with esdbclient/kurrentdbclient maintainers.
43
+
44
+ **Verification**:
45
+ After applying this patch, check EventStoreDB admin UI:
46
+ - Navigate to Persistent Subscriptions → your subscription
47
+ - lastCheckpointedEventPosition should advance after processing events
48
+ - totalInFlightMessages should decrease after successful ACKs
49
+ """
50
+ try:
51
+ from esdbclient.persistent import (
52
+ AsyncPersistentSubscription, # type: ignore[import-not-found]
53
+ )
54
+
55
+ original_init = AsyncPersistentSubscription.init
56
+
57
+ async def patched_init(self) -> None:
58
+ await original_init(self)
59
+ # Propagate subscription_id to _read_reqs (missing in esdbclient async version)
60
+ if hasattr(self, "_subscription_id") and hasattr(self, "_read_reqs"):
61
+ self._read_reqs.subscription_id = self._subscription_id.encode()
62
+ log.debug(f"🔧 Patched: propagated subscription_id to _read_reqs: {self._subscription_id}")
63
+
64
+ AsyncPersistentSubscription.init = patched_init
65
+ log.info("🔧 Patched esdbclient AsyncPersistentSubscription.init() to propagate subscription_id")
66
+
67
+ except ImportError:
68
+ log.debug("esdbclient not installed, skipping AsyncPersistentSubscription patch")
69
+ except Exception as e:
70
+ log.warning(f"Failed to apply esdbclient AsyncPersistentSubscription patch: {e}")
71
+
72
+
73
+ # Auto-apply patches when this module is imported
74
+ patch_esdbclient_async_subscription_id()