kafka-python 2.2.18__tar.gz → 2.2.20__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.
- {kafka_python-2.2.18 → kafka_python-2.2.20}/CHANGES.md +19 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/PKG-INFO +2 -2
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/admin/client.py +4 -2
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/client_async.py +4 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/codec.py +2 -4
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/conn.py +16 -8
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/consumer/fetcher.py +5 -4
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/consumer/group.py +4 -2
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/consumer.py +2 -1
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/errors.py +4 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/future.py +8 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/producer/kafka.py +4 -2
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/parser.py +9 -1
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/types.py +21 -20
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/scram.py +6 -1
- kafka_python-2.2.20/kafka/version.py +1 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka_python.egg-info/PKG-INFO +2 -2
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka_python.egg-info/SOURCES.txt +0 -1
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/test_admin_integration.py +2 -10
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/test_consumer_integration.py +1 -1
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/test_sasl_integration.py +2 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_codec.py +11 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_fetcher.py +187 -0
- kafka_python-2.2.18/kafka/version.py +0 -1
- kafka_python-2.2.18/test/test_protocol.py +0 -334
- {kafka_python-2.2.18 → kafka_python-2.2.20}/AUTHORS.md +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/LICENSE +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/MANIFEST.in +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/README.rst +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/benchmarks/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/benchmarks/consumer_performance.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/benchmarks/load_example.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/benchmarks/producer_performance.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/benchmarks/record_batch_compose.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/benchmarks/record_batch_read.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/benchmarks/varint_speed.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/cluster.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/consumer/subscription_state.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/base.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/metric_name.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/metrics.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/quota.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/producer/future.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/producer/record_accumulator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/producer/sender.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/producer/transaction_manager.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/add_offsets_to_txn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/add_partitions_to_txn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/admin.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/api.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/api_versions.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/broker_api_versions.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/commit.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/end_txn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/fetch.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/find_coordinator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/group.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/init_producer_id.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/list_offsets.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/message.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/metadata.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/offset_for_leader_epoch.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/produce.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/sasl_authenticate.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/sasl_handshake.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/protocol/txn_offset_commit.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/record/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/record/abc.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/record/default_records.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/record/legacy_records.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/record/memory_records.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/record/util.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/abc.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/gssapi.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/msk.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/oauth.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/plain.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/sasl/sspi.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/socks5_wrapper.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/structs.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/util.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/vendor/six.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka_python.egg-info/requires.txt +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/kafka_python.egg-info/top_level.txt +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/pyproject.toml +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/setup.cfg +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/setup.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/conftest.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/fixtures.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/test_consumer_group.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/integration/test_producer_integration.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_admin.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_assignors.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_client_async.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_cluster.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_conn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_consumer.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_coordinator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_metrics.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_object_conversion.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_package.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_partition_movements.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_partitioner.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_producer.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_record_accumulator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_sender.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_subscription_state.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/test_util.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.20}/test/testutil.py +0 -0
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# 2.2.20 (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.2.19 (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; done pip install python-snappy
|
|
19
|
+
|
|
1
20
|
# 2.2.18 (Nov 20, 2025)
|
|
2
21
|
|
|
3
22
|
Fixes
|
|
@@ -87,6 +87,9 @@ class KafkaAdminClient(object):
|
|
|
87
87
|
max_in_flight_requests_per_connection (int): Requests are pipelined
|
|
88
88
|
to kafka brokers up to this number of maximum requests per
|
|
89
89
|
broker connection. Default: 5.
|
|
90
|
+
receive_message_max_bytes (int): Maximum allowed network frame size.
|
|
91
|
+
Used to avoid OOM when decoding malformed network message header.
|
|
92
|
+
Default: 1000000.
|
|
90
93
|
receive_buffer_bytes (int): The size of the TCP receive buffer
|
|
91
94
|
(SO_RCVBUF) to use when reading data. Default: None (relies on
|
|
92
95
|
system defaults). Java client defaults to 32768.
|
|
@@ -163,11 +166,10 @@ class KafkaAdminClient(object):
|
|
|
163
166
|
'reconnect_backoff_ms': 50,
|
|
164
167
|
'reconnect_backoff_max_ms': 30000,
|
|
165
168
|
'max_in_flight_requests_per_connection': 5,
|
|
169
|
+
'receive_message_max_bytes': 1000000,
|
|
166
170
|
'receive_buffer_bytes': None,
|
|
167
171
|
'send_buffer_bytes': None,
|
|
168
172
|
'socket_options': [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
|
|
169
|
-
'sock_chunk_bytes': 4096, # undocumented experimental option
|
|
170
|
-
'sock_chunk_buffer_count': 1000, # undocumented experimental option
|
|
171
173
|
'retry_backoff_ms': 100,
|
|
172
174
|
'metadata_max_age_ms': 300000,
|
|
173
175
|
'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
|
-
|
|
331
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
835
|
+
return True
|
|
828
836
|
else:
|
|
829
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -163,6 +163,9 @@ class KafkaConsumer(six.Iterator):
|
|
|
163
163
|
should be set no higher than 1/3 of that value. It can be
|
|
164
164
|
adjusted even lower to control the expected time for normal
|
|
165
165
|
rebalances. Default: 3000
|
|
166
|
+
receive_message_max_bytes (int): Maximum allowed network frame size.
|
|
167
|
+
Used to avoid OOM when decoding malformed network message header.
|
|
168
|
+
Default: 1000000.
|
|
166
169
|
receive_buffer_bytes (int): The size of the TCP receive buffer
|
|
167
170
|
(SO_RCVBUF) to use when reading data. Default: None (relies on
|
|
168
171
|
system defaults). The java client defaults to 32768.
|
|
@@ -303,9 +306,8 @@ class KafkaConsumer(six.Iterator):
|
|
|
303
306
|
'heartbeat_interval_ms': 3000,
|
|
304
307
|
'receive_buffer_bytes': None,
|
|
305
308
|
'send_buffer_bytes': None,
|
|
309
|
+
'receive_message_max_bytes': 1000000,
|
|
306
310
|
'socket_options': [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
|
|
307
|
-
'sock_chunk_bytes': 4096, # undocumented experimental option
|
|
308
|
-
'sock_chunk_buffer_count': 1000, # undocumented experimental option
|
|
309
311
|
'consumer_timeout_ms': float('inf'),
|
|
310
312
|
'security_protocol': 'PLAINTEXT',
|
|
311
313
|
'ssl_context': None,
|
|
@@ -443,7 +443,8 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
443
443
|
self._client.poll(future=future, timeout_ms=timer.timeout_ms)
|
|
444
444
|
|
|
445
445
|
if future.is_done:
|
|
446
|
-
|
|
446
|
+
if future_key in self._offset_fetch_futures:
|
|
447
|
+
del self._offset_fetch_futures[future_key]
|
|
447
448
|
|
|
448
449
|
if future.succeeded():
|
|
449
450
|
return future.value
|
|
@@ -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 (
|
|
276
|
+
while (value & 0xffffffffffffff80) != 0:
|
|
277
277
|
b = (value & 0x7f) | 0x80
|
|
278
278
|
ret += struct.pack('B', b)
|
|
279
|
-
|
|
280
|
-
ret += struct.pack('B',
|
|
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
|
|
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
|
-
|
|
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.2.20'
|
|
@@ -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.
|
|
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.
|
|
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,
|
|
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
|