kafka-python 2.1.3__tar.gz → 2.1.4__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 (151) hide show
  1. {kafka_python-2.1.3 → kafka_python-2.1.4}/CHANGES.md +13 -0
  2. {kafka_python-2.1.3 → kafka_python-2.1.4}/PKG-INFO +1 -1
  3. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/client_async.py +4 -2
  4. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/conn.py +33 -25
  5. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/fetcher.py +23 -8
  6. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/metadata.py +1 -0
  7. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/oauth.py +15 -2
  8. kafka_python-2.1.4/kafka/version.py +1 -0
  9. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/PKG-INFO +1 -1
  10. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_client_async.py +3 -2
  11. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_fetcher.py +2 -0
  12. kafka_python-2.1.3/kafka/version.py +0 -1
  13. {kafka_python-2.1.3 → kafka_python-2.1.4}/AUTHORS.md +0 -0
  14. {kafka_python-2.1.3 → kafka_python-2.1.4}/LICENSE +0 -0
  15. {kafka_python-2.1.3 → kafka_python-2.1.4}/MANIFEST.in +0 -0
  16. {kafka_python-2.1.3 → kafka_python-2.1.4}/README.rst +0 -0
  17. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/__init__.py +0 -0
  18. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/__init__.py +0 -0
  19. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/acl_resource.py +0 -0
  20. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/client.py +0 -0
  21. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/config_resource.py +0 -0
  22. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/new_partitions.py +0 -0
  23. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/new_topic.py +0 -0
  24. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/cluster.py +0 -0
  25. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/codec.py +0 -0
  26. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/__init__.py +0 -0
  27. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/group.py +0 -0
  28. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/subscription_state.py +2 -2
  29. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/__init__.py +0 -0
  30. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/__init__.py +0 -0
  31. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/abstract.py +0 -0
  32. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/range.py +0 -0
  33. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/roundrobin.py +0 -0
  34. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
  35. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
  36. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
  37. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
  38. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/base.py +0 -0
  39. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/consumer.py +0 -0
  40. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/heartbeat.py +0 -0
  41. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/protocol.py +0 -0
  42. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/errors.py +0 -0
  43. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/future.py +0 -0
  44. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/__init__.py +0 -0
  45. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/compound_stat.py +0 -0
  46. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/dict_reporter.py +0 -0
  47. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/kafka_metric.py +0 -0
  48. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/measurable.py +0 -0
  49. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/measurable_stat.py +0 -0
  50. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metric_config.py +0 -0
  51. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metric_name.py +0 -0
  52. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metrics.py +0 -0
  53. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metrics_reporter.py +0 -0
  54. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/quota.py +0 -0
  55. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stat.py +0 -0
  56. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/__init__.py +0 -0
  57. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/avg.py +0 -0
  58. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/count.py +0 -0
  59. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/histogram.py +0 -0
  60. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/max_stat.py +0 -0
  61. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/min_stat.py +0 -0
  62. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/percentile.py +0 -0
  63. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/percentiles.py +0 -0
  64. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/rate.py +0 -0
  65. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/sampled_stat.py +0 -0
  66. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/sensor.py +0 -0
  67. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/total.py +0 -0
  68. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/partitioner/__init__.py +0 -0
  69. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/partitioner/default.py +0 -0
  70. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/__init__.py +0 -0
  71. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/buffer.py +0 -0
  72. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/future.py +0 -0
  73. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/kafka.py +0 -0
  74. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/record_accumulator.py +0 -0
  75. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/sender.py +0 -0
  76. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/__init__.py +0 -0
  77. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/abstract.py +0 -0
  78. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/admin.py +0 -0
  79. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/api.py +0 -0
  80. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/api_versions.py +0 -0
  81. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/broker_api_versions.py +0 -0
  82. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/commit.py +0 -0
  83. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/fetch.py +0 -0
  84. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/find_coordinator.py +0 -0
  85. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/frame.py +0 -0
  86. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/group.py +0 -0
  87. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/list_offsets.py +0 -0
  88. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/message.py +0 -0
  89. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/offset_for_leader_epoch.py +0 -0
  90. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/parser.py +0 -0
  91. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/pickle.py +0 -0
  92. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/produce.py +0 -0
  93. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/sasl_authenticate.py +0 -0
  94. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/sasl_handshake.py +0 -0
  95. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/struct.py +0 -0
  96. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/types.py +0 -0
  97. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/__init__.py +0 -0
  98. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/_crc32c.py +0 -0
  99. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/abc.py +0 -0
  100. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/default_records.py +0 -0
  101. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/legacy_records.py +0 -0
  102. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/memory_records.py +0 -0
  103. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/util.py +0 -0
  104. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/__init__.py +0 -0
  105. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/abc.py +0 -0
  106. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/gssapi.py +0 -0
  107. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/msk.py +0 -0
  108. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/plain.py +0 -0
  109. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/scram.py +0 -0
  110. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/sspi.py +0 -0
  111. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/serializer/__init__.py +0 -0
  112. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/serializer/abstract.py +0 -0
  113. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/socks5_wrapper.py +0 -0
  114. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/structs.py +0 -0
  115. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/util.py +0 -0
  116. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/__init__.py +0 -0
  117. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/enum34.py +0 -0
  118. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/selectors34.py +0 -0
  119. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/six.py +0 -0
  120. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/socketpair.py +0 -0
  121. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/SOURCES.txt +0 -0
  122. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/dependency_links.txt +0 -0
  123. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/requires.txt +0 -0
  124. {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/top_level.txt +0 -0
  125. {kafka_python-2.1.3 → kafka_python-2.1.4}/pyproject.toml +0 -0
  126. {kafka_python-2.1.3 → kafka_python-2.1.4}/setup.cfg +0 -0
  127. {kafka_python-2.1.3 → kafka_python-2.1.4}/setup.py +0 -0
  128. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_acl_comparisons.py +0 -0
  129. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_admin.py +0 -0
  130. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_admin_integration.py +0 -0
  131. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_api_object_implementation.py +0 -0
  132. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_assignors.py +0 -0
  133. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_cluster.py +0 -0
  134. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_codec.py +0 -0
  135. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_conn.py +0 -0
  136. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_consumer.py +0 -0
  137. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_consumer_group.py +0 -0
  138. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_consumer_integration.py +0 -0
  139. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_coordinator.py +0 -0
  140. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_metrics.py +0 -0
  141. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_object_conversion.py +0 -0
  142. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_package.py +0 -0
  143. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_partition_movements.py +0 -0
  144. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_partitioner.py +0 -0
  145. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_producer.py +0 -0
  146. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_protocol.py +0 -0
  147. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_sasl_integration.py +0 -0
  148. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_sender.py +0 -0
  149. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_subscription_state.py +0 -0
  150. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_util.py +0 -0
  151. {kafka_python-2.1.3 → kafka_python-2.1.4}/test/testutil.py +0 -0
@@ -1,3 +1,16 @@
1
+ # 2.1.4 (Mar 28, 2025)
2
+
3
+ Fixes
4
+ * Dont block pending FetchRequests when Metadata update requested (#2576)
5
+ * Fix MetadataRequest for no topics (#2573)
6
+ * Send final error byte x01 on Sasl OAuth failure (#2572)
7
+ * Reset SASL state on disconnect (#2571)
8
+ * Try import new Sequence before old to avoid DeprecationWarning
9
+
10
+ Improvements
11
+ * Update Makefile default to 4.0 broker; add make fixture
12
+ * Improve connection state logging (#2574)
13
+
1
14
  # 2.1.3 (Mar 25, 2025)
2
15
 
3
16
  Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kafka-python
3
- Version: 2.1.3
3
+ Version: 2.1.4
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
@@ -365,7 +365,7 @@ class KafkaClient(object):
365
365
  self._connecting.remove(node_id)
366
366
  try:
367
367
  self._selector.unregister(sock)
368
- except KeyError:
368
+ except (KeyError, ValueError):
369
369
  pass
370
370
 
371
371
  if self._sensors:
@@ -978,8 +978,10 @@ class KafkaClient(object):
978
978
  topics = list(self.config['bootstrap_topics_filter'])
979
979
 
980
980
  api_version = self.api_version(MetadataRequest, max_version=7)
981
- if self.cluster.need_all_topic_metadata or not topics:
981
+ if self.cluster.need_all_topic_metadata:
982
982
  topics = MetadataRequest[api_version].ALL_TOPICS
983
+ elif not topics:
984
+ topics = MetadataRequest[api_version].NO_TOPICS
983
985
  if api_version >= 4:
984
986
  request = MetadataRequest[api_version](topics, self.config['allow_auto_create_topics'])
985
987
  else:
@@ -271,12 +271,10 @@ class BrokerConnection(object):
271
271
  assert self.config['security_protocol'] in self.SECURITY_PROTOCOLS, (
272
272
  'security_protocol must be in ' + ', '.join(self.SECURITY_PROTOCOLS))
273
273
 
274
- self._sasl_mechanism = None
275
274
  if self.config['security_protocol'] in ('SSL', 'SASL_SSL'):
276
275
  assert ssl_available, "Python wasn't built with SSL support"
277
276
 
278
- if self.config['security_protocol'] in ('SASL_PLAINTEXT', 'SASL_SSL'):
279
- self._sasl_mechanism = get_sasl_mechanism(self.config['sasl_mechanism'])(**self.config)
277
+ self._init_sasl_mechanism()
280
278
 
281
279
  # This is not a general lock / this class is not generally thread-safe yet
282
280
  # However, to avoid pushing responsibility for maintaining
@@ -312,11 +310,17 @@ class BrokerConnection(object):
312
310
  self.config['metric_group_prefix'],
313
311
  self.node_id)
314
312
 
313
+ def _init_sasl_mechanism(self):
314
+ if self.config['security_protocol'] in ('SASL_PLAINTEXT', 'SASL_SSL'):
315
+ self._sasl_mechanism = get_sasl_mechanism(self.config['sasl_mechanism'])(**self.config)
316
+ else:
317
+ self._sasl_mechanism = None
318
+
315
319
  def _dns_lookup(self):
316
320
  self._gai = dns_lookup(self.host, self.port, self.afi)
317
321
  if not self._gai:
318
- log.error('DNS lookup failed for %s:%i (%s)',
319
- self.host, self.port, self.afi)
322
+ log.error('%s: DNS lookup failed for %s:%i (%s)',
323
+ self, self.host, self.port, self.afi)
320
324
  return False
321
325
  return True
322
326
 
@@ -362,6 +366,7 @@ class BrokerConnection(object):
362
366
  def connect(self):
363
367
  """Attempt to connect and return ConnectionState"""
364
368
  if self.state is ConnectionStates.DISCONNECTED and not self.blacked_out():
369
+ self.state = ConnectionStates.CONNECTING
365
370
  self.last_attempt = time.time()
366
371
  next_lookup = self._next_afi_sockaddr()
367
372
  if not next_lookup:
@@ -386,7 +391,6 @@ class BrokerConnection(object):
386
391
  self._sock.setsockopt(*option)
387
392
 
388
393
  self._sock.setblocking(False)
389
- self.state = ConnectionStates.CONNECTING
390
394
  self.config['state_change_callback'](self.node_id, self._sock, self)
391
395
  log.info('%s: connecting to %s:%d [%s %s]', self, self.host,
392
396
  self.port, self._sock_addr, AFI_NAMES[self._sock_afi])
@@ -408,20 +412,20 @@ class BrokerConnection(object):
408
412
  log.debug('%s: established TCP connection', self)
409
413
 
410
414
  if self.config['security_protocol'] in ('SSL', 'SASL_SSL'):
411
- log.debug('%s: initiating SSL handshake', self)
412
415
  self.state = ConnectionStates.HANDSHAKE
416
+ log.debug('%s: initiating SSL handshake', self)
413
417
  self.config['state_change_callback'](self.node_id, self._sock, self)
414
418
  # _wrap_ssl can alter the connection state -- disconnects on failure
415
419
  self._wrap_ssl()
416
420
  else:
417
- log.debug('%s: checking broker Api Versions', self)
418
421
  self.state = ConnectionStates.API_VERSIONS_SEND
422
+ log.debug('%s: checking broker Api Versions', self)
419
423
  self.config['state_change_callback'](self.node_id, self._sock, self)
420
424
 
421
425
  # Connection failed
422
426
  # WSAEINVAL == 10022, but errno.WSAEINVAL is not available on non-win systems
423
427
  elif ret not in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK, 10022):
424
- log.error('Connect attempt to %s returned error %s.'
428
+ log.error('%s: Connect attempt returned error %s.'
425
429
  ' Disconnecting.', self, ret)
426
430
  errstr = errno.errorcode.get(ret, 'UNKNOWN')
427
431
  self.close(Errors.KafkaConnectionError('{} {}'.format(ret, errstr)))
@@ -434,8 +438,8 @@ class BrokerConnection(object):
434
438
  if self.state is ConnectionStates.HANDSHAKE:
435
439
  if self._try_handshake():
436
440
  log.debug('%s: completed SSL handshake.', self)
437
- log.debug('%s: checking broker Api Versions', self)
438
441
  self.state = ConnectionStates.API_VERSIONS_SEND
442
+ log.debug('%s: checking broker Api Versions', self)
439
443
  self.config['state_change_callback'](self.node_id, self._sock, self)
440
444
 
441
445
  if self.state in (ConnectionStates.API_VERSIONS_SEND, ConnectionStates.API_VERSIONS_RECV):
@@ -443,13 +447,13 @@ class BrokerConnection(object):
443
447
  # _try_api_versions_check has side-effects: possibly disconnected on socket errors
444
448
  if self.state in (ConnectionStates.API_VERSIONS_SEND, ConnectionStates.API_VERSIONS_RECV):
445
449
  if self.config['security_protocol'] in ('SASL_PLAINTEXT', 'SASL_SSL'):
446
- log.debug('%s: initiating SASL authentication', self)
447
450
  self.state = ConnectionStates.AUTHENTICATING
451
+ log.debug('%s: initiating SASL authentication', self)
448
452
  self.config['state_change_callback'](self.node_id, self._sock, self)
449
453
  else:
450
454
  # security_protocol PLAINTEXT
451
- log.info('%s: Connection complete.', self)
452
455
  self.state = ConnectionStates.CONNECTED
456
+ log.info('%s: Connection complete.', self)
453
457
  self._reset_reconnect_backoff()
454
458
  self.config['state_change_callback'](self.node_id, self._sock, self)
455
459
 
@@ -458,8 +462,8 @@ class BrokerConnection(object):
458
462
  if self._try_authenticate():
459
463
  # _try_authenticate has side-effects: possibly disconnected on socket errors
460
464
  if self.state is ConnectionStates.AUTHENTICATING:
461
- log.info('%s: Connection complete.', self)
462
465
  self.state = ConnectionStates.CONNECTED
466
+ log.info('%s: Connection complete.', self)
463
467
  self._reset_reconnect_backoff()
464
468
  self.config['state_change_callback'](self.node_id, self._sock, self)
465
469
 
@@ -468,7 +472,7 @@ class BrokerConnection(object):
468
472
  # Connection timed out
469
473
  request_timeout = self.config['request_timeout_ms'] / 1000.0
470
474
  if time.time() > request_timeout + self.last_attempt:
471
- log.error('Connection attempt to %s timed out', self)
475
+ log.error('%s: Connection attempt timed out', self)
472
476
  self.close(Errors.KafkaConnectionError('timeout'))
473
477
  return self.state
474
478
 
@@ -527,7 +531,7 @@ class BrokerConnection(object):
527
531
  except (SSLWantReadError, SSLWantWriteError):
528
532
  pass
529
533
  except (SSLZeroReturnError, ConnectionError, TimeoutError, SSLEOFError):
530
- log.warning('SSL connection closed by server during handshake.')
534
+ log.warning('%s: SSL connection closed by server during handshake.', self)
531
535
  self.close(Errors.KafkaConnectionError('SSL connection closed by server during handshake'))
532
536
  # Other SSLErrors will be raised to user
533
537
 
@@ -607,7 +611,7 @@ class BrokerConnection(object):
607
611
  for api_key, min_version, max_version, *rest in response.api_versions
608
612
  ])
609
613
  self._api_version = self._infer_broker_version_from_api_versions(self._api_versions)
610
- log.info('Broker version identified as %s', '.'.join(map(str, self._api_version)))
614
+ log.info('%s: Broker version identified as %s', self, '.'.join(map(str, self._api_version)))
611
615
  future.success(self._api_version)
612
616
  self.connect()
613
617
 
@@ -617,7 +621,7 @@ class BrokerConnection(object):
617
621
  # after failure connection is closed, so state should already be DISCONNECTED
618
622
 
619
623
  def _handle_check_version_response(self, future, version, _response):
620
- log.info('Broker version identified as %s', '.'.join(map(str, version)))
624
+ log.info('%s: Broker version identified as %s', self, '.'.join(map(str, version)))
621
625
  log.info('Set configuration api_version=%s to skip auto'
622
626
  ' check_version requests on startup', version)
623
627
  self._api_versions = BROKER_API_VERSIONS[version]
@@ -747,6 +751,7 @@ class BrokerConnection(object):
747
751
  request = SaslAuthenticateRequest[0](sasl_auth_bytes)
748
752
  self._send(request, blocking=True)
749
753
  else:
754
+ log.debug('%s: Sending %d raw sasl auth bytes to server', self, len(sasl_auth_bytes))
750
755
  try:
751
756
  self._send_bytes_blocking(Int32.encode(len(sasl_auth_bytes)) + sasl_auth_bytes)
752
757
  except (ConnectionError, TimeoutError) as e:
@@ -776,7 +781,7 @@ class BrokerConnection(object):
776
781
  latency_ms = (time.time() - timestamp) * 1000
777
782
  if self._sensors:
778
783
  self._sensors.request_time.record(latency_ms)
779
- log.debug('%s Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
784
+ log.debug('%s: Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
780
785
 
781
786
  error_type = Errors.for_code(response.error_code)
782
787
  if error_type is not Errors.NoError:
@@ -787,6 +792,7 @@ class BrokerConnection(object):
787
792
  return response.auth_bytes
788
793
  else:
789
794
  # unframed bytes w/ SaslHandhake v0
795
+ log.debug('%s: Received %d raw sasl auth bytes from server', self, nbytes)
790
796
  return data[4:]
791
797
 
792
798
  def _sasl_authenticate(self, future):
@@ -930,6 +936,7 @@ class BrokerConnection(object):
930
936
  self._update_reconnect_backoff()
931
937
  self._api_versions_future = None
932
938
  self._sasl_auth_future = None
939
+ self._init_sasl_mechanism()
933
940
  self._protocol = KafkaProtocol(
934
941
  client_id=self.config['client_id'],
935
942
  api_version=self.config['api_version'])
@@ -949,7 +956,8 @@ class BrokerConnection(object):
949
956
 
950
957
  # drop lock before state change callback and processing futures
951
958
  self.config['state_change_callback'](self.node_id, sock, self)
952
- sock.close()
959
+ if sock:
960
+ sock.close()
953
961
  for (_correlation_id, (future, _timestamp, _timeout)) in ifrs:
954
962
  future.failure(error)
955
963
 
@@ -995,7 +1003,7 @@ class BrokerConnection(object):
995
1003
 
996
1004
  correlation_id = self._protocol.send_request(request)
997
1005
 
998
- log.debug('%s Request %d (timeout_ms %s): %s', self, correlation_id, request_timeout_ms, request)
1006
+ log.debug('%s: Request %d (timeout_ms %s): %s', self, correlation_id, request_timeout_ms, request)
999
1007
  if request.expect_response():
1000
1008
  assert correlation_id not in self.in_flight_requests, 'Correlation ID already in-flight!'
1001
1009
  sent_time = time.time()
@@ -1029,7 +1037,7 @@ class BrokerConnection(object):
1029
1037
  return True
1030
1038
 
1031
1039
  except (ConnectionError, TimeoutError) as e:
1032
- log.exception("Error sending request data to %s", self)
1040
+ log.exception("%s: Error sending request data", self)
1033
1041
  error = Errors.KafkaConnectionError("%s: %s" % (self, e))
1034
1042
  self.close(error=error)
1035
1043
  return False
@@ -1062,7 +1070,7 @@ class BrokerConnection(object):
1062
1070
  return len(self._send_buffer) == 0
1063
1071
 
1064
1072
  except (ConnectionError, TimeoutError, Exception) as e:
1065
- log.exception("Error sending request data to %s", self)
1073
+ log.exception("%s: Error sending request data", self)
1066
1074
  error = Errors.KafkaConnectionError("%s: %s" % (self, e))
1067
1075
  self.close(error=error)
1068
1076
  return False
@@ -1099,7 +1107,7 @@ class BrokerConnection(object):
1099
1107
  if not responses and self.requests_timed_out():
1100
1108
  timed_out = self.timed_out_ifrs()
1101
1109
  timeout_ms = (timed_out[0][2] - timed_out[0][1]) * 1000
1102
- log.warning('%s timed out after %s ms. Closing connection.',
1110
+ log.warning('%s: timed out after %s ms. Closing connection.',
1103
1111
  self, timeout_ms)
1104
1112
  self.close(error=Errors.RequestTimedOutError(
1105
1113
  'Request timed out after %s ms' %
@@ -1118,7 +1126,7 @@ class BrokerConnection(object):
1118
1126
  if self._sensors:
1119
1127
  self._sensors.request_time.record(latency_ms)
1120
1128
 
1121
- log.debug('%s Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
1129
+ log.debug('%s: Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
1122
1130
  self._maybe_throttle(response)
1123
1131
  responses[i] = (response, future)
1124
1132
 
@@ -1130,7 +1138,7 @@ class BrokerConnection(object):
1130
1138
  err = None
1131
1139
  with self._lock:
1132
1140
  if not self._can_send_recv():
1133
- log.warning('%s cannot recv: socket not connected', self)
1141
+ log.warning('%s: cannot recv: socket not connected', self)
1134
1142
  return ()
1135
1143
 
1136
1144
  while len(recvd) < self.config['sock_chunk_buffer_count']:
@@ -114,6 +114,7 @@ class Fetcher(six.Iterator):
114
114
  self._sensors = FetchManagerMetrics(metrics, self.config['metric_group_prefix'])
115
115
  self._isolation_level = READ_UNCOMMITTED
116
116
  self._session_handlers = {}
117
+ self._nodes_with_pending_fetch_requests = set()
117
118
 
118
119
  def send_fetches(self):
119
120
  """Send FetchRequests for all assigned partitions that do not already have
@@ -124,12 +125,12 @@ class Fetcher(six.Iterator):
124
125
  """
125
126
  futures = []
126
127
  for node_id, (request, fetch_offsets) in six.iteritems(self._create_fetch_requests()):
127
- if self._client.ready(node_id):
128
- log.debug("Sending FetchRequest to node %s", node_id)
129
- future = self._client.send(node_id, request, wakeup=False)
130
- future.add_callback(self._handle_fetch_response, node_id, fetch_offsets, time.time())
131
- future.add_errback(self._handle_fetch_error, node_id)
132
- futures.append(future)
128
+ log.debug("Sending FetchRequest to node %s", node_id)
129
+ self._nodes_with_pending_fetch_requests.add(node_id)
130
+ future = self._client.send(node_id, request, wakeup=False)
131
+ future.add_callback(self._handle_fetch_response, node_id, fetch_offsets, time.time())
132
+ future.add_errback(self._handle_fetch_error, node_id)
133
+ futures.append(future)
133
134
  self._fetch_futures.extend(futures)
134
135
  self._clean_done_fetch_futures()
135
136
  return futures
@@ -593,8 +594,20 @@ class Fetcher(six.Iterator):
593
594
  " Requesting metadata update", partition)
594
595
  self._client.cluster.request_update()
595
596
 
596
- elif self._client.in_flight_request_count(node_id) > 0:
597
- log.log(0, "Skipping fetch for partition %s because there is an inflight request to node %s",
597
+ elif not self._client.connected(node_id) and self._client.connection_delay(node_id) > 0:
598
+ # If we try to send during the reconnect backoff window, then the request is just
599
+ # going to be failed anyway before being sent, so skip the send for now
600
+ log.log(0, "Skipping fetch for partition %s because node %s is awaiting reconnect backoff",
601
+ partition, node_id)
602
+
603
+ elif self._client.throttle_delay(node_id) > 0:
604
+ # If we try to send while throttled, then the request is just
605
+ # going to be failed anyway before being sent, so skip the send for now
606
+ log.log(0, "Skipping fetch for partition %s because node %s is throttled",
607
+ partition, node_id)
608
+
609
+ elif node_id in self._nodes_with_pending_fetch_requests:
610
+ log.log(0, "Skipping fetch for partition %s because there is a pending fetch request to node %s",
598
611
  partition, node_id)
599
612
  continue
600
613
 
@@ -707,12 +720,14 @@ class Fetcher(six.Iterator):
707
720
  self._completed_fetches.append(completed_fetch)
708
721
 
709
722
  self._sensors.fetch_latency.record((time.time() - send_time) * 1000)
723
+ self._nodes_with_pending_fetch_requests.remove(node_id)
710
724
 
711
725
  def _handle_fetch_error(self, node_id, exception):
712
726
  level = logging.INFO if isinstance(exception, Errors.Cancelled) else logging.ERROR
713
727
  log.log(level, 'Fetch to node %s failed: %s', node_id, exception)
714
728
  if node_id in self._session_handlers:
715
729
  self._session_handlers[node_id].handle_error(exception)
730
+ self._nodes_with_pending_fetch_requests.remove(node_id)
716
731
 
717
732
  def _parse_fetched_data(self, completed_fetch):
718
733
  tp = completed_fetch.topic_partition
@@ -172,6 +172,7 @@ class MetadataRequest_v0(Request):
172
172
  ('topics', Array(String('utf-8')))
173
173
  )
174
174
  ALL_TOPICS = [] # Empty Array (len 0) for topics returns all topics
175
+ NO_TOPICS = [] # v0 does not support a 'no topics' request, so we'll just ask for ALL
175
176
 
176
177
 
177
178
  class MetadataRequest_v1(Request):
@@ -1,10 +1,14 @@
1
1
  from __future__ import absolute_import
2
2
 
3
3
  import abc
4
+ import logging
4
5
 
5
6
  from kafka.sasl.abc import SaslMechanism
6
7
 
7
8
 
9
+ log = logging.getLogger(__name__)
10
+
11
+
8
12
  class SaslMechanismOAuth(SaslMechanism):
9
13
 
10
14
  def __init__(self, **config):
@@ -12,17 +16,26 @@ class SaslMechanismOAuth(SaslMechanism):
12
16
  assert isinstance(config['sasl_oauth_token_provider'], AbstractTokenProvider), \
13
17
  'sasl_oauth_token_provider must implement kafka.sasl.oauth.AbstractTokenProvider'
14
18
  self.token_provider = config['sasl_oauth_token_provider']
19
+ self._error = None
15
20
  self._is_done = False
16
21
  self._is_authenticated = False
17
22
 
18
23
  def auth_bytes(self):
24
+ if self._error:
25
+ # Server should respond to this with SaslAuthenticate failure, which ends the auth process
26
+ return self._error
19
27
  token = self.token_provider.token()
20
28
  extensions = self._token_extensions()
21
29
  return "n,,\x01auth=Bearer {}{}\x01\x01".format(token, extensions).encode('utf-8')
22
30
 
23
31
  def receive(self, auth_bytes):
24
- self._is_done = True
25
- self._is_authenticated = auth_bytes == b''
32
+ if auth_bytes != b'':
33
+ error = auth_bytes.decode('utf-8')
34
+ log.debug("Sending x01 response to server after receiving SASL OAuth error: %s", error)
35
+ self._error = b'\x01'
36
+ else:
37
+ self._is_done = True
38
+ self._is_authenticated = True
26
39
 
27
40
  def is_done(self):
28
41
  return self._is_done
@@ -0,0 +1 @@
1
+ __version__ = '2.1.4'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kafka-python
3
- Version: 2.1.3
3
+ Version: 2.1.4
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
@@ -32,7 +32,7 @@ def cli(mocker, conn):
32
32
 
33
33
  def test_bootstrap(mocker, conn):
34
34
  conn.state = ConnectionStates.CONNECTED
35
- cli = KafkaClient(api_version=(0, 9))
35
+ cli = KafkaClient(api_version=(2, 1))
36
36
  mocker.patch.object(cli, '_selector')
37
37
  future = cli.cluster.request_update()
38
38
  cli.poll(future=future)
@@ -43,7 +43,7 @@ def test_bootstrap(mocker, conn):
43
43
  kwargs.pop('state_change_callback')
44
44
  kwargs.pop('node_id')
45
45
  assert kwargs == cli.config
46
- conn.send.assert_called_once_with(MetadataRequest[0]([]), blocking=False, request_timeout_ms=None)
46
+ conn.send.assert_called_once_with(MetadataRequest[7]([], True), blocking=False, request_timeout_ms=None)
47
47
  assert cli._bootstrap_fails == 0
48
48
  assert cli.cluster.brokers() == set([BrokerMetadata(0, 'foo', 12, None),
49
49
  BrokerMetadata(1, 'bar', 34, None)])
@@ -330,6 +330,7 @@ def test_maybe_refresh_metadata_update(mocker, client):
330
330
  mocker.patch.object(client, 'least_loaded_node', return_value='foobar')
331
331
  mocker.patch.object(client, '_can_send_request', return_value=True)
332
332
  send = mocker.patch.object(client, 'send')
333
+ client.cluster.need_all_topic_metadata = True
333
334
 
334
335
  client.poll(timeout_ms=12345678)
335
336
  client._poll.assert_called_with(9999.999) # request_timeout_ms
@@ -423,6 +423,7 @@ def test_fetched_records(fetcher, topic, mocker):
423
423
  ),
424
424
  ])
425
425
  def test__handle_fetch_response(fetcher, fetch_offsets, fetch_response, num_partitions):
426
+ fetcher._nodes_with_pending_fetch_requests.add(0)
426
427
  fetcher._handle_fetch_response(0, fetch_offsets, time.time(), fetch_response)
427
428
  assert len(fetcher._completed_fetches) == num_partitions
428
429
 
@@ -438,6 +439,7 @@ def test__handle_fetch_response(fetcher, fetch_offsets, fetch_response, num_part
438
439
  )
439
440
  ])
440
441
  def test__handle_fetch_error(fetcher, caplog, exception, log_level):
442
+ fetcher._nodes_with_pending_fetch_requests.add(3)
441
443
  fetcher._handle_fetch_error(3, exception)
442
444
  assert len(caplog.records) == 1
443
445
  assert caplog.records[0].levelname == logging.getLevelName(log_level)
@@ -1 +0,0 @@
1
- __version__ = '2.1.3'
File without changes
File without changes
File without changes
File without changes
@@ -3,9 +3,9 @@ from __future__ import absolute_import
3
3
  import abc
4
4
  from collections import defaultdict, OrderedDict
5
5
  try:
6
- from collections import Sequence
7
- except ImportError:
8
6
  from collections.abc import Sequence
7
+ except ImportError:
8
+ from collections import Sequence
9
9
  try:
10
10
  # enum in stdlib as of py3.4
11
11
  from enum import IntEnum # pylint: disable=import-error
File without changes
File without changes
File without changes