kafka-python 2.1.2__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.
- {kafka_python-2.1.2 → kafka_python-2.1.4}/CHANGES.md +35 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/PKG-INFO +3 -2
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/admin/client.py +1 -1
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/client_async.py +12 -3
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/cluster.py +1 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/conn.py +54 -29
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/consumer/fetcher.py +87 -60
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/consumer/group.py +18 -6
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/consumer/subscription_state.py +68 -56
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/base.py +11 -9
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/consumer.py +12 -5
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/producer/kafka.py +5 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/admin.py +2 -1
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/api.py +8 -6
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/api_versions.py +45 -1
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/broker_api_versions.py +2 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/metadata.py +1 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/parser.py +7 -6
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/oauth.py +15 -2
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/util.py +24 -0
- kafka_python-2.1.4/kafka/version.py +1 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka_python.egg-info/PKG-INFO +3 -2
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka_python.egg-info/SOURCES.txt +1 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/pyproject.toml +1 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_client_async.py +3 -2
- kafka_python-2.1.4/test/test_consumer.py +52 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_consumer_group.py +7 -6
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_consumer_integration.py +2 -2
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_coordinator.py +1 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_fetcher.py +8 -6
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_producer.py +1 -1
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_sasl_integration.py +1 -1
- kafka_python-2.1.4/test/test_subscription_state.py +57 -0
- kafka_python-2.1.2/test/test_subscription_state.py → kafka_python-2.1.4/test/test_util.py +2 -3
- kafka_python-2.1.2/kafka/version.py +0 -1
- kafka_python-2.1.2/test/test_consumer.py +0 -26
- {kafka_python-2.1.2 → kafka_python-2.1.4}/AUTHORS.md +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/LICENSE +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/MANIFEST.in +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/README.rst +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/codec.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/errors.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/future.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/metric_name.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/metrics.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/quota.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/producer/buffer.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/producer/future.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/producer/record_accumulator.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/producer/sender.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/commit.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/fetch.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/find_coordinator.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/group.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/list_offsets.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/message.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/offset_for_leader_epoch.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/produce.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/sasl_authenticate.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/sasl_handshake.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/protocol/types.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/record/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/record/abc.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/record/default_records.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/record/legacy_records.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/record/memory_records.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/record/util.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/abc.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/gssapi.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/msk.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/plain.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/scram.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/sasl/sspi.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/socks5_wrapper.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/structs.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/vendor/six.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka_python.egg-info/requires.txt +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/kafka_python.egg-info/top_level.txt +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/setup.cfg +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/setup.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_admin.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_admin_integration.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_assignors.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_cluster.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_codec.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_conn.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_metrics.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_object_conversion.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_package.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_partition_movements.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_partitioner.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_protocol.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/test_sender.py +0 -0
- {kafka_python-2.1.2 → kafka_python-2.1.4}/test/testutil.py +0 -0
|
@@ -1,3 +1,38 @@
|
|
|
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
|
+
|
|
14
|
+
# 2.1.3 (Mar 25, 2025)
|
|
15
|
+
|
|
16
|
+
Fixes
|
|
17
|
+
* Fix crash when switching to closest compatible api_version in KafkaClient (#2567)
|
|
18
|
+
* Fix maximum version to send an OffsetFetchRequest in KafkaAdminClient (#2563)
|
|
19
|
+
* Return empty set from consumer.partitions_for_topic when topic not found (#2556)
|
|
20
|
+
|
|
21
|
+
Improvements
|
|
22
|
+
* KIP-511: Use ApiVersions v4 on initial connect w/ client_software_name + version (#2558)
|
|
23
|
+
* KIP-74: Manage assigned partition order in consumer (#2562)
|
|
24
|
+
* KIP-70: Auto-commit offsets on consumer.unsubscribe(), defer assignment changes to rejoin (#2560)
|
|
25
|
+
* Use SubscriptionType to track topics/pattern/user assignment (#2565)
|
|
26
|
+
* Add optional timeout_ms kwarg to consumer.close() (#2564)
|
|
27
|
+
* Move ensure_valid_topic_name to kafka.util; use in client and producer (#2561)
|
|
28
|
+
|
|
29
|
+
Testing
|
|
30
|
+
* Support KRaft / 4.0 brokers in tests (#2559)
|
|
31
|
+
* Test older pythons against 4.0 broker
|
|
32
|
+
|
|
33
|
+
Compatibility
|
|
34
|
+
* Add python 3.13 to compatibility list
|
|
35
|
+
|
|
1
36
|
# 2.1.2 (Mar 17, 2025)
|
|
2
37
|
|
|
3
38
|
Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: kafka-python
|
|
3
|
-
Version: 2.1.
|
|
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
|
|
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
25
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
26
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
27
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -1496,7 +1496,7 @@ class KafkaAdminClient(object):
|
|
|
1496
1496
|
A message future
|
|
1497
1497
|
"""
|
|
1498
1498
|
version = self._client.api_version(OffsetFetchRequest, max_version=5)
|
|
1499
|
-
if version <=
|
|
1499
|
+
if version <= 5:
|
|
1500
1500
|
if partitions is None:
|
|
1501
1501
|
if version <= 1:
|
|
1502
1502
|
raise ValueError(
|
|
@@ -27,7 +27,7 @@ from kafka.metrics.stats import Avg, Count, Rate
|
|
|
27
27
|
from kafka.metrics.stats.rate import TimeUnit
|
|
28
28
|
from kafka.protocol.broker_api_versions import BROKER_API_VERSIONS
|
|
29
29
|
from kafka.protocol.metadata import MetadataRequest
|
|
30
|
-
from kafka.util import Dict, WeakMethod
|
|
30
|
+
from kafka.util import Dict, WeakMethod, ensure_valid_topic_name
|
|
31
31
|
# Although this looks unused, it actually monkey-patches socket.socketpair()
|
|
32
32
|
# and should be left in as long as we're using socket.socketpair() in this file
|
|
33
33
|
from kafka.vendor import socketpair # noqa: F401
|
|
@@ -276,6 +276,7 @@ class KafkaClient(object):
|
|
|
276
276
|
if compatible_version:
|
|
277
277
|
log.warning('Configured api_version %s not supported; using %s',
|
|
278
278
|
self.config['api_version'], compatible_version)
|
|
279
|
+
self.config['api_version'] = compatible_version
|
|
279
280
|
self._api_versions = BROKER_API_VERSIONS[compatible_version]
|
|
280
281
|
else:
|
|
281
282
|
raise Errors.UnrecognizedBrokerVersion(self.config['api_version'])
|
|
@@ -364,7 +365,7 @@ class KafkaClient(object):
|
|
|
364
365
|
self._connecting.remove(node_id)
|
|
365
366
|
try:
|
|
366
367
|
self._selector.unregister(sock)
|
|
367
|
-
except KeyError:
|
|
368
|
+
except (KeyError, ValueError):
|
|
368
369
|
pass
|
|
369
370
|
|
|
370
371
|
if self._sensors:
|
|
@@ -909,7 +910,13 @@ class KafkaClient(object):
|
|
|
909
910
|
|
|
910
911
|
Returns:
|
|
911
912
|
Future: resolves after metadata request/response
|
|
913
|
+
|
|
914
|
+
Raises:
|
|
915
|
+
TypeError: if topic is not a string
|
|
916
|
+
ValueError: if topic is invalid: must be chars (a-zA-Z0-9._-), and less than 250 length
|
|
912
917
|
"""
|
|
918
|
+
ensure_valid_topic_name(topic)
|
|
919
|
+
|
|
913
920
|
if topic in self._topics:
|
|
914
921
|
return Future().success(set(self._topics))
|
|
915
922
|
|
|
@@ -971,8 +978,10 @@ class KafkaClient(object):
|
|
|
971
978
|
topics = list(self.config['bootstrap_topics_filter'])
|
|
972
979
|
|
|
973
980
|
api_version = self.api_version(MetadataRequest, max_version=7)
|
|
974
|
-
if self.cluster.need_all_topic_metadata
|
|
981
|
+
if self.cluster.need_all_topic_metadata:
|
|
975
982
|
topics = MetadataRequest[api_version].ALL_TOPICS
|
|
983
|
+
elif not topics:
|
|
984
|
+
topics = MetadataRequest[api_version].NO_TOPICS
|
|
976
985
|
if api_version >= 4:
|
|
977
986
|
request = MetadataRequest[api_version](topics, self.config['allow_auto_create_topics'])
|
|
978
987
|
else:
|
|
@@ -101,6 +101,10 @@ class BrokerConnection(object):
|
|
|
101
101
|
server-side log entries that correspond to this client. Also
|
|
102
102
|
submitted to GroupCoordinator for logging with respect to
|
|
103
103
|
consumer group administration. Default: 'kafka-python-{version}'
|
|
104
|
+
client_software_name (str): Sent to kafka broker for KIP-511.
|
|
105
|
+
Default: 'kafka-python'
|
|
106
|
+
client_software_version (str): Sent to kafka broker for KIP-511.
|
|
107
|
+
Default: The kafka-python version (via kafka.version).
|
|
104
108
|
reconnect_backoff_ms (int): The amount of time in milliseconds to
|
|
105
109
|
wait before attempting to reconnect to a given host.
|
|
106
110
|
Default: 50.
|
|
@@ -191,6 +195,8 @@ class BrokerConnection(object):
|
|
|
191
195
|
|
|
192
196
|
DEFAULT_CONFIG = {
|
|
193
197
|
'client_id': 'kafka-python-' + __version__,
|
|
198
|
+
'client_software_name': 'kafka-python',
|
|
199
|
+
'client_software_version': __version__,
|
|
194
200
|
'node_id': 0,
|
|
195
201
|
'request_timeout_ms': 30000,
|
|
196
202
|
'reconnect_backoff_ms': 50,
|
|
@@ -242,7 +248,7 @@ class BrokerConnection(object):
|
|
|
242
248
|
self._api_versions = None
|
|
243
249
|
self._api_version = None
|
|
244
250
|
self._check_version_idx = None
|
|
245
|
-
self._api_versions_idx =
|
|
251
|
+
self._api_versions_idx = 4 # version of ApiVersionsRequest to try on first connect
|
|
246
252
|
self._throttle_time = None
|
|
247
253
|
self._socks5_proxy = None
|
|
248
254
|
|
|
@@ -265,12 +271,10 @@ class BrokerConnection(object):
|
|
|
265
271
|
assert self.config['security_protocol'] in self.SECURITY_PROTOCOLS, (
|
|
266
272
|
'security_protocol must be in ' + ', '.join(self.SECURITY_PROTOCOLS))
|
|
267
273
|
|
|
268
|
-
self._sasl_mechanism = None
|
|
269
274
|
if self.config['security_protocol'] in ('SSL', 'SASL_SSL'):
|
|
270
275
|
assert ssl_available, "Python wasn't built with SSL support"
|
|
271
276
|
|
|
272
|
-
|
|
273
|
-
self._sasl_mechanism = get_sasl_mechanism(self.config['sasl_mechanism'])(**self.config)
|
|
277
|
+
self._init_sasl_mechanism()
|
|
274
278
|
|
|
275
279
|
# This is not a general lock / this class is not generally thread-safe yet
|
|
276
280
|
# However, to avoid pushing responsibility for maintaining
|
|
@@ -306,11 +310,17 @@ class BrokerConnection(object):
|
|
|
306
310
|
self.config['metric_group_prefix'],
|
|
307
311
|
self.node_id)
|
|
308
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
|
+
|
|
309
319
|
def _dns_lookup(self):
|
|
310
320
|
self._gai = dns_lookup(self.host, self.port, self.afi)
|
|
311
321
|
if not self._gai:
|
|
312
|
-
log.error('DNS lookup failed for %s:%i (%s)',
|
|
313
|
-
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)
|
|
314
324
|
return False
|
|
315
325
|
return True
|
|
316
326
|
|
|
@@ -356,6 +366,7 @@ class BrokerConnection(object):
|
|
|
356
366
|
def connect(self):
|
|
357
367
|
"""Attempt to connect and return ConnectionState"""
|
|
358
368
|
if self.state is ConnectionStates.DISCONNECTED and not self.blacked_out():
|
|
369
|
+
self.state = ConnectionStates.CONNECTING
|
|
359
370
|
self.last_attempt = time.time()
|
|
360
371
|
next_lookup = self._next_afi_sockaddr()
|
|
361
372
|
if not next_lookup:
|
|
@@ -380,7 +391,6 @@ class BrokerConnection(object):
|
|
|
380
391
|
self._sock.setsockopt(*option)
|
|
381
392
|
|
|
382
393
|
self._sock.setblocking(False)
|
|
383
|
-
self.state = ConnectionStates.CONNECTING
|
|
384
394
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
385
395
|
log.info('%s: connecting to %s:%d [%s %s]', self, self.host,
|
|
386
396
|
self.port, self._sock_addr, AFI_NAMES[self._sock_afi])
|
|
@@ -402,20 +412,20 @@ class BrokerConnection(object):
|
|
|
402
412
|
log.debug('%s: established TCP connection', self)
|
|
403
413
|
|
|
404
414
|
if self.config['security_protocol'] in ('SSL', 'SASL_SSL'):
|
|
405
|
-
log.debug('%s: initiating SSL handshake', self)
|
|
406
415
|
self.state = ConnectionStates.HANDSHAKE
|
|
416
|
+
log.debug('%s: initiating SSL handshake', self)
|
|
407
417
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
408
418
|
# _wrap_ssl can alter the connection state -- disconnects on failure
|
|
409
419
|
self._wrap_ssl()
|
|
410
420
|
else:
|
|
411
|
-
log.debug('%s: checking broker Api Versions', self)
|
|
412
421
|
self.state = ConnectionStates.API_VERSIONS_SEND
|
|
422
|
+
log.debug('%s: checking broker Api Versions', self)
|
|
413
423
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
414
424
|
|
|
415
425
|
# Connection failed
|
|
416
426
|
# WSAEINVAL == 10022, but errno.WSAEINVAL is not available on non-win systems
|
|
417
427
|
elif ret not in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK, 10022):
|
|
418
|
-
log.error('Connect attempt
|
|
428
|
+
log.error('%s: Connect attempt returned error %s.'
|
|
419
429
|
' Disconnecting.', self, ret)
|
|
420
430
|
errstr = errno.errorcode.get(ret, 'UNKNOWN')
|
|
421
431
|
self.close(Errors.KafkaConnectionError('{} {}'.format(ret, errstr)))
|
|
@@ -428,8 +438,8 @@ class BrokerConnection(object):
|
|
|
428
438
|
if self.state is ConnectionStates.HANDSHAKE:
|
|
429
439
|
if self._try_handshake():
|
|
430
440
|
log.debug('%s: completed SSL handshake.', self)
|
|
431
|
-
log.debug('%s: checking broker Api Versions', self)
|
|
432
441
|
self.state = ConnectionStates.API_VERSIONS_SEND
|
|
442
|
+
log.debug('%s: checking broker Api Versions', self)
|
|
433
443
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
434
444
|
|
|
435
445
|
if self.state in (ConnectionStates.API_VERSIONS_SEND, ConnectionStates.API_VERSIONS_RECV):
|
|
@@ -437,13 +447,13 @@ class BrokerConnection(object):
|
|
|
437
447
|
# _try_api_versions_check has side-effects: possibly disconnected on socket errors
|
|
438
448
|
if self.state in (ConnectionStates.API_VERSIONS_SEND, ConnectionStates.API_VERSIONS_RECV):
|
|
439
449
|
if self.config['security_protocol'] in ('SASL_PLAINTEXT', 'SASL_SSL'):
|
|
440
|
-
log.debug('%s: initiating SASL authentication', self)
|
|
441
450
|
self.state = ConnectionStates.AUTHENTICATING
|
|
451
|
+
log.debug('%s: initiating SASL authentication', self)
|
|
442
452
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
443
453
|
else:
|
|
444
454
|
# security_protocol PLAINTEXT
|
|
445
|
-
log.info('%s: Connection complete.', self)
|
|
446
455
|
self.state = ConnectionStates.CONNECTED
|
|
456
|
+
log.info('%s: Connection complete.', self)
|
|
447
457
|
self._reset_reconnect_backoff()
|
|
448
458
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
449
459
|
|
|
@@ -452,8 +462,8 @@ class BrokerConnection(object):
|
|
|
452
462
|
if self._try_authenticate():
|
|
453
463
|
# _try_authenticate has side-effects: possibly disconnected on socket errors
|
|
454
464
|
if self.state is ConnectionStates.AUTHENTICATING:
|
|
455
|
-
log.info('%s: Connection complete.', self)
|
|
456
465
|
self.state = ConnectionStates.CONNECTED
|
|
466
|
+
log.info('%s: Connection complete.', self)
|
|
457
467
|
self._reset_reconnect_backoff()
|
|
458
468
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
459
469
|
|
|
@@ -462,7 +472,7 @@ class BrokerConnection(object):
|
|
|
462
472
|
# Connection timed out
|
|
463
473
|
request_timeout = self.config['request_timeout_ms'] / 1000.0
|
|
464
474
|
if time.time() > request_timeout + self.last_attempt:
|
|
465
|
-
log.error('Connection attempt
|
|
475
|
+
log.error('%s: Connection attempt timed out', self)
|
|
466
476
|
self.close(Errors.KafkaConnectionError('timeout'))
|
|
467
477
|
return self.state
|
|
468
478
|
|
|
@@ -521,7 +531,7 @@ class BrokerConnection(object):
|
|
|
521
531
|
except (SSLWantReadError, SSLWantWriteError):
|
|
522
532
|
pass
|
|
523
533
|
except (SSLZeroReturnError, ConnectionError, TimeoutError, SSLEOFError):
|
|
524
|
-
log.warning('SSL connection closed by server during handshake.')
|
|
534
|
+
log.warning('%s: SSL connection closed by server during handshake.', self)
|
|
525
535
|
self.close(Errors.KafkaConnectionError('SSL connection closed by server during handshake'))
|
|
526
536
|
# Other SSLErrors will be raised to user
|
|
527
537
|
|
|
@@ -538,7 +548,14 @@ class BrokerConnection(object):
|
|
|
538
548
|
log.debug('%s: Using pre-configured api_version %s for ApiVersions', self, self._api_version)
|
|
539
549
|
return True
|
|
540
550
|
elif self._check_version_idx is None:
|
|
541
|
-
|
|
551
|
+
version = self._api_versions_idx
|
|
552
|
+
if version >= 3:
|
|
553
|
+
request = ApiVersionsRequest[version](
|
|
554
|
+
client_software_name=self.config['client_software_name'],
|
|
555
|
+
client_software_version=self.config['client_software_version'],
|
|
556
|
+
_tagged_fields={})
|
|
557
|
+
else:
|
|
558
|
+
request = ApiVersionsRequest[version]()
|
|
542
559
|
future = Future()
|
|
543
560
|
response = self._send(request, blocking=True, request_timeout_ms=(self.config['api_version_auto_timeout_ms'] * 0.8))
|
|
544
561
|
response.add_callback(self._handle_api_versions_response, future)
|
|
@@ -573,11 +590,15 @@ class BrokerConnection(object):
|
|
|
573
590
|
|
|
574
591
|
def _handle_api_versions_response(self, future, response):
|
|
575
592
|
error_type = Errors.for_code(response.error_code)
|
|
576
|
-
# if error_type i UNSUPPORTED_VERSION: retry w/ latest version from response
|
|
577
593
|
if error_type is not Errors.NoError:
|
|
578
594
|
future.failure(error_type())
|
|
579
595
|
if error_type is Errors.UnsupportedVersionError:
|
|
580
596
|
self._api_versions_idx -= 1
|
|
597
|
+
for api_key, min_version, max_version, *rest in response.api_versions:
|
|
598
|
+
# If broker provides a lower max_version, skip to that
|
|
599
|
+
if api_key == response.API_KEY:
|
|
600
|
+
self._api_versions_idx = min(self._api_versions_idx, max_version)
|
|
601
|
+
break
|
|
581
602
|
if self._api_versions_idx >= 0:
|
|
582
603
|
self._api_versions_future = None
|
|
583
604
|
self.state = ConnectionStates.API_VERSIONS_SEND
|
|
@@ -587,10 +608,10 @@ class BrokerConnection(object):
|
|
|
587
608
|
return
|
|
588
609
|
self._api_versions = dict([
|
|
589
610
|
(api_key, (min_version, max_version))
|
|
590
|
-
for api_key, min_version, max_version in response.api_versions
|
|
611
|
+
for api_key, min_version, max_version, *rest in response.api_versions
|
|
591
612
|
])
|
|
592
613
|
self._api_version = self._infer_broker_version_from_api_versions(self._api_versions)
|
|
593
|
-
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)))
|
|
594
615
|
future.success(self._api_version)
|
|
595
616
|
self.connect()
|
|
596
617
|
|
|
@@ -600,7 +621,7 @@ class BrokerConnection(object):
|
|
|
600
621
|
# after failure connection is closed, so state should already be DISCONNECTED
|
|
601
622
|
|
|
602
623
|
def _handle_check_version_response(self, future, version, _response):
|
|
603
|
-
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)))
|
|
604
625
|
log.info('Set configuration api_version=%s to skip auto'
|
|
605
626
|
' check_version requests on startup', version)
|
|
606
627
|
self._api_versions = BROKER_API_VERSIONS[version]
|
|
@@ -730,6 +751,7 @@ class BrokerConnection(object):
|
|
|
730
751
|
request = SaslAuthenticateRequest[0](sasl_auth_bytes)
|
|
731
752
|
self._send(request, blocking=True)
|
|
732
753
|
else:
|
|
754
|
+
log.debug('%s: Sending %d raw sasl auth bytes to server', self, len(sasl_auth_bytes))
|
|
733
755
|
try:
|
|
734
756
|
self._send_bytes_blocking(Int32.encode(len(sasl_auth_bytes)) + sasl_auth_bytes)
|
|
735
757
|
except (ConnectionError, TimeoutError) as e:
|
|
@@ -759,7 +781,7 @@ class BrokerConnection(object):
|
|
|
759
781
|
latency_ms = (time.time() - timestamp) * 1000
|
|
760
782
|
if self._sensors:
|
|
761
783
|
self._sensors.request_time.record(latency_ms)
|
|
762
|
-
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)
|
|
763
785
|
|
|
764
786
|
error_type = Errors.for_code(response.error_code)
|
|
765
787
|
if error_type is not Errors.NoError:
|
|
@@ -770,6 +792,7 @@ class BrokerConnection(object):
|
|
|
770
792
|
return response.auth_bytes
|
|
771
793
|
else:
|
|
772
794
|
# unframed bytes w/ SaslHandhake v0
|
|
795
|
+
log.debug('%s: Received %d raw sasl auth bytes from server', self, nbytes)
|
|
773
796
|
return data[4:]
|
|
774
797
|
|
|
775
798
|
def _sasl_authenticate(self, future):
|
|
@@ -913,6 +936,7 @@ class BrokerConnection(object):
|
|
|
913
936
|
self._update_reconnect_backoff()
|
|
914
937
|
self._api_versions_future = None
|
|
915
938
|
self._sasl_auth_future = None
|
|
939
|
+
self._init_sasl_mechanism()
|
|
916
940
|
self._protocol = KafkaProtocol(
|
|
917
941
|
client_id=self.config['client_id'],
|
|
918
942
|
api_version=self.config['api_version'])
|
|
@@ -932,7 +956,8 @@ class BrokerConnection(object):
|
|
|
932
956
|
|
|
933
957
|
# drop lock before state change callback and processing futures
|
|
934
958
|
self.config['state_change_callback'](self.node_id, sock, self)
|
|
935
|
-
sock
|
|
959
|
+
if sock:
|
|
960
|
+
sock.close()
|
|
936
961
|
for (_correlation_id, (future, _timestamp, _timeout)) in ifrs:
|
|
937
962
|
future.failure(error)
|
|
938
963
|
|
|
@@ -978,7 +1003,7 @@ class BrokerConnection(object):
|
|
|
978
1003
|
|
|
979
1004
|
correlation_id = self._protocol.send_request(request)
|
|
980
1005
|
|
|
981
|
-
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)
|
|
982
1007
|
if request.expect_response():
|
|
983
1008
|
assert correlation_id not in self.in_flight_requests, 'Correlation ID already in-flight!'
|
|
984
1009
|
sent_time = time.time()
|
|
@@ -1012,7 +1037,7 @@ class BrokerConnection(object):
|
|
|
1012
1037
|
return True
|
|
1013
1038
|
|
|
1014
1039
|
except (ConnectionError, TimeoutError) as e:
|
|
1015
|
-
log.exception("Error sending request data
|
|
1040
|
+
log.exception("%s: Error sending request data", self)
|
|
1016
1041
|
error = Errors.KafkaConnectionError("%s: %s" % (self, e))
|
|
1017
1042
|
self.close(error=error)
|
|
1018
1043
|
return False
|
|
@@ -1045,7 +1070,7 @@ class BrokerConnection(object):
|
|
|
1045
1070
|
return len(self._send_buffer) == 0
|
|
1046
1071
|
|
|
1047
1072
|
except (ConnectionError, TimeoutError, Exception) as e:
|
|
1048
|
-
log.exception("Error sending request data
|
|
1073
|
+
log.exception("%s: Error sending request data", self)
|
|
1049
1074
|
error = Errors.KafkaConnectionError("%s: %s" % (self, e))
|
|
1050
1075
|
self.close(error=error)
|
|
1051
1076
|
return False
|
|
@@ -1082,7 +1107,7 @@ class BrokerConnection(object):
|
|
|
1082
1107
|
if not responses and self.requests_timed_out():
|
|
1083
1108
|
timed_out = self.timed_out_ifrs()
|
|
1084
1109
|
timeout_ms = (timed_out[0][2] - timed_out[0][1]) * 1000
|
|
1085
|
-
log.warning('%s timed out after %s ms. Closing connection.',
|
|
1110
|
+
log.warning('%s: timed out after %s ms. Closing connection.',
|
|
1086
1111
|
self, timeout_ms)
|
|
1087
1112
|
self.close(error=Errors.RequestTimedOutError(
|
|
1088
1113
|
'Request timed out after %s ms' %
|
|
@@ -1101,7 +1126,7 @@ class BrokerConnection(object):
|
|
|
1101
1126
|
if self._sensors:
|
|
1102
1127
|
self._sensors.request_time.record(latency_ms)
|
|
1103
1128
|
|
|
1104
|
-
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)
|
|
1105
1130
|
self._maybe_throttle(response)
|
|
1106
1131
|
responses[i] = (response, future)
|
|
1107
1132
|
|
|
@@ -1113,7 +1138,7 @@ class BrokerConnection(object):
|
|
|
1113
1138
|
err = None
|
|
1114
1139
|
with self._lock:
|
|
1115
1140
|
if not self._can_send_recv():
|
|
1116
|
-
log.warning('%s cannot recv: socket not connected', self)
|
|
1141
|
+
log.warning('%s: cannot recv: socket not connected', self)
|
|
1117
1142
|
return ()
|
|
1118
1143
|
|
|
1119
1144
|
while len(recvd) < self.config['sock_chunk_buffer_count']:
|