kafka-python 3.0.0__py3-none-any.whl
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/__init__.py +34 -0
- kafka/__main__.py +5 -0
- kafka/admin/__init__.py +29 -0
- kafka/admin/__main__.py +5 -0
- kafka/admin/_acls.py +355 -0
- kafka/admin/_cluster.py +359 -0
- kafka/admin/_configs.py +479 -0
- kafka/admin/_groups.py +754 -0
- kafka/admin/_partitions.py +595 -0
- kafka/admin/_topics.py +281 -0
- kafka/admin/_transactions.py +450 -0
- kafka/admin/_users.py +194 -0
- kafka/admin/client.py +373 -0
- kafka/benchmarks/__init__.py +0 -0
- kafka/benchmarks/consumer_performance.py +138 -0
- kafka/benchmarks/load_example.py +109 -0
- kafka/benchmarks/producer_encode_path.py +201 -0
- kafka/benchmarks/producer_performance.py +161 -0
- kafka/benchmarks/profile_protocol.py +138 -0
- kafka/benchmarks/protocol_old_vs_new.py +447 -0
- kafka/benchmarks/record_batch_compose.py +77 -0
- kafka/benchmarks/record_batch_read.py +82 -0
- kafka/benchmarks/varint_speed.py +426 -0
- kafka/cli/__init__.py +36 -0
- kafka/cli/admin/__init__.py +117 -0
- kafka/cli/admin/acls/__init__.py +9 -0
- kafka/cli/admin/acls/common.py +76 -0
- kafka/cli/admin/acls/create.py +19 -0
- kafka/cli/admin/acls/delete.py +23 -0
- kafka/cli/admin/acls/describe.py +16 -0
- kafka/cli/admin/cluster/__init__.py +14 -0
- kafka/cli/admin/cluster/describe.py +11 -0
- kafka/cli/admin/cluster/describe_quorum.py +11 -0
- kafka/cli/admin/cluster/features.py +52 -0
- kafka/cli/admin/cluster/log_dirs.py +43 -0
- kafka/cli/admin/cluster/versions.py +33 -0
- kafka/cli/admin/configs/__init__.py +10 -0
- kafka/cli/admin/configs/alter.py +43 -0
- kafka/cli/admin/configs/common.py +17 -0
- kafka/cli/admin/configs/describe.py +30 -0
- kafka/cli/admin/configs/list.py +16 -0
- kafka/cli/admin/configs/reset.py +20 -0
- kafka/cli/admin/groups/__init__.py +16 -0
- kafka/cli/admin/groups/alter_offsets.py +30 -0
- kafka/cli/admin/groups/delete.py +11 -0
- kafka/cli/admin/groups/delete_offsets.py +29 -0
- kafka/cli/admin/groups/describe.py +11 -0
- kafka/cli/admin/groups/list.py +28 -0
- kafka/cli/admin/groups/list_offsets.py +29 -0
- kafka/cli/admin/groups/remove_members.py +40 -0
- kafka/cli/admin/groups/reset_offsets.py +139 -0
- kafka/cli/admin/partitions/__init__.py +21 -0
- kafka/cli/admin/partitions/alter_reassignments.py +37 -0
- kafka/cli/admin/partitions/create.py +27 -0
- kafka/cli/admin/partitions/delete_records.py +31 -0
- kafka/cli/admin/partitions/describe.py +36 -0
- kafka/cli/admin/partitions/elect_leaders.py +53 -0
- kafka/cli/admin/partitions/list_offsets.py +88 -0
- kafka/cli/admin/partitions/list_reassignments.py +35 -0
- kafka/cli/admin/topics/__init__.py +10 -0
- kafka/cli/admin/topics/create.py +13 -0
- kafka/cli/admin/topics/delete.py +19 -0
- kafka/cli/admin/topics/describe.py +18 -0
- kafka/cli/admin/topics/list.py +11 -0
- kafka/cli/admin/transactions/__init__.py +17 -0
- kafka/cli/admin/transactions/abort.py +38 -0
- kafka/cli/admin/transactions/describe.py +24 -0
- kafka/cli/admin/transactions/describe_producers.py +29 -0
- kafka/cli/admin/transactions/find_hanging.py +26 -0
- kafka/cli/admin/transactions/list.py +37 -0
- kafka/cli/admin/users/__init__.py +8 -0
- kafka/cli/admin/users/alter_user_scram_credentials.py +34 -0
- kafka/cli/admin/users/describe_user_scram_credentials.py +15 -0
- kafka/cli/common.py +95 -0
- kafka/cli/consumer/__init__.py +63 -0
- kafka/cli/producer/__init__.py +57 -0
- kafka/cluster.py +824 -0
- kafka/codec.py +325 -0
- kafka/consumer/__init__.py +5 -0
- kafka/consumer/__main__.py +5 -0
- kafka/consumer/fetcher.py +2012 -0
- kafka/consumer/group.py +1347 -0
- kafka/consumer/subscription_state.py +897 -0
- kafka/coordinator/__init__.py +0 -0
- kafka/coordinator/assignors/__init__.py +0 -0
- kafka/coordinator/assignors/abstract.py +90 -0
- kafka/coordinator/assignors/cooperative_sticky.py +167 -0
- kafka/coordinator/assignors/range.py +81 -0
- kafka/coordinator/assignors/roundrobin.py +101 -0
- kafka/coordinator/assignors/sticky/StickyAssignorUserData.json +37 -0
- kafka/coordinator/assignors/sticky/__init__.py +0 -0
- kafka/coordinator/assignors/sticky/partition_movements.py +149 -0
- kafka/coordinator/assignors/sticky/sorted_set.py +63 -0
- kafka/coordinator/assignors/sticky/sticky_assignor.py +665 -0
- kafka/coordinator/assignors/sticky/user_data.py +8 -0
- kafka/coordinator/base.py +1215 -0
- kafka/coordinator/consumer.py +1224 -0
- kafka/coordinator/heartbeat.py +82 -0
- kafka/coordinator/subscription.py +34 -0
- kafka/errors.py +1004 -0
- kafka/future.py +166 -0
- kafka/metrics/__init__.py +13 -0
- kafka/metrics/compound_stat.py +33 -0
- kafka/metrics/dict_reporter.py +81 -0
- kafka/metrics/kafka_metric.py +36 -0
- kafka/metrics/measurable.py +27 -0
- kafka/metrics/measurable_stat.py +13 -0
- kafka/metrics/metric_config.py +33 -0
- kafka/metrics/metric_name.py +105 -0
- kafka/metrics/metrics.py +261 -0
- kafka/metrics/metrics_reporter.py +53 -0
- kafka/metrics/quota.py +41 -0
- kafka/metrics/stat.py +19 -0
- kafka/metrics/stats/__init__.py +15 -0
- kafka/metrics/stats/avg.py +24 -0
- kafka/metrics/stats/count.py +17 -0
- kafka/metrics/stats/histogram.py +99 -0
- kafka/metrics/stats/max_stat.py +17 -0
- kafka/metrics/stats/min_stat.py +19 -0
- kafka/metrics/stats/percentile.py +14 -0
- kafka/metrics/stats/percentiles.py +75 -0
- kafka/metrics/stats/rate.py +118 -0
- kafka/metrics/stats/sampled_stat.py +99 -0
- kafka/metrics/stats/sensor.py +136 -0
- kafka/metrics/stats/total.py +15 -0
- kafka/net/__init__.py +19 -0
- kafka/net/compat.py +165 -0
- kafka/net/connection.py +593 -0
- kafka/net/http_connect.py +144 -0
- kafka/net/inet.py +122 -0
- kafka/net/manager.py +451 -0
- kafka/net/metrics.py +149 -0
- kafka/net/sasl/__init__.py +32 -0
- kafka/net/sasl/abc.py +28 -0
- kafka/net/sasl/gssapi.py +95 -0
- kafka/net/sasl/msk.py +245 -0
- kafka/net/sasl/oauth.py +98 -0
- kafka/net/sasl/plain.py +42 -0
- kafka/net/sasl/scram.py +135 -0
- kafka/net/sasl/sspi.py +111 -0
- kafka/net/selector.py +644 -0
- kafka/net/socks5.py +262 -0
- kafka/net/transport.py +415 -0
- kafka/net/wakeup_notifier.py +72 -0
- kafka/partitioner/__init__.py +8 -0
- kafka/partitioner/abc.py +8 -0
- kafka/partitioner/default.py +89 -0
- kafka/partitioner/sticky.py +109 -0
- kafka/producer/__init__.py +5 -0
- kafka/producer/__main__.py +5 -0
- kafka/producer/future.py +101 -0
- kafka/producer/kafka.py +1123 -0
- kafka/producer/producer_batch.py +192 -0
- kafka/producer/record_accumulator.py +647 -0
- kafka/producer/sender.py +884 -0
- kafka/producer/transaction_manager.py +1326 -0
- kafka/protocol/__init__.py +0 -0
- kafka/protocol/admin/__init__.py +29 -0
- kafka/protocol/admin/acl.py +83 -0
- kafka/protocol/admin/acl.pyi +375 -0
- kafka/protocol/admin/client_quotas.py +14 -0
- kafka/protocol/admin/client_quotas.pyi +265 -0
- kafka/protocol/admin/cluster.py +31 -0
- kafka/protocol/admin/cluster.pyi +620 -0
- kafka/protocol/admin/configs.py +22 -0
- kafka/protocol/admin/configs.pyi +437 -0
- kafka/protocol/admin/groups.py +24 -0
- kafka/protocol/admin/groups.pyi +261 -0
- kafka/protocol/admin/topics.py +53 -0
- kafka/protocol/admin/topics.pyi +982 -0
- kafka/protocol/admin/transactions.py +18 -0
- kafka/protocol/admin/transactions.pyi +311 -0
- kafka/protocol/admin/users.py +14 -0
- kafka/protocol/admin/users.pyi +223 -0
- kafka/protocol/api_data.py +125 -0
- kafka/protocol/api_header.py +55 -0
- kafka/protocol/api_key.py +97 -0
- kafka/protocol/api_message.py +277 -0
- kafka/protocol/broker_version_data.py +246 -0
- kafka/protocol/consumer/__init__.py +13 -0
- kafka/protocol/consumer/fetch.py +16 -0
- kafka/protocol/consumer/fetch.pyi +298 -0
- kafka/protocol/consumer/group.py +38 -0
- kafka/protocol/consumer/group.pyi +824 -0
- kafka/protocol/consumer/metadata.py +30 -0
- kafka/protocol/consumer/metadata.pyi +89 -0
- kafka/protocol/consumer/offsets.py +75 -0
- kafka/protocol/consumer/offsets.pyi +288 -0
- kafka/protocol/data_container.py +166 -0
- kafka/protocol/frame.py +30 -0
- kafka/protocol/generate_stubs.py +468 -0
- kafka/protocol/metadata/__init__.py +10 -0
- kafka/protocol/metadata/api_versions.py +41 -0
- kafka/protocol/metadata/api_versions.pyi +128 -0
- kafka/protocol/metadata/find_coordinator.py +19 -0
- kafka/protocol/metadata/find_coordinator.pyi +105 -0
- kafka/protocol/metadata/metadata.py +34 -0
- kafka/protocol/metadata/metadata.pyi +160 -0
- kafka/protocol/old/__init__.py +0 -0
- kafka/protocol/old/abstract.py +17 -0
- kafka/protocol/old/add_offsets_to_txn.py +54 -0
- kafka/protocol/old/add_partitions_to_txn.py +71 -0
- kafka/protocol/old/admin.py +1086 -0
- kafka/protocol/old/api.py +205 -0
- kafka/protocol/old/api_versions.py +133 -0
- kafka/protocol/old/commit.py +355 -0
- kafka/protocol/old/consumer_protocol.py +36 -0
- kafka/protocol/old/end_txn.py +53 -0
- kafka/protocol/old/fetch.py +408 -0
- kafka/protocol/old/find_coordinator.py +72 -0
- kafka/protocol/old/group.py +451 -0
- kafka/protocol/old/init_producer_id.py +42 -0
- kafka/protocol/old/list_offsets.py +186 -0
- kafka/protocol/old/metadata.py +290 -0
- kafka/protocol/old/offset_for_leader_epoch.py +133 -0
- kafka/protocol/old/produce.py +247 -0
- kafka/protocol/old/sasl_authenticate.py +38 -0
- kafka/protocol/old/sasl_handshake.py +39 -0
- kafka/protocol/old/struct.py +87 -0
- kafka/protocol/old/txn_offset_commit.py +73 -0
- kafka/protocol/old/types.py +440 -0
- kafka/protocol/parser.py +191 -0
- kafka/protocol/producer/__init__.py +7 -0
- kafka/protocol/producer/produce.py +17 -0
- kafka/protocol/producer/produce.pyi +197 -0
- kafka/protocol/producer/transaction.py +30 -0
- kafka/protocol/producer/transaction.pyi +663 -0
- kafka/protocol/sasl.py +52 -0
- kafka/protocol/sasl.pyi +126 -0
- kafka/protocol/schemas/__init__.py +7 -0
- kafka/protocol/schemas/fields/__init__.py +7 -0
- kafka/protocol/schemas/fields/array.py +127 -0
- kafka/protocol/schemas/fields/base.py +156 -0
- kafka/protocol/schemas/fields/codecs/__init__.py +12 -0
- kafka/protocol/schemas/fields/codecs/encode_buffer.py +82 -0
- kafka/protocol/schemas/fields/codecs/tagged_fields.py +109 -0
- kafka/protocol/schemas/fields/codecs/types.py +505 -0
- kafka/protocol/schemas/fields/codegen.py +40 -0
- kafka/protocol/schemas/fields/simple.py +127 -0
- kafka/protocol/schemas/fields/struct.py +357 -0
- kafka/protocol/schemas/fields/struct_array.py +142 -0
- kafka/protocol/schemas/load_json.py +42 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnRequest.json +40 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnResponse.json +35 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnRequest.json +65 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnResponse.json +60 -0
- kafka/protocol/schemas/resources/AlterClientQuotasRequest.json +47 -0
- kafka/protocol/schemas/resources/AlterClientQuotasResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterConfigsRequest.json +43 -0
- kafka/protocol/schemas/resources/AlterConfigsResponse.json +39 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsRequest.json +42 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsResponse.json +47 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsRequest.json +41 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsRequest.json +45 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsResponse.json +35 -0
- kafka/protocol/schemas/resources/ApiVersionsRequest.json +34 -0
- kafka/protocol/schemas/resources/ApiVersionsResponse.json +79 -0
- kafka/protocol/schemas/resources/ConsumerProtocolAssignment.json +42 -0
- kafka/protocol/schemas/resources/ConsumerProtocolSubscription.json +49 -0
- kafka/protocol/schemas/resources/CreateAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/CreateAclsResponse.json +37 -0
- kafka/protocol/schemas/resources/CreatePartitionsRequest.json +47 -0
- kafka/protocol/schemas/resources/CreatePartitionsResponse.json +41 -0
- kafka/protocol/schemas/resources/CreateTopicsRequest.json +65 -0
- kafka/protocol/schemas/resources/CreateTopicsResponse.json +72 -0
- kafka/protocol/schemas/resources/DeleteAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/DeleteAclsResponse.json +59 -0
- kafka/protocol/schemas/resources/DeleteGroupsRequest.json +30 -0
- kafka/protocol/schemas/resources/DeleteGroupsResponse.json +36 -0
- kafka/protocol/schemas/resources/DeleteRecordsRequest.json +42 -0
- kafka/protocol/schemas/resources/DeleteRecordsResponse.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsRequest.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeAclsRequest.json +43 -0
- kafka/protocol/schemas/resources/DescribeAclsResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasRequest.json +37 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasResponse.json +47 -0
- kafka/protocol/schemas/resources/DescribeClusterRequest.json +35 -0
- kafka/protocol/schemas/resources/DescribeClusterResponse.json +56 -0
- kafka/protocol/schemas/resources/DescribeConfigsRequest.json +42 -0
- kafka/protocol/schemas/resources/DescribeConfigsResponse.json +69 -0
- kafka/protocol/schemas/resources/DescribeGroupsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeGroupsResponse.json +74 -0
- kafka/protocol/schemas/resources/DescribeLogDirsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeLogDirsResponse.json +65 -0
- kafka/protocol/schemas/resources/DescribeProducersRequest.json +32 -0
- kafka/protocol/schemas/resources/DescribeProducersResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeQuorumRequest.json +39 -0
- kafka/protocol/schemas/resources/DescribeQuorumResponse.json +82 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsRequest.json +40 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsResponse.json +66 -0
- kafka/protocol/schemas/resources/DescribeTransactionsRequest.json +27 -0
- kafka/protocol/schemas/resources/DescribeTransactionsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsRequest.json +30 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsResponse.json +45 -0
- kafka/protocol/schemas/resources/ElectLeadersRequest.json +41 -0
- kafka/protocol/schemas/resources/ElectLeadersResponse.json +45 -0
- kafka/protocol/schemas/resources/EndTxnRequest.json +43 -0
- kafka/protocol/schemas/resources/EndTxnResponse.json +41 -0
- kafka/protocol/schemas/resources/FetchRequest.json +125 -0
- kafka/protocol/schemas/resources/FetchResponse.json +124 -0
- kafka/protocol/schemas/resources/FindCoordinatorRequest.json +43 -0
- kafka/protocol/schemas/resources/FindCoordinatorResponse.json +58 -0
- kafka/protocol/schemas/resources/HeartbeatRequest.json +39 -0
- kafka/protocol/schemas/resources/HeartbeatResponse.json +35 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsRequest.json +44 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsResponse.json +38 -0
- kafka/protocol/schemas/resources/InitProducerIdRequest.json +50 -0
- kafka/protocol/schemas/resources/InitProducerIdResponse.json +47 -0
- kafka/protocol/schemas/resources/JoinGroupRequest.json +63 -0
- kafka/protocol/schemas/resources/JoinGroupResponse.json +69 -0
- kafka/protocol/schemas/resources/LeaveGroupRequest.json +47 -0
- kafka/protocol/schemas/resources/LeaveGroupResponse.json +47 -0
- kafka/protocol/schemas/resources/ListConfigResourcesRequest.json +31 -0
- kafka/protocol/schemas/resources/ListConfigResourcesResponse.json +37 -0
- kafka/protocol/schemas/resources/ListGroupsRequest.json +36 -0
- kafka/protocol/schemas/resources/ListGroupsResponse.json +49 -0
- kafka/protocol/schemas/resources/ListOffsetsRequest.json +72 -0
- kafka/protocol/schemas/resources/ListOffsetsResponse.json +71 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsRequest.json +34 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsResponse.json +46 -0
- kafka/protocol/schemas/resources/ListTransactionsRequest.json +40 -0
- kafka/protocol/schemas/resources/ListTransactionsResponse.json +42 -0
- kafka/protocol/schemas/resources/MetadataRequest.json +56 -0
- kafka/protocol/schemas/resources/MetadataResponse.json +101 -0
- kafka/protocol/schemas/resources/OffsetCommitRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetCommitResponse.json +71 -0
- kafka/protocol/schemas/resources/OffsetDeleteRequest.json +39 -0
- kafka/protocol/schemas/resources/OffsetDeleteResponse.json +42 -0
- kafka/protocol/schemas/resources/OffsetFetchRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetFetchResponse.json +107 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochRequest.json +52 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochResponse.json +51 -0
- kafka/protocol/schemas/resources/ProduceRequest.json +73 -0
- kafka/protocol/schemas/resources/ProduceResponse.json +96 -0
- kafka/protocol/schemas/resources/RequestHeader.json +44 -0
- kafka/protocol/schemas/resources/ResponseHeader.json +26 -0
- kafka/protocol/schemas/resources/SaslAuthenticateRequest.json +29 -0
- kafka/protocol/schemas/resources/SaslAuthenticateResponse.json +34 -0
- kafka/protocol/schemas/resources/SaslHandshakeRequest.json +31 -0
- kafka/protocol/schemas/resources/SaslHandshakeResponse.json +32 -0
- kafka/protocol/schemas/resources/SyncGroupRequest.json +56 -0
- kafka/protocol/schemas/resources/SyncGroupResponse.json +46 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitRequest.json +68 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitResponse.json +47 -0
- kafka/protocol/schemas/resources/UpdateFeaturesRequest.json +43 -0
- kafka/protocol/schemas/resources/UpdateFeaturesResponse.json +39 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersRequest.json +49 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersResponse.json +45 -0
- kafka/protocol/schemas/resources/__init__.py +0 -0
- kafka/record/__init__.py +3 -0
- kafka/record/_crc32c.py +161 -0
- kafka/record/abc.py +144 -0
- kafka/record/default_records.py +782 -0
- kafka/record/legacy_records.py +587 -0
- kafka/record/memory_records.py +255 -0
- kafka/record/util.py +135 -0
- kafka/serializer/__init__.py +4 -0
- kafka/serializer/abstract.py +20 -0
- kafka/serializer/default.py +16 -0
- kafka/serializer/json.py +17 -0
- kafka/serializer/wrapper.py +21 -0
- kafka/structs.py +69 -0
- kafka/util.py +159 -0
- kafka/vendor/__init__.py +0 -0
- kafka/version.py +1 -0
- kafka_python-3.0.0.dist-info/METADATA +319 -0
- kafka_python-3.0.0.dist-info/RECORD +373 -0
- kafka_python-3.0.0.dist-info/WHEEL +5 -0
- kafka_python-3.0.0.dist-info/entry_points.txt +2 -0
- kafka_python-3.0.0.dist-info/licenses/LICENSE +202 -0
- kafka_python-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
"""Partition management mixin for KafkaAdminClient.
|
|
2
|
+
|
|
3
|
+
Also defines NewPartitions data class.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
import kafka.errors as Errors
|
|
13
|
+
from kafka.errors import UnknownTopicOrPartitionError
|
|
14
|
+
from kafka.protocol.admin import (
|
|
15
|
+
AlterPartitionReassignmentsRequest,
|
|
16
|
+
CreatePartitionsRequest,
|
|
17
|
+
DeleteRecordsRequest,
|
|
18
|
+
DescribeTopicPartitionsRequest,
|
|
19
|
+
ElectLeadersRequest,
|
|
20
|
+
ElectionType,
|
|
21
|
+
ListPartitionReassignmentsRequest,
|
|
22
|
+
)
|
|
23
|
+
from kafka.protocol.consumer import (
|
|
24
|
+
ListOffsetsRequest, IsolationLevel, OffsetSpec, OffsetTimestamp,
|
|
25
|
+
)
|
|
26
|
+
from kafka.structs import TopicPartition, OffsetAndTimestamp
|
|
27
|
+
from kafka.util import Timer
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from kafka.net.manager import KafkaConnectionManager
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PartitionAdminMixin:
|
|
37
|
+
"""Mixin providing partition and record management methods."""
|
|
38
|
+
_manager: KafkaConnectionManager
|
|
39
|
+
config: dict
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def _process_create_partitions_input(topic_partitions):
|
|
43
|
+
_Topic = CreatePartitionsRequest.CreatePartitionsTopic
|
|
44
|
+
_Assignment = CreatePartitionsRequest.CreatePartitionsTopic.CreatePartitionsAssignment
|
|
45
|
+
topics = []
|
|
46
|
+
for topic, count in topic_partitions.items():
|
|
47
|
+
if isinstance(count, int):
|
|
48
|
+
topics.append(_Topic(name=topic, count=count))
|
|
49
|
+
elif isinstance(count, dict):
|
|
50
|
+
topics.append(
|
|
51
|
+
_Topic(
|
|
52
|
+
name=topic,
|
|
53
|
+
count=count['count'],
|
|
54
|
+
assignments=[_Assignment(broker_ids=broker_ids)
|
|
55
|
+
for broker_ids in count['assignments']]))
|
|
56
|
+
else:
|
|
57
|
+
topics.append(
|
|
58
|
+
_Topic(
|
|
59
|
+
name=topic,
|
|
60
|
+
count=count.total_count,
|
|
61
|
+
assignments=[_Assignment(broker_ids=broker_ids)
|
|
62
|
+
for broker_ids in count.new_assignments]))
|
|
63
|
+
return topics
|
|
64
|
+
|
|
65
|
+
def create_partitions(self, topic_partitions, timeout_ms=None, validate_only=False, raise_errors=True):
|
|
66
|
+
"""Create additional partitions for an existing topic.
|
|
67
|
+
|
|
68
|
+
Arguments:
|
|
69
|
+
topic_partitions: A dict of topic name strings to total partition count (int),
|
|
70
|
+
or a dict of {topic_name: {count: int, assignments: [[broker_ids]]}}
|
|
71
|
+
if manual assignment is desired.
|
|
72
|
+
dict of {topic_name: NewPartitions} is deprecated.
|
|
73
|
+
|
|
74
|
+
Keyword Arguments:
|
|
75
|
+
timeout_ms (numeric, optional): Milliseconds to wait for new partitions to be
|
|
76
|
+
created before the broker returns.
|
|
77
|
+
validate_only (bool, optional): If True, don't actually create new partitions.
|
|
78
|
+
Default: False
|
|
79
|
+
raise_errors (bool, optional): Whether to raise errors as exceptions. Default True.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Appropriate version of CreatePartitionsResponse class.
|
|
83
|
+
"""
|
|
84
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
85
|
+
request = CreatePartitionsRequest(
|
|
86
|
+
topics=self._process_create_partitions_input(topic_partitions),
|
|
87
|
+
timeout_ms=timeout_ms,
|
|
88
|
+
validate_only=validate_only)
|
|
89
|
+
|
|
90
|
+
def response_errors(r):
|
|
91
|
+
for result in r.results:
|
|
92
|
+
yield Errors.for_code(result.error_code)
|
|
93
|
+
return self._manager.run(self._send_request_to_controller, request, response_errors, raise_errors)
|
|
94
|
+
|
|
95
|
+
async def _async_get_leader_for_partitions(self, partitions):
|
|
96
|
+
"""Finds ID of the leader node for every given topic partition."""
|
|
97
|
+
partitions = set(partitions)
|
|
98
|
+
topics = set(tp.topic for tp in partitions)
|
|
99
|
+
|
|
100
|
+
metadata = await self._get_cluster_metadata(topics)
|
|
101
|
+
|
|
102
|
+
leader2partitions = defaultdict(set)
|
|
103
|
+
valid_partitions = set()
|
|
104
|
+
for topic in metadata.get("topics", ()):
|
|
105
|
+
for partition in topic.get("partitions", ()):
|
|
106
|
+
t2p = TopicPartition(topic=topic["name"], partition=partition["partition_index"])
|
|
107
|
+
if t2p in partitions:
|
|
108
|
+
leader2partitions[partition["leader_id"]].add(t2p)
|
|
109
|
+
valid_partitions.add(t2p)
|
|
110
|
+
|
|
111
|
+
if partitions != valid_partitions:
|
|
112
|
+
unknown = partitions - valid_partitions
|
|
113
|
+
raise UnknownTopicOrPartitionError(
|
|
114
|
+
"The following partitions are not known: %s"
|
|
115
|
+
% ", ".join(str(x) for x in unknown)
|
|
116
|
+
)
|
|
117
|
+
return leader2partitions
|
|
118
|
+
|
|
119
|
+
async def _async_delete_records(self, records_to_delete, timeout_ms=None, partition_leader_id=None):
|
|
120
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
121
|
+
timer = Timer(timeout_ms)
|
|
122
|
+
backoff_secs = self.config['retry_backoff_ms'] / 1000
|
|
123
|
+
|
|
124
|
+
pending = set(records_to_delete)
|
|
125
|
+
partition2result = {}
|
|
126
|
+
partition2error = {}
|
|
127
|
+
|
|
128
|
+
while pending:
|
|
129
|
+
if partition_leader_id is None:
|
|
130
|
+
leader2partitions = await self._async_get_leader_for_partitions(pending)
|
|
131
|
+
else:
|
|
132
|
+
leader2partitions = {partition_leader_id: set(pending)}
|
|
133
|
+
|
|
134
|
+
responses = []
|
|
135
|
+
for leader, partitions in leader2partitions.items():
|
|
136
|
+
topic2partitions = defaultdict(list)
|
|
137
|
+
for partition in partitions:
|
|
138
|
+
topic2partitions[partition.topic].append(partition)
|
|
139
|
+
|
|
140
|
+
request = DeleteRecordsRequest(
|
|
141
|
+
topics=[
|
|
142
|
+
(topic, [(tp.partition, records_to_delete[tp]) for tp in parts])
|
|
143
|
+
for topic, parts in topic2partitions.items()
|
|
144
|
+
],
|
|
145
|
+
timeout_ms=int(timer.timeout_ms) if timer.timeout_ms is not None else timeout_ms,
|
|
146
|
+
)
|
|
147
|
+
response = await self._manager.send(request, node_id=leader)
|
|
148
|
+
responses.append(response.to_dict())
|
|
149
|
+
|
|
150
|
+
retry_partitions = set()
|
|
151
|
+
for response in responses:
|
|
152
|
+
for topic in response["topics"]:
|
|
153
|
+
for partition in topic["partitions"]:
|
|
154
|
+
tp = TopicPartition(topic["name"], partition["partition_index"])
|
|
155
|
+
err_code = partition["error_code"]
|
|
156
|
+
if (err_code == Errors.NotLeaderForPartitionError.errno
|
|
157
|
+
and partition_leader_id is None
|
|
158
|
+
and not timer.expired):
|
|
159
|
+
retry_partitions.add(tp)
|
|
160
|
+
continue
|
|
161
|
+
partition2result[tp] = partition
|
|
162
|
+
pending.discard(tp)
|
|
163
|
+
if err_code != 0:
|
|
164
|
+
partition2error[tp] = err_code
|
|
165
|
+
|
|
166
|
+
if not retry_partitions:
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
log.debug(
|
|
170
|
+
'delete_records: NotLeaderForPartitionError on %d partition(s); '
|
|
171
|
+
'refreshing metadata and retrying', len(retry_partitions))
|
|
172
|
+
pending = retry_partitions
|
|
173
|
+
await self._net.sleep(min(backoff_secs, max(0.0, timer.timeout_secs or 0.0)))
|
|
174
|
+
|
|
175
|
+
if partition2error:
|
|
176
|
+
if len(partition2error) == 1:
|
|
177
|
+
key, error = next(iter(partition2error.items()))
|
|
178
|
+
raise Errors.for_code(error)(
|
|
179
|
+
"Error deleting records from topic %s partition %s" % (key.topic, key.partition)
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
raise Errors.BrokerResponseError(
|
|
183
|
+
"The following errors occured when trying to delete records: " +
|
|
184
|
+
", ".join(
|
|
185
|
+
"%s(partition=%d): %s" %
|
|
186
|
+
(partition.topic, partition.partition, Errors.for_code(error).__name__)
|
|
187
|
+
for partition, error in partition2error.items()
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return partition2result
|
|
192
|
+
|
|
193
|
+
def delete_records(self, records_to_delete, timeout_ms=None, partition_leader_id=None):
|
|
194
|
+
"""Delete records whose offset is smaller than the given offset of the corresponding partition.
|
|
195
|
+
|
|
196
|
+
Partitions whose response is :class:`~kafka.errors.NotLeaderForPartitionError`
|
|
197
|
+
are retried with refreshed metadata, bounded by ``timeout_ms`` (or the
|
|
198
|
+
admin client's ``request_timeout_ms`` when ``None``). When
|
|
199
|
+
``partition_leader_id`` is supplied no retry is attempted; the caller
|
|
200
|
+
is asserting routing and any error is reported as-is.
|
|
201
|
+
|
|
202
|
+
Arguments:
|
|
203
|
+
records_to_delete ({TopicPartition: int}): The earliest available offsets for the
|
|
204
|
+
given partitions.
|
|
205
|
+
|
|
206
|
+
Keyword Arguments:
|
|
207
|
+
timeout_ms (numeric, optional): Timeout in milliseconds. Also caps
|
|
208
|
+
the total time spent retrying NotLeaderForPartitionError.
|
|
209
|
+
partition_leader_id (node_id / int, optional): If specified, all deletion requests
|
|
210
|
+
will be sent to this node.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
dict {topicPartition -> metadata}
|
|
214
|
+
"""
|
|
215
|
+
return self._manager.run(self._async_delete_records, records_to_delete, timeout_ms, partition_leader_id)
|
|
216
|
+
|
|
217
|
+
def _get_all_topic_partitions(self, topics=None):
|
|
218
|
+
return [
|
|
219
|
+
(
|
|
220
|
+
topic['name'],
|
|
221
|
+
[p['partition_index'] for p in topic['partitions']]
|
|
222
|
+
)
|
|
223
|
+
for topic in self.describe_topics(topics)
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
def _get_topic_partitions(self, topic_partitions):
|
|
227
|
+
if isinstance(topic_partitions, dict):
|
|
228
|
+
return topic_partitions.items()
|
|
229
|
+
else:
|
|
230
|
+
return self._get_all_topic_partitions(topic_partitions)
|
|
231
|
+
|
|
232
|
+
def elect_leaders(self, election_type, topic_partitions=None, timeout_ms=None, raise_errors=True):
|
|
233
|
+
"""Trigger leader election for the specified topic partitions.
|
|
234
|
+
|
|
235
|
+
Arguments:
|
|
236
|
+
election_type: Type of election to attempt. 0 for Preferred, 1 for Unclean
|
|
237
|
+
|
|
238
|
+
Keyword Arguments:
|
|
239
|
+
topic_partitions (dict, list, optional):
|
|
240
|
+
Either: dict of {topic_name: [partition ids]}.
|
|
241
|
+
Or: list of [topic_name], and election will run on all partitions for topic.
|
|
242
|
+
Or: None, and election runs against all topics / all partitions.
|
|
243
|
+
Default: None
|
|
244
|
+
timeout_ms (num, optional): Milliseconds to wait for the leader election process.
|
|
245
|
+
raise_errors (bool, optional): Whether to raise errors as exceptions. Default True.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Appropriate version of ElectLeadersResponse class.
|
|
249
|
+
"""
|
|
250
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
251
|
+
request = ElectLeadersRequest(
|
|
252
|
+
election_type=ElectionType(election_type),
|
|
253
|
+
topic_partitions=self._get_topic_partitions(topic_partitions),
|
|
254
|
+
timeout_ms=timeout_ms,
|
|
255
|
+
)
|
|
256
|
+
def response_errors(r):
|
|
257
|
+
if r.API_VERSION >= 1:
|
|
258
|
+
yield Errors.for_code(r.error_code)
|
|
259
|
+
for result in r.replica_election_results:
|
|
260
|
+
for partition in result.partition_result:
|
|
261
|
+
yield Errors.for_code(partition.error_code)
|
|
262
|
+
ignore_errors = (Errors.ElectionNotNeededError,)
|
|
263
|
+
return self._manager.run(self._send_request_to_controller, request, response_errors, raise_errors, ignore_errors)
|
|
264
|
+
|
|
265
|
+
@staticmethod
|
|
266
|
+
def _process_alter_partition_reassignments_input(reassignments):
|
|
267
|
+
_Topic = AlterPartitionReassignmentsRequest.ReassignableTopic
|
|
268
|
+
_Partition = _Topic.ReassignablePartition
|
|
269
|
+
topic2partitions = defaultdict(list)
|
|
270
|
+
for tp, replicas in reassignments.items():
|
|
271
|
+
if replicas is not None:
|
|
272
|
+
replicas = list(replicas)
|
|
273
|
+
if not replicas:
|
|
274
|
+
raise ValueError(
|
|
275
|
+
"Replica list for %s must be non-empty; "
|
|
276
|
+
"use None to cancel a reassignment." % (tp,))
|
|
277
|
+
elif not all(isinstance(item, int) for item in replicas):
|
|
278
|
+
raise ValueError(
|
|
279
|
+
"Replica list for %s must be int broker_ids." % (tp,))
|
|
280
|
+
topic2partitions[tp.topic].append(_Partition(
|
|
281
|
+
partition_index=tp.partition,
|
|
282
|
+
replicas=replicas,
|
|
283
|
+
))
|
|
284
|
+
return [_Topic(name=topic, partitions=parts) for topic, parts in topic2partitions.items()]
|
|
285
|
+
|
|
286
|
+
def alter_partition_reassignments(self, reassignments, timeout_ms=None):
|
|
287
|
+
"""Alter the replica sets for the given partitions.
|
|
288
|
+
|
|
289
|
+
Arguments:
|
|
290
|
+
reassignments (dict): A dict mapping
|
|
291
|
+
:class:`~kafka.TopicPartition` to a list of broker IDs
|
|
292
|
+
for the new replica set, or ``None`` to cancel a
|
|
293
|
+
pending reassignment for that partition.
|
|
294
|
+
|
|
295
|
+
Keyword Arguments:
|
|
296
|
+
timeout_ms (numeric, optional): The time in ms to wait for
|
|
297
|
+
the request to complete.
|
|
298
|
+
|
|
299
|
+
Raises: top-level failures that prevents processing request.
|
|
300
|
+
Does not raise partition-specific errors.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
dict: A dict mapping each :class:`~kafka.TopicPartition`
|
|
304
|
+
that the broker acknowledged to the error class for that
|
|
305
|
+
partition, or ``None`` if the reassignment was accepted.
|
|
306
|
+
Partitions the broker did not report on are absent from the
|
|
307
|
+
dict.
|
|
308
|
+
"""
|
|
309
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
310
|
+
|
|
311
|
+
request = AlterPartitionReassignmentsRequest(
|
|
312
|
+
timeout_ms=timeout_ms,
|
|
313
|
+
topics=self._process_alter_partition_reassignments_input(reassignments),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def top_level_error(r):
|
|
317
|
+
yield Errors.for_code(r.error_code)
|
|
318
|
+
response = self._manager.run(
|
|
319
|
+
self._send_request_to_controller, request, top_level_error)
|
|
320
|
+
|
|
321
|
+
results = {}
|
|
322
|
+
for topic in response.responses:
|
|
323
|
+
for partition in topic.partitions:
|
|
324
|
+
tp = TopicPartition(topic.name, partition.partition_index)
|
|
325
|
+
err = Errors.for_code(partition.error_code)
|
|
326
|
+
results[tp] = err if err is not Errors.NoError else None
|
|
327
|
+
return results
|
|
328
|
+
|
|
329
|
+
async def _async_list_partition_reassignments(self, topic_partitions=None, timeout_ms=None):
|
|
330
|
+
timeout_ms = self._validate_timeout(timeout_ms)
|
|
331
|
+
|
|
332
|
+
if topic_partitions is None:
|
|
333
|
+
topics_field = None
|
|
334
|
+
else:
|
|
335
|
+
_Topic = ListPartitionReassignmentsRequest.ListPartitionReassignmentsTopics
|
|
336
|
+
if isinstance(topic_partitions, dict):
|
|
337
|
+
topics_field = [
|
|
338
|
+
_Topic(name=topic, partition_indexes=list(partitions))
|
|
339
|
+
for topic, partitions in topic_partitions.items()
|
|
340
|
+
]
|
|
341
|
+
else:
|
|
342
|
+
topic2partitions = defaultdict(list)
|
|
343
|
+
for tp in topic_partitions:
|
|
344
|
+
topic2partitions[tp.topic].append(tp.partition)
|
|
345
|
+
topics_field = [
|
|
346
|
+
_Topic(name=topic, partition_indexes=partitions)
|
|
347
|
+
for topic, partitions in topic2partitions.items()
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
request = ListPartitionReassignmentsRequest(
|
|
351
|
+
timeout_ms=timeout_ms,
|
|
352
|
+
topics=topics_field,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def top_level_error(r):
|
|
356
|
+
yield Errors.for_code(r.error_code)
|
|
357
|
+
response = await self._send_request_to_controller(request, top_level_error)
|
|
358
|
+
|
|
359
|
+
ret = {}
|
|
360
|
+
for topic in response.topics:
|
|
361
|
+
for partition in topic.partitions:
|
|
362
|
+
ret[TopicPartition(topic.name, partition.partition_index)] = {
|
|
363
|
+
'replicas': list(partition.replicas),
|
|
364
|
+
'adding_replicas': list(partition.adding_replicas),
|
|
365
|
+
'removing_replicas': list(partition.removing_replicas),
|
|
366
|
+
}
|
|
367
|
+
return ret
|
|
368
|
+
|
|
369
|
+
def list_partition_reassignments(self, topic_partitions=None, timeout_ms=None):
|
|
370
|
+
"""List the current ongoing partition reassignments.
|
|
371
|
+
|
|
372
|
+
Arguments:
|
|
373
|
+
topic_partitions (dict, list, optional):
|
|
374
|
+
Either: a dict of ``{topic_name: [partition_ids]}``,
|
|
375
|
+
or a list of :class:`~kafka.TopicPartition`,
|
|
376
|
+
or ``None`` to list ongoing reassignments for all partitions.
|
|
377
|
+
Default: None.
|
|
378
|
+
|
|
379
|
+
Keyword Arguments:
|
|
380
|
+
timeout_ms (numeric, optional): The time in ms to wait for the
|
|
381
|
+
request to complete.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
dict: A dict mapping :class:`~kafka.TopicPartition` to a dict
|
|
385
|
+
with keys ``'replicas'``, ``'adding_replicas'``, and
|
|
386
|
+
``'removing_replicas'`` (each a list of broker IDs).
|
|
387
|
+
"""
|
|
388
|
+
return self._manager.run(
|
|
389
|
+
self._async_list_partition_reassignments, topic_partitions, timeout_ms)
|
|
390
|
+
|
|
391
|
+
async def _async_describe_topic_partitions(self, topics, response_partition_limit, cursor):
|
|
392
|
+
_Topic = DescribeTopicPartitionsRequest.TopicRequest
|
|
393
|
+
_Cursor = DescribeTopicPartitionsRequest.Cursor
|
|
394
|
+
|
|
395
|
+
if cursor is not None:
|
|
396
|
+
cursor_field = _Cursor(
|
|
397
|
+
topic_name=cursor['topic_name'],
|
|
398
|
+
partition_index=cursor['partition_index'],
|
|
399
|
+
)
|
|
400
|
+
else:
|
|
401
|
+
cursor_field = None
|
|
402
|
+
|
|
403
|
+
request = DescribeTopicPartitionsRequest(
|
|
404
|
+
topics=[_Topic(name=t) for t in topics],
|
|
405
|
+
response_partition_limit=response_partition_limit,
|
|
406
|
+
cursor=cursor_field,
|
|
407
|
+
)
|
|
408
|
+
response = await self._manager.send(request)
|
|
409
|
+
|
|
410
|
+
result = []
|
|
411
|
+
for topic in response.topics:
|
|
412
|
+
topic_dict = {
|
|
413
|
+
'error_code': topic.error_code,
|
|
414
|
+
'name': topic.name,
|
|
415
|
+
'topic_id': topic.topic_id,
|
|
416
|
+
'is_internal': topic.is_internal,
|
|
417
|
+
'partitions': [
|
|
418
|
+
{
|
|
419
|
+
'error_code': p.error_code,
|
|
420
|
+
'partition_index': p.partition_index,
|
|
421
|
+
'leader_id': p.leader_id,
|
|
422
|
+
'leader_epoch': p.leader_epoch,
|
|
423
|
+
'replica_nodes': list(p.replica_nodes),
|
|
424
|
+
'isr_nodes': list(p.isr_nodes),
|
|
425
|
+
'eligible_leader_replicas': list(p.eligible_leader_replicas) if p.eligible_leader_replicas else None,
|
|
426
|
+
'last_known_elr': list(p.last_known_elr) if p.last_known_elr else None,
|
|
427
|
+
'offline_replicas': list(p.offline_replicas),
|
|
428
|
+
}
|
|
429
|
+
for p in topic.partitions
|
|
430
|
+
],
|
|
431
|
+
'topic_authorized_operations': topic.topic_authorized_operations,
|
|
432
|
+
}
|
|
433
|
+
result.append(topic_dict)
|
|
434
|
+
|
|
435
|
+
next_cursor = None
|
|
436
|
+
if response.next_cursor is not None:
|
|
437
|
+
next_cursor = {
|
|
438
|
+
'topic_name': response.next_cursor.topic_name,
|
|
439
|
+
'partition_index': response.next_cursor.partition_index,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {'topics': result, 'next_cursor': next_cursor}
|
|
443
|
+
|
|
444
|
+
def describe_topic_partitions(self, topics, response_partition_limit=2000, cursor=None):
|
|
445
|
+
"""Describe topics with fine-grained partition-level control (KIP-966).
|
|
446
|
+
|
|
447
|
+
Unlike :meth:`describe_topics`, this uses the DescribeTopicPartitions
|
|
448
|
+
API (apiKey 75, broker 3.7+) which supports pagination via a cursor
|
|
449
|
+
and partition-level ELR (Eligible Leader Replicas) information.
|
|
450
|
+
|
|
451
|
+
Arguments:
|
|
452
|
+
topics ([str]): A list of topic names.
|
|
453
|
+
|
|
454
|
+
Keyword Arguments:
|
|
455
|
+
response_partition_limit (int, optional): Maximum number of
|
|
456
|
+
partitions to include in the response. Default: 2000.
|
|
457
|
+
cursor (dict, optional): Dict with ``'topic_name'`` and
|
|
458
|
+
``'partition_index'`` keys to start pagination from. Default:
|
|
459
|
+
None.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
dict: ``{'topics': [...], 'next_cursor': None | {...}}``.
|
|
463
|
+
``topics`` is a list of dicts (one per topic) with keys
|
|
464
|
+
``error_code``, ``name``, ``topic_id``, ``is_internal``,
|
|
465
|
+
``partitions``, and ``topic_authorized_operations``.
|
|
466
|
+
``next_cursor`` is None if pagination is complete, otherwise a
|
|
467
|
+
dict with the next page's ``topic_name`` and ``partition_index``.
|
|
468
|
+
"""
|
|
469
|
+
return self._manager.run(
|
|
470
|
+
self._async_describe_topic_partitions, topics, response_partition_limit, cursor)
|
|
471
|
+
|
|
472
|
+
# -- List partition offsets --------------------------------------------
|
|
473
|
+
|
|
474
|
+
@staticmethod
|
|
475
|
+
def _list_partition_offsets_request(partition_timestamps, isolation_level_int):
|
|
476
|
+
min_version = max(ListOffsetsRequest.min_version_for_isolation_level(isolation_level_int), 0)
|
|
477
|
+
_Topic = ListOffsetsRequest.ListOffsetsTopic
|
|
478
|
+
_Partition = _Topic.ListOffsetsPartition
|
|
479
|
+
topic2partitions = defaultdict(list)
|
|
480
|
+
for tp, ts in partition_timestamps.items():
|
|
481
|
+
if not isinstance(ts, (int, OffsetSpec)):
|
|
482
|
+
raise TypeError(f'Unsupported ts type {type(ts)}, expected int or OffsetSpec')
|
|
483
|
+
elif int(ts) < 0:
|
|
484
|
+
min_version = max(ListOffsetsRequest.min_version_for_timestamp(ts), min_version)
|
|
485
|
+
topic2partitions[tp.topic].append(
|
|
486
|
+
_Partition(partition_index=tp.partition, timestamp=ts))
|
|
487
|
+
return ListOffsetsRequest(
|
|
488
|
+
replica_id=-1,
|
|
489
|
+
isolation_level=isolation_level_int,
|
|
490
|
+
topics=[_Topic(name=name, partitions=parts)
|
|
491
|
+
for name, parts in topic2partitions.items()],
|
|
492
|
+
min_version=min_version,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
@staticmethod
|
|
496
|
+
def _list_partition_offsets_process_response(response):
|
|
497
|
+
results = {}
|
|
498
|
+
for topic in response.topics:
|
|
499
|
+
for partition in topic.partitions:
|
|
500
|
+
tp = TopicPartition(topic.name, partition.partition_index)
|
|
501
|
+
err = Errors.for_code(partition.error_code)
|
|
502
|
+
if err is not Errors.NoError:
|
|
503
|
+
raise err(
|
|
504
|
+
"ListOffsetsRequest failed for %s: %s" % (tp, err.__name__))
|
|
505
|
+
leader_epoch = getattr(partition, 'leader_epoch', -1)
|
|
506
|
+
results[tp] = OffsetAndTimestamp(
|
|
507
|
+
offset=partition.offset,
|
|
508
|
+
timestamp=partition.timestamp,
|
|
509
|
+
leader_epoch=leader_epoch if leader_epoch >= 0 else None,
|
|
510
|
+
)
|
|
511
|
+
return results
|
|
512
|
+
|
|
513
|
+
async def _async_list_partition_offsets(self, topic_partition_specs, isolation_level=IsolationLevel.READ_UNCOMMITTED, timeout_ms=None):
|
|
514
|
+
isolation_level = IsolationLevel.build_from(isolation_level)
|
|
515
|
+
timer = Timer(self._validate_timeout(timeout_ms))
|
|
516
|
+
backoff_secs = self.config['retry_backoff_ms'] / 1000
|
|
517
|
+
results = {}
|
|
518
|
+
topic_partitions = set(topic_partition_specs.keys())
|
|
519
|
+
while topic_partitions:
|
|
520
|
+
leader2partitions = await self._async_get_leader_for_partitions(topic_partitions)
|
|
521
|
+
for leader, partitions in leader2partitions.items():
|
|
522
|
+
request = self._list_partition_offsets_request(
|
|
523
|
+
{tp: spec for tp, spec in topic_partition_specs.items() if tp in partitions},
|
|
524
|
+
isolation_level.value)
|
|
525
|
+
try:
|
|
526
|
+
response = await self._manager.send(request, node_id=leader)
|
|
527
|
+
results.update(self._list_partition_offsets_process_response(response))
|
|
528
|
+
topic_partitions -= partitions
|
|
529
|
+
except Errors.NotLeaderForPartitionError:
|
|
530
|
+
continue
|
|
531
|
+
if topic_partitions:
|
|
532
|
+
if timer.expired:
|
|
533
|
+
raise Errors.NotLeaderForPartitionError(
|
|
534
|
+
'list_partition_offsets timed out retrying NotLeaderForPartitionError '
|
|
535
|
+
'for partitions: %s' % sorted(topic_partitions))
|
|
536
|
+
log.debug(
|
|
537
|
+
'list_partition_offsets: NotLeaderForPartitionError on %d partition(s); '
|
|
538
|
+
'refreshing metadata and retrying', len(topic_partitions))
|
|
539
|
+
await self._net.sleep(min(backoff_secs, max(0.0, timer.timeout_secs or 0.0)))
|
|
540
|
+
return results
|
|
541
|
+
|
|
542
|
+
def list_partition_offsets(self, topic_partition_specs, isolation_level=IsolationLevel.READ_UNCOMMITTED, timeout_ms=None):
|
|
543
|
+
"""Look up offsets for the given partitions by spec.
|
|
544
|
+
|
|
545
|
+
Partitions are routed to their respective leader brokers via cluster
|
|
546
|
+
metadata; one ``ListOffsetsRequest`` is sent per leader. Partitions
|
|
547
|
+
that return :class:`~kafka.errors.NotLeaderForPartitionError` are
|
|
548
|
+
retried with refreshed metadata, bounded by ``timeout_ms`` (or the
|
|
549
|
+
admin client's ``request_timeout_ms`` when ``None``).
|
|
550
|
+
|
|
551
|
+
Arguments:
|
|
552
|
+
topic_partition_specs: dict mapping :class:`~kafka.TopicPartition` to
|
|
553
|
+
:class:`OffsetSpec` (or a raw integer timestamp /
|
|
554
|
+
wire-level sentinel).
|
|
555
|
+
|
|
556
|
+
Keyword Arguments:
|
|
557
|
+
isolation_level (IsolationLevel, optional): Requires broker support
|
|
558
|
+
for ListOffsets v2+. Default: IsolationLevel.READ_UNCOMMITTED.
|
|
559
|
+
timeout_ms (int, optional): Maximum time to spend retrying
|
|
560
|
+
NotLeaderForPartitionError. Default: ``request_timeout_ms``.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
dict: A dict mapping :class:`~kafka.TopicPartition` to
|
|
564
|
+
:class:`~kafka.structs.OffsetAndTimestamp`
|
|
565
|
+
|
|
566
|
+
Raises:
|
|
567
|
+
KafkaError: If any partition response carries an error code.
|
|
568
|
+
NotLeaderForPartitionError: If NotLeaderForPartitionError retries
|
|
569
|
+
do not converge within ``timeout_ms``.
|
|
570
|
+
UnknownTopicOrPartitionError: If a requested partition is not
|
|
571
|
+
known to the cluster.
|
|
572
|
+
UnsupportedVersionError: If the broker does not support a version
|
|
573
|
+
of ListOffsetsRequest compatible with the requested specs.
|
|
574
|
+
"""
|
|
575
|
+
return self._manager.run(
|
|
576
|
+
self._async_list_partition_offsets, topic_partition_specs, isolation_level, timeout_ms)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
class NewPartitions:
|
|
580
|
+
"""DEPRECATED: A class for new partition creation on existing topics.
|
|
581
|
+
|
|
582
|
+
Note that the length of new_assignments, if specified, must be the
|
|
583
|
+
difference between the new total number of partitions and the existing
|
|
584
|
+
number of partitions.
|
|
585
|
+
|
|
586
|
+
Arguments:
|
|
587
|
+
total_count (int): the total number of partitions that should exist
|
|
588
|
+
on the topic
|
|
589
|
+
new_assignments ([[int]]): an array of arrays of replica assignments
|
|
590
|
+
for new partitions. If not set, broker assigns replicas per an
|
|
591
|
+
internal algorithm.
|
|
592
|
+
"""
|
|
593
|
+
def __init__(self, total_count, new_assignments=None):
|
|
594
|
+
self.total_count = total_count
|
|
595
|
+
self.new_assignments = new_assignments
|