sentry-arroyo 2.19.8__tar.gz → 2.19.10__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 (92) hide show
  1. {sentry_arroyo-2.19.8/sentry_arroyo.egg-info → sentry_arroyo-2.19.10}/PKG-INFO +12 -3
  2. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/kafka/consumer.py +3 -3
  3. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/local/backend.py +3 -3
  4. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/dlq.py +14 -4
  5. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/processor.py +5 -4
  6. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/filter.py +3 -3
  7. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/run_task_with_multiprocessing.py +3 -3
  8. sentry_arroyo-2.19.10/requirements.txt +1 -0
  9. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10/sentry_arroyo.egg-info}/PKG-INFO +12 -3
  10. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/sentry_arroyo.egg-info/SOURCES.txt +1 -0
  11. sentry_arroyo-2.19.10/sentry_arroyo.egg-info/requires.txt +1 -0
  12. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/setup.py +1 -1
  13. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/backends/mixins.py +25 -15
  14. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/backends/test_kafka.py +4 -1
  15. sentry_arroyo-2.19.10/tests/test_kip848_e2e.py +114 -0
  16. sentry_arroyo-2.19.8/requirements.txt +0 -1
  17. sentry_arroyo-2.19.8/sentry_arroyo.egg-info/requires.txt +0 -1
  18. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/LICENSE +0 -0
  19. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/MANIFEST.in +0 -0
  20. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/README.md +0 -0
  21. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/__init__.py +0 -0
  22. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/__init__.py +0 -0
  23. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/abstract.py +0 -0
  24. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/kafka/__init__.py +0 -0
  25. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/kafka/commit.py +0 -0
  26. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/kafka/configuration.py +0 -0
  27. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/local/__init__.py +0 -0
  28. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/local/storages/__init__.py +0 -0
  29. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/local/storages/abstract.py +0 -0
  30. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/backends/local/storages/memory.py +0 -0
  31. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/commit.py +0 -0
  32. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/errors.py +0 -0
  33. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/__init__.py +0 -0
  34. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/__init__.py +0 -0
  35. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/abstract.py +0 -0
  36. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/batching.py +0 -0
  37. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/buffer.py +0 -0
  38. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/commit.py +0 -0
  39. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/guard.py +0 -0
  40. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/healthcheck.py +0 -0
  41. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/noop.py +0 -0
  42. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/produce.py +0 -0
  43. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/reduce.py +0 -0
  44. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/run_task.py +0 -0
  45. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/run_task_in_threads.py +0 -0
  46. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/processing/strategies/unfold.py +0 -0
  47. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/py.typed +0 -0
  48. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/types.py +0 -0
  49. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/__init__.py +0 -0
  50. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/clock.py +0 -0
  51. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/codecs.py +0 -0
  52. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/concurrent.py +0 -0
  53. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/logging.py +0 -0
  54. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/metricDefs.json +0 -0
  55. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/metric_defs.py +0 -0
  56. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/metrics.py +0 -0
  57. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/profiler.py +0 -0
  58. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/arroyo/utils/retries.py +0 -0
  59. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/examples/transform_and_produce/__init__.py +0 -0
  60. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/examples/transform_and_produce/batched.py +0 -0
  61. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/examples/transform_and_produce/script.py +0 -0
  62. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/examples/transform_and_produce/simple.py +0 -0
  63. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/sentry_arroyo.egg-info/dependency_links.txt +0 -0
  64. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/sentry_arroyo.egg-info/not-zip-safe +0 -0
  65. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/sentry_arroyo.egg-info/top_level.txt +0 -0
  66. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/setup.cfg +0 -0
  67. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/backends/__init__.py +0 -0
  68. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/backends/test_commit.py +0 -0
  69. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/backends/test_local.py +0 -0
  70. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/__init__.py +0 -0
  71. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/__init__.py +0 -0
  72. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_all.py +0 -0
  73. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_batching.py +0 -0
  74. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_buffer.py +0 -0
  75. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_commit.py +0 -0
  76. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_filter.py +0 -0
  77. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_guard.py +0 -0
  78. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_noop.py +0 -0
  79. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_produce.py +0 -0
  80. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_reduce.py +0 -0
  81. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_run_task.py +0 -0
  82. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_run_task_in_threads.py +0 -0
  83. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_run_task_with_multiprocessing.py +0 -0
  84. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/strategies/test_unfold.py +0 -0
  85. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/processing/test_processor.py +0 -0
  86. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/test_commit.py +0 -0
  87. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/test_dlq.py +0 -0
  88. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/test_types.py +0 -0
  89. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/utils/__init__.py +0 -0
  90. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/utils/test_concurrent.py +0 -0
  91. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/utils/test_metrics.py +0 -0
  92. {sentry_arroyo-2.19.8 → sentry_arroyo-2.19.10}/tests/utils/test_retries.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: sentry-arroyo
3
- Version: 2.19.8
3
+ Version: 2.19.10
4
4
  Summary: Arroyo is a Python library for working with streaming data.
5
5
  Home-page: https://github.com/getsentry/arroyo
6
6
  Author: Sentry
@@ -13,7 +13,16 @@ Classifier: Programming Language :: Python
13
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: confluent-kafka>=2.3.0
16
+ Requires-Dist: confluent-kafka>=2.7.0
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: classifier
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: license
24
+ Dynamic: requires-dist
25
+ Dynamic: summary
17
26
 
18
27
  # Arroyo
19
28
 
@@ -157,13 +157,13 @@ class KafkaConsumer(Consumer[KafkaPayload]):
157
157
  KafkaError.REQUEST_TIMED_OUT,
158
158
  KafkaError.NOT_COORDINATOR,
159
159
  KafkaError._WAIT_COORD,
160
+ KafkaError.STALE_MEMBER_EPOCH, # kip-848
160
161
  ),
161
162
  )
162
163
 
163
164
  configuration = dict(configuration)
164
- self.__is_incremental = (
165
+ self.__is_cooperative_sticky = (
165
166
  configuration.get("partition.assignment.strategy") == "cooperative-sticky"
166
- or configuration.get("group.protocol") == "consumer"
167
167
  )
168
168
  auto_offset_reset = configuration.get("auto.offset.reset", "largest")
169
169
 
@@ -463,7 +463,7 @@ class KafkaConsumer(Consumer[KafkaPayload]):
463
463
  ConfluentTopicPartition(partition.topic.name, partition.index, offset)
464
464
  for partition, offset in offsets.items()
465
465
  ]
466
- if self.__is_incremental:
466
+ if self.__is_cooperative_sticky:
467
467
  self.__consumer.incremental_assign(partitions)
468
468
  else:
469
469
  self.__consumer.assign(partitions)
@@ -38,9 +38,9 @@ class LocalBroker(Generic[TStrategyPayload]):
38
38
  self.__message_storage = message_storage
39
39
  self.__clock = clock
40
40
 
41
- self.__offsets: MutableMapping[
42
- str, MutableMapping[Partition, int]
43
- ] = defaultdict(dict)
41
+ self.__offsets: MutableMapping[str, MutableMapping[Partition, int]] = (
42
+ defaultdict(dict)
43
+ )
44
44
 
45
45
  # The active subscriptions are stored by consumer group as a mapping
46
46
  # between the consumer and it's subscribed topics.
@@ -44,12 +44,18 @@ class InvalidMessage(Exception):
44
44
  """
45
45
 
46
46
  def __init__(
47
- self, partition: Partition, offset: int, needs_commit: bool = True, reason: Optional[str] = None,
47
+ self,
48
+ partition: Partition,
49
+ offset: int,
50
+ needs_commit: bool = True,
51
+ reason: Optional[str] = None,
52
+ log_exception: bool = True,
48
53
  ) -> None:
49
54
  self.partition = partition
50
55
  self.offset = offset
51
56
  self.needs_commit = needs_commit
52
57
  self.reason = reason
58
+ self.log_exception = log_exception
53
59
 
54
60
  @classmethod
55
61
  def from_value(cls, value: BrokerValue[Any]) -> InvalidMessage:
@@ -202,7 +208,9 @@ class NoopDlqProducer(DlqProducer[Any]):
202
208
  """
203
209
 
204
210
  def produce(
205
- self, value: BrokerValue[KafkaPayload], reason: Optional[str] = None,
211
+ self,
212
+ value: BrokerValue[KafkaPayload],
213
+ reason: Optional[str] = None,
206
214
  ) -> Future[BrokerValue[KafkaPayload]]:
207
215
  future: Future[BrokerValue[KafkaPayload]] = Future()
208
216
  future.set_running_or_notify_cancel()
@@ -334,7 +342,7 @@ class DlqPolicyWrapper(Generic[TStrategyPayload]):
334
342
  self,
335
343
  policy: DlqPolicy[TStrategyPayload],
336
344
  ) -> None:
337
- self.MAX_PENDING_FUTURES = 1000 # This is a per partition max
345
+ self.MAX_PENDING_FUTURES = 2000 # This is a per partition max
338
346
  self.__dlq_policy = policy
339
347
 
340
348
  self.__futures: MutableMapping[
@@ -356,7 +364,9 @@ class DlqPolicyWrapper(Generic[TStrategyPayload]):
356
364
  self.__dlq_policy.limit, assignment
357
365
  )
358
366
 
359
- def produce(self, message: BrokerValue[TStrategyPayload], reason: Optional[str] = None) -> None:
367
+ def produce(
368
+ self, message: BrokerValue[TStrategyPayload], reason: Optional[str] = None
369
+ ) -> None:
360
370
  """
361
371
  Removes all completed futures, then appends the given future to the list.
362
372
  Blocks if the list is full. If the DLQ limit is exceeded, an exception is raised.
@@ -143,9 +143,9 @@ class StreamProcessor(Generic[TStrategyPayload]):
143
143
  self.__processor_factory = processor_factory
144
144
  self.__metrics_buffer = MetricsBuffer()
145
145
 
146
- self.__processing_strategy: Optional[
147
- ProcessingStrategy[TStrategyPayload]
148
- ] = None
146
+ self.__processing_strategy: Optional[ProcessingStrategy[TStrategyPayload]] = (
147
+ None
148
+ )
149
149
 
150
150
  self.__message: Optional[BrokerValue[TStrategyPayload]] = None
151
151
 
@@ -365,7 +365,8 @@ class StreamProcessor(Generic[TStrategyPayload]):
365
365
  ):
366
366
  self.__message = None
367
367
 
368
- logger.exception(exc)
368
+ if exc.log_exception:
369
+ logger.exception(exc)
369
370
  self.__metrics_buffer.incr_counter("arroyo.consumer.invalid_message.count", 1)
370
371
  if self.__dlq_policy:
371
372
  start_dlq = time.time()
@@ -55,9 +55,9 @@ class FilterStep(ProcessingStrategy[Union[FilteredPayload, TStrategyPayload]]):
55
55
  self.__next_step = next_step
56
56
 
57
57
  if commit_policy is not None:
58
- self.__commit_policy_state: Optional[
59
- CommitPolicyState
60
- ] = commit_policy.get_state_machine()
58
+ self.__commit_policy_state: Optional[CommitPolicyState] = (
59
+ commit_policy.get_state_machine()
60
+ )
61
61
  else:
62
62
  self.__commit_policy_state = None
63
63
 
@@ -625,9 +625,9 @@ class RunTaskWithMultiprocessing(
625
625
  while self.__processes:
626
626
  try:
627
627
  self.__check_for_results_impl(
628
- timeout=max(deadline - time.time(), 0)
629
- if deadline is not None
630
- else None
628
+ timeout=(
629
+ max(deadline - time.time(), 0) if deadline is not None else None
630
+ )
631
631
  )
632
632
  except NextStepTimeoutError:
633
633
  if deadline is None or deadline > time.time():
@@ -0,0 +1 @@
1
+ confluent-kafka>=2.7.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: sentry-arroyo
3
- Version: 2.19.8
3
+ Version: 2.19.10
4
4
  Summary: Arroyo is a Python library for working with streaming data.
5
5
  Home-page: https://github.com/getsentry/arroyo
6
6
  Author: Sentry
@@ -13,7 +13,16 @@ Classifier: Programming Language :: Python
13
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: confluent-kafka>=2.3.0
16
+ Requires-Dist: confluent-kafka>=2.7.0
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: classifier
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: license
24
+ Dynamic: requires-dist
25
+ Dynamic: summary
17
26
 
18
27
  # Arroyo
19
28
 
@@ -59,6 +59,7 @@ sentry_arroyo.egg-info/requires.txt
59
59
  sentry_arroyo.egg-info/top_level.txt
60
60
  tests/test_commit.py
61
61
  tests/test_dlq.py
62
+ tests/test_kip848_e2e.py
62
63
  tests/test_types.py
63
64
  tests/backends/__init__.py
64
65
  tests/backends/mixins.py
@@ -0,0 +1 @@
1
+ confluent-kafka>=2.7.0
@@ -10,7 +10,7 @@ def get_requirements() -> Sequence[str]:
10
10
 
11
11
  setup(
12
12
  name="sentry-arroyo",
13
- version="2.19.8",
13
+ version="2.19.10",
14
14
  author="Sentry",
15
15
  author_email="oss@sentry.io",
16
16
  license="Apache-2.0",
@@ -15,6 +15,7 @@ from tests.assertions import assert_changes, assert_does_not_change
15
15
 
16
16
  class StreamsTestMixin(ABC, Generic[TStrategyPayload]):
17
17
  cooperative_sticky = False
18
+ kip_848 = False
18
19
 
19
20
  @abstractmethod
20
21
  def get_topic(self, partitions: int = 1) -> ContextManager[Topic]:
@@ -421,7 +422,7 @@ class StreamsTestMixin(ABC, Generic[TStrategyPayload]):
421
422
  def wait_until_rebalancing(
422
423
  from_consumer: Consumer[Any], to_consumer: Consumer[Any]
423
424
  ) -> None:
424
- for _ in range(10):
425
+ for _ in range(20):
425
426
  assert from_consumer.poll(0) is None
426
427
  if to_consumer.poll(1.0) is not None:
427
428
  return
@@ -453,9 +454,10 @@ class StreamsTestMixin(ABC, Generic[TStrategyPayload]):
453
454
 
454
455
  wait_until_rebalancing(consumer_a, consumer_b)
455
456
 
456
- if self.cooperative_sticky:
457
+ if self.cooperative_sticky or self.kip_848:
457
458
  # within incremental rebalancing, only one partition should have been reassigned to the consumer_b, and consumer_a should remain paused
458
- assert consumer_a.paused() == [Partition(topic, 1)]
459
+ # Either partition 0 or 1 might be the paused one
460
+ assert len(consumer_a.paused()) == 1
459
461
  assert consumer_a.poll(10.0) is None
460
462
  else:
461
463
  # The first consumer should have had its offsets rolled back, as
@@ -481,20 +483,28 @@ class StreamsTestMixin(ABC, Generic[TStrategyPayload]):
481
483
 
482
484
  assert len(consumer_b.tell()) == 2
483
485
 
484
- if self.cooperative_sticky:
486
+ if self.cooperative_sticky or self.kip_848:
487
+ consumer_a_on_assign.assert_has_calls(
488
+ [
489
+ mock.call({Partition(topic, 0): 0, Partition(topic, 1): 0}),
490
+ ]
491
+ )
485
492
 
486
- assert consumer_a_on_assign.mock_calls == [
487
- mock.call({Partition(topic, 0): 0, Partition(topic, 1): 0}),
488
- ]
489
- assert consumer_a_on_revoke.mock_calls == [
490
- mock.call([Partition(topic, 0)]),
491
- mock.call([Partition(topic, 1)]),
492
- ]
493
+ consumer_a_on_revoke.assert_has_calls(
494
+ [
495
+ mock.call([Partition(topic, 0)]),
496
+ mock.call([Partition(topic, 1)]),
497
+ ],
498
+ any_order=True,
499
+ )
493
500
 
494
- assert consumer_b_on_assign.mock_calls == [
495
- mock.call({Partition(topic, 0): 0}),
496
- mock.call({Partition(topic, 1): 0}),
497
- ]
501
+ consumer_b_on_assign.assert_has_calls(
502
+ [
503
+ mock.call({Partition(topic, 0): 0}),
504
+ mock.call({Partition(topic, 1): 0}),
505
+ ],
506
+ any_order=True,
507
+ )
498
508
  assert consumer_b_on_revoke.mock_calls == []
499
509
  else:
500
510
  assert consumer_a_on_assign.mock_calls == [
@@ -271,10 +271,13 @@ class TestKafkaStreamsIncrementalRebalancing(TestKafkaStreams):
271
271
  cooperative_sticky = True
272
272
 
273
273
 
274
- @pytest.mark.skip("kip-848 not functional yet")
275
274
  class TestKafkaStreamsKip848(TestKafkaStreams):
276
275
  kip_848 = True
277
276
 
277
+ @pytest.mark.xfail(reason="To be fixed")
278
+ def test_pause_resume_rebalancing(self) -> None:
279
+ super().test_pause_resume_rebalancing()
280
+
278
281
 
279
282
  def test_commit_codec() -> None:
280
283
  commit = Commit(
@@ -0,0 +1,114 @@
1
+ from typing import Any
2
+
3
+ import time
4
+ import contextlib
5
+ from contextlib import closing
6
+ import os
7
+ import threading
8
+ import logging
9
+ from typing import Iterator, Mapping
10
+
11
+ from confluent_kafka.admin import AdminClient, NewTopic
12
+ from arroyo.types import Commit, Message, Partition, Topic
13
+ from arroyo.backends.kafka.configuration import build_kafka_consumer_configuration
14
+ from arroyo.backends.kafka.consumer import KafkaConsumer, KafkaPayload
15
+ from arroyo.processing.strategies import RunTask, CommitOffsets, ProcessingStrategy
16
+ from arroyo.processing.strategies.abstract import ProcessingStrategyFactory
17
+ from arroyo.processing.processor import StreamProcessor
18
+ from arroyo.backends.kafka import KafkaProducer
19
+
20
+ logging.basicConfig(level=logging.INFO)
21
+
22
+ TOPIC = "test-kip848"
23
+
24
+
25
+ @contextlib.contextmanager
26
+ def get_topic(
27
+ configuration: Mapping[str, Any], partitions_count: int
28
+ ) -> Iterator[Topic]:
29
+ name = TOPIC
30
+ configuration = dict(configuration)
31
+ client = AdminClient(configuration)
32
+ [[key, future]] = client.create_topics(
33
+ [NewTopic(name, num_partitions=partitions_count, replication_factor=1)]
34
+ ).items()
35
+ assert key == name
36
+ assert future.result() is None
37
+ try:
38
+ yield Topic(name)
39
+ finally:
40
+ [[key, future]] = client.delete_topics([name]).items()
41
+ assert key == name
42
+ assert future.result() is None
43
+
44
+
45
+ def test_kip848_e2e() -> None:
46
+ counter = 0
47
+
48
+ def print_msg(message: Message[Any]) -> Message[Any]:
49
+ nonlocal counter
50
+ ((partition, offset),) = message.committable.items()
51
+ print(f"message: {partition.index}-{offset}")
52
+ counter += 1
53
+ return message
54
+
55
+ class Strat(RunTask[Any, Any]):
56
+ def join(self, *args: Any, **kwargs: Any) -> None:
57
+ print("joining strategy, sleeping 5 seconds")
58
+ time.sleep(5)
59
+ print("joining strategy, sleeping 5 seconds -- DONE")
60
+ return super().join(*args, **kwargs)
61
+
62
+ class Factory(ProcessingStrategyFactory[KafkaPayload]):
63
+ def create_with_partitions(
64
+ self, commit: Commit, partitions: Mapping[Partition, int]
65
+ ) -> ProcessingStrategy[KafkaPayload]:
66
+ print("assign: ", [p.index for p in partitions])
67
+ return Strat(print_msg, CommitOffsets(commit))
68
+
69
+ default_config = {
70
+ "bootstrap.servers": os.environ.get("DEFAULT_BROKERS", "localhost:9092")
71
+ }
72
+
73
+ with get_topic(default_config, 2) as topic:
74
+ producer = KafkaProducer(default_config)
75
+
76
+ with closing(producer):
77
+ for i in range(30):
78
+ message = KafkaPayload(None, i.to_bytes(1, "big"), [])
79
+ producer.produce(topic, message).result()
80
+
81
+ consumer_config = build_kafka_consumer_configuration(
82
+ default_config,
83
+ group_id="kip848",
84
+ )
85
+
86
+ consumer_config["group.protocol"] = "consumer"
87
+ consumer_config.pop("session.timeout.ms", None)
88
+ consumer_config.pop("max.poll.interval.ms", None)
89
+ consumer_config.pop("partition.assignment.strategy", None)
90
+ consumer_config.pop("group.protocol.type", None)
91
+ consumer_config.pop("heartbeat.interval.ms", None)
92
+
93
+ consumer = KafkaConsumer(consumer_config)
94
+
95
+ processor = StreamProcessor(
96
+ consumer=consumer, topic=Topic(TOPIC), processor_factory=Factory()
97
+ )
98
+
99
+ def shutdown() -> None:
100
+ for i in range(100):
101
+ time.sleep(0.1)
102
+ if counter == 30:
103
+ break
104
+ print("shutting down")
105
+ processor.signal_shutdown()
106
+
107
+ t = threading.Thread(target=shutdown)
108
+ t.start()
109
+
110
+ processor.run()
111
+
112
+ assert counter == 30
113
+
114
+ t.join()
@@ -1 +0,0 @@
1
- confluent-kafka>=2.3.0
@@ -1 +0,0 @@
1
- confluent-kafka>=2.3.0
File without changes