kafka-python 2.3.0__tar.gz → 2.3.2__tar.gz

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 (194) hide show
  1. {kafka_python-2.3.0 → kafka_python-2.3.2}/CHANGES.md +19 -0
  2. {kafka_python-2.3.0 → kafka_python-2.3.2}/PKG-INFO +12 -15
  3. {kafka_python-2.3.0 → kafka_python-2.3.2}/README.rst +10 -13
  4. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/admin/client.py +4 -2
  5. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/client_async.py +4 -0
  6. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/codec.py +2 -4
  7. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/conn.py +16 -8
  8. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/consumer/fetcher.py +5 -4
  9. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/consumer/group.py +4 -2
  10. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/consumer.py +2 -1
  11. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/errors.py +4 -0
  12. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/future.py +8 -0
  13. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/kafka.py +4 -2
  14. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/parser.py +9 -1
  15. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/types.py +21 -20
  16. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/scram.py +6 -1
  17. kafka_python-2.3.2/kafka/version.py +1 -0
  18. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka_python.egg-info/PKG-INFO +12 -15
  19. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka_python.egg-info/SOURCES.txt +0 -1
  20. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/test_admin_integration.py +2 -10
  21. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/test_consumer_integration.py +1 -1
  22. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/test_sasl_integration.py +2 -0
  23. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_codec.py +11 -0
  24. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_fetcher.py +187 -0
  25. kafka_python-2.3.0/kafka/version.py +0 -1
  26. kafka_python-2.3.0/test/test_protocol.py +0 -344
  27. {kafka_python-2.3.0 → kafka_python-2.3.2}/AUTHORS.md +0 -0
  28. {kafka_python-2.3.0 → kafka_python-2.3.2}/LICENSE +0 -0
  29. {kafka_python-2.3.0 → kafka_python-2.3.2}/MANIFEST.in +0 -0
  30. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/__init__.py +0 -0
  31. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/__main__.py +0 -0
  32. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/admin/__init__.py +0 -0
  33. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/admin/__main__.py +0 -0
  34. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/admin/acl_resource.py +0 -0
  35. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/admin/config_resource.py +0 -0
  36. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/admin/new_partitions.py +0 -0
  37. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/admin/new_topic.py +0 -0
  38. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/benchmarks/__init__.py +0 -0
  39. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/benchmarks/consumer_performance.py +0 -0
  40. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/benchmarks/load_example.py +0 -0
  41. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/benchmarks/producer_performance.py +0 -0
  42. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/benchmarks/record_batch_compose.py +0 -0
  43. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/benchmarks/record_batch_read.py +0 -0
  44. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/benchmarks/varint_speed.py +0 -0
  45. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/__init__.py +0 -0
  46. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/__init__.py +0 -0
  47. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/cluster/__init__.py +0 -0
  48. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/cluster/describe.py +0 -0
  49. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/configs/__init__.py +0 -0
  50. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/configs/describe.py +0 -0
  51. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/consumer_groups/__init__.py +0 -0
  52. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/consumer_groups/delete.py +0 -0
  53. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/consumer_groups/describe.py +0 -0
  54. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/consumer_groups/list.py +0 -0
  55. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/consumer_groups/list_offsets.py +0 -0
  56. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/log_dirs/__init__.py +0 -0
  57. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/log_dirs/describe.py +0 -0
  58. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/topics/__init__.py +0 -0
  59. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/topics/create.py +0 -0
  60. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/topics/delete.py +0 -0
  61. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/topics/describe.py +0 -0
  62. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/admin/topics/list.py +0 -0
  63. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/consumer/__init__.py +0 -0
  64. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cli/producer/__init__.py +0 -0
  65. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/cluster.py +0 -0
  66. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/consumer/__init__.py +0 -0
  67. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/consumer/__main__.py +0 -0
  68. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/consumer/subscription_state.py +0 -0
  69. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/__init__.py +0 -0
  70. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/__init__.py +0 -0
  71. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/abstract.py +0 -0
  72. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/range.py +0 -0
  73. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/roundrobin.py +0 -0
  74. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
  75. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
  76. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
  77. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
  78. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/base.py +0 -0
  79. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/heartbeat.py +0 -0
  80. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/protocol.py +0 -0
  81. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/coordinator/subscription.py +0 -0
  82. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/__init__.py +0 -0
  83. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/compound_stat.py +0 -0
  84. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/dict_reporter.py +0 -0
  85. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/kafka_metric.py +0 -0
  86. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/measurable.py +0 -0
  87. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/measurable_stat.py +0 -0
  88. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/metric_config.py +0 -0
  89. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/metric_name.py +0 -0
  90. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/metrics.py +0 -0
  91. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/metrics_reporter.py +0 -0
  92. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/quota.py +0 -0
  93. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stat.py +0 -0
  94. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/__init__.py +0 -0
  95. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/avg.py +0 -0
  96. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/count.py +0 -0
  97. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/histogram.py +0 -0
  98. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/max_stat.py +0 -0
  99. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/min_stat.py +0 -0
  100. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/percentile.py +0 -0
  101. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/percentiles.py +0 -0
  102. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/rate.py +0 -0
  103. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/sampled_stat.py +0 -0
  104. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/sensor.py +0 -0
  105. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/metrics/stats/total.py +0 -0
  106. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/partitioner/__init__.py +0 -0
  107. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/partitioner/default.py +0 -0
  108. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/__init__.py +0 -0
  109. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/__main__.py +0 -0
  110. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/future.py +0 -0
  111. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/producer_batch.py +0 -0
  112. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/record_accumulator.py +0 -0
  113. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/sender.py +0 -0
  114. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/producer/transaction_manager.py +0 -0
  115. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/__init__.py +0 -0
  116. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/abstract.py +0 -0
  117. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/add_offsets_to_txn.py +0 -0
  118. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/add_partitions_to_txn.py +0 -0
  119. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/admin.py +0 -0
  120. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/api.py +0 -0
  121. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/api_versions.py +0 -0
  122. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/broker_api_versions.py +0 -0
  123. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/commit.py +0 -0
  124. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/end_txn.py +0 -0
  125. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/fetch.py +0 -0
  126. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/find_coordinator.py +0 -0
  127. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/frame.py +0 -0
  128. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/group.py +0 -0
  129. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/init_producer_id.py +0 -0
  130. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/list_offsets.py +0 -0
  131. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/message.py +0 -0
  132. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/metadata.py +0 -0
  133. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/offset_for_leader_epoch.py +0 -0
  134. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/produce.py +0 -0
  135. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/sasl_authenticate.py +0 -0
  136. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/sasl_handshake.py +0 -0
  137. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/struct.py +0 -0
  138. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/protocol/txn_offset_commit.py +0 -0
  139. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/record/__init__.py +0 -0
  140. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/record/_crc32c.py +0 -0
  141. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/record/abc.py +0 -0
  142. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/record/default_records.py +0 -0
  143. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/record/legacy_records.py +0 -0
  144. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/record/memory_records.py +0 -0
  145. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/record/util.py +0 -0
  146. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/__init__.py +0 -0
  147. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/abc.py +0 -0
  148. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/gssapi.py +0 -0
  149. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/msk.py +0 -0
  150. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/oauth.py +0 -0
  151. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/plain.py +0 -0
  152. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/sasl/sspi.py +0 -0
  153. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/serializer/__init__.py +0 -0
  154. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/serializer/abstract.py +0 -0
  155. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/socks5_wrapper.py +0 -0
  156. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/structs.py +0 -0
  157. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/util.py +0 -0
  158. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/vendor/__init__.py +0 -0
  159. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/vendor/enum34.py +0 -0
  160. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/vendor/selectors34.py +0 -0
  161. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/vendor/six.py +0 -0
  162. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka/vendor/socketpair.py +0 -0
  163. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka_python.egg-info/dependency_links.txt +0 -0
  164. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka_python.egg-info/requires.txt +0 -0
  165. {kafka_python-2.3.0 → kafka_python-2.3.2}/kafka_python.egg-info/top_level.txt +0 -0
  166. {kafka_python-2.3.0 → kafka_python-2.3.2}/pyproject.toml +0 -0
  167. {kafka_python-2.3.0 → kafka_python-2.3.2}/setup.cfg +0 -0
  168. {kafka_python-2.3.0 → kafka_python-2.3.2}/setup.py +0 -0
  169. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/__init__.py +0 -0
  170. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/conftest.py +0 -0
  171. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/fixtures.py +0 -0
  172. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/test_consumer_group.py +0 -0
  173. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/integration/test_producer_integration.py +0 -0
  174. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_acl_comparisons.py +0 -0
  175. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_admin.py +0 -0
  176. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_api_object_implementation.py +0 -0
  177. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_assignors.py +0 -0
  178. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_client_async.py +0 -0
  179. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_cluster.py +0 -0
  180. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_conn.py +0 -0
  181. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_consumer.py +0 -0
  182. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_coordinator.py +0 -0
  183. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_metrics.py +0 -0
  184. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_object_conversion.py +0 -0
  185. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_package.py +0 -0
  186. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_partition_movements.py +0 -0
  187. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_partitioner.py +0 -0
  188. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_producer.py +0 -0
  189. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_producer_batch.py +0 -0
  190. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_record_accumulator.py +0 -0
  191. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_sender.py +0 -0
  192. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_subscription_state.py +0 -0
  193. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/test_util.py +0 -0
  194. {kafka_python-2.3.0 → kafka_python-2.3.2}/test/testutil.py +0 -0
@@ -1,3 +1,22 @@
1
+ # 2.3.2 (Jun 3, 2026)
2
+
3
+ Fixes
4
+ * kafka.conn: Improve error handling for sasl authenticate mechanisms
5
+ * kafka.net: Validate SASL/SCRAM iterations (#3026)
6
+ * KafkaProtocol: validate network frame size (backport of #3019)
7
+ * Clear `_callbacks`/`_errbacks` list when future `is_done` to avoid reference cycles (#2891)
8
+
9
+ # 2.3.1 (Apr 9, 2026)
10
+
11
+ Fixes
12
+ * Fix TaggedFields value encoding; add test coverage (#2725)
13
+ * Fix zstd multi-frame decompression failure (#2717)
14
+ * Fix KeyError in KafkaConsumer.committed() (#2710)
15
+ * Fix VarInt/VarLong encoding; move tests to test/protocol/ (#2706)
16
+ * Fix `Fetcher._fetch_offsets_by_times` retry handling (#2833)
17
+ * Fixes to support integration testing with external KAFKA_URI (#2838)
18
+ * Minor py2 fixes: consumer integration test; dont pip install python-snappy
19
+
1
20
  # 2.3.0 (Nov 20, 2025)
2
21
 
3
22
  CLI
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: kafka-python
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: Pure Python client for Apache Kafka
5
5
  Author-email: Dana Powers <dana.powers@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/dpkp/kafka-python
@@ -66,22 +66,11 @@ Python client for the Apache Kafka distributed stream processing system.
66
66
  kafka-python is designed to function much like the official java client, with a
67
67
  sprinkling of pythonic interfaces (e.g., consumer iterators).
68
68
 
69
- kafka-python is best used with newer brokers (0.9+), but is backwards-compatible with
70
- older versions (to 0.8.0). Some features will only be enabled on newer brokers.
71
- For example, fully coordinated consumer groups -- i.e., dynamic partition
72
- assignment to multiple consumers in the same group -- requires use of 0.9+ kafka
73
- brokers. Supporting this feature for earlier broker releases would require
74
- writing and maintaining custom leadership election and membership / health
75
- check code (perhaps using zookeeper or consul). For older brokers, you can
76
- achieve something similar by manually assigning different partitions to each
77
- consumer instance with config management tools like chef, ansible, etc. This
78
- approach will work fine, though it does not support rebalancing on failures.
79
- See https://kafka-python.readthedocs.io/en/master/compatibility.html
80
- for more details.
81
-
82
69
  Please note that the master branch may contain unreleased features. For release
83
70
  documentation, please see readthedocs and/or python's inline help.
84
71
 
72
+ New in 2.3 release: python -m kafka.* interfaces for quick scripts and testing.
73
+
85
74
  .. code-block:: bash
86
75
 
87
76
  $ pip install kafka-python
@@ -232,6 +221,14 @@ for more details.
232
221
  metrics = producer.metrics()
233
222
 
234
223
 
224
+ Module CLI Interface
225
+ ********************
226
+
227
+ kafka-python also provides simple command-line interfaces for consumer, producer, and admin clients.
228
+ Access via ``python -m kafka.consumer``, ``python -m kafka.producer``, and ``python -m kafka.admin``.
229
+ See https://kafka-python.readthedocs.io/en/master/usage.html for more details.
230
+
231
+
235
232
  Thread safety
236
233
  *************
237
234
 
@@ -22,22 +22,11 @@ Python client for the Apache Kafka distributed stream processing system.
22
22
  kafka-python is designed to function much like the official java client, with a
23
23
  sprinkling of pythonic interfaces (e.g., consumer iterators).
24
24
 
25
- kafka-python is best used with newer brokers (0.9+), but is backwards-compatible with
26
- older versions (to 0.8.0). Some features will only be enabled on newer brokers.
27
- For example, fully coordinated consumer groups -- i.e., dynamic partition
28
- assignment to multiple consumers in the same group -- requires use of 0.9+ kafka
29
- brokers. Supporting this feature for earlier broker releases would require
30
- writing and maintaining custom leadership election and membership / health
31
- check code (perhaps using zookeeper or consul). For older brokers, you can
32
- achieve something similar by manually assigning different partitions to each
33
- consumer instance with config management tools like chef, ansible, etc. This
34
- approach will work fine, though it does not support rebalancing on failures.
35
- See https://kafka-python.readthedocs.io/en/master/compatibility.html
36
- for more details.
37
-
38
25
  Please note that the master branch may contain unreleased features. For release
39
26
  documentation, please see readthedocs and/or python's inline help.
40
27
 
28
+ New in 2.3 release: python -m kafka.* interfaces for quick scripts and testing.
29
+
41
30
  .. code-block:: bash
42
31
 
43
32
  $ pip install kafka-python
@@ -188,6 +177,14 @@ for more details.
188
177
  metrics = producer.metrics()
189
178
 
190
179
 
180
+ Module CLI Interface
181
+ ********************
182
+
183
+ kafka-python also provides simple command-line interfaces for consumer, producer, and admin clients.
184
+ Access via ``python -m kafka.consumer``, ``python -m kafka.producer``, and ``python -m kafka.admin``.
185
+ See https://kafka-python.readthedocs.io/en/master/usage.html for more details.
186
+
187
+
191
188
  Thread safety
192
189
  *************
193
190
 
@@ -88,6 +88,9 @@ class KafkaAdminClient(object):
88
88
  max_in_flight_requests_per_connection (int): Requests are pipelined
89
89
  to kafka brokers up to this number of maximum requests per
90
90
  broker connection. Default: 5.
91
+ receive_message_max_bytes (int): Maximum allowed network frame size.
92
+ Used to avoid OOM when decoding malformed network message header.
93
+ Default: 1000000.
91
94
  receive_buffer_bytes (int): The size of the TCP receive buffer
92
95
  (SO_RCVBUF) to use when reading data. Default: None (relies on
93
96
  system defaults). Java client defaults to 32768.
@@ -164,11 +167,10 @@ class KafkaAdminClient(object):
164
167
  'reconnect_backoff_ms': 50,
165
168
  'reconnect_backoff_max_ms': 30000,
166
169
  'max_in_flight_requests_per_connection': 5,
170
+ 'receive_message_max_bytes': 1000000,
167
171
  'receive_buffer_bytes': None,
168
172
  'send_buffer_bytes': None,
169
173
  'socket_options': [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
170
- 'sock_chunk_bytes': 4096, # undocumented experimental option
171
- 'sock_chunk_buffer_count': 1000, # undocumented experimental option
172
174
  'retry_backoff_ms': 100,
173
175
  'metadata_max_age_ms': 300000,
174
176
  'security_protocol': 'PLAINTEXT',
@@ -89,6 +89,9 @@ class KafkaClient(object):
89
89
  max_in_flight_requests_per_connection (int): Requests are pipelined
90
90
  to kafka brokers up to this number of maximum requests per
91
91
  broker connection. Default: 5.
92
+ receive_message_max_bytes (int): Maximum allowed network frame size.
93
+ Used to avoid OOM when decoding malformed network message header.
94
+ Default: 1000000.
92
95
  receive_buffer_bytes (int): The size of the TCP receive buffer
93
96
  (SO_RCVBUF) to use when reading data. Default: None (relies on
94
97
  system defaults). Java client defaults to 32768.
@@ -186,6 +189,7 @@ class KafkaClient(object):
186
189
  'reconnect_backoff_ms': 50,
187
190
  'reconnect_backoff_max_ms': 30000,
188
191
  'max_in_flight_requests_per_connection': 5,
192
+ 'receive_message_max_bytes': 1000000,
189
193
  'receive_buffer_bytes': None,
190
194
  'send_buffer_bytes': None,
191
195
  'socket_options': [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
@@ -327,7 +327,5 @@ def zstd_encode(payload):
327
327
  def zstd_decode(payload):
328
328
  if not zstd:
329
329
  raise NotImplementedError("Zstd codec is not available")
330
- try:
331
- return zstd.ZstdDecompressor().decompress(payload)
332
- except zstd.ZstdError:
333
- return zstd.ZstdDecompressor().decompress(payload, max_output_size=ZSTD_MAX_OUTPUT_SIZE)
330
+ with zstd.ZstdDecompressor().stream_reader(io.BytesIO(payload), read_across_frames=True) as reader:
331
+ return reader.read()
@@ -122,6 +122,9 @@ class BrokerConnection(object):
122
122
  max_in_flight_requests_per_connection (int): Requests are pipelined
123
123
  to kafka brokers up to this number of maximum requests per
124
124
  broker connection. Default: 5.
125
+ receive_message_max_bytes (int): Maximum allowed network frame size.
126
+ Used to avoid OOM when decoding malformed network message header.
127
+ Default: 1000000.
125
128
  receive_buffer_bytes (int): The size of the TCP receive buffer
126
129
  (SO_RCVBUF) to use when reading data. Default: None (relies on
127
130
  system defaults). Java client defaults to 32768.
@@ -202,6 +205,7 @@ class BrokerConnection(object):
202
205
  'reconnect_backoff_ms': 50,
203
206
  'reconnect_backoff_max_ms': 30000,
204
207
  'max_in_flight_requests_per_connection': 5,
208
+ 'receive_message_max_bytes': 1000000,
205
209
  'receive_buffer_bytes': None,
206
210
  'send_buffer_bytes': None,
207
211
  'socket_options': [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
@@ -292,7 +296,8 @@ class BrokerConnection(object):
292
296
 
293
297
  self._protocol = KafkaProtocol(
294
298
  client_id=self.config['client_id'],
295
- api_version=self.config['api_version'])
299
+ api_version=self.config['api_version'],
300
+ max_frame_size=self.config['receive_message_max_bytes'])
296
301
  self.state = ConnectionStates.DISCONNECTED
297
302
  self._reset_reconnect_backoff()
298
303
  self._sock = None
@@ -695,9 +700,12 @@ class BrokerConnection(object):
695
700
  'Kafka broker does not support %s sasl mechanism. Enabled mechanisms are: %s'
696
701
  % (self.config['sasl_mechanism'], response.enabled_mechanisms)))
697
702
  else:
698
- self._sasl_authenticate(future)
703
+ try:
704
+ ret = self._sasl_authenticate()
705
+ future.success(ret)
706
+ except Exception as exc:
707
+ future.failure(exc)
699
708
 
700
- assert future.is_done, 'SASL future not complete after mechanism processing!'
701
709
  if future.failed():
702
710
  self.close(error=future.exception)
703
711
  else:
@@ -809,24 +817,24 @@ class BrokerConnection(object):
809
817
  log.debug('%s: Received %d raw sasl auth bytes from server', self, nbytes)
810
818
  return data[4:]
811
819
 
812
- def _sasl_authenticate(self, future):
820
+ def _sasl_authenticate(self):
813
821
  while not self._sasl_mechanism.is_done():
814
822
  send_token = self._sasl_mechanism.auth_bytes()
815
823
  self._send_sasl_authenticate(send_token)
816
824
  if not self._can_send_recv():
817
- return future.failure(Errors.KafkaConnectionError("%s: Connection failure during Sasl Authenticate" % self))
825
+ raise Errors.KafkaConnectionError("%s: Connection failure during Sasl Authenticate" % self)
818
826
 
819
827
  recv_token = self._recv_sasl_authenticate()
820
828
  if recv_token is None:
821
- return future.failure(Errors.KafkaConnectionError("%s: Connection failure during Sasl Authenticate" % self))
829
+ raise Errors.KafkaConnectionError("%s: Connection failure during Sasl Authenticate" % self)
822
830
  else:
823
831
  self._sasl_mechanism.receive(recv_token)
824
832
 
825
833
  if self._sasl_mechanism.is_authenticated():
826
834
  log.info('%s: %s', self, self._sasl_mechanism.auth_details())
827
- return future.success(True)
835
+ return True
828
836
  else:
829
- return future.failure(Errors.SaslAuthenticationFailedError('Failed to authenticate via SASL %s' % self.config['sasl_mechanism']))
837
+ raise Errors.SaslAuthenticationFailedError('Failed to authenticate via SASL %s' % self.config['sasl_mechanism'])
830
838
 
831
839
  def blacked_out(self):
832
840
  """
@@ -250,16 +250,17 @@ class Fetcher(six.Iterator):
250
250
  break
251
251
 
252
252
  if future.succeeded():
253
- fetched_offsets.update(future.value[0])
254
- if not future.value[1]:
253
+ offsets, retry = future.value
254
+ fetched_offsets.update(offsets)
255
+ if not retry:
255
256
  return fetched_offsets
256
257
 
257
- timestamps = {tp: timestamps[tp] for tp in future.value[1]}
258
+ timestamps = {tp: timestamps[tp] for tp in retry}
258
259
 
259
260
  elif not future.retriable():
260
261
  raise future.exception # pylint: disable-msg=raising-bad-type
261
262
 
262
- if future.exception.invalid_metadata or self._client.cluster.need_update:
263
+ elif future.exception.invalid_metadata or self._client.cluster.need_update:
263
264
  refresh_future = self._client.cluster.request_update()
264
265
  self._client.poll(future=refresh_future, timeout_ms=timer.timeout_ms)
265
266
 
@@ -172,6 +172,9 @@ class KafkaConsumer(six.Iterator):
172
172
  should be set no higher than 1/3 of that value. It can be
173
173
  adjusted even lower to control the expected time for normal
174
174
  rebalances. Default: 3000
175
+ receive_message_max_bytes (int): Maximum allowed network frame size.
176
+ Used to avoid OOM when decoding malformed network message header.
177
+ Default: 1000000.
175
178
  receive_buffer_bytes (int): The size of the TCP receive buffer
176
179
  (SO_RCVBUF) to use when reading data. Default: None (relies on
177
180
  system defaults). The java client defaults to 32768.
@@ -313,9 +316,8 @@ class KafkaConsumer(six.Iterator):
313
316
  'heartbeat_interval_ms': 3000,
314
317
  'receive_buffer_bytes': None,
315
318
  'send_buffer_bytes': None,
319
+ 'receive_message_max_bytes': 1000000,
316
320
  'socket_options': [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
317
- 'sock_chunk_bytes': 4096, # undocumented experimental option
318
- 'sock_chunk_buffer_count': 1000, # undocumented experimental option
319
321
  'consumer_timeout_ms': float('inf'),
320
322
  'security_protocol': 'PLAINTEXT',
321
323
  'ssl_context': None,
@@ -457,7 +457,8 @@ class ConsumerCoordinator(BaseCoordinator):
457
457
  self._client.poll(future=future, timeout_ms=timer.timeout_ms)
458
458
 
459
459
  if future.is_done:
460
- del self._offset_fetch_futures[future_key]
460
+ if future_key in self._offset_fetch_futures:
461
+ del self._offset_fetch_futures[future_key]
461
462
 
462
463
  if future.succeeded():
463
464
  return future.value
@@ -60,6 +60,10 @@ class CorrelationIdError(KafkaProtocolError):
60
60
  retriable = True
61
61
 
62
62
 
63
+ class InvalidReceiveError(KafkaProtocolError):
64
+ pass
65
+
66
+
63
67
  class KafkaTimeoutError(KafkaError):
64
68
  retriable = True
65
69
 
@@ -37,6 +37,10 @@ class Future(object):
37
37
  self.is_done = True
38
38
  if self._callbacks:
39
39
  self._call_backs('callback', self._callbacks, self.value)
40
+ # Clearing the lists releases any reference cycle held via stored
41
+ # bound methods (e.g. FutureProduceResult<->FutureRecordMetadata).
42
+ self._callbacks = None
43
+ self._errbacks = None
40
44
  return self
41
45
 
42
46
  def failure(self, e):
@@ -48,6 +52,10 @@ class Future(object):
48
52
  self.exception = exception
49
53
  self.is_done = True
50
54
  self._call_backs('errback', self._errbacks, self.exception)
55
+ # Clearing the lists releases any reference cycle held via stored
56
+ # bound methods (e.g. FutureProduceResult<->FutureRecordMetadata).
57
+ self._callbacks = None
58
+ self._errbacks = None
51
59
  return self
52
60
 
53
61
  def add_callback(self, f, *args, **kwargs):
@@ -261,6 +261,9 @@ class KafkaProducer(object):
261
261
  errors. Default: 100.
262
262
  request_timeout_ms (int): Client request timeout in milliseconds.
263
263
  Default: 30000.
264
+ receive_message_max_bytes (int): Maximum allowed network frame size.
265
+ Used to avoid OOM when decoding malformed network message header.
266
+ Default: 1000000.
264
267
  receive_buffer_bytes (int): The size of the TCP receive buffer
265
268
  (SO_RCVBUF) to use when reading data. Default: None (relies on
266
269
  system defaults). Java client defaults to 32768.
@@ -392,11 +395,10 @@ class KafkaProducer(object):
392
395
  'metadata_max_age_ms': 300000,
393
396
  'retry_backoff_ms': 100,
394
397
  'request_timeout_ms': 30000,
398
+ 'receive_message_max_bytes': 1000000,
395
399
  'receive_buffer_bytes': None,
396
400
  'send_buffer_bytes': None,
397
401
  'socket_options': [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
398
- 'sock_chunk_bytes': 4096, # undocumented experimental option
399
- 'sock_chunk_buffer_count': 1000, # undocumented experimental option
400
402
  'reconnect_backoff_ms': 50,
401
403
  'reconnect_backoff_max_ms': 30000,
402
404
  'max_in_flight_requests_per_connection': 5,
@@ -23,12 +23,15 @@ class KafkaProtocol(object):
23
23
  api_version (tuple): Optional tuple to specify api_version to use.
24
24
  Currently only used to check for 0.8.2 protocol quirks, but
25
25
  may be used for more in the future.
26
+ max_frame_size (int): Maximum allowed message frame size.
27
+ Default: 100000000 (100MB).
26
28
  """
27
- def __init__(self, client_id=None, api_version=None):
29
+ def __init__(self, client_id=None, api_version=None, max_frame_size=100000000):
28
30
  if client_id is None:
29
31
  client_id = self._gen_client_id()
30
32
  self._client_id = client_id
31
33
  self._api_version = api_version
34
+ self._max_frame_size = max_frame_size
32
35
  self._correlation_id = 0
33
36
  self._header = KafkaBytes(4)
34
37
  self._rbuffer = None
@@ -105,6 +108,7 @@ class KafkaProtocol(object):
105
108
  if self._header.tell() == 4:
106
109
  self._header.seek(0)
107
110
  nbytes = Int32.decode(self._header)
111
+ self._validate_frame_size(nbytes)
108
112
  # reset buffer and switch state to receiving payload bytes
109
113
  self._rbuffer = KafkaBytes(nbytes)
110
114
  self._receiving = True
@@ -132,6 +136,10 @@ class KafkaProtocol(object):
132
136
  self._reset_buffer()
133
137
  return responses
134
138
 
139
+ def _validate_frame_size(self, nbytes):
140
+ if nbytes < 0 or nbytes > self._max_frame_size:
141
+ raise Errors.InvalidReceiveError('Invalid frame length: %d' % nbytes)
142
+
135
143
  def _process_response(self, read_buffer):
136
144
  if not self.in_flight_requests:
137
145
  raise Errors.CorrelationIdError('No in-flight-request found for server response')
@@ -213,6 +213,17 @@ class Array(AbstractType):
213
213
 
214
214
 
215
215
  class UnsignedVarInt32(AbstractType):
216
+ @classmethod
217
+ def decode(cls, data):
218
+ value = VarInt32.decode(data)
219
+ return (value << 1) ^ (value >> 31)
220
+
221
+ @classmethod
222
+ def encode(cls, value):
223
+ return VarInt32.encode((value >> 1) ^ -(value & 1))
224
+
225
+
226
+ class VarInt32(AbstractType):
216
227
  @classmethod
217
228
  def decode(cls, data):
218
229
  value, i = 0, 0
@@ -225,10 +236,12 @@ class UnsignedVarInt32(AbstractType):
225
236
  if i > 28:
226
237
  raise ValueError('Invalid value {}'.format(value))
227
238
  value |= b << i
228
- return value
239
+ return (value >> 1) ^ -(value & 1)
229
240
 
230
241
  @classmethod
231
242
  def encode(cls, value):
243
+ # bring it in line with the java binary repr
244
+ value = (value << 1) ^ (value >> 31)
232
245
  value &= 0xffffffff
233
246
  ret = b''
234
247
  while (value & 0xffffff80) != 0:
@@ -239,25 +252,12 @@ class UnsignedVarInt32(AbstractType):
239
252
  return ret
240
253
 
241
254
 
242
- class VarInt32(AbstractType):
243
- @classmethod
244
- def decode(cls, data):
245
- value = UnsignedVarInt32.decode(data)
246
- return (value >> 1) ^ -(value & 1)
247
-
248
- @classmethod
249
- def encode(cls, value):
250
- # bring it in line with the java binary repr
251
- value &= 0xffffffff
252
- return UnsignedVarInt32.encode((value << 1) ^ (value >> 31))
253
-
254
-
255
255
  class VarInt64(AbstractType):
256
256
  @classmethod
257
257
  def decode(cls, data):
258
258
  value, i = 0, 0
259
259
  while True:
260
- b = data.read(1)
260
+ b, = struct.unpack('B', data.read(1))
261
261
  if not (b & 0x80):
262
262
  break
263
263
  value |= (b & 0x7f) << i
@@ -270,14 +270,14 @@ class VarInt64(AbstractType):
270
270
  @classmethod
271
271
  def encode(cls, value):
272
272
  # bring it in line with the java binary repr
273
+ value = (value << 1) ^ (value >> 63)
273
274
  value &= 0xffffffffffffffff
274
- v = (value << 1) ^ (value >> 63)
275
275
  ret = b''
276
- while (v & 0xffffffffffffff80) != 0:
276
+ while (value & 0xffffffffffffff80) != 0:
277
277
  b = (value & 0x7f) | 0x80
278
278
  ret += struct.pack('B', b)
279
- v >>= 7
280
- ret += struct.pack('B', v)
279
+ value >>= 7
280
+ ret += struct.pack('B', value)
281
281
  return ret
282
282
 
283
283
 
@@ -322,8 +322,9 @@ class TaggedFields(AbstractType):
322
322
  for k, v in value.items():
323
323
  # do we allow for other data types ?? It could get complicated really fast
324
324
  assert isinstance(v, bytes), 'Value {} is not a byte array'.format(v)
325
- assert isinstance(k, int) and k > 0, 'Key {} is not a positive integer'.format(k)
325
+ assert isinstance(k, int) and k >= 0, 'Key {} is not a non-negative integer'.format(k)
326
326
  ret += UnsignedVarInt32.encode(k)
327
+ ret += UnsignedVarInt32.encode(len(v))
327
328
  ret += v
328
329
  return ret
329
330
 
@@ -106,7 +106,12 @@ class ScramClient:
106
106
  self.auth_message += b',c=biws,r=' + self.nonce
107
107
 
108
108
  salt = base64.b64decode(params['s'].encode('utf-8'))
109
- iterations = int(params['i'])
109
+ try:
110
+ iterations = int(params['i'])
111
+ if iterations > 1000000:
112
+ raise ValueError('too many iterations')
113
+ except (TypeError, ValueError):
114
+ raise ValueError('Invalid value (not integer or too large) for Iteration count in server-first-message')
110
115
  self.create_salted_password(salt, iterations)
111
116
 
112
117
  self.client_key = self.hmac(self.salted_password, b'Client Key')
@@ -0,0 +1 @@
1
+ __version__ = '2.3.2'
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: kafka-python
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: Pure Python client for Apache Kafka
5
5
  Author-email: Dana Powers <dana.powers@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/dpkp/kafka-python
@@ -66,22 +66,11 @@ Python client for the Apache Kafka distributed stream processing system.
66
66
  kafka-python is designed to function much like the official java client, with a
67
67
  sprinkling of pythonic interfaces (e.g., consumer iterators).
68
68
 
69
- kafka-python is best used with newer brokers (0.9+), but is backwards-compatible with
70
- older versions (to 0.8.0). Some features will only be enabled on newer brokers.
71
- For example, fully coordinated consumer groups -- i.e., dynamic partition
72
- assignment to multiple consumers in the same group -- requires use of 0.9+ kafka
73
- brokers. Supporting this feature for earlier broker releases would require
74
- writing and maintaining custom leadership election and membership / health
75
- check code (perhaps using zookeeper or consul). For older brokers, you can
76
- achieve something similar by manually assigning different partitions to each
77
- consumer instance with config management tools like chef, ansible, etc. This
78
- approach will work fine, though it does not support rebalancing on failures.
79
- See https://kafka-python.readthedocs.io/en/master/compatibility.html
80
- for more details.
81
-
82
69
  Please note that the master branch may contain unreleased features. For release
83
70
  documentation, please see readthedocs and/or python's inline help.
84
71
 
72
+ New in 2.3 release: python -m kafka.* interfaces for quick scripts and testing.
73
+
85
74
  .. code-block:: bash
86
75
 
87
76
  $ pip install kafka-python
@@ -232,6 +221,14 @@ for more details.
232
221
  metrics = producer.metrics()
233
222
 
234
223
 
224
+ Module CLI Interface
225
+ ********************
226
+
227
+ kafka-python also provides simple command-line interfaces for consumer, producer, and admin clients.
228
+ Access via ``python -m kafka.consumer``, ``python -m kafka.producer``, and ``python -m kafka.admin``.
229
+ See https://kafka-python.readthedocs.io/en/master/usage.html for more details.
230
+
231
+
235
232
  Thread safety
236
233
  *************
237
234
 
@@ -175,7 +175,6 @@ test/test_partition_movements.py
175
175
  test/test_partitioner.py
176
176
  test/test_producer.py
177
177
  test/test_producer_batch.py
178
- test/test_protocol.py
179
178
  test/test_record_accumulator.py
180
179
  test/test_sender.py
181
180
  test/test_subscription_state.py
@@ -93,7 +93,7 @@ def test_create_describe_delete_acls(kafka_admin_client):
93
93
  def test_describe_configs_broker_resource_returns_configs(kafka_admin_client):
94
94
  """Tests that describe config returns configs for broker
95
95
  """
96
- broker_id = kafka_admin_client._client.cluster._brokers[0].nodeId
96
+ broker_id = kafka_admin_client._client.least_loaded_node()
97
97
  configs = kafka_admin_client.describe_configs([ConfigResource(ConfigResourceType.BROKER, broker_id)])
98
98
 
99
99
  assert len(configs) == 1
@@ -121,7 +121,7 @@ def test_describe_configs_topic_resource_returns_configs(topic, kafka_admin_clie
121
121
  def test_describe_configs_mixed_resources_returns_configs(topic, kafka_admin_client):
122
122
  """Tests that describe config returns configs for mixed resource types (topic + broker)
123
123
  """
124
- broker_id = kafka_admin_client._client.cluster._brokers[0].nodeId
124
+ broker_id = kafka_admin_client._client.least_loaded_node()
125
125
  configs = kafka_admin_client.describe_configs([
126
126
  ConfigResource(ConfigResourceType.TOPIC, topic),
127
127
  ConfigResource(ConfigResourceType.BROKER, broker_id)])
@@ -146,14 +146,6 @@ def test_describe_configs_invalid_broker_id_raises(kafka_admin_client):
146
146
  kafka_admin_client.describe_configs([ConfigResource(ConfigResourceType.BROKER, broker_id)])
147
147
 
148
148
 
149
- @pytest.mark.skipif(env_kafka_version() < (0, 11), reason='Describe consumer group requires broker >=0.11')
150
- def test_describe_consumer_group_does_not_exist(kafka_admin_client):
151
- """Tests that the describe consumer group call fails if the group coordinator is not available
152
- """
153
- with pytest.raises(CoordinatorNotAvailableError):
154
- kafka_admin_client.describe_consumer_groups(['test'])
155
-
156
-
157
149
  @pytest.mark.skipif(env_kafka_version() < (0, 11), reason='Describe consumer group requires broker >=0.11')
158
150
  def test_describe_consumer_group_exists(kafka_admin_client, kafka_consumer_factory, topic):
159
151
  """Tests that the describe consumer group call returns valid consumer group information
@@ -323,4 +323,4 @@ def test_kafka_consumer_position_after_seek_to_end(kafka_consumer_factory, topic
323
323
  position = consumer.position(tp, timeout_ms=1000)
324
324
 
325
325
  # Verify we got the expected position
326
- assert position == 10, f"Expected position 10, got {position}"
326
+ assert position == 10, "Expected position 10, got {}".format(position)
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  import uuid
3
4
  import time
4
5
 
@@ -8,6 +9,7 @@ from kafka.admin import NewTopic
8
9
  from kafka.protocol.metadata import MetadataRequest_v1
9
10
  from test.testutil import assert_message_count, env_kafka_version, random_string, special_to_underscore
10
11
 
12
+ pytestmark = pytest.mark.skipif("KAFKA_URI" in os.environ, reason="Testing on external Kafka Broker")
11
13
 
12
14
  @pytest.fixture(
13
15
  params=[
@@ -124,3 +124,14 @@ def test_zstd():
124
124
  b1 = random_string(100).encode('utf-8')
125
125
  b2 = zstd_decode(zstd_encode(b1))
126
126
  assert b1 == b2
127
+
128
+
129
+ @pytest.mark.skipif(not has_zstd(), reason="Zstd not available")
130
+ def test_zstd_multi_frame():
131
+ """Test that zstd_decode handles multiple concatenated zstd frames."""
132
+ frame1_data = b'some payload data ' * 100
133
+ frame2_data = b'another frame of data ' * 100
134
+ # Concatenate two independently compressed zstd frames
135
+ multi_frame_payload = zstd_encode(frame1_data) + zstd_encode(frame2_data)
136
+ result = zstd_decode(multi_frame_payload)
137
+ assert result == frame1_data + frame2_data