kafka-python 2.0.6__tar.gz → 2.1.0__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.6 → kafka_python-2.1.0}/CHANGES.md +87 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/PKG-INFO +2 -2
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/admin/client.py +246 -95
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/client_async.py +207 -84
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/cluster.py +31 -10
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/conn.py +401 -441
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/consumer/fetcher.py +437 -175
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/consumer/group.py +85 -129
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/consumer/subscription_state.py +11 -10
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -1
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/base.py +172 -134
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/consumer.py +149 -79
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/errors.py +1 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/metric_name.py +1 -1
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/metrics.py +3 -1
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/quota.py +1 -1
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/producer/kafka.py +84 -44
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/producer/record_accumulator.py +9 -7
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/producer/sender.py +13 -48
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/admin.py +115 -97
- kafka_python-2.1.0/kafka/protocol/api_versions.py +90 -0
- kafka_python-2.1.0/kafka/protocol/broker_api_versions.py +66 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/commit.py +110 -52
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/fetch.py +12 -10
- kafka_python-2.1.0/kafka/protocol/find_coordinator.py +64 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/group.py +64 -10
- kafka_python-2.0.6/kafka/protocol/offset.py → kafka_python-2.1.0/kafka/protocol/list_offsets.py +29 -29
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/metadata.py +69 -13
- kafka_python-2.1.0/kafka/protocol/offset_for_leader_epoch.py +140 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/parser.py +2 -2
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/produce.py +4 -2
- kafka_python-2.1.0/kafka/protocol/sasl_authenticate.py +42 -0
- kafka_python-2.1.0/kafka/protocol/sasl_handshake.py +39 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/record/default_records.py +4 -0
- kafka_python-2.1.0/kafka/sasl/__init__.py +34 -0
- kafka_python-2.1.0/kafka/sasl/abc.py +32 -0
- kafka_python-2.1.0/kafka/sasl/gssapi.py +87 -0
- kafka_python-2.1.0/kafka/sasl/msk.py +233 -0
- kafka_python-2.1.0/kafka/sasl/oauth.py +87 -0
- kafka_python-2.1.0/kafka/sasl/plain.py +41 -0
- kafka_python-2.1.0/kafka/sasl/scram.py +133 -0
- kafka_python-2.1.0/kafka/sasl/sspi.py +111 -0
- kafka_python-2.1.0/kafka/socks5_wrapper.py +248 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/structs.py +5 -4
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/util.py +22 -1
- kafka_python-2.1.0/kafka/version.py +1 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka_python.egg-info/PKG-INFO +2 -2
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka_python.egg-info/SOURCES.txt +16 -4
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka_python.egg-info/requires.txt +3 -1
- {kafka_python-2.0.6 → kafka_python-2.1.0}/pyproject.toml +1 -1
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_admin.py +4 -4
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_admin_integration.py +74 -3
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_client_async.py +11 -9
- kafka_python-2.1.0/test/test_cluster.py +134 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_conn.py +48 -7
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_consumer.py +1 -1
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_consumer_group.py +39 -36
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_consumer_integration.py +7 -3
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_coordinator.py +60 -20
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_fetcher.py +120 -71
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_object_conversion.py +2 -2
- kafka_python-2.1.0/test/test_producer.py +158 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_protocol.py +4 -6
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_sender.py +7 -9
- kafka_python-2.0.6/kafka/oauth/__init__.py +0 -3
- kafka_python-2.0.6/kafka/oauth/abstract.py +0 -42
- kafka_python-2.0.6/kafka/scram.py +0 -81
- kafka_python-2.0.6/kafka/version.py +0 -1
- kafka_python-2.0.6/test/test_cluster.py +0 -22
- kafka_python-2.0.6/test/test_producer.py +0 -137
- {kafka_python-2.0.6 → kafka_python-2.1.0}/AUTHORS.md +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/LICENSE +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/MANIFEST.in +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/README.rst +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/codec.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/future.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/producer/buffer.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/producer/future.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/api.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/message.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/protocol/types.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/record/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/record/abc.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/record/legacy_records.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/record/memory_records.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/record/util.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/vendor/six.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/kafka_python.egg-info/top_level.txt +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/setup.cfg +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/setup.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_assignors.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_codec.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_metrics.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_package.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_partition_movements.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_partitioner.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_sasl_integration.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/test_subscription_state.py +0 -0
- {kafka_python-2.0.6 → kafka_python-2.1.0}/test/testutil.py +0 -0
|
@@ -1,3 +1,90 @@
|
|
|
1
|
+
# 2.1.0 (Mar 14, 2025)
|
|
2
|
+
|
|
3
|
+
Support Kafka Broker 2.1 API Baseline
|
|
4
|
+
* Add baseline leader_epoch support for ListOffsets v4 / FetchRequest v10 (#2511)
|
|
5
|
+
* Support OffsetFetch v5 / OffsetCommit v6 (2.1 baseline) (#2505)
|
|
6
|
+
* Support 2.1 baseline consumer group apis (#2503)
|
|
7
|
+
* Support FindCoordinatorRequest v2 in consumer and admin client (#2502)
|
|
8
|
+
* Support ListOffsets v3 in consumer (#2501)
|
|
9
|
+
* Support Fetch Request/Response v6 in consumer (#2500)
|
|
10
|
+
* Add support for Metadata Request/Response v7 (#2497)
|
|
11
|
+
* Implement Incremental Fetch Sessions / KIP-227 (#2508)
|
|
12
|
+
* Implement client-side connection throttling / KIP-219 (#2510)
|
|
13
|
+
* Add KafkaClient.api_version(operation) for best available from api_versions (#2495)
|
|
14
|
+
|
|
15
|
+
Consumer
|
|
16
|
+
* Timeout coordinator poll / ensure_coordinator_ready / ensure_active_group (#2526)
|
|
17
|
+
* Add optional timeout_ms kwarg to remaining consumer/coordinator methods (#2544)
|
|
18
|
+
* Check for coordinator.poll failure in KafkaConsumer
|
|
19
|
+
* Only mark coordinator dead if connection_delay > 0 (#2530)
|
|
20
|
+
* Delay group coordinator until after bootstrap (#2539)
|
|
21
|
+
* KAFKA-4160: Ensure rebalance listener not called with coordinator lock (#1438)
|
|
22
|
+
* Call default_offset_commit_callback after `_maybe_auto_commit_offsets_async` (#2546)
|
|
23
|
+
* Remove legacy/v1 consumer message iterator (#2543)
|
|
24
|
+
* Log warning when attempting to list offsets for unknown topic/partition (#2540)
|
|
25
|
+
* Add heartbeat thread id to debug logs on start
|
|
26
|
+
* Add inner_timeout_ms handler to fetcher; add fallback (#2529)
|
|
27
|
+
|
|
28
|
+
Producer
|
|
29
|
+
* KafkaProducer: Flush pending records before close() (#2537)
|
|
30
|
+
* Raise immediate error on producer.send after close (#2542)
|
|
31
|
+
* Limit producer close timeout to 1sec in __del__; use context managers to close in test_producer
|
|
32
|
+
* Use NullLogger in producer atexit cleanup
|
|
33
|
+
* Attempt to fix metadata race condition when partitioning in producer.send (#2523)
|
|
34
|
+
* Remove unused partial KIP-467 implementation (ProduceResponse batch error details) (#2524)
|
|
35
|
+
|
|
36
|
+
AdminClient
|
|
37
|
+
* Implement perform leader election (#2536)
|
|
38
|
+
* Support delete_records (#2535)
|
|
39
|
+
|
|
40
|
+
Networking
|
|
41
|
+
* Call ApiVersionsRequest during connection, prior to Sasl Handshake (#2493)
|
|
42
|
+
* Fake api_versions for old brokers, rename to ApiVersionsRequest, and handle error decoding (#2494)
|
|
43
|
+
* Debug log when skipping api_versions request with pre-configured api_version
|
|
44
|
+
* Only refresh metadata if connection fails all dns records (#2532)
|
|
45
|
+
* Support connections through SOCKS5 proxies (#2531)
|
|
46
|
+
* Fix OverflowError when connection_max_idle_ms is 0 or inf (#2538)
|
|
47
|
+
* socket.setblocking for eventlet/gevent compatibility
|
|
48
|
+
* Support custom per-request timeouts (#2498)
|
|
49
|
+
* Include request_timeout_ms in request debug log
|
|
50
|
+
* Support client.poll with future and timeout_ms
|
|
51
|
+
* mask unused afi var
|
|
52
|
+
* Debug log if check_version connection attempt fails
|
|
53
|
+
|
|
54
|
+
SASL Modules
|
|
55
|
+
* Refactor Sasl authentication with SaslMechanism abstract base class; support SaslAuthenticate (#2515)
|
|
56
|
+
* Add SSPI (Kerberos for Windows) authentication mechanism (#2521)
|
|
57
|
+
* Support AWS_MSK_IAM authentication (#2519)
|
|
58
|
+
* Cleanup sasl mechanism configuration checks; fix gssapi bugs; add sasl_kerberos_name config (#2520)
|
|
59
|
+
* Move kafka.oauth.AbstractTokenProvider -> kafka.sasl.oauth.AbstractTokenProvider (#2525)
|
|
60
|
+
|
|
61
|
+
Testing
|
|
62
|
+
* Bump default python to 3.13 in CI tests (#2541)
|
|
63
|
+
* Update pytest log_format: use logger instead of filename; add thread id
|
|
64
|
+
* Improve test_consumer_group::test_group logging before group stabilized (#2534)
|
|
65
|
+
* Limit test duration to 5mins w/ pytest-timeout
|
|
66
|
+
* Fix external kafka/zk fixtures for testing (#2533)
|
|
67
|
+
* Disable zookeeper admin server to avoid port conflicts
|
|
68
|
+
* Set default pytest log level to debug
|
|
69
|
+
* test_group: shorter timeout, more logging, more sleep
|
|
70
|
+
* Cache servers/dist in github actions workflow (#2527)
|
|
71
|
+
* Remove tox.ini; update testing docs
|
|
72
|
+
* Use thread-specific client_id in test_group
|
|
73
|
+
* Fix subprocess log warning; specify timeout_ms kwarg in consumer.poll tests
|
|
74
|
+
* Only set KAFKA_JVM_PERFORMANCE_OPTS in makefile if unset; add note re: 2.0-2.3 broker testing
|
|
75
|
+
* Add kafka command to test.fixtures; raise FileNotFoundError if version not installed
|
|
76
|
+
|
|
77
|
+
Documentation
|
|
78
|
+
* Improve ClusterMetadata docs re: node_id/broker_id str/int types
|
|
79
|
+
* Document api_version_auto_timeout_ms default; override in group tests
|
|
80
|
+
|
|
81
|
+
Fixes
|
|
82
|
+
* Signal close to metrics expire_loop
|
|
83
|
+
* Add kafka.util timeout_ms_fn
|
|
84
|
+
* fixup TopicAuthorizationFailedError construction
|
|
85
|
+
* Fix lint issues via ruff check (#2522)
|
|
86
|
+
* Make the "mock" dependency optional (only used in Python < 3.3). (#2518)
|
|
87
|
+
|
|
1
88
|
# 2.0.6 (Mar 4, 2025)
|
|
2
89
|
|
|
3
90
|
Networking
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: kafka-python
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
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
|
|
@@ -35,7 +35,7 @@ Provides-Extra: zstd
|
|
|
35
35
|
Requires-Dist: zstandard; extra == "zstd"
|
|
36
36
|
Provides-Extra: testing
|
|
37
37
|
Requires-Dist: pytest; extra == "testing"
|
|
38
|
-
Requires-Dist: mock; extra == "testing"
|
|
38
|
+
Requires-Dist: mock; python_version < "3.3" and extra == "testing"
|
|
39
39
|
Requires-Dist: pytest-mock; extra == "testing"
|
|
40
40
|
|
|
41
41
|
Kafka Python client
|
|
@@ -15,15 +15,15 @@ from kafka.client_async import KafkaClient, selectors
|
|
|
15
15
|
from kafka.coordinator.protocol import ConsumerProtocolMemberMetadata, ConsumerProtocolMemberAssignment, ConsumerProtocol
|
|
16
16
|
import kafka.errors as Errors
|
|
17
17
|
from kafka.errors import (
|
|
18
|
-
IncompatibleBrokerVersion, KafkaConfigurationError, NotControllerError,
|
|
18
|
+
IncompatibleBrokerVersion, KafkaConfigurationError, NotControllerError, UnknownTopicOrPartitionError,
|
|
19
19
|
UnrecognizedBrokerVersion, IllegalArgumentError)
|
|
20
20
|
from kafka.metrics import MetricConfig, Metrics
|
|
21
21
|
from kafka.protocol.admin import (
|
|
22
22
|
CreateTopicsRequest, DeleteTopicsRequest, DescribeConfigsRequest, AlterConfigsRequest, CreatePartitionsRequest,
|
|
23
23
|
ListGroupsRequest, DescribeGroupsRequest, DescribeAclsRequest, CreateAclsRequest, DeleteAclsRequest,
|
|
24
|
-
DeleteGroupsRequest, DescribeLogDirsRequest
|
|
25
|
-
|
|
26
|
-
from kafka.protocol.
|
|
24
|
+
DeleteGroupsRequest, DeleteRecordsRequest, DescribeLogDirsRequest, ElectLeadersRequest, ElectionType)
|
|
25
|
+
from kafka.protocol.commit import OffsetFetchRequest
|
|
26
|
+
from kafka.protocol.find_coordinator import FindCoordinatorRequest
|
|
27
27
|
from kafka.protocol.metadata import MetadataRequest
|
|
28
28
|
from kafka.protocol.types import Array
|
|
29
29
|
from kafka.structs import TopicPartition, OffsetAndMetadata, MemberInformation, GroupInformation
|
|
@@ -141,14 +141,17 @@ class KafkaAdminClient(object):
|
|
|
141
141
|
Required if sasl_mechanism is PLAIN or one of the SCRAM mechanisms.
|
|
142
142
|
sasl_plain_password (str): password for sasl PLAIN and SCRAM authentication.
|
|
143
143
|
Required if sasl_mechanism is PLAIN or one of the SCRAM mechanisms.
|
|
144
|
+
sasl_kerberos_name (str or gssapi.Name): Constructed gssapi.Name for use with
|
|
145
|
+
sasl mechanism handshake. If provided, sasl_kerberos_service_name and
|
|
146
|
+
sasl_kerberos_domain name are ignored. Default: None.
|
|
144
147
|
sasl_kerberos_service_name (str): Service name to include in GSSAPI
|
|
145
148
|
sasl mechanism handshake. Default: 'kafka'
|
|
146
149
|
sasl_kerberos_domain_name (str): kerberos domain name to use in GSSAPI
|
|
147
150
|
sasl mechanism handshake. Default: one of bootstrap servers
|
|
148
|
-
sasl_oauth_token_provider (AbstractTokenProvider): OAuthBearer
|
|
149
|
-
|
|
151
|
+
sasl_oauth_token_provider (kafka.sasl.oauth.AbstractTokenProvider): OAuthBearer
|
|
152
|
+
token provider instance. Default: None
|
|
153
|
+
socks5_proxy (str): Socks5 proxy url. Default: None
|
|
150
154
|
kafka_client (callable): Custom class / callable for creating KafkaClient instances
|
|
151
|
-
|
|
152
155
|
"""
|
|
153
156
|
DEFAULT_CONFIG = {
|
|
154
157
|
# client configs
|
|
@@ -180,9 +183,11 @@ class KafkaAdminClient(object):
|
|
|
180
183
|
'sasl_mechanism': None,
|
|
181
184
|
'sasl_plain_username': None,
|
|
182
185
|
'sasl_plain_password': None,
|
|
186
|
+
'sasl_kerberos_name': None,
|
|
183
187
|
'sasl_kerberos_service_name': 'kafka',
|
|
184
188
|
'sasl_kerberos_domain_name': None,
|
|
185
189
|
'sasl_oauth_token_provider': None,
|
|
190
|
+
'socks5_proxy': None,
|
|
186
191
|
|
|
187
192
|
# metrics configs
|
|
188
193
|
'metric_reporters': [],
|
|
@@ -215,11 +220,7 @@ class KafkaAdminClient(object):
|
|
|
215
220
|
)
|
|
216
221
|
|
|
217
222
|
# Get auto-discovered version from client if necessary
|
|
218
|
-
|
|
219
|
-
self.config['api_version'] = self._client.config['api_version']
|
|
220
|
-
else:
|
|
221
|
-
# need to run check_version for get_api_versions()
|
|
222
|
-
self._client.check_version(timeout=(self.config['api_version_auto_timeout_ms'] / 1000))
|
|
223
|
+
self.config['api_version'] = self._client.config['api_version']
|
|
223
224
|
|
|
224
225
|
self._closed = False
|
|
225
226
|
self._refresh_controller_id()
|
|
@@ -236,35 +237,6 @@ class KafkaAdminClient(object):
|
|
|
236
237
|
self._closed = True
|
|
237
238
|
log.debug("KafkaAdminClient is now closed.")
|
|
238
239
|
|
|
239
|
-
def _matching_api_version(self, operation):
|
|
240
|
-
"""Find the latest version of the protocol operation supported by both
|
|
241
|
-
this library and the broker.
|
|
242
|
-
|
|
243
|
-
This resolves to the lesser of either the latest api version this
|
|
244
|
-
library supports, or the max version supported by the broker.
|
|
245
|
-
|
|
246
|
-
Arguments:
|
|
247
|
-
operation: A list of protocol operation versions from kafka.protocol.
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
int: The max matching version number between client and broker.
|
|
251
|
-
"""
|
|
252
|
-
broker_api_versions = self._client.get_api_versions()
|
|
253
|
-
api_key = operation[0].API_KEY
|
|
254
|
-
if broker_api_versions is None or api_key not in broker_api_versions:
|
|
255
|
-
raise IncompatibleBrokerVersion(
|
|
256
|
-
"Kafka broker does not support the '{}' Kafka protocol."
|
|
257
|
-
.format(operation[0].__name__))
|
|
258
|
-
min_version, max_version = broker_api_versions[api_key]
|
|
259
|
-
version = min(len(operation) - 1, max_version)
|
|
260
|
-
if version < min_version:
|
|
261
|
-
# max library version is less than min broker version. Currently,
|
|
262
|
-
# no Kafka versions specify a min msg version. Maybe in the future?
|
|
263
|
-
raise IncompatibleBrokerVersion(
|
|
264
|
-
"No version of the '{}' Kafka protocol is supported by both the client and broker."
|
|
265
|
-
.format(operation[0].__name__))
|
|
266
|
-
return version
|
|
267
|
-
|
|
268
240
|
def _validate_timeout(self, timeout_ms):
|
|
269
241
|
"""Validate the timeout is set or use the configuration default.
|
|
270
242
|
|
|
@@ -278,7 +250,7 @@ class KafkaAdminClient(object):
|
|
|
278
250
|
|
|
279
251
|
def _refresh_controller_id(self, timeout_ms=30000):
|
|
280
252
|
"""Determine the Kafka cluster controller."""
|
|
281
|
-
version = self.
|
|
253
|
+
version = self._client.api_version(MetadataRequest, max_version=6)
|
|
282
254
|
if 1 <= version <= 6:
|
|
283
255
|
timeout_at = time.time() + timeout_ms / 1000
|
|
284
256
|
while time.time() < timeout_at:
|
|
@@ -318,18 +290,14 @@ class KafkaAdminClient(object):
|
|
|
318
290
|
Returns:
|
|
319
291
|
A message future
|
|
320
292
|
"""
|
|
321
|
-
|
|
322
|
-
# GroupCoordinatorRequest which was renamed to FindCoordinatorRequest.
|
|
323
|
-
# When I experimented with this, the coordinator value returned in
|
|
324
|
-
# GroupCoordinatorResponse_v1 didn't match the value returned by
|
|
325
|
-
# GroupCoordinatorResponse_v0 and I couldn't figure out why.
|
|
326
|
-
version = 0
|
|
327
|
-
# version = self._matching_api_version(GroupCoordinatorRequest)
|
|
293
|
+
version = self._client.api_version(FindCoordinatorRequest, max_version=2)
|
|
328
294
|
if version <= 0:
|
|
329
|
-
request =
|
|
295
|
+
request = FindCoordinatorRequest[version](group_id)
|
|
296
|
+
elif version <= 2:
|
|
297
|
+
request = FindCoordinatorRequest[version](group_id, 0)
|
|
330
298
|
else:
|
|
331
299
|
raise NotImplementedError(
|
|
332
|
-
"Support for
|
|
300
|
+
"Support for FindCoordinatorRequest_v{} has not yet been added to KafkaAdminClient."
|
|
333
301
|
.format(version))
|
|
334
302
|
return self._send_request_to_node(self._client.least_loaded_node(), request)
|
|
335
303
|
|
|
@@ -342,18 +310,13 @@ class KafkaAdminClient(object):
|
|
|
342
310
|
Returns:
|
|
343
311
|
The node_id of the broker that is the coordinator.
|
|
344
312
|
"""
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
.format(response))
|
|
353
|
-
else:
|
|
354
|
-
raise NotImplementedError(
|
|
355
|
-
"Support for FindCoordinatorRequest_v{} has not yet been added to KafkaAdminClient."
|
|
356
|
-
.format(response.API_VERSION))
|
|
313
|
+
error_type = Errors.for_code(response.error_code)
|
|
314
|
+
if error_type is not Errors.NoError:
|
|
315
|
+
# Note: When error_type.retriable, Java will retry... see
|
|
316
|
+
# KafkaAdminClient's handleFindCoordinatorError method
|
|
317
|
+
raise error_type(
|
|
318
|
+
"FindCoordinatorRequest failed with response '{}'."
|
|
319
|
+
.format(response))
|
|
357
320
|
return response.coordinator_id
|
|
358
321
|
|
|
359
322
|
def _find_coordinator_ids(self, group_ids):
|
|
@@ -430,27 +393,55 @@ class KafkaAdminClient(object):
|
|
|
430
393
|
# So this is a little brittle in that it assumes all responses have
|
|
431
394
|
# one of these attributes and that they always unpack into
|
|
432
395
|
# (topic, error_code) tuples.
|
|
433
|
-
topic_error_tuples = (response
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
396
|
+
topic_error_tuples = getattr(response, 'topic_errors', getattr(response, 'topic_error_codes', None))
|
|
397
|
+
if topic_error_tuples is not None:
|
|
398
|
+
success = self._parse_topic_request_response(topic_error_tuples, request, response, tries)
|
|
399
|
+
else:
|
|
400
|
+
# Leader Election request has a two layer error response (topic and partition)
|
|
401
|
+
success = self._parse_topic_partition_request_response(request, response, tries)
|
|
402
|
+
|
|
403
|
+
if success:
|
|
404
|
+
return response
|
|
405
|
+
raise RuntimeError("This should never happen, please file a bug with full stacktrace if encountered")
|
|
406
|
+
|
|
407
|
+
def _parse_topic_request_response(self, topic_error_tuples, request, response, tries):
|
|
408
|
+
# Also small py2/py3 compatibility -- py3 can ignore extra values
|
|
409
|
+
# during unpack via: for x, y, *rest in list_of_values. py2 cannot.
|
|
410
|
+
# So for now we have to map across the list and explicitly drop any
|
|
411
|
+
# extra values (usually the error_message)
|
|
412
|
+
for topic, error_code in map(lambda e: e[:2], topic_error_tuples):
|
|
413
|
+
error_type = Errors.for_code(error_code)
|
|
414
|
+
if tries and error_type is NotControllerError:
|
|
415
|
+
# No need to inspect the rest of the errors for
|
|
416
|
+
# non-retriable errors because NotControllerError should
|
|
417
|
+
# either be thrown for all errors or no errors.
|
|
418
|
+
self._refresh_controller_id()
|
|
419
|
+
return False
|
|
420
|
+
elif error_type is not Errors.NoError:
|
|
421
|
+
raise error_type(
|
|
422
|
+
"Request '{}' failed with response '{}'."
|
|
423
|
+
.format(request, response))
|
|
424
|
+
return True
|
|
425
|
+
|
|
426
|
+
def _parse_topic_partition_request_response(self, request, response, tries):
|
|
427
|
+
# Also small py2/py3 compatibility -- py3 can ignore extra values
|
|
428
|
+
# during unpack via: for x, y, *rest in list_of_values. py2 cannot.
|
|
429
|
+
# So for now we have to map across the list and explicitly drop any
|
|
430
|
+
# extra values (usually the error_message)
|
|
431
|
+
for topic, partition_results in response.replication_election_results:
|
|
432
|
+
for partition_id, error_code in map(lambda e: e[:2], partition_results):
|
|
440
433
|
error_type = Errors.for_code(error_code)
|
|
441
434
|
if tries and error_type is NotControllerError:
|
|
442
435
|
# No need to inspect the rest of the errors for
|
|
443
436
|
# non-retriable errors because NotControllerError should
|
|
444
437
|
# either be thrown for all errors or no errors.
|
|
445
438
|
self._refresh_controller_id()
|
|
446
|
-
|
|
447
|
-
elif error_type
|
|
439
|
+
return False
|
|
440
|
+
elif error_type not in [Errors.NoError, Errors.ElectionNotNeeded]:
|
|
448
441
|
raise error_type(
|
|
449
442
|
"Request '{}' failed with response '{}'."
|
|
450
443
|
.format(request, response))
|
|
451
|
-
|
|
452
|
-
return response
|
|
453
|
-
raise RuntimeError("This should never happen, please file a bug with full stacktrace if encountered")
|
|
444
|
+
return True
|
|
454
445
|
|
|
455
446
|
@staticmethod
|
|
456
447
|
def _convert_new_topic_request(new_topic):
|
|
@@ -493,7 +484,7 @@ class KafkaAdminClient(object):
|
|
|
493
484
|
Returns:
|
|
494
485
|
Appropriate version of CreateTopicResponse class.
|
|
495
486
|
"""
|
|
496
|
-
version = self.
|
|
487
|
+
version = self._client.api_version(CreateTopicsRequest, max_version=3)
|
|
497
488
|
timeout_ms = self._validate_timeout(timeout_ms)
|
|
498
489
|
if version == 0:
|
|
499
490
|
if validate_only:
|
|
@@ -531,7 +522,7 @@ class KafkaAdminClient(object):
|
|
|
531
522
|
Returns:
|
|
532
523
|
Appropriate version of DeleteTopicsResponse class.
|
|
533
524
|
"""
|
|
534
|
-
version = self.
|
|
525
|
+
version = self._client.api_version(DeleteTopicsRequest, max_version=3)
|
|
535
526
|
timeout_ms = self._validate_timeout(timeout_ms)
|
|
536
527
|
if version <= 3:
|
|
537
528
|
request = DeleteTopicsRequest[version](
|
|
@@ -550,7 +541,7 @@ class KafkaAdminClient(object):
|
|
|
550
541
|
"""
|
|
551
542
|
topics == None means "get all topics"
|
|
552
543
|
"""
|
|
553
|
-
version = self.
|
|
544
|
+
version = self._client.api_version(MetadataRequest, max_version=5)
|
|
554
545
|
if version <= 3:
|
|
555
546
|
if auto_topic_creation:
|
|
556
547
|
raise IncompatibleBrokerVersion(
|
|
@@ -667,7 +658,7 @@ class KafkaAdminClient(object):
|
|
|
667
658
|
tuple of a list of matching ACL objects and a KafkaError (NoError if successful)
|
|
668
659
|
"""
|
|
669
660
|
|
|
670
|
-
version = self.
|
|
661
|
+
version = self._client.api_version(DescribeAclsRequest, max_version=1)
|
|
671
662
|
if version == 0:
|
|
672
663
|
request = DescribeAclsRequest[version](
|
|
673
664
|
resource_type=acl_filter.resource_pattern.resource_type,
|
|
@@ -801,7 +792,7 @@ class KafkaAdminClient(object):
|
|
|
801
792
|
if not isinstance(acl, ACL):
|
|
802
793
|
raise IllegalArgumentError("acls must contain ACL objects")
|
|
803
794
|
|
|
804
|
-
version = self.
|
|
795
|
+
version = self._client.api_version(CreateAclsRequest, max_version=1)
|
|
805
796
|
if version == 0:
|
|
806
797
|
request = CreateAclsRequest[version](
|
|
807
798
|
creations=[self._convert_create_acls_resource_request_v0(acl) for acl in acls]
|
|
@@ -923,7 +914,7 @@ class KafkaAdminClient(object):
|
|
|
923
914
|
if not isinstance(acl, ACLFilter):
|
|
924
915
|
raise IllegalArgumentError("acl_filters must contain ACLFilter type objects")
|
|
925
916
|
|
|
926
|
-
version = self.
|
|
917
|
+
version = self._client.api_version(DeleteAclsRequest, max_version=1)
|
|
927
918
|
|
|
928
919
|
if version == 0:
|
|
929
920
|
request = DeleteAclsRequest[version](
|
|
@@ -992,7 +983,7 @@ class KafkaAdminClient(object):
|
|
|
992
983
|
topic_resources.append(self._convert_describe_config_resource_request(config_resource))
|
|
993
984
|
|
|
994
985
|
futures = []
|
|
995
|
-
version = self.
|
|
986
|
+
version = self._client.api_version(DescribeConfigsRequest, max_version=2)
|
|
996
987
|
if version == 0:
|
|
997
988
|
if include_synonyms:
|
|
998
989
|
raise IncompatibleBrokerVersion(
|
|
@@ -1077,7 +1068,7 @@ class KafkaAdminClient(object):
|
|
|
1077
1068
|
Returns:
|
|
1078
1069
|
Appropriate version of AlterConfigsResponse class.
|
|
1079
1070
|
"""
|
|
1080
|
-
version = self.
|
|
1071
|
+
version = self._client.api_version(AlterConfigsRequest, max_version=1)
|
|
1081
1072
|
if version <= 1:
|
|
1082
1073
|
request = AlterConfigsRequest[version](
|
|
1083
1074
|
resources=[self._convert_alter_config_resource_request(config_resource) for config_resource in config_resources]
|
|
@@ -1138,7 +1129,7 @@ class KafkaAdminClient(object):
|
|
|
1138
1129
|
Returns:
|
|
1139
1130
|
Appropriate version of CreatePartitionsResponse class.
|
|
1140
1131
|
"""
|
|
1141
|
-
version = self.
|
|
1132
|
+
version = self._client.api_version(CreatePartitionsRequest, max_version=1)
|
|
1142
1133
|
timeout_ms = self._validate_timeout(timeout_ms)
|
|
1143
1134
|
if version <= 1:
|
|
1144
1135
|
request = CreatePartitionsRequest[version](
|
|
@@ -1152,8 +1143,118 @@ class KafkaAdminClient(object):
|
|
|
1152
1143
|
.format(version))
|
|
1153
1144
|
return self._send_request_to_controller(request)
|
|
1154
1145
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1146
|
+
def _get_leader_for_partitions(self, partitions, timeout_ms=None):
|
|
1147
|
+
"""Finds ID of the leader node for every given topic partition.
|
|
1148
|
+
|
|
1149
|
+
Will raise UnknownTopicOrPartitionError if for some partition no leader can be found.
|
|
1150
|
+
|
|
1151
|
+
:param partitions: ``[TopicPartition]``: partitions for which to find leaders.
|
|
1152
|
+
:param timeout_ms: ``float``: Timeout in milliseconds, if None (default), will be read from
|
|
1153
|
+
config.
|
|
1154
|
+
|
|
1155
|
+
:return: Dictionary with ``{leader_id -> {partitions}}``
|
|
1156
|
+
"""
|
|
1157
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
1158
|
+
|
|
1159
|
+
partitions = set(partitions)
|
|
1160
|
+
topics = set(tp.topic for tp in partitions)
|
|
1161
|
+
|
|
1162
|
+
response = self._get_cluster_metadata(topics=topics).to_object()
|
|
1163
|
+
|
|
1164
|
+
leader2partitions = defaultdict(list)
|
|
1165
|
+
valid_partitions = set()
|
|
1166
|
+
for topic in response.get("topics", ()):
|
|
1167
|
+
for partition in topic.get("partitions", ()):
|
|
1168
|
+
t2p = TopicPartition(topic=topic["topic"], partition=partition["partition"])
|
|
1169
|
+
if t2p in partitions:
|
|
1170
|
+
leader2partitions[partition["leader"]].append(t2p)
|
|
1171
|
+
valid_partitions.add(t2p)
|
|
1172
|
+
|
|
1173
|
+
if len(partitions) != len(valid_partitions):
|
|
1174
|
+
unknown = set(partitions) - valid_partitions
|
|
1175
|
+
raise UnknownTopicOrPartitionError(
|
|
1176
|
+
"The following partitions are not known: %s"
|
|
1177
|
+
% ", ".join(str(x) for x in unknown)
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
return leader2partitions
|
|
1181
|
+
|
|
1182
|
+
def delete_records(self, records_to_delete, timeout_ms=None, partition_leader_id=None):
|
|
1183
|
+
"""Delete records whose offset is smaller than the given offset of the corresponding partition.
|
|
1184
|
+
|
|
1185
|
+
:param records_to_delete: ``{TopicPartition: int}``: The earliest available offsets for the
|
|
1186
|
+
given partitions.
|
|
1187
|
+
:param timeout_ms: ``float``: Timeout in milliseconds, if None (default), will be read from
|
|
1188
|
+
config.
|
|
1189
|
+
:param partition_leader_id: ``str``: If specified, all deletion requests will be sent to
|
|
1190
|
+
this node. No check is performed verifying that this is indeed the leader for all
|
|
1191
|
+
listed partitions: use with caution.
|
|
1192
|
+
|
|
1193
|
+
:return: Dictionary {topicPartition -> metadata}, where metadata is returned by the broker.
|
|
1194
|
+
See DeleteRecordsResponse for possible fields. error_code for all partitions is
|
|
1195
|
+
guaranteed to be zero, otherwise an exception is raised.
|
|
1196
|
+
"""
|
|
1197
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
1198
|
+
responses = []
|
|
1199
|
+
version = self._client.api_version(DeleteRecordsRequest, max_version=0)
|
|
1200
|
+
if version is None:
|
|
1201
|
+
raise IncompatibleBrokerVersion("Broker does not support DeleteGroupsRequest")
|
|
1202
|
+
|
|
1203
|
+
# We want to make as few requests as possible
|
|
1204
|
+
# If a single node serves as a partition leader for multiple partitions (and/or
|
|
1205
|
+
# topics), we can send all of those in a single request.
|
|
1206
|
+
# For that we store {leader -> {partitions for leader}}, and do 1 request per leader
|
|
1207
|
+
if partition_leader_id is None:
|
|
1208
|
+
leader2partitions = self._get_leader_for_partitions(
|
|
1209
|
+
set(records_to_delete), timeout_ms
|
|
1210
|
+
)
|
|
1211
|
+
else:
|
|
1212
|
+
leader2partitions = {partition_leader_id: set(records_to_delete)}
|
|
1213
|
+
|
|
1214
|
+
for leader, partitions in leader2partitions.items():
|
|
1215
|
+
topic2partitions = defaultdict(list)
|
|
1216
|
+
for partition in partitions:
|
|
1217
|
+
topic2partitions[partition.topic].append(partition)
|
|
1218
|
+
|
|
1219
|
+
request = DeleteRecordsRequest[version](
|
|
1220
|
+
topics=[
|
|
1221
|
+
(topic, [(tp.partition, records_to_delete[tp]) for tp in partitions])
|
|
1222
|
+
for topic, partitions in topic2partitions.items()
|
|
1223
|
+
],
|
|
1224
|
+
timeout_ms=timeout_ms
|
|
1225
|
+
)
|
|
1226
|
+
future = self._send_request_to_node(leader, request)
|
|
1227
|
+
self._wait_for_futures([future])
|
|
1228
|
+
|
|
1229
|
+
responses.append(future.value.to_object())
|
|
1230
|
+
|
|
1231
|
+
partition2result = {}
|
|
1232
|
+
partition2error = {}
|
|
1233
|
+
for response in responses:
|
|
1234
|
+
for topic in response["topics"]:
|
|
1235
|
+
for partition in topic["partitions"]:
|
|
1236
|
+
tp = TopicPartition(topic["name"], partition["partition_index"])
|
|
1237
|
+
partition2result[tp] = partition
|
|
1238
|
+
if partition["error_code"] != 0:
|
|
1239
|
+
partition2error[tp] = partition["error_code"]
|
|
1240
|
+
|
|
1241
|
+
if partition2error:
|
|
1242
|
+
if len(partition2error) == 1:
|
|
1243
|
+
key, error = next(iter(partition2error.items()))
|
|
1244
|
+
raise Errors.for_code(error)(
|
|
1245
|
+
"Error deleting records from topic %s partition %s" % (key.topic, key.partition)
|
|
1246
|
+
)
|
|
1247
|
+
else:
|
|
1248
|
+
raise Errors.BrokerResponseError(
|
|
1249
|
+
"The following errors occured when trying to delete records: " +
|
|
1250
|
+
", ".join(
|
|
1251
|
+
"%s(partition=%d): %s" %
|
|
1252
|
+
(partition.topic, partition.partition, Errors.for_code(error).__name__)
|
|
1253
|
+
for partition, error in partition2error.items()
|
|
1254
|
+
)
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
return partition2result
|
|
1157
1258
|
|
|
1158
1259
|
# create delegation token protocol not yet implemented
|
|
1159
1260
|
# Note: send the request to the least_loaded_node()
|
|
@@ -1177,7 +1278,7 @@ class KafkaAdminClient(object):
|
|
|
1177
1278
|
Returns:
|
|
1178
1279
|
A message future.
|
|
1179
1280
|
"""
|
|
1180
|
-
version = self.
|
|
1281
|
+
version = self._client.api_version(DescribeGroupsRequest, max_version=3)
|
|
1181
1282
|
if version <= 2:
|
|
1182
1283
|
if include_authorized_operations:
|
|
1183
1284
|
raise IncompatibleBrokerVersion(
|
|
@@ -1311,7 +1412,7 @@ class KafkaAdminClient(object):
|
|
|
1311
1412
|
Returns:
|
|
1312
1413
|
A message future
|
|
1313
1414
|
"""
|
|
1314
|
-
version = self.
|
|
1415
|
+
version = self._client.api_version(ListGroupsRequest, max_version=2)
|
|
1315
1416
|
if version <= 2:
|
|
1316
1417
|
request = ListGroupsRequest[version]()
|
|
1317
1418
|
else:
|
|
@@ -1394,7 +1495,7 @@ class KafkaAdminClient(object):
|
|
|
1394
1495
|
Returns:
|
|
1395
1496
|
A message future
|
|
1396
1497
|
"""
|
|
1397
|
-
version = self.
|
|
1498
|
+
version = self._client.api_version(OffsetFetchRequest, max_version=5)
|
|
1398
1499
|
if version <= 3:
|
|
1399
1500
|
if partitions is None:
|
|
1400
1501
|
if version <= 1:
|
|
@@ -1427,7 +1528,7 @@ class KafkaAdminClient(object):
|
|
|
1427
1528
|
A dictionary composed of TopicPartition keys and
|
|
1428
1529
|
OffsetAndMetadata values.
|
|
1429
1530
|
"""
|
|
1430
|
-
if response.API_VERSION <=
|
|
1531
|
+
if response.API_VERSION <= 5:
|
|
1431
1532
|
|
|
1432
1533
|
# OffsetFetchResponse_v1 lacks a top-level error_code
|
|
1433
1534
|
if response.API_VERSION > 1:
|
|
@@ -1442,13 +1543,18 @@ class KafkaAdminClient(object):
|
|
|
1442
1543
|
# OffsetAndMetadata values--this is what the Java AdminClient returns
|
|
1443
1544
|
offsets = {}
|
|
1444
1545
|
for topic, partitions in response.topics:
|
|
1445
|
-
for
|
|
1546
|
+
for partition_data in partitions:
|
|
1547
|
+
if response.API_VERSION <= 4:
|
|
1548
|
+
partition, offset, metadata, error_code = partition_data
|
|
1549
|
+
leader_epoch = -1
|
|
1550
|
+
else:
|
|
1551
|
+
partition, offset, leader_epoch, metadata, error_code = partition_data
|
|
1446
1552
|
error_type = Errors.for_code(error_code)
|
|
1447
1553
|
if error_type is not Errors.NoError:
|
|
1448
1554
|
raise error_type(
|
|
1449
1555
|
"Unable to fetch consumer group offsets for topic {}, partition {}"
|
|
1450
1556
|
.format(topic, partition))
|
|
1451
|
-
offsets[TopicPartition(topic, partition)] = OffsetAndMetadata(offset, metadata)
|
|
1557
|
+
offsets[TopicPartition(topic, partition)] = OffsetAndMetadata(offset, metadata, leader_epoch)
|
|
1452
1558
|
else:
|
|
1453
1559
|
raise NotImplementedError(
|
|
1454
1560
|
"Support for OffsetFetchResponse_v{} has not yet been added to KafkaAdminClient."
|
|
@@ -1480,7 +1586,7 @@ class KafkaAdminClient(object):
|
|
|
1480
1586
|
|
|
1481
1587
|
Returns:
|
|
1482
1588
|
dictionary: A dictionary with TopicPartition keys and
|
|
1483
|
-
|
|
1589
|
+
OffsetAndMetadata values. Partitions that are not specified and for
|
|
1484
1590
|
which the group_id does not have a recorded offset are omitted. An
|
|
1485
1591
|
offset value of `-1` indicates the group_id has no offset for that
|
|
1486
1592
|
TopicPartition. A `-1` can only happen for partitions that are
|
|
@@ -1564,7 +1670,7 @@ class KafkaAdminClient(object):
|
|
|
1564
1670
|
Returns:
|
|
1565
1671
|
A future representing the in-flight DeleteGroupsRequest.
|
|
1566
1672
|
"""
|
|
1567
|
-
version = self.
|
|
1673
|
+
version = self._client.api_version(DeleteGroupsRequest, max_version=1)
|
|
1568
1674
|
if version <= 1:
|
|
1569
1675
|
request = DeleteGroupsRequest[version](group_ids)
|
|
1570
1676
|
else:
|
|
@@ -1573,6 +1679,51 @@ class KafkaAdminClient(object):
|
|
|
1573
1679
|
.format(version))
|
|
1574
1680
|
return self._send_request_to_node(group_coordinator_id, request)
|
|
1575
1681
|
|
|
1682
|
+
@staticmethod
|
|
1683
|
+
def _convert_topic_partitions(topic_partitions):
|
|
1684
|
+
return [
|
|
1685
|
+
(
|
|
1686
|
+
topic,
|
|
1687
|
+
partition_ids
|
|
1688
|
+
)
|
|
1689
|
+
for topic, partition_ids in topic_partitions.items()
|
|
1690
|
+
]
|
|
1691
|
+
|
|
1692
|
+
def _get_all_topic_partitions(self):
|
|
1693
|
+
return [
|
|
1694
|
+
(
|
|
1695
|
+
topic,
|
|
1696
|
+
[partition_info.partition for partition_info in self._client.cluster._partitions[topic].values()]
|
|
1697
|
+
)
|
|
1698
|
+
for topic in self._client.cluster.topics()
|
|
1699
|
+
]
|
|
1700
|
+
|
|
1701
|
+
def _get_topic_partitions(self, topic_partitions):
|
|
1702
|
+
if topic_partitions is None:
|
|
1703
|
+
return self._get_all_topic_partitions()
|
|
1704
|
+
return self._convert_topic_partitions(topic_partitions)
|
|
1705
|
+
|
|
1706
|
+
def perform_leader_election(self, election_type, topic_partitions=None, timeout_ms=None):
|
|
1707
|
+
"""Perform leader election on the topic partitions.
|
|
1708
|
+
|
|
1709
|
+
:param election_type: Type of election to attempt. 0 for Perferred, 1 for Unclean
|
|
1710
|
+
:param topic_partitions: A map of topic name strings to partition ids list.
|
|
1711
|
+
By default, will run on all topic partitions
|
|
1712
|
+
:param timeout_ms: Milliseconds to wait for the leader election process to complete
|
|
1713
|
+
before the broker returns.
|
|
1714
|
+
|
|
1715
|
+
:return: Appropriate version of ElectLeadersResponse class.
|
|
1716
|
+
"""
|
|
1717
|
+
version = self._client.api_version(ElectLeadersRequest, max_version=1)
|
|
1718
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
1719
|
+
request = ElectLeadersRequest[version](
|
|
1720
|
+
election_type=ElectionType(election_type),
|
|
1721
|
+
topic_partitions=self._get_topic_partitions(topic_partitions),
|
|
1722
|
+
timeout=timeout_ms,
|
|
1723
|
+
)
|
|
1724
|
+
# TODO convert structs to a more pythonic interface
|
|
1725
|
+
return self._send_request_to_controller(request)
|
|
1726
|
+
|
|
1576
1727
|
def _wait_for_futures(self, futures):
|
|
1577
1728
|
"""Block until all futures complete. If any fail, raise the encountered exception.
|
|
1578
1729
|
|
|
@@ -1595,7 +1746,7 @@ class KafkaAdminClient(object):
|
|
|
1595
1746
|
Returns:
|
|
1596
1747
|
A message future
|
|
1597
1748
|
"""
|
|
1598
|
-
version = self.
|
|
1749
|
+
version = self._client.api_version(DescribeLogDirsRequest, max_version=0)
|
|
1599
1750
|
if version <= 0:
|
|
1600
1751
|
request = DescribeLogDirsRequest[version]()
|
|
1601
1752
|
future = self._send_request_to_node(self._client.least_loaded_node(), request)
|