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,647 @@
1
+ import collections
2
+ import copy
3
+ import logging
4
+ import threading
5
+ import time
6
+
7
+ import kafka.errors as Errors
8
+ from kafka.producer.producer_batch import ProducerBatch
9
+ from kafka.record.memory_records import MemoryRecords, MemoryRecordsBuilder
10
+ from kafka.structs import TopicPartition
11
+
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+
16
+ class AtomicInteger:
17
+ def __init__(self, val=0):
18
+ self._lock = threading.Lock()
19
+ self._val = val
20
+
21
+ def increment(self):
22
+ with self._lock:
23
+ self._val += 1
24
+ return self._val
25
+
26
+ def decrement(self):
27
+ with self._lock:
28
+ self._val -= 1
29
+ return self._val
30
+
31
+ def get(self):
32
+ return self._val
33
+
34
+
35
+ class RecordAccumulator:
36
+ """
37
+ This class maintains a dequeue per TopicPartition that accumulates messages
38
+ into MessageSets to be sent to the server.
39
+
40
+ The accumulator attempts to bound memory use, and append calls will block
41
+ when that memory is exhausted.
42
+
43
+ Keyword Arguments:
44
+ batch_size (int): Requests sent to brokers will contain multiple
45
+ batches, one for each partition with data available to be sent.
46
+ A small batch size will make batching less common and may reduce
47
+ throughput (a batch size of zero will disable batching entirely).
48
+ Default: 16384
49
+ compression_attrs (int): The compression type for all data generated by
50
+ the producer. Valid values are gzip(1), snappy(2), lz4(3), or
51
+ none(0).
52
+ Compression is of full batches of data, so the efficacy of batching
53
+ will also impact the compression ratio (more batching means better
54
+ compression). Default: None.
55
+ linger_ms (int): An artificial delay time to add before declaring a
56
+ record batch (that isn't full) ready for sending. This allows
57
+ time for more records to arrive. Setting a non-zero linger_ms
58
+ will trade off some latency for potentially better throughput
59
+ due to more batching (and hence fewer, larger requests).
60
+ Default: 0
61
+ retry_backoff_ms (int): An artificial delay time to retry the
62
+ produce request upon receiving an error. This avoids exhausting
63
+ all retries in a short period of time. Default: 100
64
+ """
65
+ DEFAULT_CONFIG = {
66
+ 'batch_size': 16384,
67
+ 'compression_attrs': 0,
68
+ 'linger_ms': 0,
69
+ 'request_timeout_ms': 30000,
70
+ 'delivery_timeout_ms': 120000,
71
+ 'retry_backoff_ms': 100,
72
+ 'transaction_manager': None,
73
+ 'message_version': 2,
74
+ }
75
+
76
+ def __init__(self, **configs):
77
+ self.config = copy.copy(self.DEFAULT_CONFIG)
78
+ for key in self.config:
79
+ if key in configs:
80
+ self.config[key] = configs.pop(key)
81
+
82
+ self._closed = False
83
+ self._transaction_manager = self.config['transaction_manager']
84
+ self._flushes_in_progress = AtomicInteger()
85
+ self._appends_in_progress = AtomicInteger()
86
+ self._batches = collections.defaultdict(collections.deque) # TopicPartition: [ProducerBatch]
87
+ self._tp_locks = {None: threading.Lock()} # TopicPartition: Lock, plus a lock to add entries
88
+ self._incomplete = IncompleteProducerBatches()
89
+ # The following variables should only be accessed by the sender thread,
90
+ # so we don't need to protect them w/ locking.
91
+ self.muted = set()
92
+ self._drain_index = 0
93
+ self._next_batch_expiry_time_ms = float('inf')
94
+
95
+ if self.config['delivery_timeout_ms'] < self.config['linger_ms'] + self.config['request_timeout_ms']:
96
+ raise Errors.KafkaConfigurationError("Must set delivery_timeout_ms higher than linger_ms + request_timeout_ms")
97
+
98
+ @property
99
+ def delivery_timeout_ms(self):
100
+ return self.config['delivery_timeout_ms']
101
+
102
+ @property
103
+ def next_expiry_time_ms(self):
104
+ return self._next_batch_expiry_time_ms
105
+
106
+ def _tp_lock(self, tp):
107
+ if tp not in self._tp_locks:
108
+ with self._tp_locks[None]:
109
+ if tp not in self._tp_locks:
110
+ self._tp_locks[tp] = threading.Lock()
111
+ return self._tp_locks[tp]
112
+
113
+ def append(self, tp, timestamp_ms, key, value, headers, now=None,
114
+ abort_on_new_batch=False):
115
+ """Add a record to the accumulator, return the append result.
116
+
117
+ The append result will contain the future metadata, and flag for
118
+ whether the appended batch is full or a new batch is created
119
+
120
+ Arguments:
121
+ tp (TopicPartition): The topic/partition to which this record is
122
+ being sent
123
+ timestamp_ms (int): The timestamp of the record (epoch ms)
124
+ key (bytes): The key for the record
125
+ value (bytes): The value for the record
126
+ headers (List[Tuple[str, bytes]]): The header fields for the record
127
+ abort_on_new_batch (bool): KIP-480. When True, return early with
128
+ ``abort_for_new_batch=True`` instead of allocating a new
129
+ batch when no in-progress batch has room. Caller is expected
130
+ to consult the partitioner's ``on_new_batch`` hook, re-pick
131
+ the partition, and retry with ``abort_on_new_batch=False``.
132
+
133
+ Returns:
134
+ tuple: (future, batch_is_full, new_batch_created, abort_for_new_batch)
135
+ """
136
+ if not isinstance(tp, TopicPartition):
137
+ raise TypeError('not TopicPartition')
138
+ if self._closed:
139
+ raise Errors.IllegalStateError('RecordAccumulator is closed')
140
+ now = time.monotonic() if now is None else now
141
+ # We keep track of the number of appending thread to make sure we do
142
+ # not miss batches in abortIncompleteBatches().
143
+ self._appends_in_progress.increment()
144
+ try:
145
+ with self._tp_lock(tp):
146
+ # check if we have an in-progress batch
147
+ dq = self._batches[tp]
148
+ if dq:
149
+ last = dq[-1]
150
+ future = last.try_append(timestamp_ms, key, value, headers, now=now)
151
+ if future is not None:
152
+ batch_is_full = len(dq) > 1 or last.records.is_full()
153
+ return future, batch_is_full, False, False
154
+
155
+ if abort_on_new_batch:
156
+ # KIP-480: don't allocate a new batch yet. Caller will
157
+ # rotate the sticky partition and retry.
158
+ return None, False, False, True
159
+
160
+ with self._tp_lock(tp):
161
+ # Need to check if producer is closed again after grabbing the
162
+ # dequeue lock.
163
+ if self._closed:
164
+ raise Errors.IllegalStateError('RecordAccumulator is closed')
165
+
166
+ if dq:
167
+ last = dq[-1]
168
+ future = last.try_append(timestamp_ms, key, value, headers, now=now)
169
+ if future is not None:
170
+ # Somebody else found us a batch, return the one we
171
+ # waited for! Hopefully this doesn't happen often...
172
+ batch_is_full = len(dq) > 1 or last.records.is_full()
173
+ return future, batch_is_full, False, False
174
+
175
+ if self._transaction_manager and self.config['message_version'] < 2:
176
+ raise Errors.UnsupportedVersionError("Attempting to use idempotence with a broker which"
177
+ " does not support the required message format (v2)."
178
+ " The broker must be version 0.11 or later.")
179
+ records = MemoryRecordsBuilder(
180
+ self.config['message_version'],
181
+ self.config['compression_attrs'],
182
+ self.config['batch_size']
183
+ )
184
+
185
+ batch = ProducerBatch(tp, records, now=now)
186
+ future = batch.try_append(timestamp_ms, key, value, headers, now=now)
187
+ if not future:
188
+ raise Exception()
189
+
190
+ dq.append(batch)
191
+ self._incomplete.add(batch)
192
+ batch_is_full = len(dq) > 1 or batch.records.is_full()
193
+ return future, batch_is_full, True, False
194
+ finally:
195
+ self._appends_in_progress.decrement()
196
+
197
+ def reset_next_batch_expiry_time(self):
198
+ self._next_batch_expiry_time_ms = float('inf')
199
+
200
+ def maybe_update_next_batch_expiry_time(self, batch):
201
+ self._next_batch_expiry_time_ms = min(self._next_batch_expiry_time_ms, batch.created * 1000 + self.delivery_timeout_ms)
202
+
203
+ def expired_batches(self, now=None):
204
+ """Get a list of batches which have been sitting in the accumulator too long and need to be expired."""
205
+ expired_batches = []
206
+ for tp in list(self._batches.keys()):
207
+ with self._tp_lock(tp):
208
+ # iterate over the batches and expire them if they have stayed
209
+ # in accumulator for more than request_timeout_ms
210
+ dq = self._batches[tp]
211
+ while dq:
212
+ batch = dq[0]
213
+ if batch.has_reached_delivery_timeout(self.delivery_timeout_ms, now=now):
214
+ dq.popleft()
215
+ batch.records.close()
216
+ expired_batches.append(batch)
217
+ else:
218
+ # Stop at the first batch that has not expired.
219
+ self.maybe_update_next_batch_expiry_time(batch)
220
+ break
221
+ return expired_batches
222
+
223
+ def split_and_reenqueue(self, batch, now=None):
224
+ """Split an oversized batch into smaller batches and reenqueue them.
225
+
226
+ When a produce request fails with MESSAGE_TOO_LARGE, this method splits
227
+ the batch into two sub-batches (by record count) and enqueues them at
228
+ the front of the partition's deque. The original FutureRecordMetadata
229
+ objects are rebound to the new batches' futures.
230
+
231
+ If the new batches are still too large, they will be split again on the
232
+ next MESSAGE_TOO_LARGE response.
233
+
234
+ Only supported for message_version >= 2 (DefaultRecordBatch).
235
+
236
+ Arguments:
237
+ batch (ProducerBatch): The oversized batch to split.
238
+
239
+ Returns:
240
+ int: The number of new batches created.
241
+ """
242
+ now = time.monotonic() if now is None else now
243
+ tp = batch.topic_partition
244
+
245
+ # Roll back the partition's sequence counter to the failed batch's
246
+ # base sequence. The failed batch was never committed by the broker,
247
+ # so its sequence range is free to be reused by the split batches.
248
+ # They will get fresh sequences assigned during drain.
249
+ if self._transaction_manager:
250
+ base_sequence = batch.records.base_sequence
251
+ if base_sequence is not None and base_sequence != -1:
252
+ self._transaction_manager.set_sequence_number(tp, base_sequence)
253
+
254
+ # Read all records from the closed batch
255
+ records_list = []
256
+ for record_batch in MemoryRecords(batch.records.buffer()):
257
+ for record in record_batch:
258
+ records_list.append(record)
259
+
260
+ # Split records into two halves by count
261
+ mid = (len(records_list) + 1) // 2
262
+ groups = [records_list[:mid], records_list[mid:]]
263
+
264
+ new_batches = []
265
+ future_index = 0
266
+ for group in groups:
267
+ if not group:
268
+ continue
269
+ builder = MemoryRecordsBuilder(
270
+ self.config['message_version'],
271
+ self.config['compression_attrs'],
272
+ self.config['batch_size'],
273
+ )
274
+ current_batch = ProducerBatch(tp, builder, now=now)
275
+ current_batch.created = batch.created
276
+
277
+ for record in group:
278
+ metadata = builder.append(record.timestamp, record.key, record.value, record.headers)
279
+ if metadata is None:
280
+ # Record doesn't fit (extremely unlikely for split batches).
281
+ # Finalize this batch and start a new one.
282
+ new_batches.append(current_batch)
283
+ builder = MemoryRecordsBuilder(
284
+ self.config['message_version'],
285
+ self.config['compression_attrs'],
286
+ self.config['batch_size'],
287
+ )
288
+ current_batch = ProducerBatch(tp, builder, now=now)
289
+ current_batch.created = batch.created
290
+ metadata = builder.append(record.timestamp, record.key, record.value, record.headers)
291
+
292
+ # Rebind original future to new batch
293
+ if future_index < len(batch._record_futures):
294
+ original_future = batch._record_futures[future_index]
295
+ original_future.rebind(current_batch.produce_future, metadata.offset)
296
+ current_batch._record_futures.append(original_future)
297
+ future_index += 1
298
+
299
+ new_batches.append(current_batch)
300
+
301
+ # Enqueue in reverse order so first batch is at front of deque
302
+ with self._tp_lock(tp):
303
+ dq = self._batches[tp]
304
+ for new_batch in reversed(new_batches):
305
+ new_batch.attempts = batch.attempts
306
+ new_batch.last_attempt = now
307
+ dq.appendleft(new_batch)
308
+ self._incomplete.add(new_batch)
309
+
310
+ log.info("Split oversized batch for %s into %d new batches (%d total records)",
311
+ tp, len(new_batches), future_index)
312
+ return len(new_batches)
313
+
314
+ def reenqueue(self, batch, now=None):
315
+ """
316
+ Re-enqueue the given record batch in the accumulator. In Sender._complete_batch method, we check
317
+ whether the batch has reached delivery_timeout_ms or not. Hence we do not do the delivery timeout check here.
318
+ """
319
+ batch.retry(now=now)
320
+ with self._tp_lock(batch.topic_partition):
321
+ dq = self._batches[batch.topic_partition]
322
+ dq.appendleft(batch)
323
+
324
+ def ready(self, cluster, now=None):
325
+ """
326
+ Get a list of nodes whose partitions are ready to be sent, and the
327
+ earliest time at which any non-sendable partition will be ready;
328
+ Also return the flag for whether there are any unknown leaders for the
329
+ accumulated partition batches.
330
+
331
+ A destination node is ready to send if:
332
+
333
+ * There is at least one partition that is not backing off its send
334
+ * and those partitions are not muted (to prevent reordering if
335
+ max_in_flight_requests_per_connection is set to 1)
336
+ * and any of the following are true:
337
+
338
+ * The record set is full
339
+ * The record set has sat in the accumulator for at least linger_ms
340
+ milliseconds
341
+ * The accumulator is out of memory and threads are blocking waiting
342
+ for data (in this case all partitions are immediately considered
343
+ ready).
344
+ * The accumulator has been closed
345
+
346
+ Arguments:
347
+ cluster (ClusterMetadata):
348
+
349
+ Returns:
350
+ tuple:
351
+ ready_nodes (set): node_ids that have ready batches
352
+ next_ready_check (float): secs until next ready after backoff
353
+ unknown_leaders_exist (bool): True if metadata refresh needed
354
+ """
355
+ ready_nodes = set()
356
+ next_ready_check = 9999999.99
357
+ unknown_leaders_exist = False
358
+ now = time.monotonic() if now is None else now
359
+
360
+ # several threads are accessing self._batches -- to simplify
361
+ # concurrent access, we iterate over a snapshot of partitions
362
+ # and lock each partition separately as needed
363
+ partitions = list(self._batches.keys())
364
+ for tp in partitions:
365
+ leader = cluster.leader_for_partition(tp)
366
+ if leader is None or leader == -1:
367
+ unknown_leaders_exist = True
368
+ continue
369
+ elif leader in ready_nodes:
370
+ continue
371
+ elif tp in self.muted:
372
+ continue
373
+
374
+ with self._tp_lock(tp):
375
+ dq = self._batches[tp]
376
+ if not dq:
377
+ continue
378
+ batch = dq[0]
379
+ retry_backoff = self.config['retry_backoff_ms'] / 1000
380
+ linger = self.config['linger_ms'] / 1000
381
+ backing_off = bool(batch.attempts > 0
382
+ and (batch.last_attempt + retry_backoff) > now)
383
+ waited_time = now - batch.last_attempt
384
+ time_to_wait = retry_backoff if backing_off else linger
385
+ time_left = max(time_to_wait - waited_time, 0)
386
+ full = bool(len(dq) > 1 or batch.records.is_full())
387
+ expired = bool(waited_time >= time_to_wait)
388
+
389
+ sendable = (full or expired or self._closed or
390
+ self.flush_in_progress())
391
+
392
+ if sendable and not backing_off:
393
+ ready_nodes.add(leader)
394
+ else:
395
+ # Note that this results in a conservative estimate since
396
+ # an un-sendable partition may have a leader that will
397
+ # later be found to have sendable data. However, this is
398
+ # good enough since we'll just wake up and then sleep again
399
+ # for the remaining time.
400
+ next_ready_check = min(time_left, next_ready_check)
401
+
402
+ return ready_nodes, next_ready_check, unknown_leaders_exist
403
+
404
+ def has_undrained(self):
405
+ """Check whether there are any batches which haven't been drained"""
406
+ for tp in list(self._batches.keys()):
407
+ with self._tp_lock(tp):
408
+ dq = self._batches[tp]
409
+ if len(dq):
410
+ return True
411
+ return False
412
+
413
+ def _should_stop_drain_batches_for_partition(self, first, tp):
414
+ if self._transaction_manager:
415
+ if not self._transaction_manager.is_send_to_partition_allowed(tp):
416
+ return True
417
+ if not self._transaction_manager.producer_id_and_epoch.is_valid:
418
+ # we cannot send the batch until we have refreshed the PID
419
+ log.debug("Waiting to send ready batches because transaction producer id is not valid")
420
+ return True
421
+ return False
422
+
423
+ def drain_batches_for_one_node(self, cluster, node_id, max_size, now=None):
424
+ now = time.monotonic() if now is None else now
425
+ size = 0
426
+ ready = []
427
+ partitions = list(cluster.partitions_for_broker(node_id))
428
+ if not partitions:
429
+ return ready
430
+ # to make starvation less likely this loop doesn't start at 0
431
+ self._drain_index %= len(partitions)
432
+ start = None
433
+ while start != self._drain_index:
434
+ tp = partitions[self._drain_index]
435
+ if start is None:
436
+ start = self._drain_index
437
+ self._drain_index += 1
438
+ self._drain_index %= len(partitions)
439
+
440
+ # Only proceed if the partition has no in-flight batches.
441
+ if tp in self.muted:
442
+ continue
443
+
444
+ if tp not in self._batches:
445
+ continue
446
+
447
+ with self._tp_lock(tp):
448
+ dq = self._batches[tp]
449
+ if len(dq) == 0:
450
+ continue
451
+ first = dq[0]
452
+ backoff = bool(first.attempts > 0 and
453
+ first.last_attempt + self.config['retry_backoff_ms'] / 1000 > now)
454
+ # Only drain the batch if it is not during backoff
455
+ if backoff:
456
+ continue
457
+
458
+ if (size + first.records.size_in_bytes() > max_size
459
+ and len(ready) > 0):
460
+ # there is a rare case that a single batch
461
+ # size is larger than the request size due
462
+ # to compression; in this case we will
463
+ # still eventually send this batch in a
464
+ # single request
465
+ break
466
+ else:
467
+ if self._should_stop_drain_batches_for_partition(first, tp):
468
+ break
469
+
470
+ batch = dq.popleft()
471
+ if self._transaction_manager and not batch.in_retry():
472
+ # If the batch is in retry, then we should not change the pid and
473
+ # sequence number, since this may introduce duplicates. In particular,
474
+ # the previous attempt may actually have been accepted, and if we change
475
+ # the pid and sequence here, this attempt will also be accepted, causing
476
+ # a duplicate.
477
+ sequence_number = self._transaction_manager.sequence_number(batch.topic_partition)
478
+ log.debug("Dest: %s: %s producer_id=%s epoch=%s sequence=%s",
479
+ node_id, batch.topic_partition,
480
+ self._transaction_manager.producer_id_and_epoch.producer_id,
481
+ self._transaction_manager.producer_id_and_epoch.epoch,
482
+ sequence_number)
483
+ batch.records.set_producer_state(
484
+ self._transaction_manager.producer_id_and_epoch.producer_id,
485
+ self._transaction_manager.producer_id_and_epoch.epoch,
486
+ sequence_number,
487
+ self._transaction_manager.is_transactional()
488
+ )
489
+ # Increment sequence now so subsequent in-flight batches
490
+ # for the same partition get the correct next sequence.
491
+ self._transaction_manager.increment_sequence_number(
492
+ batch.topic_partition, batch.records.next_offset())
493
+ batch.records.close()
494
+ size += batch.records.size_in_bytes()
495
+ ready.append(batch)
496
+ batch.drained = now
497
+ return ready
498
+
499
+ def drain(self, cluster, nodes, max_size, now=None):
500
+ """
501
+ Drain all the data for the given nodes and collate them into a list of
502
+ batches that will fit within the specified size on a per-node basis.
503
+ This method attempts to avoid choosing the same topic-node repeatedly.
504
+
505
+ Arguments:
506
+ cluster (ClusterMetadata): The current cluster metadata
507
+ nodes (list): list of node_ids to drain
508
+ max_size (int): maximum number of bytes to drain
509
+
510
+ Returns:
511
+ dict: {node_id: list of ProducerBatch} with total size less than the
512
+ requested max_size.
513
+ """
514
+ if not nodes:
515
+ return {}
516
+
517
+ now = time.monotonic() if now is None else now
518
+ batches = {}
519
+ for node_id in nodes:
520
+ batches[node_id] = self.drain_batches_for_one_node(cluster, node_id, max_size, now=now)
521
+ return batches
522
+
523
+ def deallocate(self, batch):
524
+ """Deallocate the record batch."""
525
+ self._incomplete.remove(batch)
526
+
527
+ def flush_in_progress(self):
528
+ """Are there any threads currently waiting on a flush?"""
529
+ return self._flushes_in_progress.get() > 0
530
+
531
+ def begin_flush(self):
532
+ """
533
+ Initiate the flushing of data from the accumulator...this makes all
534
+ requests immediately ready
535
+ """
536
+ self._flushes_in_progress.increment()
537
+
538
+ def await_flush_completion(self, timeout=None):
539
+ """
540
+ Mark all partitions as ready to send and block until the send is complete
541
+ """
542
+ try:
543
+ for batch in self._incomplete.all():
544
+ log.debug('Waiting on produce to %s',
545
+ batch.produce_future.topic_partition)
546
+ if not batch.produce_future.wait(timeout=timeout):
547
+ raise Errors.KafkaTimeoutError('Timeout waiting for future')
548
+ if not batch.produce_future.is_done:
549
+ raise Errors.UnknownError('Future not done')
550
+
551
+ if batch.produce_future.failed():
552
+ log.warning(batch.produce_future.exception)
553
+ finally:
554
+ self._flushes_in_progress.decrement()
555
+
556
+ @property
557
+ def has_incomplete(self):
558
+ return bool(self._incomplete)
559
+
560
+ def abort_incomplete_batches(self):
561
+ """
562
+ This function is only called when sender is closed forcefully. It will fail all the
563
+ incomplete batches and return.
564
+ """
565
+ # We need to keep aborting the incomplete batch until no thread is trying to append to
566
+ # 1. Avoid losing batches.
567
+ # 2. Free up memory in case appending threads are blocked on buffer full.
568
+ # This is a tight loop but should be able to get through very quickly.
569
+ error = Errors.IllegalStateError("Producer is closed forcefully.")
570
+ while True:
571
+ self.abort_batches(error)
572
+ if not self._appends_in_progress.get():
573
+ break
574
+ # After this point, no thread will append any messages because they will see the close
575
+ # flag set. We need to do the last abort after no thread was appending in case the there was a new
576
+ # batch appended by the last appending thread.
577
+ self.abort_batches(error)
578
+ self._batches.clear()
579
+
580
+ def abort_batches(self, error):
581
+ """Abort every incomplete batch, including in-flight (drained but
582
+ not-yet-acked) ones. Use for fatal-error / force-close paths where
583
+ the user's pending futures must resolve immediately rather than
584
+ hang waiting for broker responses that aren't coming."""
585
+ for batch in self._incomplete.all():
586
+ tp = batch.topic_partition
587
+ with self._tp_lock(tp):
588
+ batch.records.close()
589
+ # Drained batches were popleft()'d out of _batches[tp] -- only
590
+ # remove if still present (matches Java's LinkedList.remove,
591
+ # which returns false rather than raising).
592
+ try:
593
+ self._batches[tp].remove(batch)
594
+ except ValueError:
595
+ pass
596
+ batch.abort(error)
597
+ self.deallocate(batch)
598
+
599
+ def abort_undrained_batches(self, error):
600
+ for batch in self._incomplete.all():
601
+ tp = batch.topic_partition
602
+ with self._tp_lock(tp):
603
+ aborted = False
604
+ # Skip in-flight batches (already drained, awaiting broker
605
+ # response): drain() popped them from _batches[tp], so
606
+ # .remove() would ValueError; and we want their futures to
607
+ # resolve via the broker response, not be cancelled locally.
608
+ if not batch.is_done and batch.drained is None:
609
+ aborted = True
610
+ batch.records.close()
611
+ self._batches[tp].remove(batch)
612
+ if aborted:
613
+ batch.abort(error)
614
+ self.deallocate(batch)
615
+
616
+ def close(self):
617
+ """Close this accumulator and force all the record buffers to be drained."""
618
+ self._closed = True
619
+
620
+
621
+ class IncompleteProducerBatches:
622
+ """A threadsafe helper class to hold ProducerBatches that haven't been ack'd yet"""
623
+
624
+ def __init__(self):
625
+ self._incomplete = set()
626
+ self._lock = threading.Lock()
627
+
628
+ def add(self, batch):
629
+ with self._lock:
630
+ self._incomplete.add(batch)
631
+
632
+ def remove(self, batch):
633
+ with self._lock:
634
+ try:
635
+ self._incomplete.remove(batch)
636
+ except KeyError:
637
+ pass
638
+
639
+ def all(self):
640
+ with self._lock:
641
+ return list(self._incomplete)
642
+
643
+ def __bool__(self):
644
+ return bool(self._incomplete)
645
+
646
+
647
+ __nonzero__ = __bool__