buz 2.13.1rc7__tar.gz → 2.13.1rc9__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 (238) hide show
  1. {buz-2.13.1rc7 → buz-2.13.1rc9}/PKG-INFO +1 -1
  2. {buz-2.13.1rc7 → buz-2.13.1rc9}/pyproject.toml +2 -1
  3. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/base_buz_aiokafka_async_consumer.py +10 -22
  4. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/buz_kafka_event_bus.py +17 -12
  5. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/kafka_event_sync_subscriber_executor.py +0 -5
  6. buz-2.13.1rc9/src/buz/kafka/domain/exceptions/not_all_partition_assigned_exception.py +8 -0
  7. buz-2.13.1rc9/src/buz/kafka/domain/exceptions/topic_not_found_exception.py +6 -0
  8. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/services/kafka_admin_client.py +14 -0
  9. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/aiokafka/aiokafka_consumer.py +1 -1
  10. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/cdc/cdc_message.py +3 -1
  11. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/implementations/cdc/cdc_record_bytes_to_event_deserializer.py +9 -4
  12. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/kafka_python_admin_client.py +98 -5
  13. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/kafka_python_admin_test_client.py +11 -2
  14. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/serializers/implementations/cdc_record_bytes_to_event_serializer.py +6 -1
  15. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/queue/in_memory/in_memory_multiqueue_repository.py +0 -20
  16. {buz-2.13.1rc7 → buz-2.13.1rc9}/LICENSE +0 -0
  17. {buz-2.13.1rc7 → buz-2.13.1rc9}/README.md +0 -0
  18. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/__init__.py +0 -0
  19. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/__init__.py +0 -0
  20. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/__init__.py +0 -0
  21. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/base_command_handler.py +0 -0
  22. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/command_bus.py +0 -0
  23. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/command_handler.py +0 -0
  24. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/middleware/__init__.py +0 -0
  25. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/middleware/base_handle_middleware.py +0 -0
  26. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/middleware/handle_middleware.py +0 -0
  27. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/middleware/handle_middleware_chain_resolver.py +0 -0
  28. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/self_process/__init__.py +0 -0
  29. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/asynchronous/self_process/self_process_command_bus.py +0 -0
  30. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/command.py +0 -0
  31. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/more_than_one_command_handler_related_exception.py +0 -0
  32. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/__init__.py +0 -0
  33. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/base_command_handler.py +0 -0
  34. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/command_bus.py +0 -0
  35. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/command_handler.py +0 -0
  36. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/middleware/__init__.py +0 -0
  37. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/middleware/base_handle_middleware.py +0 -0
  38. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/middleware/handle_middleware.py +0 -0
  39. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/middleware/handle_middleware_chain_resolver.py +0 -0
  40. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/self_process/__init__.py +0 -0
  41. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/self_process/self_process_command_bus.py +0 -0
  42. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/synced_async/__init__.py +0 -0
  43. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/command/synchronous/synced_async/synced_async_command_bus.py +0 -0
  44. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/__init__.py +0 -0
  45. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/async_consumer.py +0 -0
  46. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/async_subscriber.py +0 -0
  47. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/async_worker.py +0 -0
  48. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/base_async_subscriber.py +0 -0
  49. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/base_subscriber.py +0 -0
  50. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/consumer.py +0 -0
  51. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/dead_letter_queue/__init__.py +0 -0
  52. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/dead_letter_queue/dlq_criteria.py +0 -0
  53. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/dead_letter_queue/dlq_record.py +0 -0
  54. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/dead_letter_queue/dlq_repository.py +0 -0
  55. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/event.py +0 -0
  56. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/event_bus.py +0 -0
  57. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/exceptions/__init__.py +0 -0
  58. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/exceptions/event_not_published_exception.py +0 -0
  59. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/exceptions/event_restore_exception.py +0 -0
  60. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/exceptions/subscribers_not_found_exception.py +0 -0
  61. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/exceptions/term_signal_interruption_exception.py +0 -0
  62. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/exceptions/worker_execution_exception.py +0 -0
  63. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/__init__.py +0 -0
  64. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/__init__.py +0 -0
  65. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/buz_aiokafka_async_consumer.py +0 -0
  66. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/buz_aiokafka_multi_threaded_consumer.py +0 -0
  67. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/consume_strategy/__init__.py +0 -0
  68. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/consume_strategy/consume_strategy.py +0 -0
  69. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/consume_strategy/kafka_on_fail_strategy.py +0 -0
  70. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/consume_strategy/topic_and_subscription_group_per_subscriber_kafka_consumer_strategy.py +0 -0
  71. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/exceptions/__init__.py +0 -0
  72. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/exceptions/kafka_event_bus_config_not_valid_exception.py +0 -0
  73. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/kafka_event_async_subscriber_executor.py +0 -0
  74. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/kafka_event_subscriber_executor.py +0 -0
  75. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/publish_strategy/__init__.py +0 -0
  76. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/publish_strategy/publish_strategy.py +0 -0
  77. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/buz_kafka/publish_strategy/topic_per_event_kafka_publish_strategy.py +0 -0
  78. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/__init__.py +0 -0
  79. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/allowed_kombu_serializer.py +0 -0
  80. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/consume_strategy/__init__.py +0 -0
  81. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/consume_strategy/consume_strategy.py +0 -0
  82. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/consume_strategy/queue_per_subscriber_consume_strategy.py +0 -0
  83. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/kombu_consumer.py +0 -0
  84. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/kombu_event_bus.py +0 -0
  85. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/publish_strategy/__init__.py +0 -0
  86. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/publish_strategy/fanout_exchange_per_event_publish_strategy.py +0 -0
  87. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/publish_strategy/publish_strategy.py +0 -0
  88. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/retry_strategy/__init__.py +0 -0
  89. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/retry_strategy/publish_retry_policy.py +0 -0
  90. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/kombu/retry_strategy/simple_publish_retry_policy.py +0 -0
  91. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/models/consuming_task.py +0 -0
  92. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/infrastructure/queue/__init__.py +0 -0
  93. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/meta_base_subscriber.py +0 -0
  94. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/meta_subscriber.py +0 -0
  95. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/__init__.py +0 -0
  96. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/async_consume_middleware.py +0 -0
  97. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/async_consume_middleware_chain_resolver.py +0 -0
  98. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/base_consume_middleware.py +0 -0
  99. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/base_publish_middleware.py +0 -0
  100. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/consume_middleware.py +0 -0
  101. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/consume_middleware_chain_resolver.py +0 -0
  102. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/exceptions/__init__.py +0 -0
  103. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/exceptions/event_already_in_progress_exception.py +0 -0
  104. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/publish_middleware.py +0 -0
  105. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/middleware/publish_middleware_chain_resolver.py +0 -0
  106. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/__init__.py +0 -0
  107. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/execution_strategy/__init__.py +0 -0
  108. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/execution_strategy/async_execution_strategy.py +0 -0
  109. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/execution_strategy/async_self_process_execution_strategy.py +0 -0
  110. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/execution_strategy/cyclic_iterator_execution_strategy.py +0 -0
  111. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/execution_strategy/execution_strategy.py +0 -0
  112. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/execution_strategy/self_process_execution_strategy.py +0 -0
  113. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/retry/__init__.py +0 -0
  114. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/retry/consume_retrier.py +0 -0
  115. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/retry/consumed_event_retry.py +0 -0
  116. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/retry/consumed_event_retry_repository.py +0 -0
  117. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/retry/max_retries_consume_retrier.py +0 -0
  118. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/retry/max_retries_negative_exception.py +0 -0
  119. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/strategies/retry/reject_callback.py +0 -0
  120. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/subscriber.py +0 -0
  121. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/sync/__init__.py +0 -0
  122. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/sync/sync_event_bus.py +0 -0
  123. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/__init__.py +0 -0
  124. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/event_to_outbox_record_translator.py +0 -0
  125. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/fqn_to_event_mapper.py +0 -0
  126. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_criteria/__init__.py +0 -0
  127. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_criteria/deliverable_records_outbox_criteria_factory.py +0 -0
  128. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_criteria/outbox_criteria.py +0 -0
  129. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_criteria/outbox_criteria_factory.py +0 -0
  130. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_criteria/outbox_sorting_criteria.py +0 -0
  131. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record.py +0 -0
  132. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_finder/__init__.py +0 -0
  133. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_finder/outbox_record_stream_finder.py +0 -0
  134. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_finder/polling_outbox_record_stream_finder.py +0 -0
  135. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_to_event_translator.py +0 -0
  136. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_validation/__init__.py +0 -0
  137. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_validation/abstract_outbox_record_validator.py +0 -0
  138. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_validation/outbox_record_size_not_allowed_exception.py +0 -0
  139. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_validation/outbox_record_validation_exception.py +0 -0
  140. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_validation/outbox_record_validator.py +0 -0
  141. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_record_validation/size_outbox_record_validator.py +0 -0
  142. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/outbox_repository.py +0 -0
  143. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/transactional_outbox_event_bus.py +0 -0
  144. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/transactional_outbox/transactional_outbox_worker.py +0 -0
  145. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/event/worker.py +0 -0
  146. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/handler.py +0 -0
  147. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/__init__.py +0 -0
  148. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/exceptions/not_valid_kafka_message_exception.py +0 -0
  149. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/exceptions/topic_already_created_exception.py +0 -0
  150. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/auto_create_topic_configuration.py +0 -0
  151. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/consumer_initial_offset_position.py +0 -0
  152. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/create_kafka_topic.py +0 -0
  153. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_connection_config.py +0 -0
  154. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_connection_credentials.py +0 -0
  155. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_connection_plain_text_credentials.py +0 -0
  156. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_connection_sasl_credentials.py +0 -0
  157. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_consumer_record.py +0 -0
  158. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_poll_record.py +0 -0
  159. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_supported_sasl_mechanisms.py +0 -0
  160. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/models/kafka_supported_security_protocols.py +0 -0
  161. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/services/kafka_admin_test_client.py +0 -0
  162. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/domain/services/kafka_producer.py +0 -0
  163. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/__init__.py +0 -0
  164. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/aiokafka/__init__.py +0 -0
  165. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/aiokafka/rebalance/__init__.py +0 -0
  166. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/aiokafka/rebalance/kafka_callback_rebalancer.py +0 -0
  167. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/aiokafka/translators/__init__.py +0 -0
  168. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/aiokafka/translators/consumer_initial_offset_position_translator.py +0 -0
  169. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/__init__.py +0 -0
  170. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/byte_deserializer.py +0 -0
  171. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/bytes_to_message_deserializer.py +0 -0
  172. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/implementations/__init__.py +0 -0
  173. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/implementations/cdc/not_valid_cdc_message_exception.py +0 -0
  174. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/implementations/json_byte_deserializer.py +0 -0
  175. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/deserializers/implementations/json_bytes_to_message_deserializer.py +0 -0
  176. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/__init__.py +0 -0
  177. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/exception/consumer_interrupted_exception.py +0 -0
  178. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/factories/__init__.py +0 -0
  179. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/factories/kafka_python_producer_factory.py +0 -0
  180. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/kafka_python_producer.py +0 -0
  181. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/translators/__init__.py +0 -0
  182. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/kafka_python/translators/consumer_initial_offset_position_translator.py +0 -0
  183. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/serializers/byte_serializer.py +0 -0
  184. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/serializers/implementations/json_byte_serializer.py +0 -0
  185. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/kafka/infrastructure/serializers/kafka_header_serializer.py +0 -0
  186. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/__init__.py +0 -0
  187. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/handler_fqn_not_found_exception.py +0 -0
  188. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/locator.py +0 -0
  189. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/message_fqn_not_found_exception.py +0 -0
  190. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/pypendency/__init__.py +0 -0
  191. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/pypendency/container_locator.py +0 -0
  192. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/pypendency/container_locator_resolution_configuration.py +0 -0
  193. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/pypendency/handler_not_found_exception.py +0 -0
  194. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/pypendency/handler_not_registered_exception.py +0 -0
  195. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/sync/__init__.py +0 -0
  196. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/sync/handler_already_registered_exception.py +0 -0
  197. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/sync/handler_not_registered_exception.py +0 -0
  198. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/locator/sync/instance_locator.py +0 -0
  199. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/message.py +0 -0
  200. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/middleware/__init__.py +0 -0
  201. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/middleware/middleware.py +0 -0
  202. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/middleware/middleware_chain_builder.py +0 -0
  203. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/py.typed +0 -0
  204. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/__init__.py +0 -0
  205. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/__init__.py +0 -0
  206. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/base_query_handler.py +0 -0
  207. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/middleware/__init__.py +0 -0
  208. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/middleware/base_handle_middleware.py +0 -0
  209. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/middleware/handle_middleware.py +0 -0
  210. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/middleware/handle_middleware_chain_resolver.py +0 -0
  211. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/query_bus.py +0 -0
  212. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/query_handler.py +0 -0
  213. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/self_process/__init__.py +0 -0
  214. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/asynchronous/self_process/self_process_query_bus.py +0 -0
  215. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/more_than_one_query_handler_related_exception.py +0 -0
  216. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/query.py +0 -0
  217. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/query_response.py +0 -0
  218. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/__init__.py +0 -0
  219. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/base_query_handler.py +0 -0
  220. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/middleware/__init__.py +0 -0
  221. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/middleware/base_handle_middleware.py +0 -0
  222. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/middleware/handle_middleware.py +0 -0
  223. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/middleware/handle_middleware_chain_resolver.py +0 -0
  224. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/query_bus.py +0 -0
  225. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/query_handler.py +0 -0
  226. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/self_process/__init__.py +0 -0
  227. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/self_process/self_process_query_bus.py +0 -0
  228. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/synced_async/__init__.py +0 -0
  229. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/query/synchronous/synced_async/synced_async_query_bus.py +0 -0
  230. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/queue/__init__.py +0 -0
  231. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/queue/in_memory/in_memory_queue_repository.py +0 -0
  232. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/queue/multiqueue_repository.py +0 -0
  233. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/queue/queue_repository.py +0 -0
  234. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/serializer/message_to_bytes_serializer.py +0 -0
  235. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/serializer/message_to_json_bytes_serializer.py +0 -0
  236. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/wrapper/__init__.py +0 -0
  237. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/wrapper/async_to_sync.py +0 -0
  238. {buz-2.13.1rc7 → buz-2.13.1rc9}/src/buz/wrapper/event_loop.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: buz
3
- Version: 2.13.1rc7
3
+ Version: 2.13.1rc9
4
4
  Summary: Buz is a set of light, simple and extensible implementations of event, command and query buses.
5
5
  License: MIT
6
6
  Author: Luis Pintado Lozano
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "buz"
3
- version = "2.13.1rc7"
3
+ version = "2.13.1rc9"
4
4
  description = "Buz is a set of light, simple and extensible implementations of event, command and query buses."
5
5
  readme = "README.md"
6
6
  authors = ["Luis Pintado Lozano <luis.pintado.lozano@gmail.com>", "Gerardo Parra <gprauxiliar@gmail.com>"]
@@ -37,6 +37,7 @@ black = "^23.3"
37
37
  mypy = "^1.2"
38
38
  flake8 = "^5.0.4"
39
39
  tenacity = "^8.5.0"
40
+ freezegun = "^1.5.1"
40
41
  types-cachetools = "^5.5.0.20240820"
41
42
 
42
43
  [tool.poetry.extras]
@@ -1,5 +1,5 @@
1
- from abc import abstractmethod
2
1
  import traceback
2
+ from abc import abstractmethod
3
3
  from asyncio import Lock, Task, create_task, gather, Semaphore, Event as AsyncIOEvent, sleep
4
4
  from datetime import timedelta, datetime
5
5
  from itertools import cycle
@@ -28,7 +28,6 @@ from buz.kafka.infrastructure.aiokafka.aiokafka_consumer import AIOKafkaConsumer
28
28
  from buz.queue.in_memory.in_memory_multiqueue_repository import InMemoryMultiqueueRepository
29
29
  from buz.queue.multiqueue_repository import MultiqueueRepository
30
30
 
31
-
32
31
  T = TypeVar("T", bound=Event)
33
32
 
34
33
 
@@ -87,17 +86,14 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
87
86
  async def run(self) -> None:
88
87
  start_time = datetime.now()
89
88
  await self.__generate_kafka_consumers()
89
+ self.__initial_coroutines_created_elapsed_time = datetime.now() - start_time
90
90
 
91
91
  if len(self.__executor_per_consumer_mapper) == 0:
92
92
  self._logger.error("There are no valid subscribers to execute, finalizing consumer")
93
93
  return
94
94
 
95
- self.__initial_coroutines_created_elapsed_time = datetime.now() - start_time
96
-
97
95
  start_consumption_time = datetime.now()
98
-
99
96
  worker_errors = await self.__run_worker()
100
-
101
97
  self.__events_processed_elapsed_time = datetime.now() - start_consumption_time
102
98
 
103
99
  await self.__handle_graceful_stop(worker_errors)
@@ -112,9 +108,9 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
112
108
  if self.__exceptions_are_thrown(worker_errors):
113
109
  consume_events_exception, polling_task_exception = worker_errors
114
110
  if consume_events_exception:
115
- self._logger.error(consume_events_exception)
111
+ self._logger.exception(consume_events_exception)
116
112
  if polling_task_exception:
117
- self._logger.error(polling_task_exception)
113
+ self._logger.exception(polling_task_exception)
118
114
 
119
115
  raise WorkerExecutionException("The worker was closed by an unexpected exception")
120
116
 
@@ -227,41 +223,32 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
227
223
  if len(kafka_poll_records) == 0:
228
224
  await sleep(self.__seconds_between_polls_if_there_are_no_new_tasks)
229
225
 
230
- return
231
-
232
226
  async def __consume_events_task(self) -> None:
233
227
  self._logger.info("Initializing consuming task")
234
-
235
- blocked_tasks_iterator = self.generate_blocked_consuming_tasks_iterator()
228
+ blocked_tasks_iterator = self.__generate_blocked_consuming_tasks_iterator()
236
229
 
237
230
  async for consuming_task in blocked_tasks_iterator:
238
231
  consumer = consuming_task.consumer
239
232
  kafka_poll_record = consuming_task.kafka_poll_record
240
- executor = self.__executor_per_consumer_mapper[consuming_task.consumer]
241
233
 
234
+ executor = self.__executor_per_consumer_mapper[consumer]
242
235
  await executor.consume(kafka_poll_record=kafka_poll_record)
243
-
244
236
  await consumer.commit_poll_record(kafka_poll_record)
245
237
 
246
238
  self.__events_processed += 1
247
239
 
248
240
  # This iterator return a blocked task, that will be blocked for other process (like rebalancing), until the next task will be requested
249
- async def generate_blocked_consuming_tasks_iterator(self) -> AsyncIterator[ConsumingTask]:
241
+ async def __generate_blocked_consuming_tasks_iterator(self) -> AsyncIterator[ConsumingTask]:
250
242
  consumer_queues_cyclic_iterator = cycle(self.__queue_per_consumer_mapper.items())
251
243
  last_consumer, _ = next(consumer_queues_cyclic_iterator)
252
244
 
253
245
  while not self.__should_stop.is_set():
254
- all_queues_are_empty = all(
255
- [queue.is_totally_empty() for queue in self.__queue_per_consumer_mapper.values()]
256
- )
257
-
258
- if all_queues_are_empty:
246
+ if await self.__all_queues_are_empty():
259
247
  await sleep(self.__seconds_between_executions_if_there_are_no_tasks_in_the_queue)
260
248
  continue
261
249
 
262
250
  async with self.__task_execution_mutex:
263
251
  consumer: Optional[AIOKafkaConsumer] = None
264
- kafka_poll_record: Optional[KafkaPollRecord] = None
265
252
 
266
253
  while consumer != last_consumer:
267
254
  consumer, queue = next(consumer_queues_cyclic_iterator)
@@ -272,7 +259,8 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
272
259
  last_consumer = consumer
273
260
  break
274
261
 
275
- return
262
+ async def __all_queues_are_empty(self) -> bool:
263
+ return all([queue.is_totally_empty() for queue in self.__queue_per_consumer_mapper.values()])
276
264
 
277
265
  async def __on_partition_revoked(self, consumer: AIOKafkaConsumer, topics_partitions: set[TopicPartition]) -> None:
278
266
  async with self.__task_execution_mutex:
@@ -14,6 +14,7 @@ from buz.event.middleware.publish_middleware_chain_resolver import PublishMiddle
14
14
  from buz.kafka import (
15
15
  KafkaPythonProducer,
16
16
  )
17
+ from buz.kafka.domain.exceptions.topic_already_created_exception import KafkaTopicsAlreadyCreatedException
17
18
  from buz.kafka.domain.models.auto_create_topic_configuration import AutoCreateTopicConfiguration
18
19
  from buz.kafka.domain.models.create_kafka_topic import CreateKafkaTopic
19
20
  from buz.kafka.domain.services.kafka_admin_client import KafkaAdminClient
@@ -53,18 +54,22 @@ class BuzKafkaEventBus(EventBus):
53
54
  topic = self.__publish_strategy.get_topic(event)
54
55
 
55
56
  if self.__auto_create_topic_configuration is not None and self.__is_topic_created(topic) is False:
56
- self.__get_kafka_admin_client().create_topics(
57
- topics=[
58
- CreateKafkaTopic(
59
- name=topic,
60
- partitions=self.__auto_create_topic_configuration.partitions,
61
- replication_factor=self.__auto_create_topic_configuration.replication_factor,
62
- configs=self.__auto_create_topic_configuration.configs,
63
- )
64
- ]
65
- )
66
- self.__logger.info(f"Created missing topic: {topic}")
67
- self.__topics_checked[topic] = True
57
+ try:
58
+ self.__logger.info(f"Creating missing topic: {topic}..")
59
+ self.__get_kafka_admin_client().create_topics(
60
+ topics=[
61
+ CreateKafkaTopic(
62
+ name=topic,
63
+ partitions=self.__auto_create_topic_configuration.partitions,
64
+ replication_factor=self.__auto_create_topic_configuration.replication_factor,
65
+ configs=self.__auto_create_topic_configuration.configs,
66
+ )
67
+ ]
68
+ )
69
+ self.__logger.info(f"Created missing topic: {topic}")
70
+ self.__topics_checked[topic] = True
71
+ except KafkaTopicsAlreadyCreatedException:
72
+ pass
68
73
 
69
74
  headers = self.__get_event_headers(event)
70
75
  self.__producer.produce(
@@ -67,11 +67,6 @@ class KafkaEventSyncSubscriberExecutor(KafkaEventSubscriberExecutor):
67
67
  self.__logger.error(
68
68
  f'The message "{str(kafka_poll_record.value)}" is not valid, it will be consumed but not processed'
69
69
  )
70
- except Exception as exception:
71
- if self.__on_fail_strategy == KafkaOnFailStrategy.CONSUME_ON_FAIL:
72
- self.__logger.error(f"Error consuming event: {exception}")
73
- return
74
- raise exception
75
70
 
76
71
  def __execution_callback(self, subscriber: Subscriber, message: KafkaConsumerRecord[Event]) -> None:
77
72
  self.__consume_middleware_chain_resolver.resolve(
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class NotAllPartitionAssignedException(Exception):
5
+ def __init__(self, topic_name: str) -> None:
6
+ super().__init__(
7
+ f'Not all the partition were assigned for the topic "{topic_name}", please disconnect the rest of subscribers'
8
+ )
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class TopicNotFoundException(Exception):
5
+ def __init__(self, topic_name: str) -> None:
6
+ super().__init__(f'The topic "{topic_name}", has not been found')
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import abstractmethod, ABC
4
+ from datetime import datetime
4
5
  from typing import Sequence
5
6
 
6
7
  from buz.kafka.domain.models.create_kafka_topic import CreateKafkaTopic
@@ -37,3 +38,16 @@ class KafkaAdminClient(ABC):
37
38
  self,
38
39
  ) -> set[str]:
39
40
  pass
41
+
42
+ # This function moves the following offset from the provided date
43
+ # if there are no messages with a date greater than the provided offset
44
+ # the offset will be moved to the end
45
+ @abstractmethod
46
+ def move_offsets_to_datetime(
47
+ self,
48
+ *,
49
+ consumer_group: str,
50
+ topic: str,
51
+ target_datetime: datetime,
52
+ ) -> None:
53
+ pass
@@ -123,7 +123,7 @@ class AIOKafkaConsumer:
123
123
  try:
124
124
  self.__logger.info(f"Creating missing topics: {non_created_topics}...")
125
125
  kafka_admin_client.create_topics(topics=topics_to_create)
126
- self.__logger.info(f"Created missing topics: {non_created_topics}...")
126
+ self.__logger.info(f"Created missing topics: {non_created_topics}")
127
127
  except KafkaTopicsAlreadyCreatedException:
128
128
  # there is a possibility to have a race condition between the check and the creation
129
129
  # but it does not matters, the important part is that the topic is created
@@ -3,9 +3,11 @@ from dataclasses import dataclass
3
3
 
4
4
  @dataclass(frozen=True)
5
5
  class CDCPayload:
6
+ DATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
7
+
6
8
  payload: str # json encoded
7
9
  event_id: str # uuid
8
- created_at: str # date and hour ISO 8601
10
+ created_at: str
9
11
  event_fqn: str
10
12
 
11
13
  def validate(self) -> None:
@@ -1,16 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from datetime import datetime
3
4
  from typing import TypeVar, Type, Generic
4
5
 
5
6
  import orjson
6
7
  from dacite import from_dict
7
8
 
8
- from buz.kafka.infrastructure.deserializers.implementations.cdc.not_valid_cdc_message_exception import (
9
- NotValidCDCMessageException,
10
- )
11
9
  from buz.event import Event
12
10
  from buz.kafka.infrastructure.cdc.cdc_message import CDCMessage, CDCPayload
13
11
  from buz.kafka.infrastructure.deserializers.bytes_to_message_deserializer import BytesToMessageDeserializer
12
+ from buz.kafka.infrastructure.deserializers.implementations.cdc.not_valid_cdc_message_exception import (
13
+ NotValidCDCMessageException,
14
+ )
14
15
 
15
16
  T = TypeVar("T", bound=Event)
16
17
 
@@ -27,12 +28,16 @@ class CDCRecordBytesToEventDeserializer(BytesToMessageDeserializer[Event], Gener
27
28
  cdc_message = self.__get_outbox_record_as_dict(decoded_string)
28
29
  return self.__event_class.restore(
29
30
  id=cdc_message.payload.event_id,
30
- created_at=cdc_message.payload.created_at,
31
+ created_at=self.__get_created_at_in_event_format(cdc_message.payload.created_at),
31
32
  **orjson.loads(cdc_message.payload.payload),
32
33
  )
33
34
  except Exception as exception:
34
35
  raise NotValidCDCMessageException(decoded_string, exception) from exception
35
36
 
37
+ def __get_created_at_in_event_format(self, cdc_payload_created_at: str) -> str:
38
+ created_at_datetime = datetime.strptime(cdc_payload_created_at, CDCPayload.DATE_TIME_FORMAT)
39
+ return created_at_datetime.strftime(Event.DATE_TIME_FORMAT)
40
+
36
41
  def __get_outbox_record_as_dict(self, decoded_string: str) -> CDCMessage:
37
42
  decoded_record: dict = orjson.loads(decoded_string)
38
43
 
@@ -1,19 +1,30 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from datetime import datetime
4
+ from logging import Logger
3
5
  import re
4
- from typing import Any, Callable, Sequence
6
+ from typing import Any, Callable, Optional, Sequence, cast
5
7
 
6
8
  from cachetools import TTLCache
7
- from kafka import KafkaClient
9
+ from kafka import KafkaClient, KafkaConsumer
8
10
  from kafka.admin import KafkaAdminClient as KafkaPythonLibraryAdminClient, NewTopic
9
11
  from kafka.errors import TopicAlreadyExistsError
12
+ from kafka.structs import TopicPartition, OffsetAndTimestamp
10
13
 
14
+ from buz.kafka.domain.exceptions.not_all_partition_assigned_exception import NotAllPartitionAssignedException
11
15
  from buz.kafka.domain.exceptions.topic_already_created_exception import KafkaTopicsAlreadyCreatedException
16
+ from buz.kafka.domain.exceptions.topic_not_found_exception import TopicNotFoundException
17
+ from buz.kafka.domain.models.consumer_initial_offset_position import ConsumerInitialOffsetPosition
12
18
  from buz.kafka.domain.models.create_kafka_topic import CreateKafkaTopic
13
19
  from buz.kafka.domain.models.kafka_connection_config import KafkaConnectionConfig
14
20
  from buz.kafka.domain.services.kafka_admin_client import KafkaAdminClient
15
21
 
22
+ from buz.kafka.infrastructure.kafka_python.translators.consumer_initial_offset_position_translator import (
23
+ KafkaPythonConsumerInitialOffsetPositionTranslator,
24
+ )
25
+
16
26
  INTERNAL_KAFKA_TOPICS = {"__consumer_offsets", "_schema"}
27
+ TOPIC_CACHE_KEY = "topics"
17
28
 
18
29
 
19
30
  class KafkaPythonAdminClient(KafkaAdminClient):
@@ -22,9 +33,11 @@ class KafkaPythonAdminClient(KafkaAdminClient):
22
33
  def __init__(
23
34
  self,
24
35
  *,
36
+ logger: Logger,
25
37
  config: KafkaConnectionConfig,
26
38
  cache_ttl_seconds: int = 0,
27
39
  ):
40
+ self._logger = logger
28
41
  self._config = config
29
42
  self._config_in_library_format = self.__get_kafka_config_in_library_format(config)
30
43
  self._kafka_admin = KafkaPythonLibraryAdminClient(**self._config_in_library_format)
@@ -75,14 +88,13 @@ class KafkaPythonAdminClient(KafkaAdminClient):
75
88
  self,
76
89
  topic: str,
77
90
  ) -> bool:
78
- topics = self.get_topics()
79
- return topic in topics
91
+ return topic in self.get_topics()
80
92
 
81
93
  def get_topics(
82
94
  self,
83
95
  ) -> set[str]:
84
96
  return self.__resolve_cached_property(
85
- "topics", lambda: set(self._kafka_admin.list_topics()) - INTERNAL_KAFKA_TOPICS
97
+ TOPIC_CACHE_KEY, lambda: set(self._kafka_admin.list_topics()) - INTERNAL_KAFKA_TOPICS
86
98
  )
87
99
 
88
100
  def __resolve_cached_property(self, property_key: str, callback: Callable) -> Any:
@@ -101,6 +113,10 @@ class KafkaPythonAdminClient(KafkaAdminClient):
101
113
  self._kafka_admin.delete_topics(
102
114
  topics=topics,
103
115
  )
116
+ self.__remove_cache_property(TOPIC_CACHE_KEY)
117
+
118
+ def __remove_cache_property(self, property_key: str) -> None:
119
+ self.__ttl_cache.pop(property_key, None)
104
120
 
105
121
  def delete_subscription_groups(
106
122
  self,
@@ -119,3 +135,80 @@ class KafkaPythonAdminClient(KafkaAdminClient):
119
135
  def _wait_for_cluster_update(self) -> None:
120
136
  future = self._kafka_client.cluster.request_update()
121
137
  self._kafka_client.poll(future=future)
138
+
139
+ def move_offsets_to_datetime(
140
+ self,
141
+ *,
142
+ consumer_group: str,
143
+ topic: str,
144
+ target_datetime: datetime,
145
+ ) -> None:
146
+ consumer = KafkaConsumer(
147
+ group_id=consumer_group,
148
+ enable_auto_commit=False,
149
+ auto_offset_reset=KafkaPythonConsumerInitialOffsetPositionTranslator.to_kafka_supported_format(
150
+ ConsumerInitialOffsetPosition.BEGINNING
151
+ ),
152
+ **self._config_in_library_format,
153
+ )
154
+
155
+ partitions = consumer.partitions_for_topic(topic)
156
+
157
+ if partitions is None:
158
+ raise TopicNotFoundException(topic)
159
+
160
+ topic_partitions = [TopicPartition(topic, p) for p in partitions]
161
+ consumer.subscribe(topics=[topic])
162
+
163
+ self.__force_partition_assignment(consumer)
164
+
165
+ # We need all the partitions in order to update the offsets
166
+ if len(consumer.assignment()) != len(topic_partitions):
167
+ raise NotAllPartitionAssignedException(topic)
168
+
169
+ offsets_for_date = self.__get_first_offset_after_date(
170
+ consumer=consumer,
171
+ topic_partitions=topic_partitions,
172
+ target_datetime=target_datetime,
173
+ )
174
+
175
+ end_offsets = consumer.end_offsets(topic_partitions)
176
+
177
+ if end_offsets is None or len(end_offsets.keys()) != len(topic_partitions):
178
+ raise Exception(f'There was an error extracting the end offsets of the topic "{topic}"')
179
+
180
+ for topic_partition in topic_partitions:
181
+ offset_and_timestamp = offsets_for_date.get(topic_partition)
182
+ if offset_and_timestamp:
183
+ self._logger.info(f'moving "{topic_partition}" to the offset "{offset_and_timestamp.offset}"')
184
+ consumer.seek(topic_partition, offset_and_timestamp.offset)
185
+ else:
186
+ self._logger.info(
187
+ f'moving "{topic_partition}" to the end of the topic because there are no messages later than "{target_datetime}"'
188
+ )
189
+ consumer.seek(topic_partition, end_offsets[topic_partition])
190
+
191
+ consumer.commit()
192
+ consumer.close()
193
+
194
+ def __get_first_offset_after_date(
195
+ self,
196
+ *,
197
+ consumer: KafkaConsumer,
198
+ topic_partitions: Sequence[TopicPartition],
199
+ target_datetime: datetime,
200
+ ) -> dict[TopicPartition, Optional[OffsetAndTimestamp]]:
201
+ offset_for_times: dict[TopicPartition, Optional[int]] = {}
202
+ timestamp_ms = int(target_datetime.timestamp() * 1000)
203
+
204
+ for topic_partition in topic_partitions:
205
+ offset_for_times[topic_partition] = timestamp_ms
206
+
207
+ return cast(
208
+ dict[TopicPartition, Optional[OffsetAndTimestamp]],
209
+ consumer.offsets_for_times(offset_for_times),
210
+ )
211
+
212
+ # We are not to commit the new offset, but we need to execute a polling in order to start the partition assignment
213
+ def __force_partition_assignment(self, consumer: KafkaConsumer) -> None:
214
+ consumer.poll(max_records=1, timeout_ms=0)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from logging import Logger
3
4
  from typing import Optional
4
5
 
5
6
  from kafka import KafkaConsumer, KafkaProducer
@@ -22,8 +23,16 @@ CONSUMER_POLL_TIMEOUT_MS = 1000
22
23
 
23
24
 
24
25
  class KafkaPythonAdminTestClient(KafkaPythonAdminClient, KafkaAdminTestClient):
25
- def __init__(self, *, config: KafkaConnectionConfig):
26
- super().__init__(config=config)
26
+ def __init__(
27
+ self,
28
+ *,
29
+ logger: Logger,
30
+ config: KafkaConnectionConfig,
31
+ ):
32
+ super().__init__(
33
+ config=config,
34
+ logger=logger,
35
+ )
27
36
 
28
37
  def send_message_to_topic(
29
38
  self,
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import asdict
4
+ from datetime import datetime
4
5
 
5
6
  from buz.event import Event
6
7
  from buz.kafka.infrastructure.cdc.cdc_message import CDCMessage, CDCPayload
@@ -16,13 +17,17 @@ class CDCRecordBytesToEventSerializer(ByteSerializer):
16
17
  cdc_message: CDCMessage = CDCMessage(
17
18
  payload=CDCPayload(
18
19
  event_id=data.id,
19
- created_at=data.created_at,
20
+ created_at=self.__adapt_created_to_cdc_format(data.created_at),
20
21
  event_fqn=data.fqn(),
21
22
  payload=self.__serialize_payload(data),
22
23
  )
23
24
  )
24
25
  return self.__json_serializer.serialize(asdict(cdc_message))
25
26
 
27
+ def __adapt_created_to_cdc_format(self, created_at: str) -> str:
28
+ created_at_datetime = datetime.strptime(created_at, Event.DATE_TIME_FORMAT)
29
+ return created_at_datetime.strftime(CDCPayload.DATE_TIME_FORMAT)
30
+
26
31
  def __serialize_payload(self, event: Event) -> str:
27
32
  # Remove id and created at, because Transactional outbox is not adding them
28
33
  payload = asdict(event)
@@ -1,37 +1,20 @@
1
- from threading import Lock
2
1
  from queue import Queue, Empty
3
2
  from typing import Optional, TypeVar, cast
4
3
 
5
4
  from buz.queue.multiqueue_repository import MultiqueueRepository
6
5
 
7
-
8
6
  K = TypeVar("K")
9
7
  R = TypeVar("R")
10
8
 
11
9
 
12
- def self_mutex(method):
13
- def call(self, *args, **kwargs):
14
- lock: Lock = self._get_method_lock() # type: ignore
15
- with lock:
16
- return method(self, *args, **kwargs)
17
-
18
- return call
19
-
20
-
21
10
  class InMemoryMultiqueueRepository(MultiqueueRepository[K, R]):
22
11
  def __init__(self):
23
12
  self.__queues = cast(dict[K, Queue[R]], {})
24
- self.__mutex = Lock()
25
13
  self.__last_key_index = 0
26
14
 
27
- def _get_method_lock(self) -> Lock:
28
- return self.__mutex
29
-
30
- @self_mutex
31
15
  def clear(self, key: K) -> None:
32
16
  self.__queues.pop(key, None)
33
17
 
34
- @self_mutex
35
18
  def push(self, key: K, record: R) -> None:
36
19
  if key not in self.__queues:
37
20
  self.__add_key(key)
@@ -41,7 +24,6 @@ class InMemoryMultiqueueRepository(MultiqueueRepository[K, R]):
41
24
  def __add_key(self, key: K) -> None:
42
25
  self.__queues[key] = Queue[R]()
43
26
 
44
- @self_mutex
45
27
  def pop(self) -> Optional[R]:
46
28
  if not self.__queues:
47
29
  return None
@@ -65,10 +47,8 @@ class InMemoryMultiqueueRepository(MultiqueueRepository[K, R]):
65
47
 
66
48
  return None
67
49
 
68
- @self_mutex
69
50
  def get_total_size(self) -> int:
70
51
  return sum([queue.qsize() for queue in self.__queues.values()])
71
52
 
72
- @self_mutex
73
53
  def is_totally_empty(self) -> bool:
74
54
  return all([queue.empty() for queue in self.__queues.values()])
File without changes
File without changes
File without changes