kafka-python 2.2.15__tar.gz → 2.2.17__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.15 → kafka_python-2.2.17}/CHANGES.md +18 -1
  2. {kafka_python-2.2.15 → kafka_python-2.2.17}/PKG-INFO +1 -1
  3. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/conn.py +14 -0
  4. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/consumer/group.py +8 -7
  5. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/base.py +1 -0
  6. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/producer/kafka.py +5 -1
  7. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/socks5_wrapper.py +31 -5
  8. kafka_python-2.2.17/kafka/version.py +1 -0
  9. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka_python.egg-info/PKG-INFO +1 -1
  10. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/test_consumer_integration.py +22 -0
  11. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_conn.py +55 -2
  12. kafka_python-2.2.15/kafka/version.py +0 -1
  13. {kafka_python-2.2.15 → kafka_python-2.2.17}/AUTHORS.md +0 -0
  14. {kafka_python-2.2.15 → kafka_python-2.2.17}/LICENSE +0 -0
  15. {kafka_python-2.2.15 → kafka_python-2.2.17}/MANIFEST.in +0 -0
  16. {kafka_python-2.2.15 → kafka_python-2.2.17}/README.rst +0 -0
  17. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/__init__.py +0 -0
  18. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/admin/__init__.py +0 -0
  19. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/admin/acl_resource.py +0 -0
  20. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/admin/client.py +0 -0
  21. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/admin/config_resource.py +0 -0
  22. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/admin/new_partitions.py +0 -0
  23. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/admin/new_topic.py +0 -0
  24. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/benchmarks/__init__.py +0 -0
  25. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/benchmarks/consumer_performance.py +0 -0
  26. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/benchmarks/load_example.py +0 -0
  27. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/benchmarks/producer_performance.py +0 -0
  28. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/benchmarks/record_batch_compose.py +0 -0
  29. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/benchmarks/record_batch_read.py +0 -0
  30. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/benchmarks/varint_speed.py +0 -0
  31. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/client_async.py +0 -0
  32. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/cluster.py +0 -0
  33. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/codec.py +0 -0
  34. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/consumer/__init__.py +0 -0
  35. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/consumer/fetcher.py +0 -0
  36. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/consumer/subscription_state.py +0 -0
  37. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/__init__.py +0 -0
  38. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/__init__.py +0 -0
  39. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/abstract.py +0 -0
  40. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/range.py +0 -0
  41. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/roundrobin.py +0 -0
  42. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
  43. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
  44. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
  45. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
  46. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/consumer.py +0 -0
  47. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/heartbeat.py +0 -0
  48. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/coordinator/protocol.py +0 -0
  49. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/errors.py +0 -0
  50. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/future.py +0 -0
  51. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/__init__.py +0 -0
  52. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/compound_stat.py +0 -0
  53. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/dict_reporter.py +0 -0
  54. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/kafka_metric.py +0 -0
  55. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/measurable.py +0 -0
  56. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/measurable_stat.py +0 -0
  57. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/metric_config.py +0 -0
  58. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/metric_name.py +0 -0
  59. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/metrics.py +0 -0
  60. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/metrics_reporter.py +0 -0
  61. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/quota.py +0 -0
  62. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stat.py +0 -0
  63. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/__init__.py +0 -0
  64. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/avg.py +0 -0
  65. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/count.py +0 -0
  66. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/histogram.py +0 -0
  67. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/max_stat.py +0 -0
  68. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/min_stat.py +0 -0
  69. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/percentile.py +0 -0
  70. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/percentiles.py +0 -0
  71. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/rate.py +0 -0
  72. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/sampled_stat.py +0 -0
  73. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/sensor.py +0 -0
  74. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/metrics/stats/total.py +0 -0
  75. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/partitioner/__init__.py +0 -0
  76. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/partitioner/default.py +0 -0
  77. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/producer/__init__.py +0 -0
  78. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/producer/future.py +0 -0
  79. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/producer/record_accumulator.py +0 -0
  80. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/producer/sender.py +0 -0
  81. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/producer/transaction_manager.py +0 -0
  82. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/__init__.py +0 -0
  83. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/abstract.py +0 -0
  84. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/add_offsets_to_txn.py +0 -0
  85. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/add_partitions_to_txn.py +0 -0
  86. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/admin.py +0 -0
  87. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/api.py +0 -0
  88. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/api_versions.py +0 -0
  89. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/broker_api_versions.py +0 -0
  90. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/commit.py +0 -0
  91. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/end_txn.py +0 -0
  92. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/fetch.py +0 -0
  93. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/find_coordinator.py +0 -0
  94. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/frame.py +0 -0
  95. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/group.py +0 -0
  96. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/init_producer_id.py +0 -0
  97. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/list_offsets.py +0 -0
  98. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/message.py +0 -0
  99. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/metadata.py +0 -0
  100. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/offset_for_leader_epoch.py +0 -0
  101. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/parser.py +0 -0
  102. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/pickle.py +0 -0
  103. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/produce.py +0 -0
  104. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/sasl_authenticate.py +0 -0
  105. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/sasl_handshake.py +0 -0
  106. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/struct.py +0 -0
  107. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/txn_offset_commit.py +0 -0
  108. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/protocol/types.py +0 -0
  109. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/record/__init__.py +0 -0
  110. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/record/_crc32c.py +0 -0
  111. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/record/abc.py +0 -0
  112. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/record/default_records.py +0 -0
  113. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/record/legacy_records.py +0 -0
  114. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/record/memory_records.py +0 -0
  115. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/record/util.py +0 -0
  116. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/__init__.py +0 -0
  117. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/abc.py +0 -0
  118. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/gssapi.py +0 -0
  119. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/msk.py +0 -0
  120. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/oauth.py +0 -0
  121. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/plain.py +0 -0
  122. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/scram.py +0 -0
  123. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/sasl/sspi.py +0 -0
  124. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/serializer/__init__.py +0 -0
  125. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/serializer/abstract.py +0 -0
  126. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/structs.py +0 -0
  127. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/util.py +0 -0
  128. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/vendor/__init__.py +0 -0
  129. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/vendor/enum34.py +0 -0
  130. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/vendor/selectors34.py +0 -0
  131. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/vendor/six.py +0 -0
  132. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka/vendor/socketpair.py +0 -0
  133. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka_python.egg-info/SOURCES.txt +0 -0
  134. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka_python.egg-info/dependency_links.txt +0 -0
  135. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka_python.egg-info/requires.txt +0 -0
  136. {kafka_python-2.2.15 → kafka_python-2.2.17}/kafka_python.egg-info/top_level.txt +0 -0
  137. {kafka_python-2.2.15 → kafka_python-2.2.17}/pyproject.toml +0 -0
  138. {kafka_python-2.2.15 → kafka_python-2.2.17}/setup.cfg +0 -0
  139. {kafka_python-2.2.15 → kafka_python-2.2.17}/setup.py +0 -0
  140. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/__init__.py +0 -0
  141. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/conftest.py +0 -0
  142. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/fixtures.py +0 -0
  143. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/test_admin_integration.py +0 -0
  144. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/test_consumer_group.py +0 -0
  145. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/test_producer_integration.py +0 -0
  146. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/integration/test_sasl_integration.py +0 -0
  147. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_acl_comparisons.py +0 -0
  148. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_admin.py +0 -0
  149. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_api_object_implementation.py +0 -0
  150. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_assignors.py +0 -0
  151. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_client_async.py +0 -0
  152. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_cluster.py +0 -0
  153. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_codec.py +0 -0
  154. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_consumer.py +0 -0
  155. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_coordinator.py +0 -0
  156. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_fetcher.py +0 -0
  157. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_metrics.py +0 -0
  158. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_object_conversion.py +0 -0
  159. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_package.py +0 -0
  160. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_partition_movements.py +0 -0
  161. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_partitioner.py +0 -0
  162. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_producer.py +0 -0
  163. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_protocol.py +0 -0
  164. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_record_accumulator.py +0 -0
  165. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_sender.py +0 -0
  166. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_subscription_state.py +0 -0
  167. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/test_util.py +0 -0
  168. {kafka_python-2.2.15 → kafka_python-2.2.17}/test/testutil.py +0 -0
@@ -1,3 +1,21 @@
1
+ # 2.2.17 (Nov 20, 2025)
2
+
3
+ Fixes
4
+ * Add internal poll to consumer.position() (#2696)
5
+ * Initiate Coordinator Reconnect w/ Backoff from Heartbeat Thread (#2695)
6
+
7
+ Networking
8
+ * SOCKS5: support looking up names remotely (jschwartzenberg / #2666)
9
+
10
+ Documentation
11
+ * Add `transactional_id` to KafkaProducer Keyword Arguments docstring
12
+
13
+ # 2.2.16 (Nov 18, 2025)
14
+
15
+ Fixes
16
+ * Fix thread not waking up when there is still data to be sent (gqmelo / #2670)
17
+ * Ensure timeout is checked after each fetch position update in `Consumer.position()` (k61n / #2668)
18
+
1
19
  # 2.2.15 (July 1, 2025)
2
20
 
3
21
  Fixes
@@ -26,7 +44,6 @@ Fixes
26
44
  * Avoid RuntimeError on mutated `_completed_fetches` deque in consumer fetcher (#2646)
27
45
  * Throw exception on invalid bucket type (#2642)
28
46
 
29
-
30
47
  # 2.2.11 (June 5, 2025)
31
48
 
32
49
  Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kafka-python
3
- Version: 2.2.15
3
+ Version: 2.2.17
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
@@ -326,6 +326,9 @@ class BrokerConnection(object):
326
326
  return True
327
327
 
328
328
  def _next_afi_sockaddr(self):
329
+ if self.config["socks5_proxy"] and Socks5Wrapper.use_remote_lookup(self.config["socks5_proxy"]):
330
+ return (socket.AF_UNSPEC, (self.host, self.port))
331
+
329
332
  if not self._gai:
330
333
  if not self._dns_lookup():
331
334
  return
@@ -379,6 +382,7 @@ class BrokerConnection(object):
379
382
  self._sock_afi, self._sock_addr = next_lookup
380
383
  try:
381
384
  if self.config["socks5_proxy"] is not None:
385
+ log.debug('%s: initializing Socks5 proxy at %s', self, self.config["socks5_proxy"])
382
386
  self._socks5_proxy = Socks5Wrapper(self.config["socks5_proxy"], self.afi)
383
387
  self._sock = self._socks5_proxy.socket(self._sock_afi, socket.SOCK_STREAM)
384
388
  else:
@@ -864,6 +868,8 @@ class BrokerConnection(object):
864
868
  if self.disconnected() or self.connecting():
865
869
  if len(self._gai) > 0:
866
870
  return 0
871
+ elif self.config["socks5_proxy"] and Socks5Wrapper.use_remote_lookup(self.config["socks5_proxy"]):
872
+ return 0
867
873
  else:
868
874
  time_waited = time.time() - self.last_attempt
869
875
  return max(self._reconnect_backoff - time_waited, 0) * 1000
@@ -964,6 +970,7 @@ class BrokerConnection(object):
964
970
  # the socket fd from selectors cleanly.
965
971
  sock = self._sock
966
972
  self._sock = None
973
+ self._socks5_proxy = None
967
974
 
968
975
  # drop lock before state change callback and processing futures
969
976
  self.config['state_change_callback'](self.node_id, sock, self)
@@ -1075,6 +1082,13 @@ class BrokerConnection(object):
1075
1082
  total_bytes = self._send_bytes(self._send_buffer)
1076
1083
  self._send_buffer = self._send_buffer[total_bytes:]
1077
1084
 
1085
+ # If all data was sent, we need to get the new data from the protocol now, otherwise
1086
+ # this function would return True, indicating that there are no more pending
1087
+ # requests. This could cause the calling thread to wait indefinitely as it won't
1088
+ # know that there is still buffered data to send.
1089
+ if not self._send_buffer:
1090
+ self._send_buffer = self._protocol.send_bytes()
1091
+
1078
1092
  if self._sensors:
1079
1093
  self._sensors.bytes_sent.record(total_bytes)
1080
1094
  # Return True iff send buffer is empty
@@ -723,7 +723,9 @@ class KafkaConsumer(six.Iterator):
723
723
 
724
724
  # We do not want to be stuck blocking in poll if we are missing some positions
725
725
  # since the offset lookup may be backing off after a failure
726
- poll_timeout_ms = min(timer.timeout_ms, self._coordinator.time_to_next_poll() * 1000)
726
+ poll_timeout_ms = timer.timeout_ms
727
+ if self.config['group_id'] is not None:
728
+ poll_timeout_ms = min(poll_timeout_ms, self._coordinator.time_to_next_poll() * 1000)
727
729
  if not has_all_fetch_positions:
728
730
  log.debug('poll: do not have all fetch positions...')
729
731
  poll_timeout_ms = min(poll_timeout_ms, self.config['retry_backoff_ms'])
@@ -753,13 +755,12 @@ class KafkaConsumer(six.Iterator):
753
755
 
754
756
  timer = Timer(timeout_ms)
755
757
  position = self._subscription.assignment[partition].position
756
- while position is None:
758
+ while position is None and not timer.expired:
757
759
  # batch update fetch positions for any partitions without a valid position
758
- if self._update_fetch_positions(timeout_ms=timer.timeout_ms):
759
- position = self._subscription.assignment[partition].position
760
- elif timer.expired:
761
- return None
762
- else:
760
+ self._update_fetch_positions(timeout_ms=timer.timeout_ms)
761
+ self._client.poll(timeout_ms=timer.timeout_ms)
762
+ position = self._subscription.assignment[partition].position
763
+ if position is not None:
763
764
  return position.offset
764
765
 
765
766
  def highwater(self, partition):
@@ -1120,6 +1120,7 @@ class HeartbeatThread(threading.Thread):
1120
1120
  self.coordinator._lock.wait(self.coordinator.config['retry_backoff_ms'] / 1000)
1121
1121
 
1122
1122
  elif not self.coordinator.connected():
1123
+ self.coordinator._client.maybe_connect(self.coordinator.coordinator_id)
1123
1124
  self.coordinator._client._lock.release()
1124
1125
  self.coordinator._lock.wait(self.coordinator.config['retry_backoff_ms'] / 1000)
1125
1126
 
@@ -134,10 +134,14 @@ class KafkaProducer(object):
134
134
  value_serializer (callable): used to convert user-supplied message
135
135
  values to bytes. If not None, called as f(value), should return
136
136
  bytes. Default: None.
137
+ transactional_id (str): Enable transactional producer with a unique
138
+ identifier. This will be used to identify the same producer
139
+ instance across process restarts. Default: None.
137
140
  enable_idempotence (bool): When set to True, the producer will ensure
138
141
  that exactly one copy of each message is written in the stream.
139
142
  If False, producer retries due to broker failures, etc., may write
140
- duplicates of the retried message in the stream. Default: False.
143
+ duplicates of the retried message in the stream.
144
+ Default: True if `transactional_id` is provided, otherwise False.
141
145
 
142
146
  Note that enabling idempotence requires
143
147
  `max_in_flight_requests_per_connection` to be set to 1 and `retries`
@@ -64,6 +64,15 @@ class Socks5Wrapper:
64
64
  log.warning("DNS lookup failed for proxy %s:%d, %r", host, port, ex)
65
65
  return []
66
66
 
67
+ @classmethod
68
+ def use_remote_lookup(cls, proxy_url):
69
+ if proxy_url is None:
70
+ return False
71
+ return urlparse(proxy_url).scheme == 'socks5h'
72
+
73
+ def _use_remote_lookup(self):
74
+ return self._proxy_url.scheme == 'socks5h'
75
+
67
76
  def socket(self, family, sock_type):
68
77
  """Open and record a socket.
69
78
 
@@ -187,7 +196,10 @@ class Socks5Wrapper:
187
196
  return errno.ECONNREFUSED
188
197
 
189
198
  if self._state == ProxyConnectionStates.REQUEST_SUBMIT:
190
- if self._target_afi == socket.AF_INET:
199
+ if self._use_remote_lookup():
200
+ addr_type = 3
201
+ addr_len = len(addr[0])
202
+ elif self._target_afi == socket.AF_INET:
191
203
  addr_type = 1
192
204
  addr_len = 4
193
205
  elif self._target_afi == socket.AF_INET6:
@@ -200,14 +212,28 @@ class Socks5Wrapper:
200
212
  return errno.ECONNREFUSED
201
213
 
202
214
  self._buffer_out = struct.pack(
203
- "!bbbb{}sh".format(addr_len),
215
+ "!bbbb",
204
216
  5, # version
205
217
  1, # command: connect
206
218
  0, # reserved
207
- addr_type, # 1 for ipv4, 4 for ipv6 address
208
- socket.inet_pton(self._target_afi, addr[0]), # either 4 or 16 bytes of actual address
209
- addr[1], # port
219
+ addr_type, # 1 for ipv4, 4 for ipv6 address, 3 for domain name
210
220
  )
221
+ # Addr format depends on type
222
+ if addr_type == 3:
223
+ # len + domain name (no null terminator)
224
+ self._buffer_out += struct.pack(
225
+ "!b{}s".format(addr_len),
226
+ addr_len,
227
+ addr[0].encode('ascii'),
228
+ )
229
+ else:
230
+ # either 4 (type 1) or 16 (type 4) bytes of actual address
231
+ self._buffer_out += struct.pack(
232
+ "!{}s".format(addr_len),
233
+ socket.inet_pton(self._target_afi, addr[0]),
234
+ )
235
+ self._buffer_out += struct.pack("!H", addr[1]) # port
236
+
211
237
  self._state = ProxyConnectionStates.REQUESTING
212
238
 
213
239
  if self._state == ProxyConnectionStates.REQUESTING:
@@ -0,0 +1 @@
1
+ __version__ = '2.2.17'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kafka-python
3
- Version: 2.2.15
3
+ Version: 2.2.17
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
@@ -302,3 +302,25 @@ def test_kafka_consumer_offsets_for_times_errors(kafka_consumer_factory, topic):
302
302
 
303
303
  with pytest.raises(KafkaTimeoutError):
304
304
  consumer.offsets_for_times({bad_tp: 0})
305
+
306
+
307
+ @pytest.mark.skipif(not env_kafka_version(), reason="No KAFKA_VERSION set")
308
+ def test_kafka_consumer_position_after_seek_to_end(kafka_consumer_factory, topic, send_messages):
309
+ send_messages(range(0, 10), partition=0)
310
+
311
+ # Start a consumer with manual partition assignment.
312
+ consumer = kafka_consumer_factory(
313
+ topics=(),
314
+ group_id=None,
315
+ enable_auto_commit=False,
316
+ )
317
+ tp = TopicPartition(topic, 0)
318
+ consumer.assign([tp])
319
+
320
+ # Seek to the end of the partition, and call position() to synchronize the
321
+ # partition's offset without calling poll().
322
+ consumer.seek_to_end(tp)
323
+ position = consumer.position(tp, timeout_ms=1000)
324
+
325
+ # Verify we got the expected position
326
+ assert position == 10, f"Expected position 10, got {position}"
@@ -12,6 +12,9 @@ import pytest
12
12
 
13
13
  from kafka.conn import BrokerConnection, ConnectionStates
14
14
  from kafka.future import Future
15
+ from kafka.conn import BrokerConnection, ConnectionStates, SSLWantWriteError
16
+ from kafka.metrics.metrics import Metrics
17
+ from kafka.metrics.stats.sensor import Sensor
15
18
  from kafka.protocol.api import RequestHeader
16
19
  from kafka.protocol.group import HeartbeatResponse
17
20
  from kafka.protocol.metadata import MetadataRequest
@@ -43,10 +46,20 @@ def _socket(mocker):
43
46
  mocker.patch('socket.socket', return_value=socket)
44
47
  return socket
45
48
 
49
+ @pytest.fixture
50
+ def metrics(mocker):
51
+ metrics = mocker.MagicMock(Metrics)
52
+ metrics.mocked_sensors = {}
53
+ def sensor(name, **kwargs):
54
+ if name not in metrics.mocked_sensors:
55
+ metrics.mocked_sensors[name] = mocker.MagicMock(Sensor)
56
+ return metrics.mocked_sensors[name]
57
+ metrics.sensor.side_effect = sensor
58
+ return metrics
46
59
 
47
60
  @pytest.fixture
48
- def conn(_socket, dns_lookup, mocker):
49
- conn = BrokerConnection('localhost', 9092, socket.AF_INET)
61
+ def conn(_socket, dns_lookup, metrics, mocker):
62
+ conn = BrokerConnection('localhost', 9092, socket.AF_INET, metrics=metrics)
50
63
  mocker.patch.object(conn, '_try_api_versions_check', return_value=True)
51
64
  return conn
52
65
 
@@ -228,6 +241,46 @@ def test_send_response(_socket, conn):
228
241
  assert len(conn.in_flight_requests) == 1
229
242
 
230
243
 
244
+ def test_send_async_request_while_other_request_is_already_in_buffer(_socket, conn, metrics):
245
+ conn.connect()
246
+ assert conn.state is ConnectionStates.CONNECTED
247
+ assert 'node-0.bytes-sent' in metrics.mocked_sensors
248
+ bytes_sent_sensor = metrics.mocked_sensors['node-0.bytes-sent']
249
+
250
+ req1 = MetadataRequest[0](topics='foo')
251
+ header1 = RequestHeader(req1, client_id=conn.config['client_id'])
252
+ payload_bytes1 = len(header1.encode()) + len(req1.encode())
253
+ req2 = MetadataRequest[0]([])
254
+ header2 = RequestHeader(req2, client_id=conn.config['client_id'])
255
+ payload_bytes2 = len(header2.encode()) + len(req2.encode())
256
+
257
+ # The first call to the socket will raise a transient SSL exception. This will make the first
258
+ # request to be kept in the internal buffer to be sent in the next call of
259
+ # send_pending_requests_v2.
260
+ _socket.send.side_effect = [SSLWantWriteError, 4 + payload_bytes1, 4 + payload_bytes2]
261
+
262
+ conn.send(req1, blocking=False)
263
+ # This won't send any bytes because of the SSL exception and the request bytes will be kept in
264
+ # the buffer.
265
+ assert conn.send_pending_requests_v2() is False
266
+ assert bytes_sent_sensor.record.call_args_list[0].args == (0,)
267
+
268
+ conn.send(req2, blocking=False)
269
+ # This will send the remaining bytes in the buffer from the first request, but should notice
270
+ # that the second request was queued, therefore it should return False.
271
+ bytes_sent_sensor.record.reset_mock()
272
+ assert conn.send_pending_requests_v2() is False
273
+ bytes_sent_sensor.record.assert_called_once_with(4 + payload_bytes1)
274
+
275
+ bytes_sent_sensor.record.reset_mock()
276
+ assert conn.send_pending_requests_v2() is True
277
+ bytes_sent_sensor.record.assert_called_once_with(4 + payload_bytes2)
278
+
279
+ bytes_sent_sensor.record.reset_mock()
280
+ assert conn.send_pending_requests_v2() is True
281
+ bytes_sent_sensor.record.assert_called_once_with(0)
282
+
283
+
231
284
  def test_send_error(_socket, conn):
232
285
  conn.connect()
233
286
  assert conn.state is ConnectionStates.CONNECTED
@@ -1 +0,0 @@
1
- __version__ = '2.2.15'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes