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
@@ -0,0 +1,450 @@
1
+ """Hanging-transaction tooling mixin for KafkaAdminClient (KIP-664).
2
+
3
+ Exposes four wire APIs (ListTransactions, DescribeTransactions,
4
+ DescribeProducers, WriteTxnMarkers in admin abort mode) plus the
5
+ ``find_hanging_transactions`` convenience that ties them together,
6
+ mirroring the Java tool's ``kafka-transactions.sh --find-hanging``.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from collections import defaultdict
11
+ from enum import Enum
12
+ import logging
13
+ from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Set
14
+
15
+ import kafka.errors as Errors
16
+ from kafka.protocol.admin.transactions import (
17
+ DescribeProducersRequest, DescribeTransactionsRequest,
18
+ ListTransactionsRequest,
19
+ )
20
+ from kafka.protocol.metadata import CoordinatorType
21
+ from kafka.protocol.producer.transaction import WriteTxnMarkersRequest
22
+ from kafka.structs import TopicPartition
23
+ from kafka.util import EnumHelper
24
+
25
+ if TYPE_CHECKING:
26
+ from kafka.net.manager import KafkaConnectionManager
27
+
28
+ log = logging.getLogger(__name__)
29
+
30
+
31
+ class TransactionState(EnumHelper, str, Enum):
32
+ """Broker-reported transaction states (DescribeTransactions /
33
+ ListTransactions wire values)."""
34
+ EMPTY = 'Empty'
35
+ ONGOING = 'Ongoing'
36
+ PREPARE_COMMIT = 'PrepareCommit'
37
+ PREPARE_ABORT = 'PrepareAbort'
38
+ COMPLETE_COMMIT = 'CompleteCommit'
39
+ COMPLETE_ABORT = 'CompleteAbort'
40
+ DEAD = 'Dead'
41
+ PREPARE_EPOCH_FENCE = 'PrepareEpochFence'
42
+ UNKNOWN = 'Unknown'
43
+
44
+
45
+ class TransactionListing(NamedTuple):
46
+ """One row from a ListTransactions response."""
47
+ transactional_id: str
48
+ producer_id: int
49
+ state: TransactionState
50
+
51
+
52
+ class TransactionDescription(NamedTuple):
53
+ """One transactional id's state as returned by DescribeTransactions,
54
+ plus the coordinator that owns it."""
55
+ coordinator_id: int
56
+ state: TransactionState
57
+ producer_id: int
58
+ producer_epoch: int
59
+ transaction_timeout_ms: int
60
+ transaction_start_time_ms: int
61
+ topic_partitions: Set[TopicPartition]
62
+
63
+
64
+ class ProducerState(NamedTuple):
65
+ """One ActiveProducer row from DescribeProducers."""
66
+ producer_id: int
67
+ producer_epoch: int
68
+ last_sequence: int
69
+ last_timestamp: int
70
+ coordinator_epoch: int
71
+ current_transaction_start_offset: int
72
+
73
+
74
+ class PartitionProducerState(NamedTuple):
75
+ active_producers: List[ProducerState]
76
+
77
+
78
+ class AbortTransactionSpec(NamedTuple):
79
+ """Inputs for ``abort_transaction``. ``coordinator_epoch=-1`` is the
80
+ sentinel used by the Java admin tool to bypass the epoch check; the
81
+ partition leader still validates ``producer_id``/``producer_epoch``
82
+ against current state."""
83
+ topic_partition: TopicPartition
84
+ producer_id: int
85
+ producer_epoch: int
86
+ coordinator_epoch: int = -1
87
+
88
+
89
+ class TransactionsAdminMixin:
90
+ """Mixin providing KIP-664 hanging-transaction tooling."""
91
+ _manager: "KafkaConnectionManager"
92
+ config: dict
93
+
94
+ # -- ListTransactions ---------------------------------------------------
95
+
96
+ @staticmethod
97
+ def _list_transactions_request(state_filters=None, producer_id_filters=None,
98
+ duration_filter_ms=None,
99
+ transactional_id_pattern=None):
100
+ kwargs = {'min_version': 0}
101
+ kwargs['state_filters'] = list(state_filters) if state_filters else []
102
+ kwargs['producer_id_filters'] = list(producer_id_filters) if producer_id_filters else []
103
+ if duration_filter_ms is not None:
104
+ kwargs['duration_filter'] = int(duration_filter_ms)
105
+ kwargs['min_version'] = max(kwargs['min_version'], 1)
106
+ if transactional_id_pattern is not None:
107
+ kwargs['transactional_id_pattern'] = transactional_id_pattern
108
+ kwargs['min_version'] = max(kwargs['min_version'], 2)
109
+ return ListTransactionsRequest(**kwargs)
110
+
111
+ @staticmethod
112
+ def _list_transactions_process_response(response):
113
+ error_type = Errors.for_code(response.error_code)
114
+ if error_type is not Errors.NoError:
115
+ raise error_type(
116
+ "ListTransactionsRequest failed with response '{}'.".format(response))
117
+ listings = []
118
+ for txn in response.transaction_states:
119
+ listings.append(TransactionListing(
120
+ transactional_id=txn.transactional_id,
121
+ producer_id=txn.producer_id,
122
+ state=TransactionState(txn.transaction_state),
123
+ ))
124
+ return listings
125
+
126
+ async def _async_list_transactions(self, broker_ids=None, producer_id_filters=None,
127
+ state_filters=None, duration_filter_ms=None,
128
+ transactional_id_pattern=None):
129
+ # Semantic version pre-checks: the user-visible flag silently
130
+ # disappears at the wire if we don't enforce it.
131
+ if duration_filter_ms is not None:
132
+ if self._manager.broker_version_data.api_version(ListTransactionsRequest) < 1:
133
+ raise Errors.UnsupportedVersionError(
134
+ 'duration_filter_ms requires broker support for '
135
+ 'ListTransactions v1+ (Apache Kafka 3.8+).')
136
+ if transactional_id_pattern is not None:
137
+ if self._manager.broker_version_data.api_version(ListTransactionsRequest) < 2:
138
+ raise Errors.UnsupportedVersionError(
139
+ 'transactional_id_pattern requires broker support for '
140
+ 'ListTransactions v2+ (Apache Kafka 4.1+, KIP-1152).')
141
+
142
+ if broker_ids is None:
143
+ broker_ids = [broker.node_id for broker in self._manager.cluster.brokers()]
144
+ results = {}
145
+ for broker_id in broker_ids:
146
+ request = self._list_transactions_request(
147
+ state_filters=state_filters,
148
+ producer_id_filters=producer_id_filters,
149
+ duration_filter_ms=duration_filter_ms,
150
+ transactional_id_pattern=transactional_id_pattern,
151
+ )
152
+ response = await self._manager.send(request, node_id=broker_id)
153
+ results[broker_id] = self._list_transactions_process_response(response)
154
+ return results
155
+
156
+ def list_transactions(self, broker_ids=None, producer_id_filters=None,
157
+ state_filters=None, duration_filter_ms=None,
158
+ transactional_id_pattern=None):
159
+ """List active transactions across all brokers (or a subset).
160
+
161
+ Each broker hosts a slice of the ``__transaction_state`` topic,
162
+ so a full listing requires sharding the request to every broker
163
+ and concatenating the results.
164
+
165
+ Keyword Arguments:
166
+ broker_ids ([int], optional): Brokers to query. Default: every
167
+ broker in the cluster metadata.
168
+ producer_id_filters ([int], optional): Only return transactions
169
+ whose ``producer_id`` is in this list.
170
+ state_filters ([str], optional): Only return transactions whose
171
+ broker-reported state matches. Accepts :class:`TransactionState`
172
+ members or their string wire values.
173
+ duration_filter_ms (int, optional): Only return transactions
174
+ running longer than this. Requires broker >= 3.8
175
+ (ListTransactions v1+).
176
+ transactional_id_pattern (str, optional): Only return
177
+ transactions whose transactional id matches this regex.
178
+ Requires broker >= 4.1 (ListTransactions v2+, KIP-1152).
179
+
180
+ Returns:
181
+ dict: A dict mapping broker ``node_id`` to a list of
182
+ :class:`TransactionListing`.
183
+ """
184
+ return self._manager.run(
185
+ self._async_list_transactions, broker_ids, producer_id_filters,
186
+ state_filters, duration_filter_ms, transactional_id_pattern)
187
+
188
+ # -- DescribeTransactions ----------------------------------------------
189
+
190
+ @staticmethod
191
+ def _describe_transactions_request(transactional_ids):
192
+ return DescribeTransactionsRequest(transactional_ids=list(transactional_ids))
193
+
194
+ @staticmethod
195
+ def _describe_transactions_process_response(response, coordinator_id):
196
+ results = {}
197
+ for txn in response.transaction_states:
198
+ error_type = Errors.for_code(txn.error_code)
199
+ if error_type is not Errors.NoError:
200
+ raise error_type(
201
+ "DescribeTransactionsRequest failed for transactional id '{}'."
202
+ .format(txn.transactional_id))
203
+ topic_partitions = set()
204
+ for topic in txn.topics:
205
+ for partition in topic.partitions:
206
+ topic_partitions.add(TopicPartition(topic.topic, partition))
207
+ results[txn.transactional_id] = TransactionDescription(
208
+ coordinator_id=coordinator_id,
209
+ state=TransactionState(txn.transaction_state),
210
+ producer_id=txn.producer_id,
211
+ producer_epoch=txn.producer_epoch,
212
+ transaction_timeout_ms=txn.transaction_timeout_ms,
213
+ transaction_start_time_ms=txn.transaction_start_time_ms,
214
+ topic_partitions=topic_partitions,
215
+ )
216
+ return results
217
+
218
+ async def _async_describe_transactions(self, transactional_ids):
219
+ transactional_ids = list(transactional_ids)
220
+ if not transactional_ids:
221
+ return {}
222
+ coordinator_ids = await self._find_coordinator_ids(
223
+ transactional_ids, key_type=CoordinatorType.TRANSACTION)
224
+ coordinator_to_txn_ids = defaultdict(list)
225
+ for txn_id, coord_id in coordinator_ids.items():
226
+ coordinator_to_txn_ids[coord_id].append(txn_id)
227
+ results = {}
228
+ for coord_id, txn_ids in coordinator_to_txn_ids.items():
229
+ request = self._describe_transactions_request(txn_ids)
230
+ response = await self._manager.send(request, node_id=coord_id)
231
+ results.update(self._describe_transactions_process_response(response, coord_id))
232
+ return results
233
+
234
+ def describe_transactions(self, transactional_ids):
235
+ """Describe one or more transactions by transactional id.
236
+
237
+ Each request is routed to the transaction coordinator that owns
238
+ the transactional id (discovered via FindCoordinator with
239
+ ``CoordinatorType.TRANSACTION``).
240
+
241
+ Arguments:
242
+ transactional_ids: Iterable of transactional id strings.
243
+
244
+ Returns:
245
+ dict: A dict mapping ``transactional_id`` (str) to
246
+ :class:`TransactionDescription`.
247
+
248
+ Raises:
249
+ TransactionalIdNotFoundError: If a transactional id is unknown
250
+ to its coordinator.
251
+ BrokerResponseError: For any other per-id error.
252
+ """
253
+ return self._manager.run(self._async_describe_transactions, transactional_ids)
254
+
255
+ # -- DescribeProducers --------------------------------------------------
256
+
257
+ @staticmethod
258
+ def _describe_producers_request(partitions_by_topic):
259
+ Topic = DescribeProducersRequest.TopicRequest
260
+ topics = [
261
+ Topic(name=name, partition_indexes=list(parts))
262
+ for name, parts in partitions_by_topic.items()
263
+ ]
264
+ return DescribeProducersRequest(topics=topics)
265
+
266
+ @staticmethod
267
+ def _describe_producers_process_response(response):
268
+ results = {}
269
+ for topic in response.topics:
270
+ for partition in topic.partitions:
271
+ tp = TopicPartition(topic.name, partition.partition_index)
272
+ error_type = Errors.for_code(partition.error_code)
273
+ if error_type is not Errors.NoError:
274
+ raise error_type(
275
+ "DescribeProducersRequest failed for {}: {}"
276
+ .format(tp, partition.error_message))
277
+ producers = [
278
+ ProducerState(
279
+ producer_id=p.producer_id,
280
+ producer_epoch=p.producer_epoch,
281
+ last_sequence=p.last_sequence,
282
+ last_timestamp=p.last_timestamp,
283
+ coordinator_epoch=p.coordinator_epoch,
284
+ current_transaction_start_offset=p.current_txn_start_offset,
285
+ ) for p in partition.active_producers
286
+ ]
287
+ results[tp] = PartitionProducerState(active_producers=producers)
288
+ return results
289
+
290
+ async def _async_describe_producers(self, partitions, broker_id=None):
291
+ partitions = list(partitions)
292
+ if not partitions:
293
+ return {}
294
+
295
+ if broker_id is not None:
296
+ # Send a single request to the specified replica.
297
+ partitions_by_topic = defaultdict(list)
298
+ for tp in partitions:
299
+ partitions_by_topic[tp.topic].append(tp.partition)
300
+ request = self._describe_producers_request(partitions_by_topic)
301
+ response = await self._manager.send(request, node_id=broker_id)
302
+ return self._describe_producers_process_response(response)
303
+
304
+ # Route per-partition to the current leader. Shares the metadata
305
+ # round-trip helper used by partition-level operations.
306
+ leader2partitions = await self._async_get_leader_for_partitions(partitions)
307
+ results = {}
308
+ for leader, leader_tps in leader2partitions.items():
309
+ partitions_by_topic = defaultdict(list)
310
+ for tp in leader_tps:
311
+ partitions_by_topic[tp.topic].append(tp.partition)
312
+ request = self._describe_producers_request(partitions_by_topic)
313
+ response = await self._manager.send(request, node_id=leader)
314
+ results.update(self._describe_producers_process_response(response))
315
+ return results
316
+
317
+ def describe_producers(self, partitions, broker_id=None):
318
+ """Describe active producer state on a set of topic partitions.
319
+
320
+ Arguments:
321
+ partitions: Iterable of :class:`~kafka.TopicPartition`.
322
+
323
+ Keyword Arguments:
324
+ broker_id (int, optional): Replica to query. Default: the
325
+ partition leader (discovered from cluster metadata).
326
+
327
+ Returns:
328
+ dict: A dict mapping :class:`~kafka.TopicPartition` to
329
+ :class:`PartitionProducerState`.
330
+
331
+ Raises:
332
+ BrokerResponseError: For any per-partition error (e.g.
333
+ ``NotLeaderOrFollowerError`` if the chosen broker is not
334
+ a replica).
335
+ """
336
+ return self._manager.run(self._async_describe_producers, partitions, broker_id)
337
+
338
+ # -- AbortTransaction (WriteTxnMarkers) --------------------------------
339
+
340
+ @staticmethod
341
+ def _abort_transaction_request(spec):
342
+ Marker = WriteTxnMarkersRequest.WritableTxnMarker
343
+ Topic = Marker.WritableTxnMarkerTopic
344
+ marker = Marker(
345
+ producer_id=spec.producer_id,
346
+ producer_epoch=spec.producer_epoch,
347
+ transaction_result=False, # False -> ABORT
348
+ topics=[Topic(name=spec.topic_partition.topic,
349
+ partition_indexes=[spec.topic_partition.partition])],
350
+ coordinator_epoch=spec.coordinator_epoch,
351
+ )
352
+ return WriteTxnMarkersRequest(markers=[marker])
353
+
354
+ @staticmethod
355
+ def _abort_transaction_process_response(response, spec):
356
+ for result in response.markers:
357
+ for topic in result.topics:
358
+ for partition in topic.partitions:
359
+ error_type = Errors.for_code(partition.error_code)
360
+ if error_type is not Errors.NoError:
361
+ raise error_type(
362
+ "WriteTxnMarkers (abort) failed for {}".format(spec.topic_partition))
363
+
364
+ async def _async_abort_transaction(self, spec):
365
+ leader2partitions = await self._async_get_leader_for_partitions([spec.topic_partition])
366
+ leader = next(iter(leader2partitions))
367
+ request = self._abort_transaction_request(spec)
368
+ response = await self._manager.send(request, node_id=leader)
369
+ self._abort_transaction_process_response(response, spec)
370
+
371
+ def abort_transaction(self, spec):
372
+ """Administratively abort an open transaction on a partition.
373
+
374
+ Sends a WriteTxnMarkers request (with ``transaction_result=False``)
375
+ to the partition leader. The leader validates ``producer_id`` /
376
+ ``producer_epoch`` against current state before writing the
377
+ abort marker. Pass ``coordinator_epoch=-1`` (the default) to
378
+ signal an admin abort that bypasses the coordinator-epoch
379
+ guard, matching the Java AdminClient behaviour.
380
+
381
+ Arguments:
382
+ spec (:class:`AbortTransactionSpec`): Target partition,
383
+ producer id/epoch, and optional coordinator epoch.
384
+ """
385
+ return self._manager.run(self._async_abort_transaction, spec)
386
+
387
+ # -- find_hanging convenience ------------------------------------------
388
+
389
+ async def _async_find_hanging_transactions(self, broker_ids=None,
390
+ max_transaction_timeout_ms=900000):
391
+ # Padding matches the Java tool: a transaction is only flagged
392
+ # "hanging" if it has been alive longer than the broker-side
393
+ # max-transaction-timeout plus the 5-minute slack the tool uses.
394
+ threshold_ms = max_transaction_timeout_ms + 5 * 60 * 1000
395
+ listings_by_broker = await self._async_list_transactions(broker_ids=broker_ids)
396
+ txn_ids = sorted({t.transactional_id for txns in listings_by_broker.values() for t in txns})
397
+ if not txn_ids:
398
+ return []
399
+ descriptions = await self._async_describe_transactions(txn_ids)
400
+ # Resolve "now" via the latest start-time we observed; we don't
401
+ # have a reliable broker clock otherwise. Fall back to local time
402
+ # for an empty result.
403
+ import time
404
+ now_ms = int(time.time() * 1000)
405
+ hanging = []
406
+ for txn_id, desc in descriptions.items():
407
+ if desc.state in (TransactionState.EMPTY, TransactionState.COMPLETE_COMMIT,
408
+ TransactionState.COMPLETE_ABORT, TransactionState.DEAD):
409
+ continue
410
+ if desc.transaction_start_time_ms < 0:
411
+ continue
412
+ age_ms = now_ms - desc.transaction_start_time_ms
413
+ if age_ms < threshold_ms:
414
+ continue
415
+ hanging.append({
416
+ 'transactional_id': txn_id,
417
+ 'producer_id': desc.producer_id,
418
+ 'producer_epoch': desc.producer_epoch,
419
+ 'state': desc.state.value,
420
+ 'age_ms': age_ms,
421
+ 'coordinator_id': desc.coordinator_id,
422
+ 'topic_partitions': sorted(desc.topic_partitions),
423
+ })
424
+ return hanging
425
+
426
+ def find_hanging_transactions(self, broker_ids=None,
427
+ max_transaction_timeout_ms=900000):
428
+ """Detect transactions whose age exceeds the broker timeout + 5min.
429
+
430
+ Convenience wrapper that runs :meth:`list_transactions` against
431
+ each broker, then :meth:`describe_transactions` to read
432
+ ``transaction_start_time_ms``, and filters to transactions in an
433
+ active state whose age exceeds the threshold. Mirrors
434
+ ``kafka-transactions.sh --find-hanging``.
435
+
436
+ Keyword Arguments:
437
+ broker_ids ([int], optional): Brokers to query. Default: all.
438
+ max_transaction_timeout_ms (int): Suspected-hang threshold.
439
+ Default: 900000 (15 minutes -- Kafka's default
440
+ ``transaction.max.timeout.ms``).
441
+
442
+ Returns:
443
+ list: One dict per suspected hanging transaction with keys
444
+ ``transactional_id``, ``producer_id``, ``producer_epoch``,
445
+ ``state``, ``age_ms``, ``coordinator_id``,
446
+ ``topic_partitions``.
447
+ """
448
+ return self._manager.run(
449
+ self._async_find_hanging_transactions, broker_ids, max_transaction_timeout_ms)
450
+
kafka/admin/_users.py ADDED
@@ -0,0 +1,194 @@
1
+ """User management mixin for KafkaAdminClient.
2
+
3
+ Also defines ScramMechanism, UserCredentialDeletion,
4
+ and UserCredentialUpsertion data classes.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import IntEnum
10
+ import hashlib
11
+ import logging
12
+ import os
13
+ from typing import TYPE_CHECKING
14
+
15
+ import kafka.errors as Errors
16
+ from kafka.errors import IllegalArgumentError
17
+ from kafka.protocol.admin import (
18
+ AlterUserScramCredentialsRequest,
19
+ DescribeUserScramCredentialsRequest,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ from kafka.net.manager import KafkaConnectionManager
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+
28
+ class UserAdminMixin:
29
+ """Mixin providing user management methods for KafkaAdminClient."""
30
+ _manager: KafkaConnectionManager
31
+
32
+ async def _async_alter_user_scram_credentials(self, alterations):
33
+ deletions = []
34
+ upsertions = []
35
+ for alt in alterations:
36
+ if isinstance(alt, UserScramCredentialDeletion):
37
+ deletions.append((alt.user, int(alt.mechanism)))
38
+ elif isinstance(alt, UserScramCredentialUpsertion):
39
+ upsertions.append((
40
+ alt.user,
41
+ int(alt.mechanism),
42
+ alt.iterations,
43
+ alt.salt,
44
+ alt.salted_password,
45
+ ))
46
+ else:
47
+ raise IllegalArgumentError(
48
+ "alterations must be UserScramCredentialDeletion or "
49
+ "UserScramCredentialUpsertion, got %s" % type(alt).__name__)
50
+
51
+ request = AlterUserScramCredentialsRequest(
52
+ deletions=deletions,
53
+ upsertions=upsertions,
54
+ )
55
+ response = await self._manager.send(request)
56
+
57
+ ret = {}
58
+ for result in response.results:
59
+ ret[result.user] = result.error_message if result.error_code else None
60
+ return ret
61
+
62
+ def alter_user_scram_credentials(self, alterations):
63
+ """Alter SCRAM credentials for one or more users.
64
+
65
+ Arguments:
66
+ alterations: A list of UserScramCredentialDeletion and/or
67
+ UserScramCredentialUpsertion objects describing the
68
+ credentials to delete and/or insert/update.
69
+
70
+ Returns:
71
+ A dict mapping user name -> error message (or None on success).
72
+ """
73
+ return self._manager.run(self._async_alter_user_scram_credentials, alterations)
74
+
75
+ async def _async_describe_user_scram_credentials(self, users=None):
76
+ if users is None:
77
+ users_field = None
78
+ else:
79
+ users_field = [(user,) for user in users]
80
+ request = DescribeUserScramCredentialsRequest(users=users_field)
81
+ response = await self._manager.send(request)
82
+
83
+ error_type = Errors.for_code(response.error_code)
84
+ if error_type is not Errors.NoError:
85
+ raise error_type(
86
+ "DescribeUserScramCredentialsRequest failed: %s"
87
+ % (response.error_message,))
88
+
89
+ ret = {}
90
+ for result in response.results:
91
+ if result.error_code:
92
+ ret[result.user] = {
93
+ 'error': result.error_message,
94
+ 'credential_infos': [],
95
+ }
96
+ else:
97
+ ret[result.user] = {
98
+ 'error': None,
99
+ 'credential_infos': [
100
+ {
101
+ 'mechanism': ScramMechanism(ci.mechanism),
102
+ 'iterations': ci.iterations,
103
+ }
104
+ for ci in result.credential_infos
105
+ ],
106
+ }
107
+ return ret
108
+
109
+ def describe_user_scram_credentials(self, users=None):
110
+ """Describe SCRAM credentials for one or more users.
111
+
112
+ Arguments:
113
+ users (list of str, optional): User names to describe. If None,
114
+ describe all users with SCRAM credentials.
115
+
116
+ Returns:
117
+ A dict mapping user name to a dict with keys
118
+ ``'error'`` (None or error message) and ``'credential_infos'``
119
+ (list of {'mechanism': ScramMechanism, 'iterations': int}).
120
+ """
121
+ return self._manager.run(self._async_describe_user_scram_credentials, users)
122
+
123
+
124
+ class ScramMechanism(IntEnum):
125
+ UNKNOWN = 0
126
+ SCRAM_SHA_256 = 1
127
+ SCRAM_SHA_512 = 2
128
+
129
+ @property
130
+ def hash_name(self):
131
+ return {
132
+ ScramMechanism.SCRAM_SHA_256: 'sha256',
133
+ ScramMechanism.SCRAM_SHA_512: 'sha512',
134
+ }[self]
135
+
136
+
137
+ class UserScramCredentialDeletion:
138
+ """Specifies that a SCRAM credential should be deleted.
139
+
140
+ Arguments:
141
+ user (str): The user name.
142
+ mechanism (ScramMechanism or int or str): The SCRAM mechanism to
143
+ delete for this user.
144
+ """
145
+ def __init__(self, user, mechanism):
146
+ if not isinstance(mechanism, ScramMechanism):
147
+ if isinstance(mechanism, str):
148
+ mechanism = ScramMechanism[mechanism.upper().replace('-', '_')]
149
+ else:
150
+ mechanism = ScramMechanism(mechanism)
151
+ self.user = user
152
+ self.mechanism = mechanism
153
+
154
+ def __repr__(self):
155
+ return f"UserScramCredentialDeletion({self.user}, {self.mechanism.name})"
156
+
157
+
158
+ class UserScramCredentialUpsertion:
159
+ """Specifies that a SCRAM credential should be inserted or updated.
160
+
161
+ Arguments:
162
+ user (str): The user name.
163
+ mechanism (ScramMechanism or int or str): The SCRAM mechanism.
164
+ password (bytes or str): The plaintext password. The salted
165
+ password sent to the broker is derived via PBKDF2-HMAC using
166
+ the given salt and iteration count.
167
+
168
+ Keyword Arguments:
169
+ iterations (int, optional): PBKDF2 iteration count. Default: 4096.
170
+ salt (bytes, optional): Salt to use. If omitted, a random 24-byte
171
+ salt is generated.
172
+ """
173
+ DEFAULT_ITERATIONS = 4096
174
+
175
+ def __init__(self, user, mechanism, password, iterations=None, salt=None):
176
+ if not isinstance(mechanism, ScramMechanism):
177
+ if isinstance(mechanism, str):
178
+ mechanism = ScramMechanism[mechanism.upper().replace('-', '_')]
179
+ else:
180
+ mechanism = ScramMechanism(mechanism)
181
+ if mechanism == ScramMechanism.UNKNOWN:
182
+ raise IllegalArgumentError("SCRAM mechanism must not be UNKNOWN")
183
+ self.user = user
184
+ self.mechanism = mechanism
185
+ self.iterations = iterations if iterations is not None else self.DEFAULT_ITERATIONS
186
+ self.salt = salt if salt is not None else os.urandom(24)
187
+ if isinstance(password, str):
188
+ password = password.encode('utf-8')
189
+ self.salted_password = hashlib.pbkdf2_hmac(
190
+ mechanism.hash_name, password, self.salt, self.iterations)
191
+
192
+ def __repr__(self):
193
+ return (f"UserScramCredentialUpsertion({self.user}, "
194
+ f"{self.mechanism.name}, iterations={self.iterations})")