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.
Files changed (373) hide show
  1. kafka/__init__.py +34 -0
  2. kafka/__main__.py +5 -0
  3. kafka/admin/__init__.py +29 -0
  4. kafka/admin/__main__.py +5 -0
  5. kafka/admin/_acls.py +355 -0
  6. kafka/admin/_cluster.py +359 -0
  7. kafka/admin/_configs.py +479 -0
  8. kafka/admin/_groups.py +754 -0
  9. kafka/admin/_partitions.py +595 -0
  10. kafka/admin/_topics.py +281 -0
  11. kafka/admin/_transactions.py +450 -0
  12. kafka/admin/_users.py +194 -0
  13. kafka/admin/client.py +373 -0
  14. kafka/benchmarks/__init__.py +0 -0
  15. kafka/benchmarks/consumer_performance.py +138 -0
  16. kafka/benchmarks/load_example.py +109 -0
  17. kafka/benchmarks/producer_encode_path.py +201 -0
  18. kafka/benchmarks/producer_performance.py +161 -0
  19. kafka/benchmarks/profile_protocol.py +138 -0
  20. kafka/benchmarks/protocol_old_vs_new.py +447 -0
  21. kafka/benchmarks/record_batch_compose.py +77 -0
  22. kafka/benchmarks/record_batch_read.py +82 -0
  23. kafka/benchmarks/varint_speed.py +426 -0
  24. kafka/cli/__init__.py +36 -0
  25. kafka/cli/admin/__init__.py +117 -0
  26. kafka/cli/admin/acls/__init__.py +9 -0
  27. kafka/cli/admin/acls/common.py +76 -0
  28. kafka/cli/admin/acls/create.py +19 -0
  29. kafka/cli/admin/acls/delete.py +23 -0
  30. kafka/cli/admin/acls/describe.py +16 -0
  31. kafka/cli/admin/cluster/__init__.py +14 -0
  32. kafka/cli/admin/cluster/describe.py +11 -0
  33. kafka/cli/admin/cluster/describe_quorum.py +11 -0
  34. kafka/cli/admin/cluster/features.py +52 -0
  35. kafka/cli/admin/cluster/log_dirs.py +43 -0
  36. kafka/cli/admin/cluster/versions.py +33 -0
  37. kafka/cli/admin/configs/__init__.py +10 -0
  38. kafka/cli/admin/configs/alter.py +43 -0
  39. kafka/cli/admin/configs/common.py +17 -0
  40. kafka/cli/admin/configs/describe.py +30 -0
  41. kafka/cli/admin/configs/list.py +16 -0
  42. kafka/cli/admin/configs/reset.py +20 -0
  43. kafka/cli/admin/groups/__init__.py +16 -0
  44. kafka/cli/admin/groups/alter_offsets.py +30 -0
  45. kafka/cli/admin/groups/delete.py +11 -0
  46. kafka/cli/admin/groups/delete_offsets.py +29 -0
  47. kafka/cli/admin/groups/describe.py +11 -0
  48. kafka/cli/admin/groups/list.py +28 -0
  49. kafka/cli/admin/groups/list_offsets.py +29 -0
  50. kafka/cli/admin/groups/remove_members.py +40 -0
  51. kafka/cli/admin/groups/reset_offsets.py +139 -0
  52. kafka/cli/admin/partitions/__init__.py +21 -0
  53. kafka/cli/admin/partitions/alter_reassignments.py +37 -0
  54. kafka/cli/admin/partitions/create.py +27 -0
  55. kafka/cli/admin/partitions/delete_records.py +31 -0
  56. kafka/cli/admin/partitions/describe.py +36 -0
  57. kafka/cli/admin/partitions/elect_leaders.py +53 -0
  58. kafka/cli/admin/partitions/list_offsets.py +88 -0
  59. kafka/cli/admin/partitions/list_reassignments.py +35 -0
  60. kafka/cli/admin/topics/__init__.py +10 -0
  61. kafka/cli/admin/topics/create.py +13 -0
  62. kafka/cli/admin/topics/delete.py +19 -0
  63. kafka/cli/admin/topics/describe.py +18 -0
  64. kafka/cli/admin/topics/list.py +11 -0
  65. kafka/cli/admin/transactions/__init__.py +17 -0
  66. kafka/cli/admin/transactions/abort.py +38 -0
  67. kafka/cli/admin/transactions/describe.py +24 -0
  68. kafka/cli/admin/transactions/describe_producers.py +29 -0
  69. kafka/cli/admin/transactions/find_hanging.py +26 -0
  70. kafka/cli/admin/transactions/list.py +37 -0
  71. kafka/cli/admin/users/__init__.py +8 -0
  72. kafka/cli/admin/users/alter_user_scram_credentials.py +34 -0
  73. kafka/cli/admin/users/describe_user_scram_credentials.py +15 -0
  74. kafka/cli/common.py +95 -0
  75. kafka/cli/consumer/__init__.py +63 -0
  76. kafka/cli/producer/__init__.py +57 -0
  77. kafka/cluster.py +824 -0
  78. kafka/codec.py +325 -0
  79. kafka/consumer/__init__.py +5 -0
  80. kafka/consumer/__main__.py +5 -0
  81. kafka/consumer/fetcher.py +2012 -0
  82. kafka/consumer/group.py +1347 -0
  83. kafka/consumer/subscription_state.py +897 -0
  84. kafka/coordinator/__init__.py +0 -0
  85. kafka/coordinator/assignors/__init__.py +0 -0
  86. kafka/coordinator/assignors/abstract.py +90 -0
  87. kafka/coordinator/assignors/cooperative_sticky.py +167 -0
  88. kafka/coordinator/assignors/range.py +81 -0
  89. kafka/coordinator/assignors/roundrobin.py +101 -0
  90. kafka/coordinator/assignors/sticky/StickyAssignorUserData.json +37 -0
  91. kafka/coordinator/assignors/sticky/__init__.py +0 -0
  92. kafka/coordinator/assignors/sticky/partition_movements.py +149 -0
  93. kafka/coordinator/assignors/sticky/sorted_set.py +63 -0
  94. kafka/coordinator/assignors/sticky/sticky_assignor.py +665 -0
  95. kafka/coordinator/assignors/sticky/user_data.py +8 -0
  96. kafka/coordinator/base.py +1215 -0
  97. kafka/coordinator/consumer.py +1224 -0
  98. kafka/coordinator/heartbeat.py +82 -0
  99. kafka/coordinator/subscription.py +34 -0
  100. kafka/errors.py +1004 -0
  101. kafka/future.py +166 -0
  102. kafka/metrics/__init__.py +13 -0
  103. kafka/metrics/compound_stat.py +33 -0
  104. kafka/metrics/dict_reporter.py +81 -0
  105. kafka/metrics/kafka_metric.py +36 -0
  106. kafka/metrics/measurable.py +27 -0
  107. kafka/metrics/measurable_stat.py +13 -0
  108. kafka/metrics/metric_config.py +33 -0
  109. kafka/metrics/metric_name.py +105 -0
  110. kafka/metrics/metrics.py +261 -0
  111. kafka/metrics/metrics_reporter.py +53 -0
  112. kafka/metrics/quota.py +41 -0
  113. kafka/metrics/stat.py +19 -0
  114. kafka/metrics/stats/__init__.py +15 -0
  115. kafka/metrics/stats/avg.py +24 -0
  116. kafka/metrics/stats/count.py +17 -0
  117. kafka/metrics/stats/histogram.py +99 -0
  118. kafka/metrics/stats/max_stat.py +17 -0
  119. kafka/metrics/stats/min_stat.py +19 -0
  120. kafka/metrics/stats/percentile.py +14 -0
  121. kafka/metrics/stats/percentiles.py +75 -0
  122. kafka/metrics/stats/rate.py +118 -0
  123. kafka/metrics/stats/sampled_stat.py +99 -0
  124. kafka/metrics/stats/sensor.py +136 -0
  125. kafka/metrics/stats/total.py +15 -0
  126. kafka/net/__init__.py +19 -0
  127. kafka/net/compat.py +165 -0
  128. kafka/net/connection.py +593 -0
  129. kafka/net/http_connect.py +144 -0
  130. kafka/net/inet.py +122 -0
  131. kafka/net/manager.py +451 -0
  132. kafka/net/metrics.py +149 -0
  133. kafka/net/sasl/__init__.py +32 -0
  134. kafka/net/sasl/abc.py +28 -0
  135. kafka/net/sasl/gssapi.py +95 -0
  136. kafka/net/sasl/msk.py +245 -0
  137. kafka/net/sasl/oauth.py +98 -0
  138. kafka/net/sasl/plain.py +42 -0
  139. kafka/net/sasl/scram.py +135 -0
  140. kafka/net/sasl/sspi.py +111 -0
  141. kafka/net/selector.py +644 -0
  142. kafka/net/socks5.py +262 -0
  143. kafka/net/transport.py +415 -0
  144. kafka/net/wakeup_notifier.py +72 -0
  145. kafka/partitioner/__init__.py +8 -0
  146. kafka/partitioner/abc.py +8 -0
  147. kafka/partitioner/default.py +89 -0
  148. kafka/partitioner/sticky.py +109 -0
  149. kafka/producer/__init__.py +5 -0
  150. kafka/producer/__main__.py +5 -0
  151. kafka/producer/future.py +101 -0
  152. kafka/producer/kafka.py +1123 -0
  153. kafka/producer/producer_batch.py +192 -0
  154. kafka/producer/record_accumulator.py +647 -0
  155. kafka/producer/sender.py +884 -0
  156. kafka/producer/transaction_manager.py +1326 -0
  157. kafka/protocol/__init__.py +0 -0
  158. kafka/protocol/admin/__init__.py +29 -0
  159. kafka/protocol/admin/acl.py +83 -0
  160. kafka/protocol/admin/acl.pyi +375 -0
  161. kafka/protocol/admin/client_quotas.py +14 -0
  162. kafka/protocol/admin/client_quotas.pyi +265 -0
  163. kafka/protocol/admin/cluster.py +31 -0
  164. kafka/protocol/admin/cluster.pyi +620 -0
  165. kafka/protocol/admin/configs.py +22 -0
  166. kafka/protocol/admin/configs.pyi +437 -0
  167. kafka/protocol/admin/groups.py +24 -0
  168. kafka/protocol/admin/groups.pyi +261 -0
  169. kafka/protocol/admin/topics.py +53 -0
  170. kafka/protocol/admin/topics.pyi +982 -0
  171. kafka/protocol/admin/transactions.py +18 -0
  172. kafka/protocol/admin/transactions.pyi +311 -0
  173. kafka/protocol/admin/users.py +14 -0
  174. kafka/protocol/admin/users.pyi +223 -0
  175. kafka/protocol/api_data.py +125 -0
  176. kafka/protocol/api_header.py +55 -0
  177. kafka/protocol/api_key.py +97 -0
  178. kafka/protocol/api_message.py +277 -0
  179. kafka/protocol/broker_version_data.py +246 -0
  180. kafka/protocol/consumer/__init__.py +13 -0
  181. kafka/protocol/consumer/fetch.py +16 -0
  182. kafka/protocol/consumer/fetch.pyi +298 -0
  183. kafka/protocol/consumer/group.py +38 -0
  184. kafka/protocol/consumer/group.pyi +824 -0
  185. kafka/protocol/consumer/metadata.py +30 -0
  186. kafka/protocol/consumer/metadata.pyi +89 -0
  187. kafka/protocol/consumer/offsets.py +75 -0
  188. kafka/protocol/consumer/offsets.pyi +288 -0
  189. kafka/protocol/data_container.py +166 -0
  190. kafka/protocol/frame.py +30 -0
  191. kafka/protocol/generate_stubs.py +468 -0
  192. kafka/protocol/metadata/__init__.py +10 -0
  193. kafka/protocol/metadata/api_versions.py +41 -0
  194. kafka/protocol/metadata/api_versions.pyi +128 -0
  195. kafka/protocol/metadata/find_coordinator.py +19 -0
  196. kafka/protocol/metadata/find_coordinator.pyi +105 -0
  197. kafka/protocol/metadata/metadata.py +34 -0
  198. kafka/protocol/metadata/metadata.pyi +160 -0
  199. kafka/protocol/old/__init__.py +0 -0
  200. kafka/protocol/old/abstract.py +17 -0
  201. kafka/protocol/old/add_offsets_to_txn.py +54 -0
  202. kafka/protocol/old/add_partitions_to_txn.py +71 -0
  203. kafka/protocol/old/admin.py +1086 -0
  204. kafka/protocol/old/api.py +205 -0
  205. kafka/protocol/old/api_versions.py +133 -0
  206. kafka/protocol/old/commit.py +355 -0
  207. kafka/protocol/old/consumer_protocol.py +36 -0
  208. kafka/protocol/old/end_txn.py +53 -0
  209. kafka/protocol/old/fetch.py +408 -0
  210. kafka/protocol/old/find_coordinator.py +72 -0
  211. kafka/protocol/old/group.py +451 -0
  212. kafka/protocol/old/init_producer_id.py +42 -0
  213. kafka/protocol/old/list_offsets.py +186 -0
  214. kafka/protocol/old/metadata.py +290 -0
  215. kafka/protocol/old/offset_for_leader_epoch.py +133 -0
  216. kafka/protocol/old/produce.py +247 -0
  217. kafka/protocol/old/sasl_authenticate.py +38 -0
  218. kafka/protocol/old/sasl_handshake.py +39 -0
  219. kafka/protocol/old/struct.py +87 -0
  220. kafka/protocol/old/txn_offset_commit.py +73 -0
  221. kafka/protocol/old/types.py +440 -0
  222. kafka/protocol/parser.py +191 -0
  223. kafka/protocol/producer/__init__.py +7 -0
  224. kafka/protocol/producer/produce.py +17 -0
  225. kafka/protocol/producer/produce.pyi +197 -0
  226. kafka/protocol/producer/transaction.py +30 -0
  227. kafka/protocol/producer/transaction.pyi +663 -0
  228. kafka/protocol/sasl.py +52 -0
  229. kafka/protocol/sasl.pyi +126 -0
  230. kafka/protocol/schemas/__init__.py +7 -0
  231. kafka/protocol/schemas/fields/__init__.py +7 -0
  232. kafka/protocol/schemas/fields/array.py +127 -0
  233. kafka/protocol/schemas/fields/base.py +156 -0
  234. kafka/protocol/schemas/fields/codecs/__init__.py +12 -0
  235. kafka/protocol/schemas/fields/codecs/encode_buffer.py +82 -0
  236. kafka/protocol/schemas/fields/codecs/tagged_fields.py +109 -0
  237. kafka/protocol/schemas/fields/codecs/types.py +505 -0
  238. kafka/protocol/schemas/fields/codegen.py +40 -0
  239. kafka/protocol/schemas/fields/simple.py +127 -0
  240. kafka/protocol/schemas/fields/struct.py +357 -0
  241. kafka/protocol/schemas/fields/struct_array.py +142 -0
  242. kafka/protocol/schemas/load_json.py +42 -0
  243. kafka/protocol/schemas/resources/AddOffsetsToTxnRequest.json +40 -0
  244. kafka/protocol/schemas/resources/AddOffsetsToTxnResponse.json +35 -0
  245. kafka/protocol/schemas/resources/AddPartitionsToTxnRequest.json +65 -0
  246. kafka/protocol/schemas/resources/AddPartitionsToTxnResponse.json +60 -0
  247. kafka/protocol/schemas/resources/AlterClientQuotasRequest.json +47 -0
  248. kafka/protocol/schemas/resources/AlterClientQuotasResponse.json +41 -0
  249. kafka/protocol/schemas/resources/AlterConfigsRequest.json +43 -0
  250. kafka/protocol/schemas/resources/AlterConfigsResponse.json +39 -0
  251. kafka/protocol/schemas/resources/AlterPartitionReassignmentsRequest.json +42 -0
  252. kafka/protocol/schemas/resources/AlterPartitionReassignmentsResponse.json +47 -0
  253. kafka/protocol/schemas/resources/AlterReplicaLogDirsRequest.json +41 -0
  254. kafka/protocol/schemas/resources/AlterReplicaLogDirsResponse.json +41 -0
  255. kafka/protocol/schemas/resources/AlterUserScramCredentialsRequest.json +45 -0
  256. kafka/protocol/schemas/resources/AlterUserScramCredentialsResponse.json +35 -0
  257. kafka/protocol/schemas/resources/ApiVersionsRequest.json +34 -0
  258. kafka/protocol/schemas/resources/ApiVersionsResponse.json +79 -0
  259. kafka/protocol/schemas/resources/ConsumerProtocolAssignment.json +42 -0
  260. kafka/protocol/schemas/resources/ConsumerProtocolSubscription.json +49 -0
  261. kafka/protocol/schemas/resources/CreateAclsRequest.json +46 -0
  262. kafka/protocol/schemas/resources/CreateAclsResponse.json +37 -0
  263. kafka/protocol/schemas/resources/CreatePartitionsRequest.json +47 -0
  264. kafka/protocol/schemas/resources/CreatePartitionsResponse.json +41 -0
  265. kafka/protocol/schemas/resources/CreateTopicsRequest.json +65 -0
  266. kafka/protocol/schemas/resources/CreateTopicsResponse.json +72 -0
  267. kafka/protocol/schemas/resources/DeleteAclsRequest.json +46 -0
  268. kafka/protocol/schemas/resources/DeleteAclsResponse.json +59 -0
  269. kafka/protocol/schemas/resources/DeleteGroupsRequest.json +30 -0
  270. kafka/protocol/schemas/resources/DeleteGroupsResponse.json +36 -0
  271. kafka/protocol/schemas/resources/DeleteRecordsRequest.json +42 -0
  272. kafka/protocol/schemas/resources/DeleteRecordsResponse.json +43 -0
  273. kafka/protocol/schemas/resources/DeleteTopicsRequest.json +43 -0
  274. kafka/protocol/schemas/resources/DeleteTopicsResponse.json +52 -0
  275. kafka/protocol/schemas/resources/DescribeAclsRequest.json +43 -0
  276. kafka/protocol/schemas/resources/DescribeAclsResponse.json +55 -0
  277. kafka/protocol/schemas/resources/DescribeClientQuotasRequest.json +37 -0
  278. kafka/protocol/schemas/resources/DescribeClientQuotasResponse.json +47 -0
  279. kafka/protocol/schemas/resources/DescribeClusterRequest.json +35 -0
  280. kafka/protocol/schemas/resources/DescribeClusterResponse.json +56 -0
  281. kafka/protocol/schemas/resources/DescribeConfigsRequest.json +42 -0
  282. kafka/protocol/schemas/resources/DescribeConfigsResponse.json +69 -0
  283. kafka/protocol/schemas/resources/DescribeGroupsRequest.json +38 -0
  284. kafka/protocol/schemas/resources/DescribeGroupsResponse.json +74 -0
  285. kafka/protocol/schemas/resources/DescribeLogDirsRequest.json +38 -0
  286. kafka/protocol/schemas/resources/DescribeLogDirsResponse.json +65 -0
  287. kafka/protocol/schemas/resources/DescribeProducersRequest.json +32 -0
  288. kafka/protocol/schemas/resources/DescribeProducersResponse.json +55 -0
  289. kafka/protocol/schemas/resources/DescribeQuorumRequest.json +39 -0
  290. kafka/protocol/schemas/resources/DescribeQuorumResponse.json +82 -0
  291. kafka/protocol/schemas/resources/DescribeTopicPartitionsRequest.json +40 -0
  292. kafka/protocol/schemas/resources/DescribeTopicPartitionsResponse.json +66 -0
  293. kafka/protocol/schemas/resources/DescribeTransactionsRequest.json +27 -0
  294. kafka/protocol/schemas/resources/DescribeTransactionsResponse.json +52 -0
  295. kafka/protocol/schemas/resources/DescribeUserScramCredentialsRequest.json +30 -0
  296. kafka/protocol/schemas/resources/DescribeUserScramCredentialsResponse.json +45 -0
  297. kafka/protocol/schemas/resources/ElectLeadersRequest.json +41 -0
  298. kafka/protocol/schemas/resources/ElectLeadersResponse.json +45 -0
  299. kafka/protocol/schemas/resources/EndTxnRequest.json +43 -0
  300. kafka/protocol/schemas/resources/EndTxnResponse.json +41 -0
  301. kafka/protocol/schemas/resources/FetchRequest.json +125 -0
  302. kafka/protocol/schemas/resources/FetchResponse.json +124 -0
  303. kafka/protocol/schemas/resources/FindCoordinatorRequest.json +43 -0
  304. kafka/protocol/schemas/resources/FindCoordinatorResponse.json +58 -0
  305. kafka/protocol/schemas/resources/HeartbeatRequest.json +39 -0
  306. kafka/protocol/schemas/resources/HeartbeatResponse.json +35 -0
  307. kafka/protocol/schemas/resources/IncrementalAlterConfigsRequest.json +44 -0
  308. kafka/protocol/schemas/resources/IncrementalAlterConfigsResponse.json +38 -0
  309. kafka/protocol/schemas/resources/InitProducerIdRequest.json +50 -0
  310. kafka/protocol/schemas/resources/InitProducerIdResponse.json +47 -0
  311. kafka/protocol/schemas/resources/JoinGroupRequest.json +63 -0
  312. kafka/protocol/schemas/resources/JoinGroupResponse.json +69 -0
  313. kafka/protocol/schemas/resources/LeaveGroupRequest.json +47 -0
  314. kafka/protocol/schemas/resources/LeaveGroupResponse.json +47 -0
  315. kafka/protocol/schemas/resources/ListConfigResourcesRequest.json +31 -0
  316. kafka/protocol/schemas/resources/ListConfigResourcesResponse.json +37 -0
  317. kafka/protocol/schemas/resources/ListGroupsRequest.json +36 -0
  318. kafka/protocol/schemas/resources/ListGroupsResponse.json +49 -0
  319. kafka/protocol/schemas/resources/ListOffsetsRequest.json +72 -0
  320. kafka/protocol/schemas/resources/ListOffsetsResponse.json +71 -0
  321. kafka/protocol/schemas/resources/ListPartitionReassignmentsRequest.json +34 -0
  322. kafka/protocol/schemas/resources/ListPartitionReassignmentsResponse.json +46 -0
  323. kafka/protocol/schemas/resources/ListTransactionsRequest.json +40 -0
  324. kafka/protocol/schemas/resources/ListTransactionsResponse.json +42 -0
  325. kafka/protocol/schemas/resources/MetadataRequest.json +56 -0
  326. kafka/protocol/schemas/resources/MetadataResponse.json +101 -0
  327. kafka/protocol/schemas/resources/OffsetCommitRequest.json +76 -0
  328. kafka/protocol/schemas/resources/OffsetCommitResponse.json +71 -0
  329. kafka/protocol/schemas/resources/OffsetDeleteRequest.json +39 -0
  330. kafka/protocol/schemas/resources/OffsetDeleteResponse.json +42 -0
  331. kafka/protocol/schemas/resources/OffsetFetchRequest.json +76 -0
  332. kafka/protocol/schemas/resources/OffsetFetchResponse.json +107 -0
  333. kafka/protocol/schemas/resources/OffsetForLeaderEpochRequest.json +52 -0
  334. kafka/protocol/schemas/resources/OffsetForLeaderEpochResponse.json +51 -0
  335. kafka/protocol/schemas/resources/ProduceRequest.json +73 -0
  336. kafka/protocol/schemas/resources/ProduceResponse.json +96 -0
  337. kafka/protocol/schemas/resources/RequestHeader.json +44 -0
  338. kafka/protocol/schemas/resources/ResponseHeader.json +26 -0
  339. kafka/protocol/schemas/resources/SaslAuthenticateRequest.json +29 -0
  340. kafka/protocol/schemas/resources/SaslAuthenticateResponse.json +34 -0
  341. kafka/protocol/schemas/resources/SaslHandshakeRequest.json +31 -0
  342. kafka/protocol/schemas/resources/SaslHandshakeResponse.json +32 -0
  343. kafka/protocol/schemas/resources/SyncGroupRequest.json +56 -0
  344. kafka/protocol/schemas/resources/SyncGroupResponse.json +46 -0
  345. kafka/protocol/schemas/resources/TxnOffsetCommitRequest.json +68 -0
  346. kafka/protocol/schemas/resources/TxnOffsetCommitResponse.json +47 -0
  347. kafka/protocol/schemas/resources/UpdateFeaturesRequest.json +43 -0
  348. kafka/protocol/schemas/resources/UpdateFeaturesResponse.json +39 -0
  349. kafka/protocol/schemas/resources/WriteTxnMarkersRequest.json +49 -0
  350. kafka/protocol/schemas/resources/WriteTxnMarkersResponse.json +45 -0
  351. kafka/protocol/schemas/resources/__init__.py +0 -0
  352. kafka/record/__init__.py +3 -0
  353. kafka/record/_crc32c.py +161 -0
  354. kafka/record/abc.py +144 -0
  355. kafka/record/default_records.py +782 -0
  356. kafka/record/legacy_records.py +587 -0
  357. kafka/record/memory_records.py +255 -0
  358. kafka/record/util.py +135 -0
  359. kafka/serializer/__init__.py +4 -0
  360. kafka/serializer/abstract.py +20 -0
  361. kafka/serializer/default.py +16 -0
  362. kafka/serializer/json.py +17 -0
  363. kafka/serializer/wrapper.py +21 -0
  364. kafka/structs.py +69 -0
  365. kafka/util.py +159 -0
  366. kafka/vendor/__init__.py +0 -0
  367. kafka/version.py +1 -0
  368. kafka_python-3.0.0.dist-info/METADATA +319 -0
  369. kafka_python-3.0.0.dist-info/RECORD +373 -0
  370. kafka_python-3.0.0.dist-info/WHEEL +5 -0
  371. kafka_python-3.0.0.dist-info/entry_points.txt +2 -0
  372. kafka_python-3.0.0.dist-info/licenses/LICENSE +202 -0
  373. kafka_python-3.0.0.dist-info/top_level.txt +1 -0
kafka/admin/_groups.py ADDED
@@ -0,0 +1,754 @@
1
+ """Group management mixin for KafkaAdminClient."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+ import itertools
7
+ import logging
8
+ from collections import defaultdict
9
+ import struct
10
+ from typing import TYPE_CHECKING
11
+
12
+ import kafka.errors as Errors
13
+ from kafka.admin._acls import valid_acl_operations
14
+ from kafka.protocol.admin import DeleteGroupsRequest, DescribeGroupsRequest, ListGroupsRequest
15
+ from kafka.protocol.consumer import (
16
+ LeaveGroupRequest, OffsetCommitRequest, OffsetDeleteRequest, OffsetFetchRequest,
17
+ OffsetSpec, OffsetTimestamp,
18
+ )
19
+ from kafka.protocol.consumer.group import DEFAULT_GENERATION_ID, UNKNOWN_MEMBER_ID
20
+ from kafka.protocol.consumer.metadata import (
21
+ ConsumerProtocolAssignment, ConsumerProtocolSubscription, ConsumerProtocolType,
22
+ )
23
+ from kafka.structs import OffsetAndMetadata, TopicPartition
24
+ from kafka.util import EnumHelper
25
+
26
+ if TYPE_CHECKING:
27
+ from kafka.net.manager import KafkaConnectionManager
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ class GroupAdminMixin:
33
+ """Mixin providing consumer group management methods for KafkaAdminClient."""
34
+ _manager: KafkaConnectionManager
35
+ config: dict
36
+
37
+ # -- Describe groups ----------------------------------------------
38
+
39
+ def _describe_groups_request(self, group_ids):
40
+ request = DescribeGroupsRequest(
41
+ groups=list(group_ids),
42
+ include_authorized_operations=True
43
+ )
44
+ return request
45
+
46
+ def _describe_groups_process_response(self, response):
47
+ """Process a DescribeGroupsResponse into a group description."""
48
+ for group in response.groups:
49
+ for member in group.members:
50
+ if member.member_metadata:
51
+ try:
52
+ member.member_metadata = ConsumerProtocolSubscription.decode(member.member_metadata)
53
+ except struct.error:
54
+ log.warning(f'Unable to decode member_metadata for {group}/{member.member_id}')
55
+ pass
56
+ if member.member_assignment:
57
+ try:
58
+ member.member_assignment = ConsumerProtocolAssignment.decode(member.member_assignment)
59
+ except struct.error:
60
+ log.warning(f'Unable to decode member_assignment for {group}/{member.member_id}')
61
+ pass
62
+ # Return dict (key, val) tuples
63
+ results = {}
64
+ for group in response.groups:
65
+ group_id = group.group_id
66
+ result = self._process_acl_operations(group.to_dict())
67
+ error_code = result.pop('error_code')
68
+ error_message = result.pop('error_message', '') # v6+
69
+ result['error'] = str(Errors.for_code(error_code)(error_message)) if error_code else None
70
+ results[group_id] = result
71
+ return results
72
+
73
+ async def _async_describe_groups(self, group_ids, group_coordinator_id=None):
74
+ # Bucket groups by coordinator. One DescribeGroups per coordinator.
75
+ coordinators_groups = defaultdict(list)
76
+ if group_coordinator_id is not None:
77
+ coordinators_groups[group_coordinator_id] = list(group_ids)
78
+ else:
79
+ coordinator_ids = await self._find_coordinator_ids(group_ids)
80
+ for group_id, coordinator_id in coordinator_ids.items():
81
+ coordinators_groups[coordinator_id].append(group_id)
82
+
83
+ results = {}
84
+ for coordinator_id, coordinator_group_ids in coordinators_groups.items():
85
+ request = self._describe_groups_request(coordinator_group_ids)
86
+ response = await self._manager.send(request, node_id=coordinator_id)
87
+ results.update(self._describe_groups_process_response(response))
88
+ return results
89
+
90
+ def describe_groups(self, group_ids, group_coordinator_id=None, include_authorized_operations=False):
91
+ """Describe a set of consumer groups.
92
+
93
+ Any errors are immediately raised.
94
+
95
+ Arguments:
96
+ group_ids: A list of consumer group IDs. These are typically the
97
+ group names as strings.
98
+
99
+ Keyword Arguments:
100
+ group_coordinator_id (int, optional): The node_id of the groups' coordinator
101
+ broker. If set to None, it will query the cluster for each group to
102
+ find that group's coordinator. Explicitly specifying this can be
103
+ useful for avoiding extra network round trips if you already know
104
+ the group coordinator. This is only useful when all the group_ids
105
+ have the same coordinator, otherwise it will error. Default: None.
106
+
107
+ Returns:
108
+ A dict of {group_id: {key: val}}. key/vals are simple to_dict translations
109
+ of the raw results from DescribeGroupsResponse (with inline decoding
110
+ of ConsumerSubscription and ConsumerAssignment metadata, and conversion
111
+ of acl set ints to semantic enums).
112
+ """
113
+ return self._manager.run(self._async_describe_groups, group_ids, group_coordinator_id)
114
+
115
+ # -- List groups --------------------------------------------------
116
+
117
+ @staticmethod
118
+ def _list_groups_request(states_filter=None, types_filter=None):
119
+ kwargs = {'min_version': 0}
120
+ if states_filter:
121
+ kwargs['states_filter'] = [GroupState.value_for(s) for s in states_filter]
122
+ kwargs['min_version'] = 4
123
+ if types_filter:
124
+ kwargs['types_filter'] = [GroupType.value_for(t) for t in types_filter]
125
+ kwargs['min_version'] = 5
126
+ return ListGroupsRequest(**kwargs)
127
+
128
+ def _list_groups_process_response(self, response):
129
+ """Process a ListGroupsResponse into a list of groups."""
130
+ error_type = Errors.for_code(response.error_code)
131
+ if error_type is not Errors.NoError:
132
+ raise error_type(
133
+ "ListGroupsRequest failed with response '{}'."
134
+ .format(response))
135
+ return [group.to_dict() for group in response.groups]
136
+
137
+ async def _async_list_groups(self, broker_ids=None, states_filter=None, types_filter=None):
138
+ if broker_ids is None:
139
+ broker_ids = [broker.node_id for broker in self._manager.cluster.brokers()]
140
+ groups = []
141
+ for broker_id in broker_ids:
142
+ request = self._list_groups_request(states_filter=states_filter,
143
+ types_filter=types_filter)
144
+ response = await self._manager.send(request, node_id=broker_id)
145
+ groups.extend(self._list_groups_process_response(response))
146
+ return groups
147
+
148
+ def list_groups(self, broker_ids=None, states_filter=None, types_filter=None):
149
+ """List all consumer groups known to the cluster.
150
+
151
+ This returns a list of Group dicts. The tuples are
152
+ composed of the consumer group name and the consumer group protocol
153
+ type.
154
+
155
+ Only consumer groups that store their offsets in Kafka are returned.
156
+ The protocol type will be an empty string for groups created using
157
+ Kafka < 0.9 APIs because, although they store their offsets in Kafka,
158
+ they don't use Kafka for group coordination. For groups created using
159
+ Kafka >= 0.9, the protocol type will typically be "consumer".
160
+
161
+ As soon as any error is encountered, it is immediately raised.
162
+
163
+ Keyword Arguments:
164
+ broker_ids ([int], optional): A list of broker node_ids to query for consumer
165
+ groups. If set to None, will query all brokers in the cluster.
166
+ Explicitly specifying broker(s) can be useful for determining which
167
+ consumer groups are coordinated by those broker(s). Default: None
168
+ states_filter (list, optional): Filter groups by state. Values
169
+ may be :class:`GroupState` members, their string names
170
+ (case-insensitive, hyphen or underscore), or raw protocol
171
+ strings (e.g. ``['Stable', 'Empty']``). Requires broker
172
+ >= 3.0 (KIP-518). Default: None (no filter).
173
+ types_filter (list, optional): Filter groups by type. Values
174
+ may be :class:`GroupType` members, their string names
175
+ (case-insensitive), or raw protocol strings (e.g.
176
+ ``['consumer', 'classic', 'share']``). Requires broker
177
+ >= 4.0 (KIP-848). Default: None (no filter).
178
+
179
+ Returns:
180
+ List of group data dicts, with key/vals from ListGroupsRequest
181
+ """
182
+ return self._manager.run(self._async_list_groups, broker_ids,
183
+ states_filter, types_filter)
184
+
185
+ # -- List group offsets -------------------------------------------
186
+
187
+ def _list_group_offsets_requests(self, group_specs):
188
+ _Topic = OffsetFetchRequest.OffsetFetchRequestTopic
189
+ _Group = OffsetFetchRequest.OffsetFetchRequestGroup
190
+ _GroupTopic = _Group.OffsetFetchRequestTopics
191
+ max_version = 8
192
+
193
+ groups = []
194
+ for group_id, partitions in group_specs.items():
195
+ if partitions is None:
196
+ group_topics = None
197
+ else:
198
+ topics_partitions = defaultdict(set)
199
+ for topic, partition in partitions:
200
+ topics_partitions[topic].add(partition)
201
+ group_topics = [
202
+ _GroupTopic(name=name, partition_indexes=list(parts))
203
+ for name, parts in topics_partitions.items()
204
+ ]
205
+ groups.append(_Group(group_id=group_id, topics=group_topics))
206
+
207
+ if len(groups) == 0:
208
+ raise ValueError('Empty group_specs!')
209
+ # Return multple requests when broker does not support v8+
210
+ if self._manager.broker_version_data.api_version(OffsetFetchRequest) < 8:
211
+ for group in groups:
212
+ min_version = 2 if group.topics is None else 0
213
+ yield (group.group_id, OffsetFetchRequest(group_id=group.group_id,
214
+ topics=group.topics,
215
+ min_version=min_version,
216
+ max_version=max_version))
217
+ else:
218
+ yield (None, OffsetFetchRequest(groups=groups,
219
+ min_version=8,
220
+ max_version=max_version))
221
+
222
+ @staticmethod
223
+ def _parse_group_offsets(group):
224
+ """Build {TopicPartition: OffsetAndMetadata} from an OffsetFetchResponse or OffsetFetchResponseGroup."""
225
+ error_type = Errors.for_code(group.error_code)
226
+ if error_type is not Errors.NoError:
227
+ raise error_type(
228
+ "OffsetFetchResponse failed for group '{}'.".format(group.group_id))
229
+ results = {}
230
+ for topic in group.topics:
231
+ for partition in topic.partitions:
232
+ tp = TopicPartition(topic.name, partition.partition_index)
233
+ partition_error = Errors.for_code(partition.error_code)
234
+ if partition_error is not Errors.NoError:
235
+ raise partition_error(
236
+ f"OffsetFetchResponse failed for partition {tp.partition}")
237
+ results[tp] = OffsetAndMetadata(
238
+ offset=partition.committed_offset,
239
+ metadata=partition.metadata,
240
+ leader_epoch=partition.committed_leader_epoch,
241
+ )
242
+ return results
243
+
244
+ def _list_group_offsets_process_response(self, response, group_id=None):
245
+ """Process an OffsetFetchResponse."""
246
+ error_type = Errors.for_code(response.error_code)
247
+ if error_type is not Errors.NoError:
248
+ raise error_type(
249
+ "OffsetFetchResponse failed with response '{}'."
250
+ .format(response))
251
+ if response.API_VERSION >= 8:
252
+ return {group.group_id: self._parse_group_offsets(group)
253
+ for group in response.groups}
254
+ else:
255
+ return {group_id: self._parse_group_offsets(response)}
256
+
257
+ async def _async_list_group_offsets(self, group_specs):
258
+ # Bucket groups by coordinator. One OffsetFetch per coordinator.
259
+ coordinators_groups = defaultdict(list)
260
+ coordinator_ids = await self._find_coordinator_ids(list(group_specs))
261
+ for group_id, coordinator_id in coordinator_ids.items():
262
+ coordinators_groups[coordinator_id].append(group_id)
263
+
264
+ results = {}
265
+ _Group = OffsetFetchRequest.OffsetFetchRequestGroup
266
+ _GroupTopic = _Group.OffsetFetchRequestTopics
267
+ for coordinator_id, group_ids in coordinators_groups.items():
268
+ for group_id, request in self._list_group_offsets_requests({group_id: group_specs[group_id]
269
+ for group_id in group_ids}):
270
+ response = await self._manager.send(request, node_id=coordinator_id)
271
+ results.update(self._list_group_offsets_process_response(response, group_id=group_id))
272
+ return results
273
+
274
+ def list_group_offsets(self, group_specs):
275
+ """Fetch committed offsets for one or more consumer groups.
276
+
277
+ On brokers supporting OffsetFetch v8+ (Apache Kafka 3.0+, KIP-709), this
278
+ issues a single OffsetFetch per coordinator covering all groups
279
+ hosted by that coordinator. On older brokers it currently only supports
280
+ one consumer group (per coordinator).
281
+
282
+ Arguments:
283
+ group_specs (dict): Mapping of group_id (str) to either a list of
284
+ :class:`~kafka.TopicPartition` to fetch, or None to fetch all
285
+ committed offsets for that group.
286
+ Or, one or more group_id (str or list[str]) to fetch all offsets
287
+ for each group.
288
+
289
+ Returns:
290
+ A dict mapping group_id (str) to a dict mapping
291
+ :class:`~kafka.TopicPartition` to
292
+ :class:`~kafka.structs.OffsetAndMetadata`.
293
+
294
+ Raises:
295
+ UnsupportedVersionError: if multiple groups are requested against
296
+ a broker that does not support OffsetFetch v8+; or if group_spec
297
+ with value None against a broker that does not support
298
+ OffsetFetch v2+.
299
+ BrokerResponseError: as soon as any group- or partition-level error
300
+ is encountered.
301
+ """
302
+ if isinstance(group_specs, list):
303
+ group_specs = {group_id: None for group_id in group_specs}
304
+ elif isinstance(group_specs, str):
305
+ group_specs = {group_specs: None}
306
+ return self._manager.run(self._async_list_group_offsets, group_specs)
307
+
308
+ # -- Delete groups ------------------------------------------------
309
+
310
+ def _delete_groups_request(self, group_ids):
311
+ return DeleteGroupsRequest(groups_names=group_ids)
312
+
313
+ def _convert_delete_groups_response(self, response):
314
+ """Parse a DeleteGroupsResponse."""
315
+ results = []
316
+ for group_id, error_code in response.results:
317
+ res = 'OK' if error_code == 0 else Errors.for_code(error_code).__name__
318
+ results.append((group_id, res))
319
+ return results
320
+
321
+ async def _async_delete_groups(self, group_ids, group_coordinator_id=None):
322
+ coordinators_groups = defaultdict(list)
323
+ if group_coordinator_id is not None:
324
+ coordinators_groups[group_coordinator_id] = group_ids
325
+ else:
326
+ coordinator_ids = await self._find_coordinator_ids(group_ids)
327
+ for group_id, coordinator_id in coordinator_ids.items():
328
+ coordinators_groups[coordinator_id].append(group_id)
329
+
330
+ results = []
331
+ for coordinator_id, coordinator_group_ids in coordinators_groups.items():
332
+ request = self._delete_groups_request(coordinator_group_ids)
333
+ response = await self._manager.send(request, node_id=coordinator_id)
334
+ results.extend(self._convert_delete_groups_response(response))
335
+ return dict(results)
336
+
337
+ def delete_groups(self, group_ids, group_coordinator_id=None):
338
+ """Delete Group Offsets for given consumer groups.
339
+
340
+ Note:
341
+ This does not verify that the group ids actually exist and
342
+ group_coordinator_id is the correct coordinator for all these groups.
343
+
344
+ The result needs checking for potential errors.
345
+
346
+ Arguments:
347
+ group_ids ([str]): The consumer group ids of the groups which are to be deleted.
348
+
349
+ Keyword Arguments:
350
+ group_coordinator_id (int, optional): The node_id of the broker which is
351
+ the coordinator for all the groups. Default: None.
352
+
353
+ Returns:
354
+ A list of tuples (group_id, KafkaError)
355
+ """
356
+ return self._manager.run(self._async_delete_groups, group_ids, group_coordinator_id)
357
+
358
+ # -- Alter group offsets -----------------------------------------------
359
+
360
+ @staticmethod
361
+ def _alter_group_offsets_request(group_id, offsets):
362
+ _Topic = OffsetCommitRequest.OffsetCommitRequestTopic
363
+ _Partition = _Topic.OffsetCommitRequestPartition
364
+ topic2partitions = defaultdict(list)
365
+ for tp, oam in offsets.items():
366
+ topic2partitions[tp.topic].append(_Partition(
367
+ partition_index=tp.partition,
368
+ committed_offset=oam.offset,
369
+ committed_leader_epoch=-1 if oam.leader_epoch is None else oam.leader_epoch,
370
+ committed_metadata=oam.metadata,
371
+ ))
372
+ return OffsetCommitRequest(
373
+ group_id=group_id,
374
+ generation_id_or_member_epoch=DEFAULT_GENERATION_ID,
375
+ member_id=UNKNOWN_MEMBER_ID,
376
+ group_instance_id=None,
377
+ retention_time_ms=-1,
378
+ topics=[_Topic(name=name, partitions=parts)
379
+ for name, parts in topic2partitions.items()],
380
+ max_version=8,
381
+ )
382
+
383
+ @staticmethod
384
+ def _alter_group_offsets_process_response(response):
385
+ results = {}
386
+ for topic in response.topics:
387
+ for partition in topic.partitions:
388
+ results[TopicPartition(topic.name, partition.partition_index)] = \
389
+ Errors.for_code(partition.error_code)
390
+ return results
391
+
392
+ async def _async_alter_group_offsets(self, group_id, offsets, group_coordinator_id=None):
393
+ if not offsets:
394
+ return {}
395
+ if group_coordinator_id is None:
396
+ group_coordinator_id = await self._find_coordinator_id(group_id)
397
+ request = self._alter_group_offsets_request(group_id, offsets)
398
+ response = await self._manager.send(request, node_id=group_coordinator_id)
399
+ return self._alter_group_offsets_process_response(response)
400
+
401
+ def alter_group_offsets(self, group_id, offsets, group_coordinator_id=None):
402
+ """Alter committed offsets for a consumer group.
403
+
404
+ The group must have no active members (i.e. be empty or dead) for
405
+ the commit to succeed; otherwise individual partitions may return
406
+ ``UNKNOWN_MEMBER_ID`` or similar errors.
407
+
408
+ Arguments:
409
+ group_id (str): The consumer group id.
410
+ offsets (dict): A dict mapping :class:`~kafka.TopicPartition` to
411
+ :class:`~kafka.structs.OffsetAndMetadata`.
412
+
413
+ Keyword Arguments:
414
+ group_coordinator_id (int, optional): The node_id of the group's
415
+ coordinator broker. If None, the cluster will be queried to
416
+ locate the coordinator. Default: None.
417
+
418
+ Returns:
419
+ dict: A dict mapping :class:`~kafka.TopicPartition` to the
420
+ partition-level :class:`~kafka.errors.KafkaError` class
421
+ (``NoError`` on success).
422
+ """
423
+ return self._manager.run(
424
+ self._async_alter_group_offsets, group_id, offsets, group_coordinator_id)
425
+
426
+ # -- Reset group offsets ----------------------------------------------
427
+
428
+ @staticmethod
429
+ def _reset_group_offsets_process_response(response, to_reset):
430
+ results = {}
431
+ for topic in response.topics:
432
+ for partition in topic.partitions:
433
+ tp = TopicPartition(topic.name, partition.partition_index)
434
+ results[tp] = {
435
+ 'error': Errors.for_code(partition.error_code),
436
+ 'offset': to_reset[tp].offset
437
+ }
438
+ return results
439
+
440
+ @staticmethod
441
+ def _clamp_offset(raw, earliest, latest):
442
+ if raw < 0 or raw > latest:
443
+ return latest
444
+ if raw < earliest:
445
+ return earliest
446
+ return raw
447
+
448
+ async def _async_reset_group_offsets(self, group_id, offset_specs, group_coordinator_id=None):
449
+ if not offset_specs:
450
+ return {}
451
+ all_tps = set(offset_specs.keys())
452
+
453
+ explicit_offsets = {}
454
+ for tp, val in list(offset_specs.items()):
455
+ if isinstance(val, (OffsetSpec, OffsetTimestamp)):
456
+ pass
457
+ elif isinstance(val, int):
458
+ explicit_offsets[tp] = offset_specs.pop(tp)
459
+ else:
460
+ raise TypeError(
461
+ f'Unsupported reset target for {tp}: {val!r} '
462
+ '(expected OffsetSpec, OffsetTimestamp, or int offset)')
463
+
464
+ if group_coordinator_id is None:
465
+ group_coordinator_id = await self._find_coordinator_id(group_id)
466
+
467
+ current = (await self._async_list_group_offsets({group_id: list(all_tps)}))[group_id]
468
+ earliest = await self._async_list_partition_offsets({tp: OffsetSpec.EARLIEST for tp in all_tps})
469
+ latest = await self._async_list_partition_offsets({tp: OffsetSpec.LATEST for tp in all_tps})
470
+
471
+ offsets = {}
472
+ if offset_specs:
473
+ offsets = await self._async_list_partition_offsets(offset_specs)
474
+
475
+ to_reset = {}
476
+ for tp in all_tps:
477
+ if tp in offsets:
478
+ raw = offsets[tp].offset
479
+ else:
480
+ raw = explicit_offsets[tp]
481
+ clamped = self._clamp_offset(raw, earliest[tp].offset, latest[tp].offset)
482
+ if tp in current:
483
+ to_reset[tp] = current[tp]._replace(offset=clamped)
484
+ else:
485
+ to_reset[tp] = OffsetAndMetadata(offset=clamped, metadata='', leader_epoch=None)
486
+
487
+ request = self._alter_group_offsets_request(group_id, to_reset)
488
+ response = await self._manager.send(request, node_id=group_coordinator_id)
489
+ return self._reset_group_offsets_process_response(response, to_reset)
490
+
491
+ def reset_group_offsets(self, group_id, offset_specs, group_coordinator_id=None):
492
+ """Reset committed offsets for a consumer group.
493
+
494
+ The group must have no active members (i.e. be empty or dead) for
495
+ the reset to succeed; otherwise individual partitions may return
496
+ ``UNKNOWN_MEMBER_ID`` or similar errors.
497
+
498
+ Each dict value selects how the target offset is produced. All
499
+ resulting offsets are clamped to the partition's
500
+ ``[earliest, latest]`` range; values that resolve to
501
+ ``UNKNOWN_OFFSET`` (e.g. a timestamp beyond the last record) are
502
+ clamped to ``latest``.
503
+
504
+ Arguments:
505
+ group_id (str): The consumer group id.
506
+ offset_specs (dict): A dict mapping :class:`~kafka.TopicPartition` to
507
+ one of:
508
+
509
+ * :class:`~kafka.admin.OffsetSpec` (e.g. ``OffsetSpec.EARLIEST``,
510
+ ``OffsetSpec.LATEST``, ``OffsetSpec.MAX_TIMESTAMP``):
511
+ resolved server-side via ListOffsets.
512
+ * :class:`~kafka.admin.OffsetTimestamp` (ms since epoch):
513
+ resolved server-side to the earliest offset whose timestamp
514
+ is ``>=`` the given value.
515
+ * Plain ``int``: an explicit committed offset (no server-side
516
+ resolution), which is still clamped to the valid range.
517
+
518
+ Keyword Arguments:
519
+ group_coordinator_id (int, optional): The node_id of the group's
520
+ coordinator broker. If None, the cluster will be queried to
521
+ locate the coordinator. Default: None.
522
+
523
+ Returns:
524
+ dict: A dict mapping :class:`~kafka.TopicPartition` to dict of
525
+ {'error': :class:`~kafka.errors.KafkaError` class, 'offset': int}.
526
+ The ``offset`` value is the post-clamp value that was committed.
527
+ """
528
+ return self._manager.run(
529
+ self._async_reset_group_offsets, group_id, offset_specs, group_coordinator_id)
530
+
531
+ # -- Delete group offsets ----------------------------------------------
532
+
533
+ @staticmethod
534
+ def _delete_group_offsets_request(group_id, partitions):
535
+ _Topic = OffsetDeleteRequest.OffsetDeleteRequestTopic
536
+ _Partition = _Topic.OffsetDeleteRequestPartition
537
+ topic2partitions = defaultdict(list)
538
+ for tp in partitions:
539
+ topic2partitions[tp.topic].append(
540
+ _Partition(partition_index=tp.partition))
541
+ return OffsetDeleteRequest(
542
+ group_id=group_id,
543
+ topics=[_Topic(name=name, partitions=parts)
544
+ for name, parts in topic2partitions.items()],
545
+ )
546
+
547
+ @staticmethod
548
+ def _delete_group_offsets_process_response(response):
549
+ top_level = Errors.for_code(response.error_code)
550
+ if top_level is not Errors.NoError:
551
+ raise top_level(
552
+ "OffsetDeleteRequest failed with response '{}'.".format(response))
553
+ results = {}
554
+ for topic in response.topics:
555
+ for partition in topic.partitions:
556
+ results[TopicPartition(topic.name, partition.partition_index)] = \
557
+ Errors.for_code(partition.error_code)
558
+ return results
559
+
560
+ async def _async_delete_group_offsets(self, group_id, partitions, group_coordinator_id=None):
561
+ if not partitions:
562
+ return {}
563
+ if group_coordinator_id is None:
564
+ group_coordinator_id = await self._find_coordinator_id(group_id)
565
+ request = self._delete_group_offsets_request(group_id, partitions)
566
+ response = await self._manager.send(request, node_id=group_coordinator_id)
567
+ return self._delete_group_offsets_process_response(response)
568
+
569
+ def delete_group_offsets(self, group_id, partitions, group_coordinator_id=None):
570
+ """Delete committed offsets for a consumer group.
571
+
572
+ The group must have no active members subscribed to the given topics;
573
+ otherwise partitions may fail with ``GROUP_SUBSCRIBED_TO_TOPIC``.
574
+
575
+ Arguments:
576
+ group_id (str): The consumer group id.
577
+ partitions: An iterable of :class:`~kafka.TopicPartition` whose
578
+ committed offsets should be deleted.
579
+
580
+ Keyword Arguments:
581
+ group_coordinator_id (int, optional): The node_id of the group's
582
+ coordinator broker. If None, the cluster will be queried to
583
+ locate the coordinator. Default: None.
584
+
585
+ Returns:
586
+ dict: A dict mapping :class:`~kafka.TopicPartition` to the
587
+ partition-level :class:`~kafka.errors.KafkaError` class
588
+ (``NoError`` on success).
589
+
590
+ Raises:
591
+ KafkaError: If the response contains a top-level error (e.g.
592
+ ``GroupIdNotFoundError``, ``NonEmptyGroupError``).
593
+ """
594
+ return self._manager.run(
595
+ self._async_delete_group_offsets, group_id, partitions, group_coordinator_id)
596
+
597
+ # -- Remove group members ---------------------------------------------
598
+
599
+ @staticmethod
600
+ def _remove_group_members_batch_request(group_id, members, version):
601
+ _Member = LeaveGroupRequest.MemberIdentity
602
+ identities = []
603
+ for m in members:
604
+ kwargs = {
605
+ 'member_id': m.member_id if m.member_id is not None else '',
606
+ 'group_instance_id': m.group_instance_id,
607
+ }
608
+ if version >= 5:
609
+ kwargs['reason'] = m.reason
610
+ identities.append(_Member(**kwargs))
611
+ return LeaveGroupRequest(
612
+ group_id=group_id,
613
+ members=identities,
614
+ min_version=3,
615
+ max_version=version,
616
+ )
617
+
618
+ @staticmethod
619
+ def _remove_group_members_process_batch_response(response):
620
+ top_level = Errors.for_code(response.error_code)
621
+ if top_level is not Errors.NoError:
622
+ raise top_level(
623
+ "LeaveGroupRequest failed with response '{}'.".format(response))
624
+ return {
625
+ (m.member_id or m.group_instance_id): Errors.for_code(m.error_code)
626
+ for m in response.members
627
+ }
628
+
629
+ async def _async_remove_group_members(self, group_id, members,
630
+ group_coordinator_id=None):
631
+ if not members:
632
+ return {}
633
+ if group_coordinator_id is None:
634
+ group_coordinator_id = await self._find_coordinator_id(group_id)
635
+
636
+ version = self._manager.broker_version_data.api_version(LeaveGroupRequest)
637
+ batch_supported = version >= 3
638
+
639
+ if batch_supported:
640
+ request = self._remove_group_members_batch_request(
641
+ group_id, members, version)
642
+ response = await self._manager.send(request, node_id=group_coordinator_id)
643
+ return self._remove_group_members_process_batch_response(response)
644
+
645
+ results = {}
646
+ for m in members:
647
+ if m.group_instance_id is not None:
648
+ raise Errors.UnsupportedVersionError(
649
+ "Broker does not support removing members by group.instance.id; "
650
+ "requires LeaveGroup v3+ (Kafka 2.3+).")
651
+ if not m.member_id:
652
+ raise ValueError(
653
+ "MemberToRemove.member_id is required when broker does not "
654
+ "support batched LeaveGroupRequest (v3+).")
655
+ request = LeaveGroupRequest(
656
+ group_id=group_id,
657
+ member_id=m.member_id,
658
+ max_version=2,
659
+ )
660
+ response = await self._manager.send(request, node_id=group_coordinator_id)
661
+ results[m.member_id or m.group_instance_id] = Errors.for_code(response.error_code)
662
+ return results
663
+
664
+ def remove_group_members(self, group_id, members, group_coordinator_id=None):
665
+ """Remove members from a consumer group.
666
+
667
+ On brokers supporting LeaveGroup v3+ (Kafka 2.3+), a single batched
668
+ request is sent. On older brokers, falls back to one single-member
669
+ LeaveGroupRequest per member (in which case ``group_instance_id`` is
670
+ not supported and ``member_id`` is required).
671
+
672
+ Arguments:
673
+ group_id (str): The consumer group id.
674
+ members: An iterable of :class:`~kafka.admin.MemberToRemove`.
675
+ Each entry must set at least one of ``member_id`` or,
676
+ if brokers support LeaveGroup v3+, ``group_instance_id``.
677
+ ``reason`` is only sent to brokers supporting
678
+ LeaveGroup v5+ (KIP-800).
679
+
680
+ Keyword Arguments:
681
+ group_coordinator_id (int, optional): The node_id of the group's
682
+ coordinator broker. If None, the cluster will be queried to
683
+ locate the coordinator. Default: None.
684
+
685
+ Returns:
686
+ dict: A dict mapping :class:`~kafka.admin.MemberToRemove` to the
687
+ per-member :class:`~kafka.errors.KafkaError` class
688
+ (``NoError`` on success). The key's ``reason`` is always None in
689
+ the result (not echoed by the broker).
690
+
691
+ Raises:
692
+ KafkaError: If a batched response contains a top-level error.
693
+ UnsupportedVersionError: If the broker does not support batched
694
+ LeaveGroupRequest and any member uses ``group_instance_id``.
695
+ """
696
+ return self._manager.run(
697
+ self._async_remove_group_members, group_id, members, group_coordinator_id)
698
+
699
+
700
+ class MemberToRemove:
701
+ """A consumer group member to remove via Admin.remove_group_members
702
+
703
+ At least one of ``member_id`` (identifying a dynamic group member)
704
+ or ``group_instance_id`` (identifying a static group member) must be set.
705
+
706
+ Keyword Arguments:
707
+ member_id (str or None): The dynamic member id (as assigned by the
708
+ coordinator in JoinGroupResponse). Use None for static-only removal.
709
+ group_instance_id (str or None): The static member instance id (the
710
+ ``group.instance.id`` configured on the member). Requires LeaveGroup
711
+ v3+ (Kafka 2.3+).
712
+ reason (str or None): Optional reason for removal (propagated to the
713
+ broker on LeaveGroup v5+; ignored on older brokers).
714
+ """
715
+ __slots__ = ('member_id', 'group_instance_id', 'reason')
716
+
717
+ def __init__(self, member_id=None, group_instance_id=None, reason=None):
718
+ self.member_id = member_id
719
+ self.group_instance_id = group_instance_id
720
+ self.reason = reason
721
+
722
+ def __repr__(self):
723
+ return "<MemberToRemove member_id={}, group_instance_id={}, reason={}>".format(
724
+ self.member_id, self.group_instance_id, self.reason)
725
+
726
+ def __eq__(self, other):
727
+ return all((
728
+ self.member_id == other.member_id,
729
+ self.group_instance_id == other.group_instance_id,
730
+ self.reason == other.reason,
731
+ ))
732
+
733
+ def __hash__(self):
734
+ return hash((self.member_id, self.group_instance_id, self.reason))
735
+
736
+
737
+ class GroupState(EnumHelper, str, Enum):
738
+ """Consumer group states as reported by the broker (KIP-518, KIP-848)."""
739
+ UNKNOWN = 'Unknown'
740
+ PREPARING_REBALANCE = 'PreparingRebalance'
741
+ COMPLETING_REBALANCE = 'CompletingRebalance'
742
+ STABLE = 'Stable'
743
+ DEAD = 'Dead'
744
+ EMPTY = 'Empty'
745
+ ASSIGNING = 'Assigning'
746
+ RECONCILING = 'Reconciling'
747
+
748
+
749
+ class GroupType(EnumHelper, str, Enum):
750
+ """Consumer group protocol types (KIP-848)."""
751
+ UNKNOWN = 'Unknown'
752
+ CLASSIC = 'classic'
753
+ CONSUMER = 'consumer'
754
+ SHARE = 'share'