kafka-python 2.2.18__tar.gz → 2.2.19__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.19}/CHANGES.md +11 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/PKG-INFO +1 -1
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/codec.py +2 -4
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/consumer/fetcher.py +5 -4
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/consumer.py +2 -1
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/types.py +21 -20
- kafka_python-2.2.19/kafka/version.py +1 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka_python.egg-info/PKG-INFO +1 -1
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka_python.egg-info/SOURCES.txt +0 -1
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/test_admin_integration.py +2 -10
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/test_consumer_integration.py +1 -1
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/test_sasl_integration.py +2 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_codec.py +11 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/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.19}/AUTHORS.md +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/LICENSE +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/MANIFEST.in +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/README.rst +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/admin/client.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/benchmarks/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/benchmarks/consumer_performance.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/benchmarks/load_example.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/benchmarks/producer_performance.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/benchmarks/record_batch_compose.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/benchmarks/record_batch_read.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/benchmarks/varint_speed.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/client_async.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/cluster.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/conn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/consumer/group.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/consumer/subscription_state.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/base.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/errors.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/future.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/metric_name.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/metrics.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/quota.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/producer/future.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/producer/kafka.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/producer/record_accumulator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/producer/sender.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/producer/transaction_manager.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/add_offsets_to_txn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/add_partitions_to_txn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/admin.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/api.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/api_versions.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/broker_api_versions.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/commit.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/end_txn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/fetch.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/find_coordinator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/group.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/init_producer_id.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/list_offsets.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/message.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/metadata.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/offset_for_leader_epoch.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/parser.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/produce.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/sasl_authenticate.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/sasl_handshake.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/protocol/txn_offset_commit.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/record/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/record/abc.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/record/default_records.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/record/legacy_records.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/record/memory_records.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/record/util.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/abc.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/gssapi.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/msk.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/oauth.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/plain.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/scram.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/sasl/sspi.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/socks5_wrapper.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/structs.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/util.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/vendor/six.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka_python.egg-info/requires.txt +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/kafka_python.egg-info/top_level.txt +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/pyproject.toml +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/setup.cfg +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/setup.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/__init__.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/conftest.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/fixtures.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/test_consumer_group.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/integration/test_producer_integration.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_admin.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_assignors.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_client_async.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_cluster.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_conn.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_consumer.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_coordinator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_metrics.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_object_conversion.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_package.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_partition_movements.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_partitioner.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_producer.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_record_accumulator.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_sender.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_subscription_state.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/test_util.py +0 -0
- {kafka_python-2.2.18 → kafka_python-2.2.19}/test/testutil.py +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
# 2.2.19 (Apr 9, 2026)
|
|
2
|
+
|
|
3
|
+
Fixes
|
|
4
|
+
* Fix TaggedFields value encoding; add test coverage (#2725)
|
|
5
|
+
* Fix zstd multi-frame decompression failure (#2717)
|
|
6
|
+
* Fix KeyError in KafkaConsumer.committed() (#2710)
|
|
7
|
+
* Fix VarInt/VarLong encoding; move tests to test/protocol/ (#2706)
|
|
8
|
+
* Fix `Fetcher._fetch_offsets_by_times retry handling` (#2833)
|
|
9
|
+
* Fixes to support integration testing with external KAFKA_URI (#2838)
|
|
10
|
+
* Minor py2 fixes: consumer integration test; done pip install python-snappy
|
|
11
|
+
|
|
1
12
|
# 2.2.18 (Nov 20, 2025)
|
|
2
13
|
|
|
3
14
|
Fixes
|
|
@@ -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()
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.2.19'
|
|
@@ -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
|
|
@@ -770,3 +770,190 @@ def test_seek_before_exception(client, mocker):
|
|
|
770
770
|
# Should not throw OffsetOutOfRangeError after the seek
|
|
771
771
|
records, partial = fetcher.fetched_records()
|
|
772
772
|
assert len(records) == 0
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
class TestFetchOffsetsByTimes:
|
|
776
|
+
def _make_fetcher(self, client, mocker):
|
|
777
|
+
subscription_state = SubscriptionState()
|
|
778
|
+
subscription_state.subscribe(topics=['test'])
|
|
779
|
+
tp = TopicPartition('test', 0)
|
|
780
|
+
subscription_state.assign_from_subscribed([tp])
|
|
781
|
+
subscription_state.seek(tp, 0)
|
|
782
|
+
return Fetcher(client, subscription_state)
|
|
783
|
+
|
|
784
|
+
def test_empty_timestamps(self, client, metrics, mocker):
|
|
785
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
786
|
+
assert fetcher._fetch_offsets_by_times({}) == {}
|
|
787
|
+
|
|
788
|
+
def test_success_no_retry(self, client, mocker):
|
|
789
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
790
|
+
tp = TopicPartition('test', 0)
|
|
791
|
+
timestamps = {tp: 1000}
|
|
792
|
+
expected_offset = OffsetAndTimestamp(10, 1000, -1)
|
|
793
|
+
|
|
794
|
+
future = Future()
|
|
795
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', return_value=future)
|
|
796
|
+
mocker.patch.object(fetcher._client, 'poll', side_effect=lambda **kw: future.success(({tp: expected_offset}, set())))
|
|
797
|
+
|
|
798
|
+
result = fetcher._fetch_offsets_by_times(timestamps, timeout_ms=10000)
|
|
799
|
+
assert result == {tp: expected_offset}
|
|
800
|
+
|
|
801
|
+
def test_success_with_retry(self, client, mocker):
|
|
802
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
803
|
+
tp0 = TopicPartition('test', 0)
|
|
804
|
+
tp1 = TopicPartition('test', 1)
|
|
805
|
+
timestamps = {tp0: 1000, tp1: 2000}
|
|
806
|
+
offset0 = OffsetAndTimestamp(10, 1000, -1)
|
|
807
|
+
offset1 = OffsetAndTimestamp(20, 2000, -1)
|
|
808
|
+
|
|
809
|
+
# First call succeeds for tp0 but needs retry for tp1
|
|
810
|
+
future1 = Future()
|
|
811
|
+
future2 = Future()
|
|
812
|
+
futures = iter([future1, future2])
|
|
813
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', side_effect=lambda ts: next(futures))
|
|
814
|
+
|
|
815
|
+
def poll_side_effect(**kw):
|
|
816
|
+
f = kw.get('future')
|
|
817
|
+
if f is future1:
|
|
818
|
+
f.success(({tp0: offset0}, {tp1}))
|
|
819
|
+
elif f is future2:
|
|
820
|
+
f.success(({tp1: offset1}, set()))
|
|
821
|
+
|
|
822
|
+
mocker.patch.object(fetcher._client, 'poll', side_effect=poll_side_effect)
|
|
823
|
+
|
|
824
|
+
result = fetcher._fetch_offsets_by_times(timestamps, timeout_ms=10000)
|
|
825
|
+
assert result == {tp0: offset0, tp1: offset1}
|
|
826
|
+
|
|
827
|
+
def test_timeout_raises(self, client, mocker):
|
|
828
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
829
|
+
tp = TopicPartition('test', 0)
|
|
830
|
+
timestamps = {tp: 1000}
|
|
831
|
+
|
|
832
|
+
future = Future()
|
|
833
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', return_value=future)
|
|
834
|
+
# poll does not complete the future
|
|
835
|
+
mocker.patch.object(fetcher._client, 'poll')
|
|
836
|
+
|
|
837
|
+
with pytest.raises(Errors.KafkaTimeoutError):
|
|
838
|
+
fetcher._fetch_offsets_by_times(timestamps, timeout_ms=10000)
|
|
839
|
+
|
|
840
|
+
def test_non_retriable_error_raises(self, client, mocker):
|
|
841
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
842
|
+
tp = TopicPartition('test', 0)
|
|
843
|
+
timestamps = {tp: 1000}
|
|
844
|
+
|
|
845
|
+
future = Future()
|
|
846
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', return_value=future)
|
|
847
|
+
# AuthorizationError is not retriable
|
|
848
|
+
error = Errors.TopicAuthorizationFailedError()
|
|
849
|
+
mocker.patch.object(fetcher._client, 'poll', side_effect=lambda **kw: future.failure(error))
|
|
850
|
+
|
|
851
|
+
with pytest.raises(Errors.TopicAuthorizationFailedError):
|
|
852
|
+
fetcher._fetch_offsets_by_times(timestamps, timeout_ms=10000)
|
|
853
|
+
|
|
854
|
+
def test_retriable_invalid_metadata_triggers_refresh(self, client, mocker):
|
|
855
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
856
|
+
tp = TopicPartition('test', 0)
|
|
857
|
+
timestamps = {tp: 1000}
|
|
858
|
+
expected_offset = OffsetAndTimestamp(10, 1000, -1)
|
|
859
|
+
|
|
860
|
+
# First call fails with invalid_metadata error, second succeeds
|
|
861
|
+
future1 = Future()
|
|
862
|
+
future2 = Future()
|
|
863
|
+
futures = iter([future1, future2])
|
|
864
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', side_effect=lambda ts: next(futures))
|
|
865
|
+
|
|
866
|
+
refresh_future = Future()
|
|
867
|
+
mocker.patch.object(fetcher._client.cluster, 'request_update', return_value=refresh_future)
|
|
868
|
+
|
|
869
|
+
call_count = [0]
|
|
870
|
+
def poll_side_effect(**kw):
|
|
871
|
+
f = kw.get('future')
|
|
872
|
+
if f is future1:
|
|
873
|
+
f.failure(NotLeaderForPartitionError())
|
|
874
|
+
elif f is refresh_future:
|
|
875
|
+
refresh_future.success(None)
|
|
876
|
+
elif f is future2:
|
|
877
|
+
f.success(({tp: expected_offset}, set()))
|
|
878
|
+
call_count[0] += 1
|
|
879
|
+
|
|
880
|
+
mocker.patch.object(fetcher._client, 'poll', side_effect=poll_side_effect)
|
|
881
|
+
|
|
882
|
+
result = fetcher._fetch_offsets_by_times(timestamps, timeout_ms=10000)
|
|
883
|
+
assert result == {tp: expected_offset}
|
|
884
|
+
fetcher._client.cluster.request_update.assert_called_once()
|
|
885
|
+
|
|
886
|
+
def test_retriable_non_metadata_error_sleeps(self, client, mocker):
|
|
887
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
888
|
+
tp = TopicPartition('test', 0)
|
|
889
|
+
timestamps = {tp: 1000}
|
|
890
|
+
expected_offset = OffsetAndTimestamp(10, 1000, -1)
|
|
891
|
+
|
|
892
|
+
# RequestTimedOutError is retriable but not invalid_metadata
|
|
893
|
+
future1 = Future()
|
|
894
|
+
future2 = Future()
|
|
895
|
+
futures = iter([future1, future2])
|
|
896
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', side_effect=lambda ts: next(futures))
|
|
897
|
+
|
|
898
|
+
# Ensure cluster does not need update
|
|
899
|
+
mocker.patch.object(type(fetcher._client.cluster), 'need_update', new_callable=mocker.PropertyMock, return_value=False)
|
|
900
|
+
|
|
901
|
+
def poll_side_effect(**kw):
|
|
902
|
+
f = kw.get('future')
|
|
903
|
+
if f is future1:
|
|
904
|
+
f.failure(Errors.RequestTimedOutError())
|
|
905
|
+
elif f is future2:
|
|
906
|
+
f.success(({tp: expected_offset}, set()))
|
|
907
|
+
|
|
908
|
+
mocker.patch.object(fetcher._client, 'poll', side_effect=poll_side_effect)
|
|
909
|
+
mock_sleep = mocker.patch('time.sleep')
|
|
910
|
+
|
|
911
|
+
result = fetcher._fetch_offsets_by_times(timestamps, timeout_ms=10000)
|
|
912
|
+
assert result == {tp: expected_offset}
|
|
913
|
+
mock_sleep.assert_called_once()
|
|
914
|
+
|
|
915
|
+
def test_success_does_not_check_exception(self, client, mocker):
|
|
916
|
+
"""Regression: successful future should not fall through to check future.exception."""
|
|
917
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
918
|
+
tp0 = TopicPartition('test', 0)
|
|
919
|
+
tp1 = TopicPartition('test', 1)
|
|
920
|
+
timestamps = {tp0: 1000, tp1: 2000}
|
|
921
|
+
offset0 = OffsetAndTimestamp(10, 1000, -1)
|
|
922
|
+
offset1 = OffsetAndTimestamp(20, 2000, -1)
|
|
923
|
+
|
|
924
|
+
future1 = Future()
|
|
925
|
+
future2 = Future()
|
|
926
|
+
futures = iter([future1, future2])
|
|
927
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', side_effect=lambda ts: next(futures))
|
|
928
|
+
|
|
929
|
+
def poll_side_effect(**kw):
|
|
930
|
+
f = kw.get('future')
|
|
931
|
+
if f is future1:
|
|
932
|
+
# Succeeds but has retry partitions -- the bug was that code
|
|
933
|
+
# would fall through to check future.exception (which is None),
|
|
934
|
+
# causing an AttributeError
|
|
935
|
+
f.success(({tp0: offset0}, {tp1}))
|
|
936
|
+
elif f is future2:
|
|
937
|
+
f.success(({tp1: offset1}, set()))
|
|
938
|
+
|
|
939
|
+
mocker.patch.object(fetcher._client, 'poll', side_effect=poll_side_effect)
|
|
940
|
+
|
|
941
|
+
# Should not raise AttributeError
|
|
942
|
+
result = fetcher._fetch_offsets_by_times(timestamps, timeout_ms=10000)
|
|
943
|
+
assert result == {tp0: offset0, tp1: offset1}
|
|
944
|
+
|
|
945
|
+
def test_no_timeout_passes_none(self, client, mocker):
|
|
946
|
+
fetcher = self._make_fetcher(client, mocker)
|
|
947
|
+
tp = TopicPartition('test', 0)
|
|
948
|
+
timestamps = {tp: 1000}
|
|
949
|
+
expected_offset = OffsetAndTimestamp(10, 1000, -1)
|
|
950
|
+
|
|
951
|
+
future = Future()
|
|
952
|
+
mocker.patch.object(fetcher, '_send_list_offsets_requests', return_value=future)
|
|
953
|
+
mocker.patch.object(fetcher._client, 'poll', side_effect=lambda **kw: future.success(({tp: expected_offset}, set())))
|
|
954
|
+
|
|
955
|
+
result = fetcher._fetch_offsets_by_times(timestamps, timeout_ms=None)
|
|
956
|
+
assert result == {tp: expected_offset}
|
|
957
|
+
# With timeout_ms=None, poll should receive None timeout
|
|
958
|
+
fetcher._client.poll.assert_called_once()
|
|
959
|
+
assert fetcher._client.poll.call_args[1]['timeout_ms'] is None
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.2.18'
|