buz 2.13.1rc6__py3-none-any.whl → 2.13.1rc7__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.
@@ -8,6 +8,7 @@ DlqRecordId = UUID
8
8
 
9
9
  @dataclass
10
10
  class DlqRecord:
11
+ __EXCEPTION_MESSAGE_MAX_CHARACTERS: ClassVar[int] = 300
11
12
  DATE_TIME_FORMAT: ClassVar[str] = "%Y-%m-%d %H:%M:%S.%f"
12
13
 
13
14
  id: DlqRecordId
@@ -18,6 +19,18 @@ class DlqRecord:
18
19
  exception_message: str
19
20
  last_failed_at: datetime
20
21
 
22
+ def __post_init__(self) -> None:
23
+ self.exception_message = self.__add_ellipsis(self.exception_message)
24
+
25
+ def __add_ellipsis(self, message: str) -> str:
26
+ if len(message) <= self.__EXCEPTION_MESSAGE_MAX_CHARACTERS:
27
+ return message
28
+ return message[: self.__EXCEPTION_MESSAGE_MAX_CHARACTERS - 3] + "..."
29
+
30
+ def set_exception(self, exception: Exception) -> None:
31
+ self.exception_type = type(exception).__name__
32
+ self.exception_message = self.__add_ellipsis(str(exception))
33
+
21
34
  def mark_as_failed(self) -> None:
22
35
  self.last_failed_at = datetime.now()
23
36
 
@@ -60,7 +60,6 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
60
60
  self._logger = logger
61
61
  self.__consumer_initial_offset_position = consumer_initial_offset_position
62
62
  self.__max_records_retrieved_per_poll = 1
63
- self.__subscriber_per_consumer_mapper: dict[AIOKafkaConsumer, MetaSubscriber] = {}
64
63
  self.__executor_per_consumer_mapper: dict[AIOKafkaConsumer, KafkaEventSubscriberExecutor] = {}
65
64
  self.__queue_per_consumer_mapper: dict[
66
65
  AIOKafkaConsumer, MultiqueueRepository[TopicPartition, KafkaPollRecord]
@@ -96,8 +95,9 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
96
95
  self.__initial_coroutines_created_elapsed_time = datetime.now() - start_time
97
96
 
98
97
  start_consumption_time = datetime.now()
99
- self._logger.info("Starting to consume events")
98
+
100
99
  worker_errors = await self.__run_worker()
100
+
101
101
  self.__events_processed_elapsed_time = datetime.now() - start_consumption_time
102
102
 
103
103
  await self.__handle_graceful_stop(worker_errors)
@@ -131,9 +131,9 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
131
131
  polling_task_exception = await self.__await_exception(polling_task)
132
132
  return (consume_events_exception, polling_task_exception)
133
133
 
134
- async def __await_exception(self, future: Task) -> Optional[Exception]:
134
+ async def __await_exception(self, task: Task) -> Optional[Exception]:
135
135
  try:
136
- await future
136
+ await task
137
137
  return None
138
138
  except Exception as exception:
139
139
  return exception
@@ -143,11 +143,11 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
143
143
 
144
144
  async def __generate_kafka_consumers(self):
145
145
  start_time = datetime.now()
146
- tasks = [self.__initialize_kafka_consumer_for_subscriber(subscriber) for subscriber in self.__subscribers]
146
+ tasks = [self.__generate_kafka_consumer_for_subscriber(subscriber) for subscriber in self.__subscribers]
147
147
  await gather(*tasks)
148
148
  self.__start_kafka_consumers_elapsed_time = datetime.now() - start_time
149
149
 
150
- async def __initialize_kafka_consumer_for_subscriber(self, subscriber: MetaSubscriber) -> None:
150
+ async def __generate_kafka_consumer_for_subscriber(self, subscriber: MetaSubscriber) -> None:
151
151
  try:
152
152
  executor = await self._create_kafka_consumer_executor(subscriber)
153
153
  topics = self.__consume_strategy.get_topics(subscriber)
@@ -167,73 +167,71 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
167
167
 
168
168
  self.__queue_per_consumer_mapper[kafka_consumer] = InMemoryMultiqueueRepository()
169
169
 
170
- # The purpose of this block is to initialize the consumer without delay the execution of the polling thread
171
- # If some of the subscribers haven't as many partitions as subscriber they will not be able to be initialized
172
- # (but maybe they should be initialized in the future)
173
- create_task(self.__init_consumer(kafka_consumer, subscriber))
174
170
  except Exception:
175
171
  self._logger.exception(
176
172
  f"Unexpected error during Kafka subscriber '{subscriber.fqn()}' creation. Skipping it: {traceback.format_exc()}"
177
173
  )
178
174
 
179
- async def __init_consumer(self, consumer: AIOKafkaConsumer, subscriber: MetaSubscriber) -> None:
175
+ @abstractmethod
176
+ async def _create_kafka_consumer_executor(self, subscriber: MetaSubscriber) -> KafkaEventSubscriberExecutor:
177
+ pass
178
+
179
+ async def __polling_task(self) -> None:
180
+ self._logger.info("Initializing subscribers")
181
+ try:
182
+ polling_task_per_consumer = [
183
+ create_task(self.__polling_consuming_tasks(consumer))
184
+ for consumer, subscriber in self.__queue_per_consumer_mapper.items()
185
+ ]
186
+
187
+ await gather(*polling_task_per_consumer)
188
+
189
+ except Exception:
190
+ self._logger.error(f"Polling task failed with exception: {traceback.format_exc()}")
191
+ self.__should_stop.set()
192
+
193
+ async def __polling_consuming_tasks(self, consumer: AIOKafkaConsumer) -> None:
194
+ queue = self.__queue_per_consumer_mapper[consumer]
195
+
180
196
  try:
197
+ self._logger.info(
198
+ f"initializing consumer group: '{consumer.get_consumer_group()}' subscribed to the topics: '{consumer.get_topics()}'"
199
+ )
181
200
  await consumer.init()
182
- self.__subscriber_per_consumer_mapper[consumer] = subscriber
183
- self._logger.info(f"initialized '{subscriber.fqn()}'")
201
+ self._logger.info(f"initialized '{consumer.get_consumer_group()}'")
184
202
  except Exception:
185
203
  self._logger.exception(
186
- f"Unexpected error during Kafka subscriber '{subscriber.fqn()}' initialization. Skipping it: {traceback.format_exc()}"
204
+ f"Unexpected error during Kafka subscriber '{consumer.get_consumer_group()}' initialization. Skipping it: {traceback.format_exc()}"
187
205
  )
188
206
 
189
- @abstractmethod
190
- async def _create_kafka_consumer_executor(self, subscriber: MetaSubscriber) -> KafkaEventSubscriberExecutor:
191
- pass
207
+ while not self.__should_stop.is_set():
208
+ total_size = sum([queue.get_total_size() for queue in self.__queue_per_consumer_mapper.values()])
209
+ if total_size >= self.__max_queue_size:
210
+ await sleep(self.__seconds_between_polls_if_there_are_tasks_in_the_queue)
211
+ continue
192
212
 
193
- async def __polling_task(self) -> None:
194
- try:
195
- while not self.__should_stop.is_set():
196
- total_size = sum([queue.get_total_size() for queue in self.__queue_per_consumer_mapper.values()])
197
- if total_size >= self.__max_queue_size:
198
- await sleep(self.__seconds_between_polls_if_there_are_tasks_in_the_queue)
199
- continue
200
-
201
- raw_consuming_tasks = await gather(
202
- *[
203
- self.__polling_consuming_tasks(kafka_consumer=consumer)
204
- for consumer, subscriber in self.__subscriber_per_consumer_mapper.items()
205
- ]
213
+ async with self.__polling_tasks_semaphore:
214
+ kafka_poll_records = await consumer.poll(
215
+ number_of_messages_to_poll=self.__max_records_retrieved_per_poll,
206
216
  )
207
217
 
208
- poll_results: list[ConsumingTask] = [
209
- consuming_task for consuming_tasks in raw_consuming_tasks for consuming_task in consuming_tasks
210
- ]
211
- if len(poll_results) == 0:
212
- await sleep(self.__seconds_between_polls_if_there_are_no_new_tasks)
213
-
214
- for poll_result in poll_results:
215
- queue = self.__queue_per_consumer_mapper[poll_result.consumer]
218
+ for kafka_poll_record in kafka_poll_records:
216
219
  queue.push(
217
220
  key=TopicPartition(
218
- topic=poll_result.kafka_poll_record.topic, partition=poll_result.kafka_poll_record.partition
221
+ topic=kafka_poll_record.topic,
222
+ partition=kafka_poll_record.partition,
219
223
  ),
220
- record=poll_result.kafka_poll_record,
224
+ record=kafka_poll_record,
221
225
  )
222
226
 
223
- except Exception:
224
- self._logger.error(f"Polling task failed with exception: {traceback.format_exc()}")
225
- self.__should_stop.set()
227
+ if len(kafka_poll_records) == 0:
228
+ await sleep(self.__seconds_between_polls_if_there_are_no_new_tasks)
226
229
 
227
230
  return
228
231
 
229
- async def __polling_consuming_tasks(self, kafka_consumer: AIOKafkaConsumer) -> list[ConsumingTask]:
230
- async with self.__polling_tasks_semaphore:
231
- results = await kafka_consumer.poll(
232
- number_of_messages_to_poll=self.__max_records_retrieved_per_poll,
233
- )
234
- return [ConsumingTask(kafka_consumer, result) for result in results]
235
-
236
232
  async def __consume_events_task(self) -> None:
233
+ self._logger.info("Initializing consuming task")
234
+
237
235
  blocked_tasks_iterator = self.generate_blocked_consuming_tasks_iterator()
238
236
 
239
237
  async for consuming_task in blocked_tasks_iterator:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from logging import Logger
4
4
  from ssl import SSLContext
5
- from typing import Awaitable, Callable, Optional, cast
5
+ from typing import Awaitable, Callable, Optional, Sequence, cast
6
6
 
7
7
  from aiokafka import AIOKafkaConsumer as AIOKafkaNativeConsumer, TopicPartition, OffsetAndMetadata
8
8
  from aiokafka.helpers import create_ssl_context
@@ -56,6 +56,12 @@ class AIOKafkaConsumer:
56
56
  self.__check_kafka_admin_client_is_needed()
57
57
  self.__consumer = self.__generate_consumer()
58
58
 
59
+ def get_topics(self) -> Sequence[str]:
60
+ return list(self.__topics)
61
+
62
+ def get_consumer_group(self) -> str:
63
+ return self.__consumer_group
64
+
59
65
  def __check_kafka_admin_client_is_needed(self) -> None:
60
66
  if self.__kafka_admin_client is None and self.__auto_create_topic_configuration is not None:
61
67
  raise KafkaEventBusConfigNotValidException(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: buz
3
- Version: 2.13.1rc6
3
+ Version: 2.13.1rc7
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
@@ -33,7 +33,7 @@ buz/event/base_subscriber.py,sha256=nQ3fXdpFmU0S0Vpg-0ozH6_IG5zFe1B-Lsn4CNdfsds,
33
33
  buz/event/consumer.py,sha256=Rf3ZtodM_637ifk5Y2CmEVQ7caYhBgXQ9gm7D57raP0,181
34
34
  buz/event/dead_letter_queue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  buz/event/dead_letter_queue/dlq_criteria.py,sha256=hxcV-BMayKTEc5suEfQZhEYkc14H7kZo_4YfDzcJTTY,290
36
- buz/event/dead_letter_queue/dlq_record.py,sha256=Puwb1wB0y4b0bJwi5PcjbMoWb0snAqDhmxNvWfRa4Mc,757
36
+ buz/event/dead_letter_queue/dlq_record.py,sha256=wEa9CdWkHmxHQVwoHFjWeEU6sjNOi7X8dLr1E-gVmDc,1341
37
37
  buz/event/dead_letter_queue/dlq_repository.py,sha256=8XsXSfO2OzEq4qfQ_v0E0OExintDYI1g55Qu3PtoxKI,630
38
38
  buz/event/event.py,sha256=x1MCBydn3qk3AkvamsAwCG-nfxR9OyP4l1UNXtnhUwU,189
39
39
  buz/event/event_bus.py,sha256=DNr1cRLxYcn9qCu4_BKecpQHAx9D_PTxLnWXN2qVhFE,293
@@ -45,7 +45,7 @@ buz/event/exceptions/term_signal_interruption_exception.py,sha256=RkRRF0v_K9Hg48
45
45
  buz/event/exceptions/worker_execution_exception.py,sha256=6mgztvXOCG_9VZ_Jptkk72kZtNWQ2CPuQ3TjXEWFE14,123
46
46
  buz/event/infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  buz/event/infrastructure/buz_kafka/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- buz/event/infrastructure/buz_kafka/base_buz_aiokafka_async_consumer.py,sha256=75I_wUUuQXAneEKYGjnboRHWwSYJ4UADSpcG-gnClSo,14641
48
+ buz/event/infrastructure/buz_kafka/base_buz_aiokafka_async_consumer.py,sha256=6V54oD8dpdLyrBel98qlvt8ZMOCnukxfL-7aRreDaPI,13915
49
49
  buz/event/infrastructure/buz_kafka/buz_aiokafka_async_consumer.py,sha256=dqQDv7taAmINE9G2geMDExbcvSlntP09_rQ0JRbc4Rw,5507
50
50
  buz/event/infrastructure/buz_kafka/buz_aiokafka_multi_threaded_consumer.py,sha256=yrEU51OBjvLjCfYJFJPxux1bcIhoTVMw1Jf0HJMWbb0,5449
51
51
  buz/event/infrastructure/buz_kafka/buz_kafka_event_bus.py,sha256=f94fmS4AVfb3LQsp49e-4Cqzj00IqxHDzuUvDbN4u2s,4258
@@ -149,7 +149,7 @@ buz/kafka/domain/services/kafka_admin_test_client.py,sha256=91l_vFIo1yhJLQQCC_Om
149
149
  buz/kafka/domain/services/kafka_producer.py,sha256=CTiwGYwuzdJY5aeb2WFbJlyCpZ0YyhzcgKQYyogKzUM,401
150
150
  buz/kafka/infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
151
  buz/kafka/infrastructure/aiokafka/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
152
- buz/kafka/infrastructure/aiokafka/aiokafka_consumer.py,sha256=zGalbfVPEr-08Uv0weSnkKayr2T76F8luO-UhfMNgx4,8560
152
+ buz/kafka/infrastructure/aiokafka/aiokafka_consumer.py,sha256=ringqMhgtVzStx_k8Y2os1nlOIKmAx5XwQroTqMQbmg,8728
153
153
  buz/kafka/infrastructure/aiokafka/rebalance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
154
154
  buz/kafka/infrastructure/aiokafka/rebalance/kafka_callback_rebalancer.py,sha256=3l7NkTrCt3rBktVIS73cTmCOvv6eFguoCbGMYIUfCFc,1774
155
155
  buz/kafka/infrastructure/aiokafka/translators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -230,7 +230,7 @@ buz/serializer/message_to_json_bytes_serializer.py,sha256=RGZJ64t4t4Pz2FCASZZCv-
230
230
  buz/wrapper/__init__.py,sha256=GnRdJFcncn-qp0hzDG9dBHLmTJSbHFVjE_yr-MdW_n4,77
231
231
  buz/wrapper/async_to_sync.py,sha256=OfK-vrVUhuN-LLLvekLdMbQYtH0ue5lfbvuasj6ovMI,698
232
232
  buz/wrapper/event_loop.py,sha256=pfBJ1g-8A2a3YgW8Gf9Fg0kkewoh3-wgTy2KIFDyfHk,266
233
- buz-2.13.1rc6.dist-info/LICENSE,sha256=Jytu2S-2SPEgsB0y6BF-_LUxIWY7402fl0JSh36TLZE,1062
234
- buz-2.13.1rc6.dist-info/METADATA,sha256=bE9USoD5Gh7NRbTZIClXx8Zvqi482Z6Y_AER8JhHeMM,1620
235
- buz-2.13.1rc6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
236
- buz-2.13.1rc6.dist-info/RECORD,,
233
+ buz-2.13.1rc7.dist-info/LICENSE,sha256=Jytu2S-2SPEgsB0y6BF-_LUxIWY7402fl0JSh36TLZE,1062
234
+ buz-2.13.1rc7.dist-info/METADATA,sha256=P_PoCYhT33nqbij8OAWpKZ0bE7cBL6ensd1sMRfzdoQ,1620
235
+ buz-2.13.1rc7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
236
+ buz-2.13.1rc7.dist-info/RECORD,,