kafka-python 2.0.5__tar.gz → 2.0.6__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.
- {kafka_python-2.0.5 → kafka_python-2.0.6}/CHANGES.md +18 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/PKG-INFO +1 -1
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/admin/client.py +1 -1
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/client_async.py +70 -37
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/consumer/fetcher.py +12 -2
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/consumer.py +4 -1
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/record/default_records.py +45 -2
- kafka_python-2.0.6/kafka/version.py +1 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka_python.egg-info/PKG-INFO +1 -1
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_client_async.py +13 -19
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_conn.py +1 -1
- kafka_python-2.0.5/kafka/version.py +0 -1
- {kafka_python-2.0.5 → kafka_python-2.0.6}/AUTHORS.md +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/LICENSE +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/MANIFEST.in +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/README.rst +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/cluster.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/codec.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/conn.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/consumer/group.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/consumer/subscription_state.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/base.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/errors.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/future.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/metric_name.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/metrics.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/quota.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/oauth/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/oauth/abstract.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/producer/buffer.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/producer/future.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/producer/kafka.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/producer/record_accumulator.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/producer/sender.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/admin.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/api.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/commit.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/fetch.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/group.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/message.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/metadata.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/offset.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/parser.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/produce.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/protocol/types.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/record/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/record/abc.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/record/legacy_records.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/record/memory_records.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/record/util.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/scram.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/structs.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/util.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/vendor/six.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka_python.egg-info/SOURCES.txt +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka_python.egg-info/requires.txt +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/kafka_python.egg-info/top_level.txt +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/pyproject.toml +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/setup.cfg +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/setup.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_admin.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_admin_integration.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_assignors.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_cluster.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_codec.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_consumer.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_consumer_group.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_consumer_integration.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_coordinator.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_fetcher.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_metrics.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_object_conversion.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_package.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_partition_movements.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_partitioner.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_producer.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_protocol.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_sasl_integration.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_sender.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/test_subscription_state.py +0 -0
- {kafka_python-2.0.5 → kafka_python-2.0.6}/test/testutil.py +0 -0
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# 2.0.6 (Mar 4, 2025)
|
|
2
|
+
|
|
3
|
+
Networking
|
|
4
|
+
* Improve error handling in `client._maybe_connect` (#2504)
|
|
5
|
+
* Client connection / `maybe_refresh_metadata` changes (#2507)
|
|
6
|
+
* Improve too-large timeout handling in client poll
|
|
7
|
+
* Default `client.check_version` timeout to `api_version_auto_timeout_ms` (#2496)
|
|
8
|
+
|
|
9
|
+
Fixes
|
|
10
|
+
* Decode and skip transactional control records in consumer (#2499)
|
|
11
|
+
* try / except in consumer coordinator `__del__`
|
|
12
|
+
|
|
13
|
+
Testing
|
|
14
|
+
* test_conn fixup for py2
|
|
15
|
+
|
|
16
|
+
Project Maintenance
|
|
17
|
+
* Add 2.0 branch for backports
|
|
18
|
+
|
|
1
19
|
# 2.0.5 (Feb 25, 2025)
|
|
2
20
|
|
|
3
21
|
Networking
|
|
@@ -294,7 +294,7 @@ class KafkaAdminClient(object):
|
|
|
294
294
|
time.sleep(1)
|
|
295
295
|
continue
|
|
296
296
|
# verify the controller is new enough to support our requests
|
|
297
|
-
controller_version = self._client.check_version(node_id=controller_id
|
|
297
|
+
controller_version = self._client.check_version(node_id=controller_id)
|
|
298
298
|
if controller_version < (0, 10, 0):
|
|
299
299
|
raise IncompatibleBrokerVersion(
|
|
300
300
|
"The controller appears to be running Kafka {}. KafkaAdminClient requires brokers >= 0.10.0.0."
|
|
@@ -237,8 +237,7 @@ class KafkaClient(object):
|
|
|
237
237
|
|
|
238
238
|
# Check Broker Version if not set explicitly
|
|
239
239
|
if self.config['api_version'] is None:
|
|
240
|
-
|
|
241
|
-
self.config['api_version'] = self.check_version(timeout=check_timeout)
|
|
240
|
+
self.config['api_version'] = self.check_version()
|
|
242
241
|
|
|
243
242
|
def _init_wakeup_socketpair(self):
|
|
244
243
|
self._wake_r, self._wake_w = socket.socketpair()
|
|
@@ -364,14 +363,24 @@ class KafkaClient(object):
|
|
|
364
363
|
|
|
365
364
|
return False
|
|
366
365
|
|
|
367
|
-
def
|
|
368
|
-
"""Idempotent non-blocking connection attempt to the given node id.
|
|
366
|
+
def _init_connect(self, node_id):
|
|
367
|
+
"""Idempotent non-blocking connection attempt to the given node id.
|
|
368
|
+
|
|
369
|
+
Returns True if connection object exists and is connected / connecting
|
|
370
|
+
"""
|
|
369
371
|
with self._lock:
|
|
370
372
|
conn = self._conns.get(node_id)
|
|
371
373
|
|
|
374
|
+
# Check if existing connection should be recreated because host/port changed
|
|
375
|
+
if conn is not None and self._should_recycle_connection(conn):
|
|
376
|
+
self._conns.pop(node_id).close()
|
|
377
|
+
conn = None
|
|
378
|
+
|
|
372
379
|
if conn is None:
|
|
373
380
|
broker = self.cluster.broker_metadata(node_id)
|
|
374
|
-
|
|
381
|
+
if broker is None:
|
|
382
|
+
log.debug('Broker id %s not in current metadata', node_id)
|
|
383
|
+
return False
|
|
375
384
|
|
|
376
385
|
log.debug("Initiating connection to node %s at %s:%s",
|
|
377
386
|
node_id, broker.host, broker.port)
|
|
@@ -383,16 +392,9 @@ class KafkaClient(object):
|
|
|
383
392
|
**self.config)
|
|
384
393
|
self._conns[node_id] = conn
|
|
385
394
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return False
|
|
390
|
-
|
|
391
|
-
elif conn.connected():
|
|
392
|
-
return True
|
|
393
|
-
|
|
394
|
-
conn.connect()
|
|
395
|
-
return conn.connected()
|
|
395
|
+
if conn.disconnected():
|
|
396
|
+
conn.connect()
|
|
397
|
+
return not conn.disconnected()
|
|
396
398
|
|
|
397
399
|
def ready(self, node_id, metadata_priority=True):
|
|
398
400
|
"""Check whether a node is connected and ok to send more requests.
|
|
@@ -576,12 +578,18 @@ class KafkaClient(object):
|
|
|
576
578
|
if self._closed:
|
|
577
579
|
break
|
|
578
580
|
|
|
579
|
-
# Send a metadata request if needed (or initiate new connection)
|
|
580
|
-
metadata_timeout_ms = self._maybe_refresh_metadata()
|
|
581
|
-
|
|
582
581
|
# Attempt to complete pending connections
|
|
583
582
|
for node_id in list(self._connecting):
|
|
584
|
-
|
|
583
|
+
# False return means no more connection progress is possible
|
|
584
|
+
# Connected nodes will update _connecting via state_change callback
|
|
585
|
+
if not self._init_connect(node_id):
|
|
586
|
+
# It's possible that the connection attempt triggered a state change
|
|
587
|
+
# but if not, make sure to remove from _connecting list
|
|
588
|
+
if node_id in self._connecting:
|
|
589
|
+
self._connecting.remove(node_id)
|
|
590
|
+
|
|
591
|
+
# Send a metadata request if needed (or initiate new connection)
|
|
592
|
+
metadata_timeout_ms = self._maybe_refresh_metadata()
|
|
585
593
|
|
|
586
594
|
# If we got a future that is already done, don't block in _poll
|
|
587
595
|
if future is not None and future.is_done:
|
|
@@ -623,6 +631,11 @@ class KafkaClient(object):
|
|
|
623
631
|
self._selector.register(conn._sock, selectors.EVENT_WRITE, conn)
|
|
624
632
|
|
|
625
633
|
def _poll(self, timeout):
|
|
634
|
+
# Python throws OverflowError if timeout is > 2147483647 milliseconds
|
|
635
|
+
# (though the param to selector.select is in seconds)
|
|
636
|
+
# so convert any too-large timeout to blocking
|
|
637
|
+
if timeout > 2147483:
|
|
638
|
+
timeout = None
|
|
626
639
|
# This needs to be locked, but since it is only called from within the
|
|
627
640
|
# locked section of poll(), there is no additional lock acquisition here
|
|
628
641
|
processed = set()
|
|
@@ -843,6 +856,26 @@ class KafkaClient(object):
|
|
|
843
856
|
log.debug("Give up sending metadata request since no node is available. (reconnect delay %d ms)", next_connect_ms)
|
|
844
857
|
return next_connect_ms
|
|
845
858
|
|
|
859
|
+
if not self._can_send_request(node_id):
|
|
860
|
+
# If there's any connection establishment underway, wait until it completes. This prevents
|
|
861
|
+
# the client from unnecessarily connecting to additional nodes while a previous connection
|
|
862
|
+
# attempt has not been completed.
|
|
863
|
+
if self._connecting:
|
|
864
|
+
return float('inf')
|
|
865
|
+
|
|
866
|
+
elif self._can_connect(node_id):
|
|
867
|
+
log.debug("Initializing connection to node %s for metadata request", node_id)
|
|
868
|
+
self._connecting.add(node_id)
|
|
869
|
+
if not self._init_connect(node_id):
|
|
870
|
+
if node_id in self._connecting:
|
|
871
|
+
self._connecting.remove(node_id)
|
|
872
|
+
# Connection attempt failed immediately, need to retry with a different node
|
|
873
|
+
return self.config['reconnect_backoff_ms']
|
|
874
|
+
else:
|
|
875
|
+
# Existing connection with max in flight requests. Wait for request to complete.
|
|
876
|
+
return self.config['request_timeout_ms']
|
|
877
|
+
|
|
878
|
+
# Recheck node_id in case we were able to connect immediately above
|
|
846
879
|
if self._can_send_request(node_id):
|
|
847
880
|
topics = list(self._topics)
|
|
848
881
|
if not topics and self.cluster.is_bootstrap(node_id):
|
|
@@ -864,20 +897,11 @@ class KafkaClient(object):
|
|
|
864
897
|
future.add_errback(refresh_done)
|
|
865
898
|
return self.config['request_timeout_ms']
|
|
866
899
|
|
|
867
|
-
#
|
|
868
|
-
# the client from unnecessarily connecting to additional nodes while a previous connection
|
|
869
|
-
# attempt has not been completed.
|
|
900
|
+
# Should only get here if still connecting
|
|
870
901
|
if self._connecting:
|
|
871
902
|
return float('inf')
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
log.debug("Initializing connection to node %s for metadata request", node_id)
|
|
875
|
-
return float('inf')
|
|
876
|
-
|
|
877
|
-
# connected but can't send more, OR connecting
|
|
878
|
-
# In either case we just need to wait for a network event
|
|
879
|
-
# to let us know the selected connection might be usable again.
|
|
880
|
-
return float('inf')
|
|
903
|
+
else:
|
|
904
|
+
return self.config['reconnect_backoff_ms']
|
|
881
905
|
|
|
882
906
|
def get_api_versions(self):
|
|
883
907
|
"""Return the ApiVersions map, if available.
|
|
@@ -890,13 +914,16 @@ class KafkaClient(object):
|
|
|
890
914
|
"""
|
|
891
915
|
return self._api_versions
|
|
892
916
|
|
|
893
|
-
def check_version(self, node_id=None, timeout=
|
|
917
|
+
def check_version(self, node_id=None, timeout=None, strict=False):
|
|
894
918
|
"""Attempt to guess the version of a Kafka broker.
|
|
895
919
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
920
|
+
Keyword Arguments:
|
|
921
|
+
node_id (str, optional): Broker node id from cluster metadata. If None, attempts
|
|
922
|
+
to connect to any available broker until version is identified.
|
|
923
|
+
Default: None
|
|
924
|
+
timeout (num, optional): Maximum time in seconds to try to check broker version.
|
|
925
|
+
If unable to identify version before timeout, raise error (see below).
|
|
926
|
+
Default: api_version_auto_timeout_ms / 1000
|
|
900
927
|
|
|
901
928
|
Returns: version tuple, i.e. (0, 10), (0, 9), (0, 8, 2), ...
|
|
902
929
|
|
|
@@ -906,6 +933,7 @@ class KafkaClient(object):
|
|
|
906
933
|
UnrecognizedBrokerVersion: please file bug if seen!
|
|
907
934
|
AssertionError (if strict=True): please file bug if seen!
|
|
908
935
|
"""
|
|
936
|
+
timeout = timeout or (self.config['api_version_auto_timeout_ms'] / 1000)
|
|
909
937
|
self._lock.acquire()
|
|
910
938
|
end = time.time() + timeout
|
|
911
939
|
while time.time() < end:
|
|
@@ -916,7 +944,12 @@ class KafkaClient(object):
|
|
|
916
944
|
if try_node is None:
|
|
917
945
|
self._lock.release()
|
|
918
946
|
raise Errors.NoBrokersAvailable()
|
|
919
|
-
self.
|
|
947
|
+
if not self._init_connect(try_node):
|
|
948
|
+
if try_node == node_id:
|
|
949
|
+
raise Errors.NodeNotReadyError("Connection failed to %s" % node_id)
|
|
950
|
+
else:
|
|
951
|
+
continue
|
|
952
|
+
|
|
920
953
|
conn = self._conns[try_node]
|
|
921
954
|
|
|
922
955
|
# We will intentionally cause socket failures
|
|
@@ -457,10 +457,20 @@ class Fetcher(six.Iterator):
|
|
|
457
457
|
batch = records.next_batch()
|
|
458
458
|
while batch is not None:
|
|
459
459
|
|
|
460
|
-
#
|
|
460
|
+
# Try DefaultsRecordBatch / message log format v2
|
|
461
|
+
# base_offset, last_offset_delta, and control batches
|
|
461
462
|
try:
|
|
462
463
|
self._subscriptions.assignment[tp].last_offset_from_message_batch = batch.base_offset + \
|
|
463
464
|
batch.last_offset_delta
|
|
465
|
+
# Control batches have a single record indicating whether a transaction
|
|
466
|
+
# was aborted or committed.
|
|
467
|
+
# When isolation_level is READ_COMMITTED (currently unsupported)
|
|
468
|
+
# we should also skip all messages from aborted transactions
|
|
469
|
+
# For now we only support READ_UNCOMMITTED and so we ignore the
|
|
470
|
+
# abort/commit signal.
|
|
471
|
+
if batch.is_control_batch:
|
|
472
|
+
batch = records.next_batch()
|
|
473
|
+
continue
|
|
464
474
|
except AttributeError:
|
|
465
475
|
pass
|
|
466
476
|
|
|
@@ -677,7 +687,7 @@ class Fetcher(six.Iterator):
|
|
|
677
687
|
if next_offset_from_batch_header > self._subscriptions.assignment[partition].position:
|
|
678
688
|
log.debug(
|
|
679
689
|
"Advance position for partition %s from %s to %s (last message batch location plus one)"
|
|
680
|
-
" to correct for deleted compacted messages",
|
|
690
|
+
" to correct for deleted compacted messages and/or transactional control records",
|
|
681
691
|
partition, self._subscriptions.assignment[partition].position, next_offset_from_batch_header)
|
|
682
692
|
self._subscriptions.assignment[partition].position = next_offset_from_batch_header
|
|
683
693
|
|
|
@@ -128,7 +128,10 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
128
128
|
|
|
129
129
|
def __del__(self):
|
|
130
130
|
if hasattr(self, '_cluster') and self._cluster:
|
|
131
|
-
|
|
131
|
+
try:
|
|
132
|
+
self._cluster.remove_listener(WeakMethod(self._handle_metadata_update))
|
|
133
|
+
except TypeError:
|
|
134
|
+
pass
|
|
132
135
|
super(ConsumerCoordinator, self).__del__()
|
|
133
136
|
|
|
134
137
|
def protocol_type(self):
|
|
@@ -269,8 +269,12 @@ class DefaultRecordBatch(DefaultRecordBase, ABCRecordBatch):
|
|
|
269
269
|
"payload, but instead read {}".format(length, pos - start_pos))
|
|
270
270
|
self._pos = pos
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
if self.is_control_batch:
|
|
273
|
+
return ControlRecord(
|
|
274
|
+
offset, timestamp, self.timestamp_type, key, value, headers)
|
|
275
|
+
else:
|
|
276
|
+
return DefaultRecord(
|
|
277
|
+
offset, timestamp, self.timestamp_type, key, value, headers)
|
|
274
278
|
|
|
275
279
|
def __iter__(self):
|
|
276
280
|
self._maybe_uncompress()
|
|
@@ -362,6 +366,45 @@ class DefaultRecord(ABCRecord):
|
|
|
362
366
|
)
|
|
363
367
|
|
|
364
368
|
|
|
369
|
+
class ControlRecord(DefaultRecord):
|
|
370
|
+
__slots__ = ("_offset", "_timestamp", "_timestamp_type", "_key", "_value",
|
|
371
|
+
"_headers", "_version", "_type")
|
|
372
|
+
|
|
373
|
+
KEY_STRUCT = struct.Struct(
|
|
374
|
+
">h" # Current Version => Int16
|
|
375
|
+
"h" # Type => Int16 (0 indicates an abort marker, 1 indicates a commit)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def __init__(self, offset, timestamp, timestamp_type, key, value, headers):
|
|
379
|
+
super(ControlRecord, self).__init__(offset, timestamp, timestamp_type, key, value, headers)
|
|
380
|
+
(self._version, self._type) = self.KEY_STRUCT.unpack(self._key)
|
|
381
|
+
|
|
382
|
+
# see https://kafka.apache.org/documentation/#controlbatch
|
|
383
|
+
@property
|
|
384
|
+
def version(self):
|
|
385
|
+
return self._version
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def type(self):
|
|
389
|
+
return self._type
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def abort(self):
|
|
393
|
+
return self._type == 0
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def commit(self):
|
|
397
|
+
return self._type == 1
|
|
398
|
+
|
|
399
|
+
def __repr__(self):
|
|
400
|
+
return (
|
|
401
|
+
"ControlRecord(offset={!r}, timestamp={!r}, timestamp_type={!r},"
|
|
402
|
+
" version={!r}, type={!r} <{!s}>)".format(
|
|
403
|
+
self._offset, self._timestamp, self._timestamp_type,
|
|
404
|
+
self._version, self._type, "abort" if self.abort else "commit")
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
365
408
|
class DefaultRecordBatchBuilder(DefaultRecordBase, ABCRecordBatchBuilder):
|
|
366
409
|
|
|
367
410
|
# excluding key, value and headers:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.0.6'
|
|
@@ -58,7 +58,7 @@ def test_can_connect(cli, conn):
|
|
|
58
58
|
assert cli._can_connect(0)
|
|
59
59
|
|
|
60
60
|
# Node is connected, can't reconnect
|
|
61
|
-
assert cli.
|
|
61
|
+
assert cli._init_connect(0) is True
|
|
62
62
|
assert not cli._can_connect(0)
|
|
63
63
|
|
|
64
64
|
# Node is disconnected, can connect
|
|
@@ -70,20 +70,15 @@ def test_can_connect(cli, conn):
|
|
|
70
70
|
assert not cli._can_connect(0)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
cli._maybe_connect(2)
|
|
77
|
-
except AssertionError:
|
|
78
|
-
pass
|
|
79
|
-
else:
|
|
80
|
-
assert False, 'Exception not raised'
|
|
73
|
+
def test_init_connect(cli, conn):
|
|
74
|
+
# Node not in metadata, return False
|
|
75
|
+
assert not cli._init_connect(2)
|
|
81
76
|
|
|
82
77
|
# New node_id creates a conn object
|
|
83
78
|
assert 0 not in cli._conns
|
|
84
79
|
conn.state = ConnectionStates.DISCONNECTED
|
|
85
80
|
conn.connect.side_effect = lambda: conn._set_conn_state(ConnectionStates.CONNECTING)
|
|
86
|
-
assert cli.
|
|
81
|
+
assert cli._init_connect(0) is True
|
|
87
82
|
assert cli._conns[0] is conn
|
|
88
83
|
|
|
89
84
|
|
|
@@ -127,8 +122,8 @@ def test_ready(mocker, cli, conn):
|
|
|
127
122
|
|
|
128
123
|
|
|
129
124
|
def test_is_ready(mocker, cli, conn):
|
|
130
|
-
cli.
|
|
131
|
-
cli.
|
|
125
|
+
cli._init_connect(0)
|
|
126
|
+
cli._init_connect(1)
|
|
132
127
|
|
|
133
128
|
# metadata refresh blocks ready nodes
|
|
134
129
|
assert cli.is_ready(0)
|
|
@@ -171,14 +166,14 @@ def test_close(mocker, cli, conn):
|
|
|
171
166
|
assert conn.close.call_count == call_count
|
|
172
167
|
|
|
173
168
|
# Single node close
|
|
174
|
-
cli.
|
|
169
|
+
cli._init_connect(0)
|
|
175
170
|
assert conn.close.call_count == call_count
|
|
176
171
|
cli.close(0)
|
|
177
172
|
call_count += 1
|
|
178
173
|
assert conn.close.call_count == call_count
|
|
179
174
|
|
|
180
175
|
# All node close
|
|
181
|
-
cli.
|
|
176
|
+
cli._init_connect(1)
|
|
182
177
|
cli.close()
|
|
183
178
|
# +2 close: node 1, node bootstrap (node 0 already closed)
|
|
184
179
|
call_count += 2
|
|
@@ -190,7 +185,7 @@ def test_is_disconnected(cli, conn):
|
|
|
190
185
|
conn.state = ConnectionStates.DISCONNECTED
|
|
191
186
|
assert not cli.is_disconnected(0)
|
|
192
187
|
|
|
193
|
-
cli.
|
|
188
|
+
cli._init_connect(0)
|
|
194
189
|
assert cli.is_disconnected(0)
|
|
195
190
|
|
|
196
191
|
conn.state = ConnectionStates.CONNECTING
|
|
@@ -215,7 +210,7 @@ def test_send(cli, conn):
|
|
|
215
210
|
assert isinstance(f.exception, Errors.NodeNotReadyError)
|
|
216
211
|
|
|
217
212
|
conn.state = ConnectionStates.CONNECTED
|
|
218
|
-
cli.
|
|
213
|
+
cli._init_connect(0)
|
|
219
214
|
# ProduceRequest w/ 0 required_acks -> no response
|
|
220
215
|
request = ProduceRequest[0](0, 0, [])
|
|
221
216
|
assert request.expect_response() is False
|
|
@@ -344,8 +339,7 @@ def test_maybe_refresh_metadata_cant_send(mocker, client):
|
|
|
344
339
|
mocker.patch.object(client, 'least_loaded_node', return_value='foobar')
|
|
345
340
|
mocker.patch.object(client, '_can_send_request', return_value=False)
|
|
346
341
|
mocker.patch.object(client, '_can_connect', return_value=True)
|
|
347
|
-
mocker.patch.object(client, '
|
|
348
|
-
mocker.patch.object(client, 'maybe_connect', return_value=True)
|
|
342
|
+
mocker.patch.object(client, '_init_connect', return_value=True)
|
|
349
343
|
|
|
350
344
|
now = time.time()
|
|
351
345
|
t = mocker.patch('time.time')
|
|
@@ -354,7 +348,7 @@ def test_maybe_refresh_metadata_cant_send(mocker, client):
|
|
|
354
348
|
# first poll attempts connection
|
|
355
349
|
client.poll(timeout_ms=12345678)
|
|
356
350
|
client._poll.assert_called_with(12345.678)
|
|
357
|
-
client.
|
|
351
|
+
client._init_connect.assert_called_once_with('foobar')
|
|
358
352
|
|
|
359
353
|
# poll while connecting should not attempt a new connection
|
|
360
354
|
client._connecting.add('foobar')
|
|
@@ -90,7 +90,7 @@ def test_connection_delay(conn, mocker):
|
|
|
90
90
|
conn.state = ConnectionStates.CONNECTED
|
|
91
91
|
assert conn.connection_delay() == float('inf')
|
|
92
92
|
|
|
93
|
-
conn._gai
|
|
93
|
+
del conn._gai[:]
|
|
94
94
|
conn._update_reconnect_backoff()
|
|
95
95
|
conn.state = ConnectionStates.DISCONNECTED
|
|
96
96
|
assert conn.connection_delay() == 1.0 * conn.config['reconnect_backoff_ms']
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.0.5'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/sticky/partition_movements.py
RENAMED
|
File without changes
|
|
File without changes
|
{kafka_python-2.0.5 → kafka_python-2.0.6}/kafka/coordinator/assignors/sticky/sticky_assignor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|