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,593 @@
1
+ import collections
2
+ import copy
3
+ import logging
4
+ import random
5
+ import struct
6
+ import time
7
+
8
+ import kafka.errors as Errors
9
+ from kafka.future import Future
10
+ from kafka.net.metrics import KafkaConnectionMetrics
11
+ from kafka.net.sasl import get_sasl_mechanism
12
+ from kafka.protocol.metadata import ApiVersionsRequest
13
+ from kafka.protocol.sasl import SaslAuthenticateRequest, SaslHandshakeRequest, SaslBytesRequest
14
+ from kafka.protocol.broker_version_data import BrokerVersionData
15
+ from kafka.protocol.parser import KafkaProtocol
16
+ from kafka.version import __version__
17
+
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+
22
+ class KafkaConnection:
23
+ DEFAULT_CONFIG = {
24
+ 'client_id': 'kafka-python-' + __version__,
25
+ 'client_software_name': 'kafka-python',
26
+ 'client_software_version': __version__,
27
+ 'max_in_flight_requests_per_connection': 5,
28
+ 'receive_message_max_bytes': 1000000,
29
+ 'request_timeout_ms': 30000,
30
+ 'security_protocol': 'PLAINTEXT',
31
+ 'sasl_mechanism': None,
32
+ 'sasl_plain_username': None,
33
+ 'sasl_plain_password': None,
34
+ 'sasl_kerberos_name': None,
35
+ 'sasl_kerberos_service_name': 'kafka',
36
+ 'sasl_kerberos_domain_name': None,
37
+ 'sasl_oauth_token_provider': None,
38
+ 'metrics': None,
39
+ 'metric_group_prefix': '',
40
+ }
41
+
42
+ def __init__(self, net, node_id=None, broker_version_data=None, **configs):
43
+ self.config = copy.copy(self.DEFAULT_CONFIG)
44
+ for key in self.config:
45
+ if key in configs:
46
+ self.config[key] = configs[key]
47
+
48
+ self.node_id = node_id
49
+ self.net = net
50
+ self.transport = None
51
+ self.parser = None
52
+ self._request_buffer = collections.deque()
53
+ self.paused = set()
54
+ self.connected = False
55
+ self.initializing = True
56
+ self._init_future = Future()
57
+ self._close_future = Future()
58
+ self.in_flight_requests = collections.deque()
59
+ self.broker_version_data = broker_version_data
60
+ self._api_versions_idx = ApiVersionsRequest.max_version # version of ApiVersionsRequest to try on first connect
61
+ self._throttle_time = 0
62
+ self._reauth = SaslReauthenticator(self)
63
+ if self.config['metrics']:
64
+ self._sensors = KafkaConnectionMetrics(
65
+ self.config['metrics'], self.config['metric_group_prefix'], node_id)
66
+ else:
67
+ self._sensors = None
68
+ self._init_future.add_errback(self.fail_in_flight_requests)
69
+ self._close_future.add_both(self.fail_in_flight_requests)
70
+
71
+ @property
72
+ def broker_version(self):
73
+ if self.broker_version_data is None:
74
+ return None
75
+ return self.broker_version_data.broker_version
76
+
77
+ @property
78
+ def closed(self):
79
+ return not self.connected and not self.initializing
80
+
81
+ def __str__(self):
82
+ if self.initializing:
83
+ state = 'initializing'
84
+ elif not self.connected:
85
+ state = 'disconnected'
86
+ elif self.paused:
87
+ state = 'paused'
88
+ else:
89
+ state = 'connected'
90
+ host_port = ' host=[%s]' % self.transport.host_port() if self.transport else ''
91
+ broker_version = self.broker_version if self.broker_version is not None else 'unknown'
92
+ return f'<KafkaConnection node_id={self.node_id}{host_port} broker_version={broker_version} ({state})>'
93
+
94
+ @property
95
+ def init_future(self):
96
+ return self._init_future
97
+
98
+ def __await__(self):
99
+ yield self.init_future
100
+ return self
101
+
102
+ @property
103
+ def close_future(self):
104
+ return self._close_future
105
+
106
+ def _timeout_at(self, now=None, timeout_ms=None):
107
+ if now is None:
108
+ now = time.monotonic()
109
+ if timeout_ms is not None:
110
+ return now + timeout_ms / 1000
111
+ else:
112
+ try:
113
+ return now + self._timeout_secs
114
+ except AttributeError:
115
+ self._timeout_secs = self.config['request_timeout_ms'] / 1000
116
+ return now + self._timeout_secs
117
+
118
+ def send_request(self, request, request_timeout_ms=None):
119
+ future = Future()
120
+ timeout_at = self._timeout_at(timeout_ms=request_timeout_ms)
121
+ if self.initializing or self._reauth.is_reauthenticating:
122
+ self._request_buffer.append((request, future, timeout_at))
123
+ return future
124
+ elif self.paused:
125
+ return future.failure(Errors.NodeNotReadyError(f'Node paused: {self.paused}'))
126
+ elif not self.connected:
127
+ return future.failure(Errors.KafkaConnectionError('Node not connected'))
128
+ else:
129
+ self._send_request(request, future=future, timeout_at=timeout_at)
130
+ return future
131
+
132
+ def _send_request(self, request, future=None, timeout_at=None):
133
+ if future is None:
134
+ future = Future()
135
+ if self.closed:
136
+ return future.failure(Errors.KafkaConnectionError('closed'))
137
+ if request.API_VERSION is None:
138
+ try:
139
+ request.API_VERSION = self.broker_version_data.api_version(request)
140
+ except Errors.IncompatibleBrokerVersion as exc:
141
+ future.failure(exc)
142
+ return future
143
+ sent_time = time.monotonic()
144
+ if timeout_at is None:
145
+ timeout_at = self._timeout_at(now=sent_time)
146
+ if timeout_at <= sent_time:
147
+ future.failure(Errors.KafkaTimeoutError())
148
+ return future
149
+ correlation_id = self.parser.send_request(request)
150
+ log.debug('%s Request %d: %s', self, correlation_id, request)
151
+ if request.expect_response():
152
+ # Each in-flight request owns its own timer so heterogeneous
153
+ # per-request timeouts (e.g. JoinGroup with a rebalance-sized
154
+ # deadline interleaved with default-timeout MetadataRequests)
155
+ # don't require monotonic-deadline FIFO ordering.
156
+ timeout_task = self.net.call_at(
157
+ timeout_at,
158
+ lambda: self._request_timed_out(future, sent_time, timeout_at))
159
+ self.in_flight_requests.append(
160
+ (correlation_id, future, sent_time, timeout_at, timeout_task))
161
+ else:
162
+ future.success(None)
163
+
164
+ # Write the current request's bytes before checking max_in_flight.
165
+ # Otherwise with max_in_flight=1, the first request would be added to
166
+ # in_flight_requests (len==1), trip the >= check, pause, and never be
167
+ # written to the transport - hanging forever.
168
+ if not self.paused:
169
+ self.transport.write(self.parser.send_bytes())
170
+ if len(self.in_flight_requests) >= self.config['max_in_flight_requests_per_connection']:
171
+ self.pause('max_in_flight')
172
+ return future
173
+
174
+ def send_buffered(self):
175
+ while self._request_buffer:
176
+ request, future, timeout_at = self._request_buffer.popleft()
177
+ self._send_request(request, future=future, timeout_at=timeout_at)
178
+
179
+ def _request_timed_out(self, future, sent_at, timeout_at):
180
+ # Defensive: a response and its timer can both be dispatched within a
181
+ # single _poll_once iteration; if data_received resolved the future
182
+ # first, skip the connection-close.
183
+ if self.closed or future.is_done:
184
+ return
185
+ timeout_ms = (timeout_at - sent_at) * 1000
186
+ log.warning('%s: Request timed out after %d ms. Closing connection.', self, timeout_ms)
187
+ self.close(Errors.RequestTimedOutError('Request timed out after %d ms' % timeout_ms))
188
+
189
+ def data_received(self, data):
190
+ """ Called when some data is received."""
191
+ if self.closed:
192
+ log.debug('%s: Ignoring %d bytes received by closed connection', self, len(data))
193
+ return
194
+ responses = self.parser.receive_bytes(data)
195
+
196
+ # augment responses w/ correlation_id, future, and timestamp
197
+ for i, (resp_correlation_id, response) in enumerate(responses):
198
+ try:
199
+ (req_correlation_id, future, sent_time, _timeout_at, timeout_task) = self.in_flight_requests.popleft()
200
+ except IndexError:
201
+ return self.close(Errors.KafkaConnectionError('Received response with no in-flight-requests!'))
202
+
203
+ if req_correlation_id != resp_correlation_id:
204
+ return self.close(Errors.KafkaConnectionError('Received unrecognized correlation id'))
205
+
206
+ self.net.unschedule(timeout_task)
207
+ latency_ms = (time.monotonic() - sent_time) * 1000
208
+ if self._sensors:
209
+ self._sensors.request_time.record(latency_ms)
210
+
211
+ log.debug('%s: Response %d (%s ms): %s', self, resp_correlation_id, latency_ms, response)
212
+ self._maybe_throttle(response)
213
+ future.success(response)
214
+ if 'max_in_flight' in self.paused and len(self.in_flight_requests) < self.config['max_in_flight_requests_per_connection']:
215
+ self.unpause('max_in_flight')
216
+ self._reauth.on_response_processed()
217
+
218
+ def eof_received(self):
219
+ """ Called when the other end calls write_eof() or equivalent.
220
+
221
+ If this returns a false value (including None), the transport
222
+ will close itself. If it returns a true value, closing the
223
+ transport is up to the protocol.
224
+ """
225
+ return False
226
+
227
+ def connection_lost(self, exc):
228
+ """ Called when the connection is lost or closed.
229
+
230
+ The argument is an exception object or None (the latter
231
+ meaning a regular EOF is received or the connection was
232
+ aborted or closed).
233
+ """
234
+ self.connected = self.initializing = False
235
+ self.transport = None
236
+ self._reauth.cancel()
237
+ error = exc or Errors.KafkaConnectionError()
238
+ if not self._init_future.is_done:
239
+ self._init_future.failure(error)
240
+ if not self._close_future.is_done:
241
+ if exc is None:
242
+ self._close_future.success(None)
243
+ else:
244
+ self._close_future.failure(exc)
245
+
246
+ def fail_in_flight_requests(self, error):
247
+ if not self.closed:
248
+ raise RuntimeError('Connection must be closed to fail in flight requests')
249
+ error = error or Errors.Cancelled()
250
+ while self._request_buffer:
251
+ _, future, _ = self._request_buffer.popleft()
252
+ future.failure(error)
253
+ while self.in_flight_requests:
254
+ _, future, _, _, timeout_task = self.in_flight_requests.popleft()
255
+ self.net.unschedule(timeout_task)
256
+ future.failure(error)
257
+
258
+ def connection_made(self, transport):
259
+ """ Called when a connection is made.
260
+
261
+ The argument is the transport representing the pipe connection.
262
+ To receive data, wait for data_received() calls.
263
+ When the connection is closed, connection_lost() is called.
264
+ """
265
+ self.transport = transport
266
+ if self.transport.get_protocol() != self:
267
+ self.transport.set_protocol(self)
268
+ self.initializing = True
269
+ self.transport.resume_reading()
270
+ try:
271
+ log_prefix = 'node=%s[%s:%s]' % (self.node_id, *self.transport.getPeer()[0:2])
272
+ except Exception:
273
+ log.exception('Failed to build connection log_prefix')
274
+ log_prefix = ''
275
+ self.parser = KafkaProtocol(
276
+ client_id=self.config['client_id'],
277
+ receive_message_max_bytes=self.config['receive_message_max_bytes'],
278
+ ident=log_prefix)
279
+
280
+ def pause(self, v):
281
+ self.paused.add(v)
282
+
283
+ def unpause(self, v):
284
+ try:
285
+ self.paused.remove(v)
286
+ except KeyError:
287
+ pass
288
+ else:
289
+ if not self.paused and self.parser and self.transport:
290
+ to_send = self.parser.send_bytes()
291
+ if to_send:
292
+ self.transport.write(to_send)
293
+
294
+ def pause_writing(self):
295
+ """ Called when the transport's buffer goes over the high-water mark.
296
+
297
+ Pause and resume calls are paired -- pause_writing() is called
298
+ once when the buffer goes strictly over the high-water mark
299
+ (even if subsequent writes increases the buffer size even
300
+ more), and eventually resume_writing() is called once when the
301
+ buffer size reaches the low-water mark.
302
+
303
+ Note that if the buffer size equals the high-water mark,
304
+ pause_writing() is not called -- it must go strictly over.
305
+ Conversely, resume_writing() is called when the buffer size is
306
+ equal or lower than the low-water mark. These end conditions
307
+ are important to ensure that things go as expected when either
308
+ mark is zero.
309
+
310
+ NOTE: This is the only Protocol callback that is not called
311
+ through EventLoop.call_soon() -- if it were, it would have no
312
+ effect when it's most needed (when the app keeps writing
313
+ without yielding until pause_writing() is called).
314
+ """
315
+ self.pause('buffer')
316
+
317
+ def resume_writing(self):
318
+ """ Called when the transport's buffer drains below the low-water mark."""
319
+ self.unpause('buffer')
320
+
321
+ def close(self, error=None):
322
+ if error is None and not self._init_future.is_done:
323
+ error = Errors.KafkaConnectionError()
324
+ if not self.transport:
325
+ self.connection_lost(error)
326
+ return
327
+ if error:
328
+ self.transport.abort(error)
329
+ else:
330
+ self.transport.close()
331
+
332
+ def _maybe_throttle(self, response):
333
+ throttle_time_ms = getattr(response, 'throttle_time_ms', 0)
334
+ if self._sensors:
335
+ self._sensors.throttle_time.record(throttle_time_ms)
336
+ if not throttle_time_ms:
337
+ return
338
+ # Client side throttling enabled in v2.0 brokers
339
+ # prior to that throttling (if present) was managed broker-side
340
+ if self.broker_version is not None and self.broker_version >= (2, 0):
341
+ throttle_time = time.monotonic() + throttle_time_ms / 1000
342
+ if throttle_time > self._throttle_time:
343
+ self._throttle_time = throttle_time
344
+ self.net.call_at(throttle_time, self._maybe_unthrottle)
345
+ self.pause('throttle')
346
+ log.warning("%s: %s throttled by broker (%d ms)", self,
347
+ response.__class__.__name__, throttle_time_ms)
348
+
349
+ def _maybe_unthrottle(self):
350
+ if time.monotonic() >= self._throttle_time:
351
+ self._throttle_time = 0
352
+ self.unpause('throttle')
353
+
354
+ async def initialize(self, timeout_at=None):
355
+ if timeout_at is None:
356
+ timeout_at = self._timeout_at()
357
+ try:
358
+ await self._get_api_versions(timeout_at)
359
+ if self.sasl_enabled:
360
+ await self._sasl_authenticate(timeout_at)
361
+ except Exception as error:
362
+ self.close(error)
363
+ else:
364
+ self._init_complete()
365
+
366
+ async def _get_api_versions(self, timeout_at=None):
367
+ if timeout_at is None:
368
+ timeout_at = self._timeout_at()
369
+ if self.broker_version_data is not None:
370
+ try:
371
+ self._api_versions_idx = self.broker_version_data.api_version(ApiVersionsRequest)
372
+ except Errors.IncompatibleBrokerVersion:
373
+ log.debug('%s: Using pre-configured api_version %s for ApiVersions', self, self.broker_version)
374
+ return
375
+
376
+ while timeout_at > time.monotonic():
377
+ version = self._api_versions_idx
378
+ request = ApiVersionsRequest(
379
+ version=version,
380
+ client_software_name=self.config['client_software_name'],
381
+ client_software_version=self.config['client_software_version'],
382
+ )
383
+ response = await self._send_request(request, timeout_at=timeout_at)
384
+ error_type = Errors.for_code(response.error_code)
385
+ if error_type is Errors.NoError:
386
+ break
387
+ elif error_type is Errors.UnsupportedVersionError:
388
+ for api_version in response.api_keys:
389
+ if api_version.api_key == response.API_KEY:
390
+ self._api_versions_idx = min(self._api_versions_idx, api_version.max_version)
391
+ break
392
+ else:
393
+ self._api_versions_idx = 0
394
+ continue
395
+ else:
396
+ raise error_type()
397
+ else:
398
+ raise Errors.KafkaTimeoutError('Timeout during ApiVersions check')
399
+
400
+ api_versions = {api_version.api_key: (api_version.min_version, api_version.max_version)
401
+ for api_version in response.api_keys}
402
+ bvd = BrokerVersionData(api_versions=api_versions)
403
+ log.info('%s: Broker version identified as %s', self, '.'.join(map(str, bvd.broker_version)))
404
+ if self.broker_version_data is None or self.broker_version_data > bvd:
405
+ self.broker_version_data = bvd
406
+ else:
407
+ log.info('%s: Clamping client to user-supplied broker version %s', self, '.'.join(map(str, self.broker_version)))
408
+
409
+ @property
410
+ def sasl_enabled(self):
411
+ return self.config['security_protocol'] in ('SASL_PLAINTEXT', 'SASL_SSL')
412
+
413
+ async def _sasl_authenticate(self, timeout_at=None):
414
+ if timeout_at is None:
415
+ timeout_at = self._timeout_at()
416
+ # Step 1: SaslHandshake to negotiate mechanism
417
+ request = SaslHandshakeRequest(
418
+ mechanism=self.config['sasl_mechanism'],
419
+ max_version=1)
420
+ response = await self._send_request(request, timeout_at=timeout_at)
421
+ error_type = Errors.for_code(response.error_code)
422
+ if error_type is not Errors.NoError:
423
+ log.error('%s: SaslHandshake failed: %s', self, error_type.__name__)
424
+ raise error_type()
425
+
426
+ if self.config['sasl_mechanism'] not in response.mechanisms:
427
+ raise Errors.UnsupportedSaslMechanismError(
428
+ 'Kafka broker does not support %s sasl mechanism. Enabled mechanisms: %s'
429
+ % (self.config['sasl_mechanism'], response.mechanisms))
430
+
431
+ # Step 2: SASL authentication exchange
432
+ version = response.API_VERSION
433
+ # Prefer the configured hostname (stored on the transport) so that
434
+ # mechanisms like GSSAPI construct service principals against the
435
+ # user-supplied name, not whichever IP getaddrinfo handed us.
436
+ sasl_host = self.transport.host if self.transport.host else self.transport.getPeer()[0]
437
+ mechanism = get_sasl_mechanism(self.config['sasl_mechanism'])(
438
+ host=sasl_host, **self.config)
439
+
440
+ auth_response = None
441
+ while not mechanism.is_done() and timeout_at > time.monotonic():
442
+ token = mechanism.auth_bytes()
443
+ if version == 1:
444
+ auth_request = SaslAuthenticateRequest(token)
445
+ else:
446
+ auth_request = SaslBytesRequest(token)
447
+ auth_response = await self._send_request(auth_request, timeout_at=timeout_at)
448
+ error_type = Errors.for_code(auth_response.error_code)
449
+ if error_type is not Errors.NoError:
450
+ raise Errors.SaslAuthenticationFailedError(
451
+ '%s: %s' % (error_type.__name__, auth_response.error_message))
452
+
453
+ # GSSAPI does not get a final recv in v0 unframed mode
454
+ if version == 0 and mechanism.is_done():
455
+ break
456
+ mechanism.receive(auth_response.auth_bytes)
457
+
458
+ if time.monotonic() > timeout_at:
459
+ raise Errors.KafkaTimeoutError('SASL Authentication timed out')
460
+ elif not mechanism.is_authenticated():
461
+ raise Errors.SaslAuthenticationFailedError(
462
+ 'Failed to authenticate via SASL %s' % self.config['sasl_mechanism'])
463
+
464
+ # KIP-368: SessionLifetimeMs is only present on SaslAuthenticateResponse v1+.
465
+ if version == 1:
466
+ self._reauth.session_updated(auth_response.session_lifetime_ms)
467
+ log.info('%s: %s', self, mechanism.auth_details())
468
+
469
+ def _init_complete(self):
470
+ if self.initializing:
471
+ self.initializing = False
472
+ self.connected = True
473
+ self.send_buffered()
474
+ self._init_future.success(True)
475
+ self._reauth.schedule()
476
+
477
+
478
+ class SaslReauthenticator:
479
+ """KIP-368 SASL re-authentication state and scheduling for a single
480
+ KafkaConnection. Owns the per-connection re-auth lifecycle so the
481
+ connection doesn't have to carry the related attributes and coroutines
482
+ inline. The connection plugs this in at five points:
483
+
484
+ - after each successful SASL auth -> session_updated()
485
+ - after init completes -> schedule()
486
+ - when send_request needs to gate the public API -> is_reauthenticating
487
+ - on every response popped from in_flight_requests -> on_response_processed()
488
+ - on connection_lost -> cancel()
489
+ """
490
+
491
+ def __init__(self, conn):
492
+ self._conn = conn
493
+ self.session_lifetime_ms = 0
494
+ self.authenticated_at = None
495
+ self._task = None
496
+ self._reauthenticating = False
497
+ self._drain_future = None
498
+
499
+ @property
500
+ def is_reauthenticating(self):
501
+ return self._reauthenticating
502
+
503
+ @property
504
+ def task(self):
505
+ """The scheduled re-auth task, or None. Exposed for tests/observability."""
506
+ return self._task
507
+
508
+ def session_updated(self, session_lifetime_ms):
509
+ """Capture broker-advertised session lifetime after each successful
510
+ auth round (initial and subsequent re-auths). Clamp negative values to 0,
511
+ and require minimum non-zero lifetime of 1sec (1000)."""
512
+ self.session_lifetime_ms = session_lifetime_ms or 0
513
+ if self.session_lifetime_ms < 0:
514
+ self.session_lifetime_ms = 0
515
+ elif 0 < self.session_lifetime_ms <= 1000:
516
+ self.session_lifetime_ms = 1000
517
+ self.authenticated_at = time.monotonic()
518
+
519
+ def schedule(self):
520
+ """Schedule the next re-auth before the lifetime elapses. Jittered to
521
+ 85-95% of the lifetime to avoid synchronised re-auth storms across
522
+ many connections (Apache Java semantics). No-op when SASL is disabled
523
+ or the broker advertised lifetime=0.
524
+ """
525
+ if not self._conn.sasl_enabled or not self.session_lifetime_ms:
526
+ return
527
+ pct = random.uniform(0.85, 0.95)
528
+ delay = (self.session_lifetime_ms * pct) / 1000
529
+ log.debug('%s: Scheduling SASL re-authentication in %.3fs (session_lifetime_ms=%d)',
530
+ self._conn, delay, self.session_lifetime_ms)
531
+ self._task = self._conn.net.call_later(delay, self._run)
532
+
533
+ def cancel(self):
534
+ """Cancel any pending re-auth and fail the drain awaiter if present.
535
+ Called from KafkaConnection.connection_lost."""
536
+ if self._task is not None:
537
+ try:
538
+ self._conn.net.unschedule(self._task)
539
+ except (ValueError, KeyError):
540
+ pass
541
+ self._task = None
542
+ if self._drain_future is not None and not self._drain_future.is_done:
543
+ self._drain_future.failure(Errors.KafkaConnectionError())
544
+ self._drain_future = None
545
+ self._reauthenticating = False
546
+
547
+ def on_response_processed(self):
548
+ """Wake the drain awaiter once in_flight_requests clears during reauth.
549
+ Called from KafkaConnection.data_received after each pop."""
550
+ if (self._reauthenticating
551
+ and self._drain_future is not None
552
+ and not self._conn.in_flight_requests
553
+ and not self._drain_future.is_done):
554
+ self._drain_future.success(None)
555
+
556
+ async def _run(self):
557
+ self._task = None
558
+ if self._conn.closed:
559
+ return
560
+ try:
561
+ await self._do_reauth()
562
+ except BaseException as exc: # pylint: disable=W0718
563
+ # Re-auth failure is transient (KIP-368: not cached like initial
564
+ # auth failure); close the connection so the manager reconnects on
565
+ # next demand.
566
+ log.warning('%s: SASL re-authentication failed: %s', self._conn, exc)
567
+ err = exc if isinstance(exc, Exception) else Errors.SaslAuthenticationFailedError(str(exc))
568
+ self._conn.close(err)
569
+
570
+ async def _do_reauth(self):
571
+ self._reauthenticating = True
572
+ try:
573
+ # Drain in-flight so the SaslHandshake/Authenticate frames are the
574
+ # next bytes on the wire (Apache Java does the same; avoids
575
+ # reasoning about FIFO interleaving with the broker's reauth
576
+ # validation).
577
+ while self._conn.in_flight_requests and not self._conn.closed:
578
+ self._drain_future = Future()
579
+ if not self._conn.in_flight_requests:
580
+ break
581
+ await self._drain_future
582
+ self._drain_future = None
583
+ if self._conn.closed:
584
+ return
585
+ log.debug('%s: Beginning SASL re-authentication', self._conn)
586
+ await self._conn._sasl_authenticate() # pylint: disable=W0212
587
+ finally:
588
+ self._reauthenticating = False
589
+ self._drain_future = None
590
+ if self._conn.closed:
591
+ return
592
+ self._conn.send_buffered()
593
+ self.schedule()