kafka-python 2.2.10__tar.gz → 2.2.11__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 (168) hide show
  1. {kafka_python-2.2.10 → kafka_python-2.2.11}/CHANGES.md +8 -0
  2. {kafka_python-2.2.10 → kafka_python-2.2.11}/PKG-INFO +1 -1
  3. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/cluster.py +9 -8
  4. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/consumer/subscription_state.py +34 -1
  5. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/producer/kafka.py +3 -1
  6. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/msk.py +14 -3
  7. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/util.py +9 -0
  8. kafka_python-2.2.11/kafka/version.py +1 -0
  9. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka_python.egg-info/PKG-INFO +1 -1
  10. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_cluster.py +15 -0
  11. kafka_python-2.2.10/kafka/version.py +0 -1
  12. {kafka_python-2.2.10 → kafka_python-2.2.11}/AUTHORS.md +0 -0
  13. {kafka_python-2.2.10 → kafka_python-2.2.11}/LICENSE +0 -0
  14. {kafka_python-2.2.10 → kafka_python-2.2.11}/MANIFEST.in +0 -0
  15. {kafka_python-2.2.10 → kafka_python-2.2.11}/README.rst +0 -0
  16. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/__init__.py +0 -0
  17. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/admin/__init__.py +0 -0
  18. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/admin/acl_resource.py +0 -0
  19. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/admin/client.py +0 -0
  20. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/admin/config_resource.py +0 -0
  21. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/admin/new_partitions.py +0 -0
  22. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/admin/new_topic.py +0 -0
  23. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/benchmarks/__init__.py +0 -0
  24. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/benchmarks/consumer_performance.py +0 -0
  25. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/benchmarks/load_example.py +0 -0
  26. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/benchmarks/producer_performance.py +0 -0
  27. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/benchmarks/record_batch_compose.py +0 -0
  28. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/benchmarks/record_batch_read.py +0 -0
  29. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/benchmarks/varint_speed.py +0 -0
  30. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/client_async.py +0 -0
  31. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/codec.py +0 -0
  32. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/conn.py +0 -0
  33. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/consumer/__init__.py +0 -0
  34. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/consumer/fetcher.py +0 -0
  35. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/consumer/group.py +0 -0
  36. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/__init__.py +0 -0
  37. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/__init__.py +0 -0
  38. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/abstract.py +0 -0
  39. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/range.py +0 -0
  40. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/roundrobin.py +0 -0
  41. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
  42. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
  43. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
  44. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
  45. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/base.py +0 -0
  46. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/consumer.py +0 -0
  47. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/heartbeat.py +0 -0
  48. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/coordinator/protocol.py +0 -0
  49. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/errors.py +0 -0
  50. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/future.py +0 -0
  51. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/__init__.py +0 -0
  52. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/compound_stat.py +0 -0
  53. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/dict_reporter.py +0 -0
  54. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/kafka_metric.py +0 -0
  55. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/measurable.py +0 -0
  56. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/measurable_stat.py +0 -0
  57. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/metric_config.py +0 -0
  58. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/metric_name.py +0 -0
  59. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/metrics.py +0 -0
  60. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/metrics_reporter.py +0 -0
  61. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/quota.py +0 -0
  62. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stat.py +0 -0
  63. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/__init__.py +0 -0
  64. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/avg.py +0 -0
  65. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/count.py +0 -0
  66. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/histogram.py +0 -0
  67. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/max_stat.py +0 -0
  68. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/min_stat.py +0 -0
  69. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/percentile.py +0 -0
  70. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/percentiles.py +0 -0
  71. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/rate.py +0 -0
  72. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/sampled_stat.py +0 -0
  73. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/sensor.py +0 -0
  74. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/metrics/stats/total.py +0 -0
  75. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/partitioner/__init__.py +0 -0
  76. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/partitioner/default.py +0 -0
  77. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/producer/__init__.py +0 -0
  78. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/producer/future.py +0 -0
  79. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/producer/record_accumulator.py +0 -0
  80. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/producer/sender.py +0 -0
  81. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/producer/transaction_manager.py +0 -0
  82. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/__init__.py +0 -0
  83. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/abstract.py +0 -0
  84. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/add_offsets_to_txn.py +0 -0
  85. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/add_partitions_to_txn.py +0 -0
  86. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/admin.py +0 -0
  87. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/api.py +0 -0
  88. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/api_versions.py +0 -0
  89. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/broker_api_versions.py +0 -0
  90. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/commit.py +0 -0
  91. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/end_txn.py +0 -0
  92. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/fetch.py +0 -0
  93. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/find_coordinator.py +0 -0
  94. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/frame.py +0 -0
  95. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/group.py +0 -0
  96. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/init_producer_id.py +0 -0
  97. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/list_offsets.py +0 -0
  98. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/message.py +0 -0
  99. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/metadata.py +0 -0
  100. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/offset_for_leader_epoch.py +0 -0
  101. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/parser.py +0 -0
  102. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/pickle.py +0 -0
  103. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/produce.py +0 -0
  104. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/sasl_authenticate.py +0 -0
  105. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/sasl_handshake.py +0 -0
  106. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/struct.py +0 -0
  107. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/txn_offset_commit.py +0 -0
  108. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/protocol/types.py +0 -0
  109. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/record/__init__.py +0 -0
  110. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/record/_crc32c.py +0 -0
  111. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/record/abc.py +0 -0
  112. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/record/default_records.py +0 -0
  113. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/record/legacy_records.py +0 -0
  114. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/record/memory_records.py +0 -0
  115. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/record/util.py +0 -0
  116. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/__init__.py +0 -0
  117. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/abc.py +0 -0
  118. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/gssapi.py +0 -0
  119. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/oauth.py +0 -0
  120. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/plain.py +0 -0
  121. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/scram.py +0 -0
  122. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/sasl/sspi.py +0 -0
  123. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/serializer/__init__.py +0 -0
  124. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/serializer/abstract.py +0 -0
  125. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/socks5_wrapper.py +0 -0
  126. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/structs.py +0 -0
  127. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/vendor/__init__.py +0 -0
  128. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/vendor/enum34.py +0 -0
  129. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/vendor/selectors34.py +0 -0
  130. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/vendor/six.py +0 -0
  131. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka/vendor/socketpair.py +0 -0
  132. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka_python.egg-info/SOURCES.txt +0 -0
  133. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka_python.egg-info/dependency_links.txt +0 -0
  134. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka_python.egg-info/requires.txt +0 -0
  135. {kafka_python-2.2.10 → kafka_python-2.2.11}/kafka_python.egg-info/top_level.txt +0 -0
  136. {kafka_python-2.2.10 → kafka_python-2.2.11}/pyproject.toml +0 -0
  137. {kafka_python-2.2.10 → kafka_python-2.2.11}/setup.cfg +0 -0
  138. {kafka_python-2.2.10 → kafka_python-2.2.11}/setup.py +0 -0
  139. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/__init__.py +0 -0
  140. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/conftest.py +0 -0
  141. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/fixtures.py +0 -0
  142. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/test_admin_integration.py +0 -0
  143. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/test_consumer_group.py +0 -0
  144. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/test_consumer_integration.py +0 -0
  145. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/test_producer_integration.py +0 -0
  146. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/integration/test_sasl_integration.py +0 -0
  147. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_acl_comparisons.py +0 -0
  148. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_admin.py +0 -0
  149. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_api_object_implementation.py +0 -0
  150. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_assignors.py +0 -0
  151. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_client_async.py +0 -0
  152. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_codec.py +0 -0
  153. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_conn.py +0 -0
  154. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_consumer.py +0 -0
  155. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_coordinator.py +0 -0
  156. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_fetcher.py +0 -0
  157. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_metrics.py +0 -0
  158. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_object_conversion.py +0 -0
  159. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_package.py +0 -0
  160. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_partition_movements.py +0 -0
  161. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_partitioner.py +0 -0
  162. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_producer.py +0 -0
  163. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_protocol.py +0 -0
  164. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_record_accumulator.py +0 -0
  165. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_sender.py +0 -0
  166. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_subscription_state.py +0 -0
  167. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/test_util.py +0 -0
  168. {kafka_python-2.2.10 → kafka_python-2.2.11}/test/testutil.py +0 -0
@@ -1,3 +1,11 @@
1
+ # 2.2.11 (June 5, 2025)
2
+
3
+ Fixes
4
+ * Do not ignore metadata response for single topic with error (#2640)
5
+ * Fix decoding bug in AWS_MSK_IAM mechanism (#2639)
6
+ * Add synchronized decorator; add lock to subscription state (#2636)
7
+ * Update build links in documentation (#2634)
8
+
1
9
  # 2.2.10 (May 22, 2025)
2
10
 
3
11
  Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kafka-python
3
- Version: 2.2.10
3
+ Version: 2.2.11
4
4
  Summary: Pure Python client for Apache Kafka
5
5
  Author-email: Dana Powers <dana.powers@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/dpkp/kafka-python
@@ -245,13 +245,6 @@ class ClusterMetadata(object):
245
245
 
246
246
  Returns: None
247
247
  """
248
- # In the common case where we ask for a single topic and get back an
249
- # error, we should fail the future
250
- if len(metadata.topics) == 1 and metadata.topics[0][0] != Errors.NoError.errno:
251
- error_code, topic = metadata.topics[0][:2]
252
- error = Errors.for_code(error_code)(topic)
253
- return self.failed_update(error)
254
-
255
248
  if not metadata.brokers:
256
249
  log.warning("No broker metadata found in MetadataResponse -- ignoring.")
257
250
  return self.failed_update(Errors.MetadataEmptyBrokerList(metadata))
@@ -349,7 +342,15 @@ class ClusterMetadata(object):
349
342
  self._last_successful_refresh_ms = now
350
343
 
351
344
  if f:
352
- f.success(self)
345
+ # In the common case where we ask for a single topic and get back an
346
+ # error, we should fail the future
347
+ if len(metadata.topics) == 1 and metadata.topics[0][0] != Errors.NoError.errno:
348
+ error_code, topic = metadata.topics[0][:2]
349
+ error = Errors.for_code(error_code)(topic)
350
+ f.failure(error)
351
+ else:
352
+ f.success(self)
353
+
353
354
  log.debug("Updated cluster metadata to %s", self)
354
355
 
355
356
  for listener in self._listeners:
@@ -15,6 +15,7 @@ except ImportError:
15
15
  import logging
16
16
  import random
17
17
  import re
18
+ import threading
18
19
  import time
19
20
 
20
21
  from kafka.vendor import six
@@ -22,7 +23,7 @@ from kafka.vendor import six
22
23
  import kafka.errors as Errors
23
24
  from kafka.protocol.list_offsets import OffsetResetStrategy
24
25
  from kafka.structs import OffsetAndMetadata
25
- from kafka.util import ensure_valid_topic_name
26
+ from kafka.util import ensure_valid_topic_name, synchronized
26
27
 
27
28
  log = logging.getLogger(__name__)
28
29
 
@@ -84,6 +85,7 @@ class SubscriptionState(object):
84
85
  self.assignment = OrderedDict()
85
86
  self.rebalance_listener = None
86
87
  self.listeners = []
88
+ self._lock = threading.RLock()
87
89
 
88
90
  def _set_subscription_type(self, subscription_type):
89
91
  if not isinstance(subscription_type, SubscriptionType):
@@ -93,6 +95,7 @@ class SubscriptionState(object):
93
95
  elif self.subscription_type != subscription_type:
94
96
  raise Errors.IllegalStateError(self._SUBSCRIPTION_EXCEPTION_MESSAGE)
95
97
 
98
+ @synchronized
96
99
  def subscribe(self, topics=(), pattern=None, listener=None):
97
100
  """Subscribe to a list of topics, or a topic regex pattern.
98
101
 
@@ -147,6 +150,7 @@ class SubscriptionState(object):
147
150
  raise TypeError('listener must be a ConsumerRebalanceListener')
148
151
  self.rebalance_listener = listener
149
152
 
153
+ @synchronized
150
154
  def change_subscription(self, topics):
151
155
  """Change the topic subscription.
152
156
 
@@ -178,6 +182,7 @@ class SubscriptionState(object):
178
182
  self.subscription = set(topics)
179
183
  self._group_subscription.update(topics)
180
184
 
185
+ @synchronized
181
186
  def group_subscribe(self, topics):
182
187
  """Add topics to the current group subscription.
183
188
 
@@ -191,6 +196,7 @@ class SubscriptionState(object):
191
196
  raise Errors.IllegalStateError(self._SUBSCRIPTION_EXCEPTION_MESSAGE)
192
197
  self._group_subscription.update(topics)
193
198
 
199
+ @synchronized
194
200
  def reset_group_subscription(self):
195
201
  """Reset the group's subscription to only contain topics subscribed by this consumer."""
196
202
  if not self.partitions_auto_assigned():
@@ -198,6 +204,7 @@ class SubscriptionState(object):
198
204
  assert self.subscription is not None, 'Subscription required'
199
205
  self._group_subscription.intersection_update(self.subscription)
200
206
 
207
+ @synchronized
201
208
  def assign_from_user(self, partitions):
202
209
  """Manually assign a list of TopicPartitions to this consumer.
203
210
 
@@ -222,6 +229,7 @@ class SubscriptionState(object):
222
229
  self._set_assignment({partition: self.assignment.get(partition, TopicPartitionState())
223
230
  for partition in partitions})
224
231
 
232
+ @synchronized
225
233
  def assign_from_subscribed(self, assignments):
226
234
  """Update the assignment to the specified partitions
227
235
 
@@ -258,6 +266,7 @@ class SubscriptionState(object):
258
266
  for tp in topic_partitions[topic]:
259
267
  self.assignment[tp] = partition_states[tp]
260
268
 
269
+ @synchronized
261
270
  def unsubscribe(self):
262
271
  """Clear all topic subscriptions and partition assignments"""
263
272
  self.subscription = None
@@ -266,6 +275,7 @@ class SubscriptionState(object):
266
275
  self.subscribed_pattern = None
267
276
  self.subscription_type = SubscriptionType.NONE
268
277
 
278
+ @synchronized
269
279
  def group_subscription(self):
270
280
  """Get the topic subscription for the group.
271
281
 
@@ -281,6 +291,7 @@ class SubscriptionState(object):
281
291
  """
282
292
  return self._group_subscription
283
293
 
294
+ @synchronized
284
295
  def seek(self, partition, offset):
285
296
  """Manually specify the fetch offset for a TopicPartition.
286
297
 
@@ -298,15 +309,18 @@ class SubscriptionState(object):
298
309
  raise TypeError("offset must be type in or OffsetAndMetadata")
299
310
  self.assignment[partition].seek(offset)
300
311
 
312
+ @synchronized
301
313
  def assigned_partitions(self):
302
314
  """Return set of TopicPartitions in current assignment."""
303
315
  return set(self.assignment.keys())
304
316
 
317
+ @synchronized
305
318
  def paused_partitions(self):
306
319
  """Return current set of paused TopicPartitions."""
307
320
  return set(partition for partition in self.assignment
308
321
  if self.is_paused(partition))
309
322
 
323
+ @synchronized
310
324
  def fetchable_partitions(self):
311
325
  """Return ordered list of TopicPartitions that should be Fetched."""
312
326
  fetchable = list()
@@ -315,10 +329,12 @@ class SubscriptionState(object):
315
329
  fetchable.append(partition)
316
330
  return fetchable
317
331
 
332
+ @synchronized
318
333
  def partitions_auto_assigned(self):
319
334
  """Return True unless user supplied partitions manually."""
320
335
  return self.subscription_type in (SubscriptionType.AUTO_TOPICS, SubscriptionType.AUTO_PATTERN)
321
336
 
337
+ @synchronized
322
338
  def all_consumed_offsets(self):
323
339
  """Returns consumed offsets as {TopicPartition: OffsetAndMetadata}"""
324
340
  all_consumed = {}
@@ -327,6 +343,7 @@ class SubscriptionState(object):
327
343
  all_consumed[partition] = state.position
328
344
  return all_consumed
329
345
 
346
+ @synchronized
330
347
  def request_offset_reset(self, partition, offset_reset_strategy=None):
331
348
  """Mark partition for offset reset using specified or default strategy.
332
349
 
@@ -338,23 +355,28 @@ class SubscriptionState(object):
338
355
  offset_reset_strategy = self._default_offset_reset_strategy
339
356
  self.assignment[partition].reset(offset_reset_strategy)
340
357
 
358
+ @synchronized
341
359
  def set_reset_pending(self, partitions, next_allowed_reset_time):
342
360
  for partition in partitions:
343
361
  self.assignment[partition].set_reset_pending(next_allowed_reset_time)
344
362
 
363
+ @synchronized
345
364
  def has_default_offset_reset_policy(self):
346
365
  """Return True if default offset reset policy is Earliest or Latest"""
347
366
  return self._default_offset_reset_strategy != OffsetResetStrategy.NONE
348
367
 
368
+ @synchronized
349
369
  def is_offset_reset_needed(self, partition):
350
370
  return self.assignment[partition].awaiting_reset
351
371
 
372
+ @synchronized
352
373
  def has_all_fetch_positions(self):
353
374
  for state in six.itervalues(self.assignment):
354
375
  if not state.has_valid_position:
355
376
  return False
356
377
  return True
357
378
 
379
+ @synchronized
358
380
  def missing_fetch_positions(self):
359
381
  missing = set()
360
382
  for partition, state in six.iteritems(self.assignment):
@@ -362,9 +384,11 @@ class SubscriptionState(object):
362
384
  missing.add(partition)
363
385
  return missing
364
386
 
387
+ @synchronized
365
388
  def has_valid_position(self, partition):
366
389
  return partition in self.assignment and self.assignment[partition].has_valid_position
367
390
 
391
+ @synchronized
368
392
  def reset_missing_positions(self):
369
393
  partitions_with_no_offsets = set()
370
394
  for tp, state in six.iteritems(self.assignment):
@@ -377,6 +401,7 @@ class SubscriptionState(object):
377
401
  if partitions_with_no_offsets:
378
402
  raise Errors.NoOffsetForPartitionError(partitions_with_no_offsets)
379
403
 
404
+ @synchronized
380
405
  def partitions_needing_reset(self):
381
406
  partitions = set()
382
407
  for tp, state in six.iteritems(self.assignment):
@@ -384,25 +409,32 @@ class SubscriptionState(object):
384
409
  partitions.add(tp)
385
410
  return partitions
386
411
 
412
+ @synchronized
387
413
  def is_assigned(self, partition):
388
414
  return partition in self.assignment
389
415
 
416
+ @synchronized
390
417
  def is_paused(self, partition):
391
418
  return partition in self.assignment and self.assignment[partition].paused
392
419
 
420
+ @synchronized
393
421
  def is_fetchable(self, partition):
394
422
  return partition in self.assignment and self.assignment[partition].is_fetchable()
395
423
 
424
+ @synchronized
396
425
  def pause(self, partition):
397
426
  self.assignment[partition].pause()
398
427
 
428
+ @synchronized
399
429
  def resume(self, partition):
400
430
  self.assignment[partition].resume()
401
431
 
432
+ @synchronized
402
433
  def reset_failed(self, partitions, next_retry_time):
403
434
  for partition in partitions:
404
435
  self.assignment[partition].reset_failed(next_retry_time)
405
436
 
437
+ @synchronized
406
438
  def move_partition_to_end(self, partition):
407
439
  if partition in self.assignment:
408
440
  try:
@@ -411,6 +443,7 @@ class SubscriptionState(object):
411
443
  state = self.assignment.pop(partition)
412
444
  self.assignment[partition] = state
413
445
 
446
+ @synchronized
414
447
  def position(self, partition):
415
448
  return self.assignment[partition].position
416
449
 
@@ -960,9 +960,11 @@ class KafkaProducer(object):
960
960
  future.add_both(lambda e, *args: e.set(), metadata_event)
961
961
  self._sender.wakeup()
962
962
  metadata_event.wait(timer.timeout_ms / 1000)
963
- if not metadata_event.is_set():
963
+ if not future.is_done:
964
964
  raise Errors.KafkaTimeoutError(
965
965
  "Failed to update metadata after %.1f secs." % (max_wait_ms / 1000,))
966
+ elif future.failed() and not future.retriable():
967
+ raise future.exception
966
968
  elif topic in self._metadata.unauthorized_topics:
967
969
  raise Errors.TopicAuthorizationFailedError(set([topic]))
968
970
  else:
@@ -4,6 +4,7 @@ import datetime
4
4
  import hashlib
5
5
  import hmac
6
6
  import json
7
+ import logging
7
8
  import string
8
9
 
9
10
  # needed for AWS_MSK_IAM authentication:
@@ -13,10 +14,14 @@ except ImportError:
13
14
  # no botocore available, will disable AWS_MSK_IAM mechanism
14
15
  BotoSession = None
15
16
 
17
+ from kafka.errors import KafkaConfigurationError
16
18
  from kafka.sasl.abc import SaslMechanism
17
19
  from kafka.vendor.six.moves import urllib
18
20
 
19
21
 
22
+ log = logging.getLogger(__name__)
23
+
24
+
20
25
  class SaslMechanismAwsMskIam(SaslMechanism):
21
26
  def __init__(self, **config):
22
27
  assert BotoSession is not None, 'AWS_MSK_IAM requires the "botocore" package'
@@ -27,22 +32,28 @@ class SaslMechanismAwsMskIam(SaslMechanism):
27
32
  self._is_done = False
28
33
  self._is_authenticated = False
29
34
 
30
- def auth_bytes(self):
35
+ def _build_client(self):
31
36
  session = BotoSession()
32
37
  credentials = session.get_credentials().get_frozen_credentials()
33
- client = AwsMskIamClient(
38
+ if not session.get_config_variable('region'):
39
+ raise KafkaConfigurationError('Unable to determine region for AWS MSK cluster. Is AWS_DEFAULT_REGION set?')
40
+ return AwsMskIamClient(
34
41
  host=self.host,
35
42
  access_key=credentials.access_key,
36
43
  secret_key=credentials.secret_key,
37
44
  region=session.get_config_variable('region'),
38
45
  token=credentials.token,
39
46
  )
47
+
48
+ def auth_bytes(self):
49
+ client = self._build_client()
50
+ log.debug("Generating auth token for MSK scope: %s", client._scope)
40
51
  return client.first_message()
41
52
 
42
53
  def receive(self, auth_bytes):
43
54
  self._is_done = True
44
55
  self._is_authenticated = auth_bytes != b''
45
- self._auth = auth_bytes.deode('utf-8')
56
+ self._auth = auth_bytes.decode('utf-8')
46
57
 
47
58
  def is_done(self):
48
59
  return self._is_done
@@ -1,6 +1,7 @@
1
1
  from __future__ import absolute_import, division
2
2
 
3
3
  import binascii
4
+ import functools
4
5
  import re
5
6
  import time
6
7
  import weakref
@@ -129,3 +130,11 @@ class Dict(dict):
129
130
  See: https://docs.python.org/2/library/weakref.html
130
131
  """
131
132
  pass
133
+
134
+
135
+ def synchronized(func):
136
+ def wrapper(self, *args, **kwargs):
137
+ with self._lock:
138
+ return func(self, *args, **kwargs)
139
+ functools.update_wrapper(wrapper, func)
140
+ return wrapper
@@ -0,0 +1 @@
1
+ __version__ = '2.2.11'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kafka-python
3
- Version: 2.2.10
3
+ Version: 2.2.11
4
4
  Summary: Pure Python client for Apache Kafka
5
5
  Author-email: Dana Powers <dana.powers@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/dpkp/kafka-python
@@ -136,6 +136,21 @@ def test_metadata_v7():
136
136
  assert cluster._partitions['topic-1'][0].leader_epoch == 0
137
137
 
138
138
 
139
+ def test_unauthorized_topic():
140
+ cluster = ClusterMetadata()
141
+ assert len(cluster.brokers()) == 0
142
+
143
+ cluster.update_metadata(MetadataResponse[0](
144
+ [(0, 'foo', 12), (1, 'bar', 34)],
145
+ [(29, 'unauthorized-topic', [])])) # single topic w/ unauthorized error
146
+
147
+ # broker metadata should get updated
148
+ assert len(cluster.brokers()) == 2
149
+
150
+ # topic should be added to unauthorized list
151
+ assert 'unauthorized-topic' in cluster.unauthorized_topics
152
+
153
+
139
154
  def test_collect_hosts__happy_path():
140
155
  hosts = "127.0.0.1:1234,127.0.0.1"
141
156
  results = collect_hosts(hosts)
@@ -1 +0,0 @@
1
- __version__ = '2.2.10'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes