kafka-python 2.1.0__py2.py3-none-any.whl → 2.1.2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kafka/conn.py +5 -1
- kafka/consumer/fetcher.py +156 -211
- kafka/consumer/group.py +9 -13
- kafka/consumer/subscription_state.py +1 -7
- kafka/coordinator/assignors/sticky/sticky_assignor.py +1 -1
- kafka/future.py +24 -13
- kafka/metrics/compound_stat.py +2 -2
- kafka/metrics/measurable_stat.py +2 -1
- kafka/metrics/metrics_reporter.py +3 -2
- kafka/metrics/stat.py +3 -2
- kafka/metrics/stats/sampled_stat.py +2 -2
- kafka/protocol/abstract.py +3 -2
- kafka/protocol/api.py +4 -3
- kafka/record/abc.py +22 -4
- kafka/record/default_records.py +17 -9
- kafka/record/legacy_records.py +33 -10
- kafka/record/memory_records.py +6 -2
- kafka/sasl/abc.py +3 -2
- kafka/version.py +1 -1
- {kafka_python-2.1.0.dist-info → kafka_python-2.1.2.dist-info}/METADATA +2 -1
- {kafka_python-2.1.0.dist-info → kafka_python-2.1.2.dist-info}/RECORD +23 -23
- {kafka_python-2.1.0.dist-info → kafka_python-2.1.2.dist-info}/WHEEL +1 -1
- {kafka_python-2.1.0.dist-info → kafka_python-2.1.2.dist-info}/top_level.txt +0 -0
kafka/conn.py
CHANGED
|
@@ -531,6 +531,9 @@ class BrokerConnection(object):
|
|
|
531
531
|
if self._api_versions_future is None:
|
|
532
532
|
if self.config['api_version'] is not None:
|
|
533
533
|
self._api_version = self.config['api_version']
|
|
534
|
+
# api_version will be normalized by KafkaClient, so this should not happen
|
|
535
|
+
if self._api_version not in BROKER_API_VERSIONS:
|
|
536
|
+
raise Errors.UnrecognizedBrokerVersion('api_version %s not found in kafka.protocol.broker_api_versions' % (self._api_version,))
|
|
534
537
|
self._api_versions = BROKER_API_VERSIONS[self._api_version]
|
|
535
538
|
log.debug('%s: Using pre-configured api_version %s for ApiVersions', self, self._api_version)
|
|
536
539
|
return True
|
|
@@ -553,7 +556,8 @@ class BrokerConnection(object):
|
|
|
553
556
|
self.state = ConnectionStates.API_VERSIONS_RECV
|
|
554
557
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
555
558
|
else:
|
|
556
|
-
|
|
559
|
+
self.close(Errors.KafkaConnectionError('Unable to determine broker version.'))
|
|
560
|
+
return False
|
|
557
561
|
|
|
558
562
|
for r, f in self.recv():
|
|
559
563
|
f.success(r)
|
kafka/consumer/fetcher.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import absolute_import, division
|
|
|
2
2
|
|
|
3
3
|
import collections
|
|
4
4
|
import copy
|
|
5
|
+
import itertools
|
|
5
6
|
import logging
|
|
6
7
|
import random
|
|
7
8
|
import sys
|
|
@@ -363,176 +364,50 @@ class Fetcher(six.Iterator):
|
|
|
363
364
|
return 0
|
|
364
365
|
|
|
365
366
|
tp = part.topic_partition
|
|
366
|
-
fetch_offset = part.fetch_offset
|
|
367
367
|
if not self._subscriptions.is_assigned(tp):
|
|
368
368
|
# this can happen when a rebalance happened before
|
|
369
369
|
# fetched records are returned to the consumer's poll call
|
|
370
370
|
log.debug("Not returning fetched records for partition %s"
|
|
371
371
|
" since it is no longer assigned", tp)
|
|
372
|
+
elif not self._subscriptions.is_fetchable(tp):
|
|
373
|
+
# this can happen when a partition is paused before
|
|
374
|
+
# fetched records are returned to the consumer's poll call
|
|
375
|
+
log.debug("Not returning fetched records for assigned partition"
|
|
376
|
+
" %s since it is no longer fetchable", tp)
|
|
377
|
+
|
|
372
378
|
else:
|
|
373
379
|
# note that the position should always be available
|
|
374
380
|
# as long as the partition is still assigned
|
|
375
381
|
position = self._subscriptions.assignment[tp].position
|
|
376
|
-
if
|
|
377
|
-
# this can happen when a partition is paused before
|
|
378
|
-
# fetched records are returned to the consumer's poll call
|
|
379
|
-
log.debug("Not returning fetched records for assigned partition"
|
|
380
|
-
" %s since it is no longer fetchable", tp)
|
|
381
|
-
|
|
382
|
-
elif fetch_offset == position.offset:
|
|
383
|
-
# we are ensured to have at least one record since we already checked for emptiness
|
|
382
|
+
if part.next_fetch_offset == position.offset:
|
|
384
383
|
part_records = part.take(max_records)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if update_offsets:
|
|
384
|
+
log.debug("Returning fetched records at offset %d for assigned"
|
|
385
|
+
" partition %s", position.offset, tp)
|
|
386
|
+
drained[tp].extend(part_records)
|
|
387
|
+
# We want to increment subscription position if (1) we're using consumer.poll(),
|
|
388
|
+
# or (2) we didn't return any records (consumer iterator will update position
|
|
389
|
+
# when each message is yielded). There may be edge cases where we re-fetch records
|
|
390
|
+
# that we'll end up skipping, but for now we'll live with that.
|
|
391
|
+
highwater = self._subscriptions.assignment[tp].highwater
|
|
392
|
+
if highwater is not None:
|
|
393
|
+
self._sensors.records_fetch_lag.record(highwater - part.next_fetch_offset)
|
|
394
|
+
if update_offsets or not part_records:
|
|
396
395
|
# TODO: save leader_epoch
|
|
397
|
-
|
|
396
|
+
log.debug("Updating fetch position for assigned partition %s to %s (leader epoch %s)",
|
|
397
|
+
tp, part.next_fetch_offset, part.leader_epoch)
|
|
398
|
+
self._subscriptions.assignment[tp].position = OffsetAndMetadata(part.next_fetch_offset, '', -1)
|
|
398
399
|
return len(part_records)
|
|
399
400
|
|
|
400
401
|
else:
|
|
401
402
|
# these records aren't next in line based on the last consumed
|
|
402
403
|
# position, ignore them they must be from an obsolete request
|
|
403
404
|
log.debug("Ignoring fetched records for %s at offset %s since"
|
|
404
|
-
" the current position is %d", tp, part.
|
|
405
|
+
" the current position is %d", tp, part.next_fetch_offset,
|
|
405
406
|
position.offset)
|
|
406
407
|
|
|
407
|
-
part.
|
|
408
|
+
part.drain()
|
|
408
409
|
return 0
|
|
409
410
|
|
|
410
|
-
def _message_generator(self):
|
|
411
|
-
"""Iterate over fetched_records"""
|
|
412
|
-
while self._next_partition_records or self._completed_fetches:
|
|
413
|
-
|
|
414
|
-
if not self._next_partition_records:
|
|
415
|
-
completion = self._completed_fetches.popleft()
|
|
416
|
-
self._next_partition_records = self._parse_fetched_data(completion)
|
|
417
|
-
continue
|
|
418
|
-
|
|
419
|
-
# Send additional FetchRequests when the internal queue is low
|
|
420
|
-
# this should enable moderate pipelining
|
|
421
|
-
if len(self._completed_fetches) <= self.config['iterator_refetch_records']:
|
|
422
|
-
self.send_fetches()
|
|
423
|
-
|
|
424
|
-
tp = self._next_partition_records.topic_partition
|
|
425
|
-
|
|
426
|
-
# We can ignore any prior signal to drop pending record batches
|
|
427
|
-
# because we are starting from a fresh one where fetch_offset == position
|
|
428
|
-
# i.e., the user seek()'d to this position
|
|
429
|
-
self._subscriptions.assignment[tp].drop_pending_record_batch = False
|
|
430
|
-
|
|
431
|
-
for msg in self._next_partition_records.take():
|
|
432
|
-
|
|
433
|
-
# Because we are in a generator, it is possible for
|
|
434
|
-
# subscription state to change between yield calls
|
|
435
|
-
# so we need to re-check on each loop
|
|
436
|
-
# this should catch assignment changes, pauses
|
|
437
|
-
# and resets via seek_to_beginning / seek_to_end
|
|
438
|
-
if not self._subscriptions.is_fetchable(tp):
|
|
439
|
-
log.debug("Not returning fetched records for partition %s"
|
|
440
|
-
" since it is no longer fetchable", tp)
|
|
441
|
-
self._next_partition_records = None
|
|
442
|
-
break
|
|
443
|
-
|
|
444
|
-
# If there is a seek during message iteration,
|
|
445
|
-
# we should stop unpacking this record batch and
|
|
446
|
-
# wait for a new fetch response that aligns with the
|
|
447
|
-
# new seek position
|
|
448
|
-
elif self._subscriptions.assignment[tp].drop_pending_record_batch:
|
|
449
|
-
log.debug("Skipping remainder of record batch for partition %s", tp)
|
|
450
|
-
self._subscriptions.assignment[tp].drop_pending_record_batch = False
|
|
451
|
-
self._next_partition_records = None
|
|
452
|
-
break
|
|
453
|
-
|
|
454
|
-
# Compressed messagesets may include earlier messages
|
|
455
|
-
elif msg.offset < self._subscriptions.assignment[tp].position.offset:
|
|
456
|
-
log.debug("Skipping message offset: %s (expecting %s)",
|
|
457
|
-
msg.offset,
|
|
458
|
-
self._subscriptions.assignment[tp].position.offset)
|
|
459
|
-
continue
|
|
460
|
-
|
|
461
|
-
self._subscriptions.assignment[tp].position = OffsetAndMetadata(msg.offset + 1, '', -1)
|
|
462
|
-
yield msg
|
|
463
|
-
|
|
464
|
-
self._next_partition_records = None
|
|
465
|
-
|
|
466
|
-
def _unpack_records(self, tp, records):
|
|
467
|
-
try:
|
|
468
|
-
batch = records.next_batch()
|
|
469
|
-
while batch is not None:
|
|
470
|
-
|
|
471
|
-
# Try DefaultsRecordBatch / message log format v2
|
|
472
|
-
# base_offset, last_offset_delta, and control batches
|
|
473
|
-
try:
|
|
474
|
-
batch_offset = batch.base_offset + batch.last_offset_delta
|
|
475
|
-
leader_epoch = batch.leader_epoch
|
|
476
|
-
self._subscriptions.assignment[tp].last_offset_from_record_batch = batch_offset
|
|
477
|
-
# Control batches have a single record indicating whether a transaction
|
|
478
|
-
# was aborted or committed.
|
|
479
|
-
# When isolation_level is READ_COMMITTED (currently unsupported)
|
|
480
|
-
# we should also skip all messages from aborted transactions
|
|
481
|
-
# For now we only support READ_UNCOMMITTED and so we ignore the
|
|
482
|
-
# abort/commit signal.
|
|
483
|
-
if batch.is_control_batch:
|
|
484
|
-
batch = records.next_batch()
|
|
485
|
-
continue
|
|
486
|
-
except AttributeError:
|
|
487
|
-
leader_epoch = -1
|
|
488
|
-
pass
|
|
489
|
-
|
|
490
|
-
for record in batch:
|
|
491
|
-
key_size = len(record.key) if record.key is not None else -1
|
|
492
|
-
value_size = len(record.value) if record.value is not None else -1
|
|
493
|
-
key = self._deserialize(
|
|
494
|
-
self.config['key_deserializer'],
|
|
495
|
-
tp.topic, record.key)
|
|
496
|
-
value = self._deserialize(
|
|
497
|
-
self.config['value_deserializer'],
|
|
498
|
-
tp.topic, record.value)
|
|
499
|
-
headers = record.headers
|
|
500
|
-
header_size = sum(
|
|
501
|
-
len(h_key.encode("utf-8")) + (len(h_val) if h_val is not None else 0) for h_key, h_val in
|
|
502
|
-
headers) if headers else -1
|
|
503
|
-
yield ConsumerRecord(
|
|
504
|
-
tp.topic, tp.partition, leader_epoch, record.offset, record.timestamp,
|
|
505
|
-
record.timestamp_type, key, value, headers, record.checksum,
|
|
506
|
-
key_size, value_size, header_size)
|
|
507
|
-
|
|
508
|
-
batch = records.next_batch()
|
|
509
|
-
|
|
510
|
-
# If unpacking raises StopIteration, it is erroneously
|
|
511
|
-
# caught by the generator. We want all exceptions to be raised
|
|
512
|
-
# back to the user. See Issue 545
|
|
513
|
-
except StopIteration:
|
|
514
|
-
log.exception('StopIteration raised unpacking messageset')
|
|
515
|
-
raise RuntimeError('StopIteration raised unpacking messageset')
|
|
516
|
-
|
|
517
|
-
def __iter__(self): # pylint: disable=non-iterator-returned
|
|
518
|
-
return self
|
|
519
|
-
|
|
520
|
-
def __next__(self):
|
|
521
|
-
if not self._iterator:
|
|
522
|
-
self._iterator = self._message_generator()
|
|
523
|
-
try:
|
|
524
|
-
return next(self._iterator)
|
|
525
|
-
except StopIteration:
|
|
526
|
-
self._iterator = None
|
|
527
|
-
raise
|
|
528
|
-
|
|
529
|
-
def _deserialize(self, f, topic, bytes_):
|
|
530
|
-
if not f:
|
|
531
|
-
return bytes_
|
|
532
|
-
if isinstance(f, Deserializer):
|
|
533
|
-
return f.deserialize(topic, bytes_)
|
|
534
|
-
return f(bytes_)
|
|
535
|
-
|
|
536
411
|
def _send_list_offsets_requests(self, timestamps):
|
|
537
412
|
"""Fetch offsets for each partition in timestamps dict. This may send
|
|
538
413
|
request to multiple nodes, based on who is Leader for partition.
|
|
@@ -711,16 +586,6 @@ class Fetcher(six.Iterator):
|
|
|
711
586
|
for partition in self._fetchable_partitions():
|
|
712
587
|
node_id = self._client.cluster.leader_for_partition(partition)
|
|
713
588
|
|
|
714
|
-
# advance position for any deleted compacted messages if required
|
|
715
|
-
if self._subscriptions.assignment[partition].last_offset_from_record_batch:
|
|
716
|
-
next_offset_from_batch_header = self._subscriptions.assignment[partition].last_offset_from_record_batch + 1
|
|
717
|
-
if next_offset_from_batch_header > self._subscriptions.assignment[partition].position.offset:
|
|
718
|
-
log.debug(
|
|
719
|
-
"Advance position for partition %s from %s to %s (last record batch location plus one)"
|
|
720
|
-
" to correct for deleted compacted messages and/or transactional control records",
|
|
721
|
-
partition, self._subscriptions.assignment[partition].position.offset, next_offset_from_batch_header)
|
|
722
|
-
self._subscriptions.assignment[partition].position = OffsetAndMetadata(next_offset_from_batch_header, '', -1)
|
|
723
|
-
|
|
724
589
|
position = self._subscriptions.assignment[partition].position
|
|
725
590
|
|
|
726
591
|
# fetch if there is a leader and no in-flight requests
|
|
@@ -856,12 +721,9 @@ class Fetcher(six.Iterator):
|
|
|
856
721
|
def _parse_fetched_data(self, completed_fetch):
|
|
857
722
|
tp = completed_fetch.topic_partition
|
|
858
723
|
fetch_offset = completed_fetch.fetched_offset
|
|
859
|
-
num_bytes = 0
|
|
860
|
-
records_count = 0
|
|
861
|
-
parsed_records = None
|
|
862
|
-
|
|
863
724
|
error_code, highwater = completed_fetch.partition_data[:2]
|
|
864
725
|
error_type = Errors.for_code(error_code)
|
|
726
|
+
parsed_records = None
|
|
865
727
|
|
|
866
728
|
try:
|
|
867
729
|
if not self._subscriptions.is_fetchable(tp):
|
|
@@ -890,13 +752,12 @@ class Fetcher(six.Iterator):
|
|
|
890
752
|
log.debug("Adding fetched record for partition %s with"
|
|
891
753
|
" offset %d to buffered record list", tp,
|
|
892
754
|
position.offset)
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
records_count = len(unpacked)
|
|
755
|
+
parsed_records = self.PartitionRecords(fetch_offset, tp, records,
|
|
756
|
+
self.config['key_deserializer'],
|
|
757
|
+
self.config['value_deserializer'],
|
|
758
|
+
self.config['check_crcs'],
|
|
759
|
+
completed_fetch.metric_aggregator)
|
|
760
|
+
return parsed_records
|
|
900
761
|
elif records.size_in_bytes() > 0:
|
|
901
762
|
# we did not read a single message from a non-empty
|
|
902
763
|
# buffer because that message's size is larger than
|
|
@@ -911,7 +772,6 @@ class Fetcher(six.Iterator):
|
|
|
911
772
|
record_too_large_partitions,
|
|
912
773
|
self.config['max_partition_fetch_bytes']),
|
|
913
774
|
record_too_large_partitions)
|
|
914
|
-
self._sensors.record_topic_fetch_metrics(tp.topic, num_bytes, records_count)
|
|
915
775
|
|
|
916
776
|
elif error_type in (Errors.NotLeaderForPartitionError,
|
|
917
777
|
Errors.ReplicaNotAvailableError,
|
|
@@ -934,60 +794,133 @@ class Fetcher(six.Iterator):
|
|
|
934
794
|
elif error_type is Errors.TopicAuthorizationFailedError:
|
|
935
795
|
log.warning("Not authorized to read from topic %s.", tp.topic)
|
|
936
796
|
raise Errors.TopicAuthorizationFailedError(set([tp.topic]))
|
|
937
|
-
elif error_type
|
|
797
|
+
elif getattr(error_type, 'retriable', False):
|
|
938
798
|
log.debug("Retriable error fetching partition %s: %s", tp, error_type())
|
|
939
|
-
if error_type
|
|
799
|
+
if getattr(error_type, 'invalid_metadata', False):
|
|
940
800
|
self._client.cluster.request_update()
|
|
941
801
|
else:
|
|
942
802
|
raise error_type('Unexpected error while fetching data')
|
|
943
803
|
|
|
944
804
|
finally:
|
|
945
|
-
|
|
805
|
+
if parsed_records is None:
|
|
806
|
+
completed_fetch.metric_aggregator.record(tp, 0, 0)
|
|
807
|
+
|
|
808
|
+
return None
|
|
946
809
|
|
|
947
|
-
|
|
810
|
+
def close(self):
|
|
811
|
+
if self._next_partition_records is not None:
|
|
812
|
+
self._next_partition_records.drain()
|
|
948
813
|
|
|
949
814
|
class PartitionRecords(object):
|
|
950
|
-
def __init__(self, fetch_offset, tp,
|
|
815
|
+
def __init__(self, fetch_offset, tp, records, key_deserializer, value_deserializer, check_crcs, metric_aggregator):
|
|
951
816
|
self.fetch_offset = fetch_offset
|
|
952
817
|
self.topic_partition = tp
|
|
953
|
-
self.
|
|
818
|
+
self.leader_epoch = -1
|
|
819
|
+
self.next_fetch_offset = fetch_offset
|
|
820
|
+
self.bytes_read = 0
|
|
821
|
+
self.records_read = 0
|
|
822
|
+
self.metric_aggregator = metric_aggregator
|
|
823
|
+
self.check_crcs = check_crcs
|
|
824
|
+
self.record_iterator = itertools.dropwhile(
|
|
825
|
+
self._maybe_skip_record,
|
|
826
|
+
self._unpack_records(tp, records, key_deserializer, value_deserializer))
|
|
827
|
+
|
|
828
|
+
def _maybe_skip_record(self, record):
|
|
954
829
|
# When fetching an offset that is in the middle of a
|
|
955
830
|
# compressed batch, we will get all messages in the batch.
|
|
956
831
|
# But we want to start 'take' at the fetch_offset
|
|
957
832
|
# (or the next highest offset in case the message was compacted)
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
else:
|
|
963
|
-
self.message_idx = i
|
|
964
|
-
break
|
|
965
|
-
|
|
833
|
+
if record.offset < self.fetch_offset:
|
|
834
|
+
log.debug("Skipping message offset: %s (expecting %s)",
|
|
835
|
+
record.offset, self.fetch_offset)
|
|
836
|
+
return True
|
|
966
837
|
else:
|
|
967
|
-
|
|
968
|
-
self.messages = None
|
|
838
|
+
return False
|
|
969
839
|
|
|
970
|
-
# For truthiness evaluation
|
|
971
|
-
def
|
|
972
|
-
|
|
973
|
-
return 0
|
|
974
|
-
return len(self.messages) - self.message_idx
|
|
840
|
+
# For truthiness evaluation
|
|
841
|
+
def __bool__(self):
|
|
842
|
+
return self.record_iterator is not None
|
|
975
843
|
|
|
976
|
-
def
|
|
977
|
-
self.
|
|
844
|
+
def drain(self):
|
|
845
|
+
if self.record_iterator is not None:
|
|
846
|
+
self.record_iterator = None
|
|
847
|
+
self.metric_aggregator.record(self.topic_partition, self.bytes_read, self.records_read)
|
|
978
848
|
|
|
979
849
|
def take(self, n=None):
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
850
|
+
return list(itertools.islice(self.record_iterator, 0, n))
|
|
851
|
+
|
|
852
|
+
def _unpack_records(self, tp, records, key_deserializer, value_deserializer):
|
|
853
|
+
try:
|
|
854
|
+
batch = records.next_batch()
|
|
855
|
+
last_batch = None
|
|
856
|
+
while batch is not None:
|
|
857
|
+
last_batch = batch
|
|
858
|
+
|
|
859
|
+
if self.check_crcs and not batch.validate_crc():
|
|
860
|
+
raise Errors.CorruptRecordException(
|
|
861
|
+
"Record batch for partition %s at offset %s failed crc check" % (
|
|
862
|
+
self.topic_partition, batch.base_offset))
|
|
863
|
+
|
|
864
|
+
# Try DefaultsRecordBatch / message log format v2
|
|
865
|
+
# base_offset, last_offset_delta, and control batches
|
|
866
|
+
if batch.magic == 2:
|
|
867
|
+
self.leader_epoch = batch.leader_epoch
|
|
868
|
+
# Control batches have a single record indicating whether a transaction
|
|
869
|
+
# was aborted or committed.
|
|
870
|
+
# When isolation_level is READ_COMMITTED (currently unsupported)
|
|
871
|
+
# we should also skip all messages from aborted transactions
|
|
872
|
+
# For now we only support READ_UNCOMMITTED and so we ignore the
|
|
873
|
+
# abort/commit signal.
|
|
874
|
+
if batch.is_control_batch:
|
|
875
|
+
self.next_fetch_offset = next(batch).offset + 1
|
|
876
|
+
batch = records.next_batch()
|
|
877
|
+
continue
|
|
878
|
+
|
|
879
|
+
for record in batch:
|
|
880
|
+
if self.check_crcs and not record.validate_crc():
|
|
881
|
+
raise Errors.CorruptRecordException(
|
|
882
|
+
"Record for partition %s at offset %s failed crc check" % (
|
|
883
|
+
self.topic_partition, record.offset))
|
|
884
|
+
key_size = len(record.key) if record.key is not None else -1
|
|
885
|
+
value_size = len(record.value) if record.value is not None else -1
|
|
886
|
+
key = self._deserialize(key_deserializer, tp.topic, record.key)
|
|
887
|
+
value = self._deserialize(value_deserializer, tp.topic, record.value)
|
|
888
|
+
headers = record.headers
|
|
889
|
+
header_size = sum(
|
|
890
|
+
len(h_key.encode("utf-8")) + (len(h_val) if h_val is not None else 0) for h_key, h_val in
|
|
891
|
+
headers) if headers else -1
|
|
892
|
+
self.records_read += 1
|
|
893
|
+
self.bytes_read += record.size_in_bytes
|
|
894
|
+
self.next_fetch_offset = record.offset + 1
|
|
895
|
+
yield ConsumerRecord(
|
|
896
|
+
tp.topic, tp.partition, self.leader_epoch, record.offset, record.timestamp,
|
|
897
|
+
record.timestamp_type, key, value, headers, record.checksum,
|
|
898
|
+
key_size, value_size, header_size)
|
|
899
|
+
|
|
900
|
+
batch = records.next_batch()
|
|
901
|
+
else:
|
|
902
|
+
# Message format v2 preserves the last offset in a batch even if the last record is removed
|
|
903
|
+
# through compaction. By using the next offset computed from the last offset in the batch,
|
|
904
|
+
# we ensure that the offset of the next fetch will point to the next batch, which avoids
|
|
905
|
+
# unnecessary re-fetching of the same batch (in the worst case, the consumer could get stuck
|
|
906
|
+
# fetching the same batch repeatedly).
|
|
907
|
+
if last_batch and last_batch.magic == 2:
|
|
908
|
+
self.next_fetch_offset = last_batch.base_offset + last_batch.last_offset_delta + 1
|
|
909
|
+
self.drain()
|
|
910
|
+
|
|
911
|
+
# If unpacking raises StopIteration, it is erroneously
|
|
912
|
+
# caught by the generator. We want all exceptions to be raised
|
|
913
|
+
# back to the user. See Issue 545
|
|
914
|
+
except StopIteration:
|
|
915
|
+
log.exception('StopIteration raised unpacking messageset')
|
|
916
|
+
raise RuntimeError('StopIteration raised unpacking messageset')
|
|
917
|
+
|
|
918
|
+
def _deserialize(self, f, topic, bytes_):
|
|
919
|
+
if not f:
|
|
920
|
+
return bytes_
|
|
921
|
+
if isinstance(f, Deserializer):
|
|
922
|
+
return f.deserialize(topic, bytes_)
|
|
923
|
+
return f(bytes_)
|
|
991
924
|
|
|
992
925
|
|
|
993
926
|
class FetchSessionHandler(object):
|
|
@@ -1196,6 +1129,14 @@ class FetchRequestData(object):
|
|
|
1196
1129
|
return list(partition_data.items())
|
|
1197
1130
|
|
|
1198
1131
|
|
|
1132
|
+
class FetchMetrics(object):
|
|
1133
|
+
__slots__ = ('total_bytes', 'total_records')
|
|
1134
|
+
|
|
1135
|
+
def __init__(self):
|
|
1136
|
+
self.total_bytes = 0
|
|
1137
|
+
self.total_records = 0
|
|
1138
|
+
|
|
1139
|
+
|
|
1199
1140
|
class FetchResponseMetricAggregator(object):
|
|
1200
1141
|
"""
|
|
1201
1142
|
Since we parse the message data for each partition from each fetch
|
|
@@ -1206,8 +1147,8 @@ class FetchResponseMetricAggregator(object):
|
|
|
1206
1147
|
def __init__(self, sensors, partitions):
|
|
1207
1148
|
self.sensors = sensors
|
|
1208
1149
|
self.unrecorded_partitions = partitions
|
|
1209
|
-
self.
|
|
1210
|
-
self.
|
|
1150
|
+
self.fetch_metrics = FetchMetrics()
|
|
1151
|
+
self.topic_fetch_metrics = collections.defaultdict(FetchMetrics)
|
|
1211
1152
|
|
|
1212
1153
|
def record(self, partition, num_bytes, num_records):
|
|
1213
1154
|
"""
|
|
@@ -1216,13 +1157,17 @@ class FetchResponseMetricAggregator(object):
|
|
|
1216
1157
|
have reported, we write the metric.
|
|
1217
1158
|
"""
|
|
1218
1159
|
self.unrecorded_partitions.remove(partition)
|
|
1219
|
-
self.total_bytes += num_bytes
|
|
1220
|
-
self.total_records += num_records
|
|
1160
|
+
self.fetch_metrics.total_bytes += num_bytes
|
|
1161
|
+
self.fetch_metrics.total_records += num_records
|
|
1162
|
+
self.topic_fetch_metrics[partition.topic].total_bytes += num_bytes
|
|
1163
|
+
self.topic_fetch_metrics[partition.topic].total_records += num_records
|
|
1221
1164
|
|
|
1222
1165
|
# once all expected partitions from the fetch have reported in, record the metrics
|
|
1223
1166
|
if not self.unrecorded_partitions:
|
|
1224
|
-
self.sensors.bytes_fetched.record(self.total_bytes)
|
|
1225
|
-
self.sensors.records_fetched.record(self.total_records)
|
|
1167
|
+
self.sensors.bytes_fetched.record(self.fetch_metrics.total_bytes)
|
|
1168
|
+
self.sensors.records_fetched.record(self.fetch_metrics.total_records)
|
|
1169
|
+
for topic, metrics in six.iteritems(self.topic_fetch_metrics):
|
|
1170
|
+
self.sensors.record_topic_fetch_metrics(topic, metrics.total_bytes, metrics.total_records)
|
|
1226
1171
|
|
|
1227
1172
|
|
|
1228
1173
|
class FetchManagerMetrics(object):
|
kafka/consumer/group.py
CHANGED
|
@@ -707,22 +707,18 @@ class KafkaConsumer(six.Iterator):
|
|
|
707
707
|
# If data is available already, e.g. from a previous network client
|
|
708
708
|
# poll() call to commit, then just return it immediately
|
|
709
709
|
records, partial = self._fetcher.fetched_records(max_records, update_offsets=update_offsets)
|
|
710
|
+
# Before returning the fetched records, we can send off the
|
|
711
|
+
# next round of fetches and avoid block waiting for their
|
|
712
|
+
# responses to enable pipelining while the user is handling the
|
|
713
|
+
# fetched records.
|
|
714
|
+
if not partial:
|
|
715
|
+
futures = self._fetcher.send_fetches()
|
|
716
|
+
if len(futures):
|
|
717
|
+
self._client.poll(timeout_ms=0)
|
|
718
|
+
|
|
710
719
|
if records:
|
|
711
|
-
# Before returning the fetched records, we can send off the
|
|
712
|
-
# next round of fetches and avoid block waiting for their
|
|
713
|
-
# responses to enable pipelining while the user is handling the
|
|
714
|
-
# fetched records.
|
|
715
|
-
if not partial:
|
|
716
|
-
futures = self._fetcher.send_fetches()
|
|
717
|
-
if len(futures):
|
|
718
|
-
self._client.poll(timeout_ms=0)
|
|
719
720
|
return records
|
|
720
721
|
|
|
721
|
-
# Send any new fetches (won't resend pending fetches)
|
|
722
|
-
futures = self._fetcher.send_fetches()
|
|
723
|
-
if len(futures):
|
|
724
|
-
self._client.poll(timeout_ms=0)
|
|
725
|
-
|
|
726
722
|
self._client.poll(timeout_ms=inner_timeout_ms(self._coordinator.time_to_next_poll() * 1000))
|
|
727
723
|
# after the long poll, we should check whether the group needs to rebalance
|
|
728
724
|
# prior to returning data so that the group can stabilize faster
|
|
@@ -382,9 +382,6 @@ class TopicPartitionState(object):
|
|
|
382
382
|
self._position = None # OffsetAndMetadata exposed to the user
|
|
383
383
|
self.highwater = None
|
|
384
384
|
self.drop_pending_record_batch = False
|
|
385
|
-
# The last message offset hint available from a record batch with
|
|
386
|
-
# magic=2 which includes deleted compacted messages
|
|
387
|
-
self.last_offset_from_record_batch = None
|
|
388
385
|
|
|
389
386
|
def _set_position(self, offset):
|
|
390
387
|
assert self.has_valid_position, 'Valid position required'
|
|
@@ -400,7 +397,6 @@ class TopicPartitionState(object):
|
|
|
400
397
|
self.awaiting_reset = True
|
|
401
398
|
self.reset_strategy = strategy
|
|
402
399
|
self._position = None
|
|
403
|
-
self.last_offset_from_record_batch = None
|
|
404
400
|
self.has_valid_position = False
|
|
405
401
|
|
|
406
402
|
def seek(self, offset):
|
|
@@ -409,7 +405,6 @@ class TopicPartitionState(object):
|
|
|
409
405
|
self.reset_strategy = None
|
|
410
406
|
self.has_valid_position = True
|
|
411
407
|
self.drop_pending_record_batch = True
|
|
412
|
-
self.last_offset_from_record_batch = None
|
|
413
408
|
|
|
414
409
|
def pause(self):
|
|
415
410
|
self.paused = True
|
|
@@ -421,6 +416,7 @@ class TopicPartitionState(object):
|
|
|
421
416
|
return not self.paused and self.has_valid_position
|
|
422
417
|
|
|
423
418
|
|
|
419
|
+
@six.add_metaclass(abc.ABCMeta)
|
|
424
420
|
class ConsumerRebalanceListener(object):
|
|
425
421
|
"""
|
|
426
422
|
A callback interface that the user can implement to trigger custom actions
|
|
@@ -462,8 +458,6 @@ class ConsumerRebalanceListener(object):
|
|
|
462
458
|
taking over that partition has their on_partitions_assigned() callback
|
|
463
459
|
called to load the state.
|
|
464
460
|
"""
|
|
465
|
-
__metaclass__ = abc.ABCMeta
|
|
466
|
-
|
|
467
461
|
@abc.abstractmethod
|
|
468
462
|
def on_partitions_revoked(self, revoked):
|
|
469
463
|
"""
|
|
@@ -659,7 +659,7 @@ class StickyPartitionAssignor(AbstractPartitionAssignor):
|
|
|
659
659
|
partitions_by_topic = defaultdict(list)
|
|
660
660
|
for topic_partition in member_assignment_partitions:
|
|
661
661
|
partitions_by_topic[topic_partition.topic].append(topic_partition.partition)
|
|
662
|
-
data = StickyAssignorUserDataV1(
|
|
662
|
+
data = StickyAssignorUserDataV1(list(partitions_by_topic.items()), generation)
|
|
663
663
|
user_data = data.encode()
|
|
664
664
|
return ConsumerProtocolMemberMetadata(cls.version, list(topics), user_data)
|
|
665
665
|
|
kafka/future.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import absolute_import
|
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
4
|
import logging
|
|
5
|
+
import threading
|
|
5
6
|
|
|
6
7
|
log = logging.getLogger(__name__)
|
|
7
8
|
|
|
@@ -15,6 +16,7 @@ class Future(object):
|
|
|
15
16
|
self.exception = None
|
|
16
17
|
self._callbacks = []
|
|
17
18
|
self._errbacks = []
|
|
19
|
+
self._lock = threading.Lock()
|
|
18
20
|
|
|
19
21
|
def succeeded(self):
|
|
20
22
|
return self.is_done and not bool(self.exception)
|
|
@@ -30,37 +32,46 @@ class Future(object):
|
|
|
30
32
|
|
|
31
33
|
def success(self, value):
|
|
32
34
|
assert not self.is_done, 'Future is already complete'
|
|
33
|
-
self.
|
|
34
|
-
|
|
35
|
+
with self._lock:
|
|
36
|
+
self.value = value
|
|
37
|
+
self.is_done = True
|
|
35
38
|
if self._callbacks:
|
|
36
39
|
self._call_backs('callback', self._callbacks, self.value)
|
|
37
40
|
return self
|
|
38
41
|
|
|
39
42
|
def failure(self, e):
|
|
40
43
|
assert not self.is_done, 'Future is already complete'
|
|
41
|
-
|
|
42
|
-
assert isinstance(
|
|
44
|
+
exception = e if type(e) is not type else e()
|
|
45
|
+
assert isinstance(exception, BaseException), (
|
|
43
46
|
'future failed without an exception')
|
|
44
|
-
self.
|
|
47
|
+
with self._lock:
|
|
48
|
+
self.exception = exception
|
|
49
|
+
self.is_done = True
|
|
45
50
|
self._call_backs('errback', self._errbacks, self.exception)
|
|
46
51
|
return self
|
|
47
52
|
|
|
48
53
|
def add_callback(self, f, *args, **kwargs):
|
|
49
54
|
if args or kwargs:
|
|
50
55
|
f = functools.partial(f, *args, **kwargs)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
self.
|
|
56
|
+
with self._lock:
|
|
57
|
+
if not self.is_done:
|
|
58
|
+
self._callbacks.append(f)
|
|
59
|
+
elif self.succeeded():
|
|
60
|
+
self._lock.release()
|
|
61
|
+
self._call_backs('callback', [f], self.value)
|
|
62
|
+
self._lock.acquire()
|
|
55
63
|
return self
|
|
56
64
|
|
|
57
65
|
def add_errback(self, f, *args, **kwargs):
|
|
58
66
|
if args or kwargs:
|
|
59
67
|
f = functools.partial(f, *args, **kwargs)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
self.
|
|
68
|
+
with self._lock:
|
|
69
|
+
if not self.is_done:
|
|
70
|
+
self._errbacks.append(f)
|
|
71
|
+
elif self.failed():
|
|
72
|
+
self._lock.release()
|
|
73
|
+
self._call_backs('errback', [f], self.exception)
|
|
74
|
+
self._lock.acquire()
|
|
64
75
|
return self
|
|
65
76
|
|
|
66
77
|
def add_both(self, f, *args, **kwargs):
|
kafka/metrics/compound_stat.py
CHANGED
|
@@ -3,16 +3,16 @@ from __future__ import absolute_import
|
|
|
3
3
|
import abc
|
|
4
4
|
|
|
5
5
|
from kafka.metrics.stat import AbstractStat
|
|
6
|
+
from kafka.vendor.six import add_metaclass
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
@add_metaclass(abc.ABCMeta)
|
|
8
10
|
class AbstractCompoundStat(AbstractStat):
|
|
9
11
|
"""
|
|
10
12
|
A compound stat is a stat where a single measurement and associated
|
|
11
13
|
data structure feeds many metrics. This is the example for a
|
|
12
14
|
histogram which has many associated percentiles.
|
|
13
15
|
"""
|
|
14
|
-
__metaclass__ = abc.ABCMeta
|
|
15
|
-
|
|
16
16
|
def stats(self):
|
|
17
17
|
"""
|
|
18
18
|
Return list of NamedMeasurable
|
kafka/metrics/measurable_stat.py
CHANGED
|
@@ -4,8 +4,10 @@ import abc
|
|
|
4
4
|
|
|
5
5
|
from kafka.metrics.measurable import AbstractMeasurable
|
|
6
6
|
from kafka.metrics.stat import AbstractStat
|
|
7
|
+
from kafka.vendor.six import add_metaclass
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
@add_metaclass(abc.ABCMeta)
|
|
9
11
|
class AbstractMeasurableStat(AbstractStat, AbstractMeasurable):
|
|
10
12
|
"""
|
|
11
13
|
An AbstractMeasurableStat is an AbstractStat that is also
|
|
@@ -13,4 +15,3 @@ class AbstractMeasurableStat(AbstractStat, AbstractMeasurable):
|
|
|
13
15
|
This is the interface used for most of the simple statistics such
|
|
14
16
|
as Avg, Max, Count, etc.
|
|
15
17
|
"""
|
|
16
|
-
__metaclass__ = abc.ABCMeta
|
|
@@ -2,14 +2,15 @@ from __future__ import absolute_import
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
|
|
5
|
+
from kafka.vendor.six import add_metaclass
|
|
5
6
|
|
|
7
|
+
|
|
8
|
+
@add_metaclass(abc.ABCMeta)
|
|
6
9
|
class AbstractMetricsReporter(object):
|
|
7
10
|
"""
|
|
8
11
|
An abstract class to allow things to listen as new metrics
|
|
9
12
|
are created so they can be reported.
|
|
10
13
|
"""
|
|
11
|
-
__metaclass__ = abc.ABCMeta
|
|
12
|
-
|
|
13
14
|
@abc.abstractmethod
|
|
14
15
|
def init(self, metrics):
|
|
15
16
|
"""
|
kafka/metrics/stat.py
CHANGED
|
@@ -2,14 +2,15 @@ from __future__ import absolute_import
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
|
|
5
|
+
from kafka.vendor.six import add_metaclass
|
|
5
6
|
|
|
7
|
+
|
|
8
|
+
@add_metaclass(abc.ABCMeta)
|
|
6
9
|
class AbstractStat(object):
|
|
7
10
|
"""
|
|
8
11
|
An AbstractStat is a quantity such as average, max, etc that is computed
|
|
9
12
|
off the stream of updates to a sensor
|
|
10
13
|
"""
|
|
11
|
-
__metaclass__ = abc.ABCMeta
|
|
12
|
-
|
|
13
14
|
@abc.abstractmethod
|
|
14
15
|
def record(self, config, value, time_ms):
|
|
15
16
|
"""
|
|
@@ -3,8 +3,10 @@ from __future__ import absolute_import
|
|
|
3
3
|
import abc
|
|
4
4
|
|
|
5
5
|
from kafka.metrics.measurable_stat import AbstractMeasurableStat
|
|
6
|
+
from kafka.vendor.six import add_metaclass
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
@add_metaclass(abc.ABCMeta)
|
|
8
10
|
class AbstractSampledStat(AbstractMeasurableStat):
|
|
9
11
|
"""
|
|
10
12
|
An AbstractSampledStat records a single scalar value measured over
|
|
@@ -20,8 +22,6 @@ class AbstractSampledStat(AbstractMeasurableStat):
|
|
|
20
22
|
Subclasses of this class define different statistics measured
|
|
21
23
|
using this basic pattern.
|
|
22
24
|
"""
|
|
23
|
-
__metaclass__ = abc.ABCMeta
|
|
24
|
-
|
|
25
25
|
def __init__(self, initial_value):
|
|
26
26
|
self._initial_value = initial_value
|
|
27
27
|
self._samples = []
|
kafka/protocol/abstract.py
CHANGED
|
@@ -2,10 +2,11 @@ from __future__ import absolute_import
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
|
|
5
|
+
from kafka.vendor.six import add_metaclass
|
|
5
6
|
|
|
6
|
-
class AbstractType(object):
|
|
7
|
-
__metaclass__ = abc.ABCMeta
|
|
8
7
|
|
|
8
|
+
@add_metaclass(abc.ABCMeta)
|
|
9
|
+
class AbstractType(object):
|
|
9
10
|
@abc.abstractmethod
|
|
10
11
|
def encode(cls, value): # pylint: disable=no-self-argument
|
|
11
12
|
pass
|
kafka/protocol/api.py
CHANGED
|
@@ -5,6 +5,8 @@ import abc
|
|
|
5
5
|
from kafka.protocol.struct import Struct
|
|
6
6
|
from kafka.protocol.types import Int16, Int32, String, Schema, Array, TaggedFields
|
|
7
7
|
|
|
8
|
+
from kafka.vendor.six import add_metaclass
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
class RequestHeader(Struct):
|
|
10
12
|
SCHEMA = Schema(
|
|
@@ -49,9 +51,8 @@ class ResponseHeaderV2(Struct):
|
|
|
49
51
|
)
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
@add_metaclass(abc.ABCMeta)
|
|
52
55
|
class Request(Struct):
|
|
53
|
-
__metaclass__ = abc.ABCMeta
|
|
54
|
-
|
|
55
56
|
FLEXIBLE_VERSION = False
|
|
56
57
|
|
|
57
58
|
@abc.abstractproperty
|
|
@@ -92,8 +93,8 @@ class Request(Struct):
|
|
|
92
93
|
return ResponseHeader.decode(read_buffer)
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
@add_metaclass(abc.ABCMeta)
|
|
95
97
|
class Response(Struct):
|
|
96
|
-
__metaclass__ = abc.ABCMeta
|
|
97
98
|
|
|
98
99
|
@abc.abstractproperty
|
|
99
100
|
def API_KEY(self):
|
kafka/record/abc.py
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
|
+
|
|
2
3
|
import abc
|
|
3
4
|
|
|
5
|
+
from kafka.vendor.six import add_metaclass
|
|
6
|
+
|
|
4
7
|
|
|
8
|
+
@add_metaclass(abc.ABCMeta)
|
|
5
9
|
class ABCRecord(object):
|
|
6
|
-
__metaclass__ = abc.ABCMeta
|
|
7
10
|
__slots__ = ()
|
|
8
11
|
|
|
12
|
+
@abc.abstractproperty
|
|
13
|
+
def size_in_bytes(self):
|
|
14
|
+
""" Number of total bytes in record
|
|
15
|
+
"""
|
|
16
|
+
|
|
9
17
|
@abc.abstractproperty
|
|
10
18
|
def offset(self):
|
|
11
19
|
""" Absolute offset of record
|
|
@@ -37,6 +45,11 @@ class ABCRecord(object):
|
|
|
37
45
|
be the checksum for v0 and v1 and None for v2 and above.
|
|
38
46
|
"""
|
|
39
47
|
|
|
48
|
+
@abc.abstractmethod
|
|
49
|
+
def validate_crc(self):
|
|
50
|
+
""" Return True if v0/v1 record matches checksum. noop/True for v2 records
|
|
51
|
+
"""
|
|
52
|
+
|
|
40
53
|
@abc.abstractproperty
|
|
41
54
|
def headers(self):
|
|
42
55
|
""" If supported by version list of key-value tuples, or empty list if
|
|
@@ -44,8 +57,8 @@ class ABCRecord(object):
|
|
|
44
57
|
"""
|
|
45
58
|
|
|
46
59
|
|
|
60
|
+
@add_metaclass(abc.ABCMeta)
|
|
47
61
|
class ABCRecordBatchBuilder(object):
|
|
48
|
-
__metaclass__ = abc.ABCMeta
|
|
49
62
|
__slots__ = ()
|
|
50
63
|
|
|
51
64
|
@abc.abstractmethod
|
|
@@ -84,11 +97,11 @@ class ABCRecordBatchBuilder(object):
|
|
|
84
97
|
"""
|
|
85
98
|
|
|
86
99
|
|
|
100
|
+
@add_metaclass(abc.ABCMeta)
|
|
87
101
|
class ABCRecordBatch(object):
|
|
88
102
|
""" For v2 encapsulates a RecordBatch, for v0/v1 a single (maybe
|
|
89
103
|
compressed) message.
|
|
90
104
|
"""
|
|
91
|
-
__metaclass__ = abc.ABCMeta
|
|
92
105
|
__slots__ = ()
|
|
93
106
|
|
|
94
107
|
@abc.abstractmethod
|
|
@@ -97,9 +110,14 @@ class ABCRecordBatch(object):
|
|
|
97
110
|
if needed.
|
|
98
111
|
"""
|
|
99
112
|
|
|
113
|
+
@abc.abstractproperty
|
|
114
|
+
def magic(self):
|
|
115
|
+
""" Return magic value (0, 1, 2) for batch.
|
|
116
|
+
"""
|
|
117
|
+
|
|
100
118
|
|
|
119
|
+
@add_metaclass(abc.ABCMeta)
|
|
101
120
|
class ABCRecords(object):
|
|
102
|
-
__metaclass__ = abc.ABCMeta
|
|
103
121
|
__slots__ = ()
|
|
104
122
|
|
|
105
123
|
@abc.abstractmethod
|
kafka/record/default_records.py
CHANGED
|
@@ -275,10 +275,10 @@ class DefaultRecordBatch(DefaultRecordBase, ABCRecordBatch):
|
|
|
275
275
|
|
|
276
276
|
if self.is_control_batch:
|
|
277
277
|
return ControlRecord(
|
|
278
|
-
offset, timestamp, self.timestamp_type, key, value, headers)
|
|
278
|
+
length, offset, timestamp, self.timestamp_type, key, value, headers)
|
|
279
279
|
else:
|
|
280
280
|
return DefaultRecord(
|
|
281
|
-
offset, timestamp, self.timestamp_type, key, value, headers)
|
|
281
|
+
length, offset, timestamp, self.timestamp_type, key, value, headers)
|
|
282
282
|
|
|
283
283
|
def __iter__(self):
|
|
284
284
|
self._maybe_uncompress()
|
|
@@ -314,10 +314,11 @@ class DefaultRecordBatch(DefaultRecordBase, ABCRecordBatch):
|
|
|
314
314
|
|
|
315
315
|
class DefaultRecord(ABCRecord):
|
|
316
316
|
|
|
317
|
-
__slots__ = ("_offset", "_timestamp", "_timestamp_type", "_key", "_value",
|
|
317
|
+
__slots__ = ("_size_in_bytes", "_offset", "_timestamp", "_timestamp_type", "_key", "_value",
|
|
318
318
|
"_headers")
|
|
319
319
|
|
|
320
|
-
def __init__(self, offset, timestamp, timestamp_type, key, value, headers):
|
|
320
|
+
def __init__(self, size_in_bytes, offset, timestamp, timestamp_type, key, value, headers):
|
|
321
|
+
self._size_in_bytes = size_in_bytes
|
|
321
322
|
self._offset = offset
|
|
322
323
|
self._timestamp = timestamp
|
|
323
324
|
self._timestamp_type = timestamp_type
|
|
@@ -325,6 +326,10 @@ class DefaultRecord(ABCRecord):
|
|
|
325
326
|
self._value = value
|
|
326
327
|
self._headers = headers
|
|
327
328
|
|
|
329
|
+
@property
|
|
330
|
+
def size_in_bytes(self):
|
|
331
|
+
return self._size_in_bytes
|
|
332
|
+
|
|
328
333
|
@property
|
|
329
334
|
def offset(self):
|
|
330
335
|
return self._offset
|
|
@@ -361,6 +366,9 @@ class DefaultRecord(ABCRecord):
|
|
|
361
366
|
def checksum(self):
|
|
362
367
|
return None
|
|
363
368
|
|
|
369
|
+
def validate_crc(self):
|
|
370
|
+
return True
|
|
371
|
+
|
|
364
372
|
def __repr__(self):
|
|
365
373
|
return (
|
|
366
374
|
"DefaultRecord(offset={!r}, timestamp={!r}, timestamp_type={!r},"
|
|
@@ -371,7 +379,7 @@ class DefaultRecord(ABCRecord):
|
|
|
371
379
|
|
|
372
380
|
|
|
373
381
|
class ControlRecord(DefaultRecord):
|
|
374
|
-
__slots__ = ("_offset", "_timestamp", "_timestamp_type", "_key", "_value",
|
|
382
|
+
__slots__ = ("_size_in_bytes", "_offset", "_timestamp", "_timestamp_type", "_key", "_value",
|
|
375
383
|
"_headers", "_version", "_type")
|
|
376
384
|
|
|
377
385
|
KEY_STRUCT = struct.Struct(
|
|
@@ -379,8 +387,8 @@ class ControlRecord(DefaultRecord):
|
|
|
379
387
|
"h" # Type => Int16 (0 indicates an abort marker, 1 indicates a commit)
|
|
380
388
|
)
|
|
381
389
|
|
|
382
|
-
def __init__(self, offset, timestamp, timestamp_type, key, value, headers):
|
|
383
|
-
super(ControlRecord, self).__init__(offset, timestamp, timestamp_type, key, value, headers)
|
|
390
|
+
def __init__(self, size_in_bytes, offset, timestamp, timestamp_type, key, value, headers):
|
|
391
|
+
super(ControlRecord, self).__init__(size_in_bytes, offset, timestamp, timestamp_type, key, value, headers)
|
|
384
392
|
(self._version, self._type) = self.KEY_STRUCT.unpack(self._key)
|
|
385
393
|
|
|
386
394
|
# see https://kafka.apache.org/documentation/#controlbatch
|
|
@@ -548,8 +556,8 @@ class DefaultRecordBatchBuilder(DefaultRecordBase, ABCRecordBatchBuilder):
|
|
|
548
556
|
0, # CRC will be set below, as we need a filled buffer for it
|
|
549
557
|
self._get_attributes(use_compression_type),
|
|
550
558
|
self._last_offset,
|
|
551
|
-
self._first_timestamp,
|
|
552
|
-
self._max_timestamp,
|
|
559
|
+
self._first_timestamp or 0,
|
|
560
|
+
self._max_timestamp or 0,
|
|
553
561
|
self._producer_id,
|
|
554
562
|
self._producer_epoch,
|
|
555
563
|
self._base_sequence,
|
kafka/record/legacy_records.py
CHANGED
|
@@ -164,6 +164,10 @@ class LegacyRecordBatch(ABCRecordBatch, LegacyRecordBase):
|
|
|
164
164
|
def compression_type(self):
|
|
165
165
|
return self._attributes & self.CODEC_MASK
|
|
166
166
|
|
|
167
|
+
@property
|
|
168
|
+
def magic(self):
|
|
169
|
+
return self._magic
|
|
170
|
+
|
|
167
171
|
def validate_crc(self):
|
|
168
172
|
crc = calc_crc32(self._buffer[self.MAGIC_OFFSET:])
|
|
169
173
|
return self._crc == crc
|
|
@@ -232,6 +236,9 @@ class LegacyRecordBatch(ABCRecordBatch, LegacyRecordBase):
|
|
|
232
236
|
value = self._buffer[pos:pos + value_size].tobytes()
|
|
233
237
|
return key, value
|
|
234
238
|
|
|
239
|
+
def _crc_bytes(self, msg_pos, length):
|
|
240
|
+
return self._buffer[msg_pos + self.MAGIC_OFFSET:msg_pos + self.LOG_OVERHEAD + length]
|
|
241
|
+
|
|
235
242
|
def __iter__(self):
|
|
236
243
|
if self._magic == 1:
|
|
237
244
|
key_offset = self.KEY_OFFSET_V1
|
|
@@ -255,7 +262,7 @@ class LegacyRecordBatch(ABCRecordBatch, LegacyRecordBase):
|
|
|
255
262
|
absolute_base_offset = -1
|
|
256
263
|
|
|
257
264
|
for header, msg_pos in headers:
|
|
258
|
-
offset,
|
|
265
|
+
offset, length, crc, _, attrs, timestamp = header
|
|
259
266
|
# There should only ever be a single layer of compression
|
|
260
267
|
assert not attrs & self.CODEC_MASK, (
|
|
261
268
|
'MessageSet at offset %d appears double-compressed. This '
|
|
@@ -271,28 +278,36 @@ class LegacyRecordBatch(ABCRecordBatch, LegacyRecordBase):
|
|
|
271
278
|
offset += absolute_base_offset
|
|
272
279
|
|
|
273
280
|
key, value = self._read_key_value(msg_pos + key_offset)
|
|
281
|
+
crc_bytes = self._crc_bytes(msg_pos, length)
|
|
274
282
|
yield LegacyRecord(
|
|
275
|
-
offset, timestamp, timestamp_type,
|
|
276
|
-
key, value, crc)
|
|
283
|
+
self._magic, offset, timestamp, timestamp_type,
|
|
284
|
+
key, value, crc, crc_bytes)
|
|
277
285
|
else:
|
|
278
286
|
key, value = self._read_key_value(key_offset)
|
|
287
|
+
crc_bytes = self._crc_bytes(0, len(self._buffer) - self.LOG_OVERHEAD)
|
|
279
288
|
yield LegacyRecord(
|
|
280
|
-
self._offset, self._timestamp, timestamp_type,
|
|
281
|
-
key, value, self._crc)
|
|
289
|
+
self._magic, self._offset, self._timestamp, timestamp_type,
|
|
290
|
+
key, value, self._crc, crc_bytes)
|
|
282
291
|
|
|
283
292
|
|
|
284
293
|
class LegacyRecord(ABCRecord):
|
|
285
294
|
|
|
286
|
-
__slots__ = ("_offset", "_timestamp", "_timestamp_type", "_key", "_value",
|
|
287
|
-
"_crc")
|
|
295
|
+
__slots__ = ("_magic", "_offset", "_timestamp", "_timestamp_type", "_key", "_value",
|
|
296
|
+
"_crc", "_crc_bytes")
|
|
288
297
|
|
|
289
|
-
def __init__(self, offset, timestamp, timestamp_type, key, value, crc):
|
|
298
|
+
def __init__(self, magic, offset, timestamp, timestamp_type, key, value, crc, crc_bytes):
|
|
299
|
+
self._magic = magic
|
|
290
300
|
self._offset = offset
|
|
291
301
|
self._timestamp = timestamp
|
|
292
302
|
self._timestamp_type = timestamp_type
|
|
293
303
|
self._key = key
|
|
294
304
|
self._value = value
|
|
295
305
|
self._crc = crc
|
|
306
|
+
self._crc_bytes = crc_bytes
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def magic(self):
|
|
310
|
+
return self._magic
|
|
296
311
|
|
|
297
312
|
@property
|
|
298
313
|
def offset(self):
|
|
@@ -330,11 +345,19 @@ class LegacyRecord(ABCRecord):
|
|
|
330
345
|
def checksum(self):
|
|
331
346
|
return self._crc
|
|
332
347
|
|
|
348
|
+
def validate_crc(self):
|
|
349
|
+
crc = calc_crc32(self._crc_bytes)
|
|
350
|
+
return self._crc == crc
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def size_in_bytes(self):
|
|
354
|
+
return LegacyRecordBatchBuilder.estimate_size_in_bytes(self._magic, None, self._key, self._value)
|
|
355
|
+
|
|
333
356
|
def __repr__(self):
|
|
334
357
|
return (
|
|
335
|
-
"LegacyRecord(offset={!r}, timestamp={!r}, timestamp_type={!r},"
|
|
358
|
+
"LegacyRecord(magic={!r} offset={!r}, timestamp={!r}, timestamp_type={!r},"
|
|
336
359
|
" key={!r}, value={!r}, crc={!r})".format(
|
|
337
|
-
self._offset, self._timestamp, self._timestamp_type,
|
|
360
|
+
self._magic, self._offset, self._timestamp, self._timestamp_type,
|
|
338
361
|
self._key, self._value, self._crc)
|
|
339
362
|
)
|
|
340
363
|
|
kafka/record/memory_records.py
CHANGED
|
@@ -115,7 +115,7 @@ class MemoryRecordsBuilder(object):
|
|
|
115
115
|
__slots__ = ("_builder", "_batch_size", "_buffer", "_next_offset", "_closed",
|
|
116
116
|
"_bytes_written")
|
|
117
117
|
|
|
118
|
-
def __init__(self, magic, compression_type, batch_size):
|
|
118
|
+
def __init__(self, magic, compression_type, batch_size, offset=0):
|
|
119
119
|
assert magic in [0, 1, 2], "Not supported magic"
|
|
120
120
|
assert compression_type in [0, 1, 2, 3, 4], "Not valid compression type"
|
|
121
121
|
if magic >= 2:
|
|
@@ -130,10 +130,14 @@ class MemoryRecordsBuilder(object):
|
|
|
130
130
|
self._batch_size = batch_size
|
|
131
131
|
self._buffer = None
|
|
132
132
|
|
|
133
|
-
self._next_offset =
|
|
133
|
+
self._next_offset = offset
|
|
134
134
|
self._closed = False
|
|
135
135
|
self._bytes_written = 0
|
|
136
136
|
|
|
137
|
+
def skip(self, offsets_to_skip):
|
|
138
|
+
# Exposed for testing compacted records
|
|
139
|
+
self._next_offset += offsets_to_skip
|
|
140
|
+
|
|
137
141
|
def append(self, timestamp, key, value, headers=[]):
|
|
138
142
|
""" Append a message to the buffer.
|
|
139
143
|
|
kafka/sasl/abc.py
CHANGED
|
@@ -2,10 +2,11 @@ from __future__ import absolute_import
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
|
|
5
|
+
from kafka.vendor.six import add_metaclass
|
|
5
6
|
|
|
6
|
-
class SaslMechanism(object):
|
|
7
|
-
__metaclass__ = abc.ABCMeta
|
|
8
7
|
|
|
8
|
+
@add_metaclass(abc.ABCMeta)
|
|
9
|
+
class SaslMechanism(object):
|
|
9
10
|
@abc.abstractmethod
|
|
10
11
|
def __init__(self, **config):
|
|
11
12
|
pass
|
kafka/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.1.
|
|
1
|
+
__version__ = '2.1.2'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: kafka-python
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -37,6 +37,7 @@ Provides-Extra: testing
|
|
|
37
37
|
Requires-Dist: pytest; extra == "testing"
|
|
38
38
|
Requires-Dist: mock; python_version < "3.3" and extra == "testing"
|
|
39
39
|
Requires-Dist: pytest-mock; extra == "testing"
|
|
40
|
+
Requires-Dist: pytest-timeout; extra == "testing"
|
|
40
41
|
|
|
41
42
|
Kafka Python client
|
|
42
43
|
------------------------
|
|
@@ -2,13 +2,13 @@ kafka/__init__.py,sha256=4dvHKZAxmD_4tfJ5wGcRV2X78vPcm8vsUoqceULevjA,1077
|
|
|
2
2
|
kafka/client_async.py,sha256=e9RsJXXPRajxODz5KtBAndiEqJytdP5xHWeb157l4xM,54921
|
|
3
3
|
kafka/cluster.py,sha256=tFv8JQfloV6tJ4Yghp5gTXpvcJjL-kJNREVijCxal44,15828
|
|
4
4
|
kafka/codec.py,sha256=8NZpnehzNrhSBIjzbPVSvyFbSeLAqEntE7BfVHu-_9I,10036
|
|
5
|
-
kafka/conn.py,sha256=
|
|
5
|
+
kafka/conn.py,sha256=3DZ-Rv_OFvlQa6iaFkbiyrZkI9QCAp9k1HRRjPjM-jE,68080
|
|
6
6
|
kafka/errors.py,sha256=LBi6SMBV-4bkJsNJhEDuClfe0pJLUvc__bqGkRyVqX0,34550
|
|
7
|
-
kafka/future.py,sha256=
|
|
7
|
+
kafka/future.py,sha256=ZQStbfUYIPJRrgMfAWxxjrIRVxsw4WCtSR0J0bkyGno,2847
|
|
8
8
|
kafka/socks5_wrapper.py,sha256=6woOaCTJXJ5e89_zdyW5BjOpyE4rCbYFH-kd-FeuPuk,9827
|
|
9
9
|
kafka/structs.py,sha256=SJGzmLdV21jZyQ7247k0WFy16UiusgTHK3I-e4qzI-E,3058
|
|
10
10
|
kafka/util.py,sha256=YvnY5HeXcg2k1sWSuH9xIC19D6OTeDWZPYBphjtICzA,2509
|
|
11
|
-
kafka/version.py,sha256=
|
|
11
|
+
kafka/version.py,sha256=m5qImnzcnIhayvILFVqEnXPYsN-vE0vxokygykKhRfw,22
|
|
12
12
|
kafka/admin/__init__.py,sha256=S_XxqyyV480_yXhttK79XZqNAmZyXRjspd3SoqYykE8,720
|
|
13
13
|
kafka/admin/acl_resource.py,sha256=ak_dUsSni4SyP0ORbSKenZpwTy0Ykxq3FSt_9XgLR8k,8265
|
|
14
14
|
kafka/admin/client.py,sha256=BBHHoOEwswCeEVOQ7zj1JFybeDF7x2pQCpYh-gdhcVA,78933
|
|
@@ -16,9 +16,9 @@ kafka/admin/config_resource.py,sha256=_JZWN_Q7jbuTtq2kdfHxWyTt_jI1LI-xnVGsf6oYGy
|
|
|
16
16
|
kafka/admin/new_partitions.py,sha256=rYSb7S6VL706ZauSmiN5J9GDsep0HYRmkkAZUgT2JIg,757
|
|
17
17
|
kafka/admin/new_topic.py,sha256=fvezLP9JXumqX-nU27Fgo0tj4d85ybcJgKluQImm3-0,1306
|
|
18
18
|
kafka/consumer/__init__.py,sha256=NDdvtyuJgFyQZahqL9i5sYXGP6rOMIXWwHQEaZ1fCcs,122
|
|
19
|
-
kafka/consumer/fetcher.py,sha256=
|
|
20
|
-
kafka/consumer/group.py,sha256=
|
|
21
|
-
kafka/consumer/subscription_state.py,sha256=
|
|
19
|
+
kafka/consumer/fetcher.py,sha256=I_vm-qRqOo1X18H_J2uxjsg21ko6vlBUYS9_wDyxgVQ,59664
|
|
20
|
+
kafka/consumer/group.py,sha256=z9GoDQZ90cQlH5dtnuI3BsMsREdtpY2ybpkYCxpCVK8,57718
|
|
21
|
+
kafka/consumer/subscription_state.py,sha256=I_4SZR4mUBLqoxHj0DFnMu6Idj44eECb6_j6gPM-Dn0,21452
|
|
22
22
|
kafka/coordinator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
kafka/coordinator/base.py,sha256=grtpvkeR_03GRxLUo71PEZCvIA4ZH1N92ggYMw_LXB4,48877
|
|
24
24
|
kafka/coordinator/consumer.py,sha256=LJKZ7ZxjWBaG_wiWNMLZ2G59_baLS3sCB-HElEEPgH4,42708
|
|
@@ -31,19 +31,19 @@ kafka/coordinator/assignors/roundrobin.py,sha256=Xt_TOvCtcdozjZSg1cxixLAPyWz1aTp
|
|
|
31
31
|
kafka/coordinator/assignors/sticky/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
32
|
kafka/coordinator/assignors/sticky/partition_movements.py,sha256=npydNO-YCG_cv--U--9CPTLGTbTWahiw_Ek295ayBjQ,6476
|
|
33
33
|
kafka/coordinator/assignors/sticky/sorted_set.py,sha256=lOckfQ7vcOMNnIx5WjfHhKC_MgToeOxbp9vc_4tPIzs,1904
|
|
34
|
-
kafka/coordinator/assignors/sticky/sticky_assignor.py,sha256=
|
|
34
|
+
kafka/coordinator/assignors/sticky/sticky_assignor.py,sha256=p5gDou3Gom7bUSLF5zpilihNPiT-fqJl1J8QxykqqMw,34216
|
|
35
35
|
kafka/metrics/__init__.py,sha256=b82LCjV5BgisjmIc3pn11CqFpme5grtIFHWiH8C_R0U,574
|
|
36
|
-
kafka/metrics/compound_stat.py,sha256=
|
|
36
|
+
kafka/metrics/compound_stat.py,sha256=bBpjfL8QN7XucKpABMd5lqXZcEwVxMSSmfkVUDm9UGU,814
|
|
37
37
|
kafka/metrics/dict_reporter.py,sha256=OvZ6SUFp-Yk3tNaWbC0ul0WXncp42ymg8bHw3O6MITA,2567
|
|
38
38
|
kafka/metrics/kafka_metric.py,sha256=fnkHEmooLjCHRoAtti6rOymQLLMN1D276ma1bYAFJDY,933
|
|
39
39
|
kafka/metrics/measurable.py,sha256=g5mp1c9816SRgJdgHXklTNqDoDnbeYp-opjoV3DOr7Q,770
|
|
40
|
-
kafka/metrics/measurable_stat.py,sha256=
|
|
40
|
+
kafka/metrics/measurable_stat.py,sha256=Y4D7yrg07E9HqZlqh_EgeVnEEk4DRoNyKEoteEicssU,542
|
|
41
41
|
kafka/metrics/metric_config.py,sha256=SsibZd09icYgqLrMhXXW-pQVICPn0yYADrD8txdIMw0,1154
|
|
42
42
|
kafka/metrics/metric_name.py,sha256=7GLhWE0d4Iq_WGXM3U0J4BU5aXZO9SKDj4qorYjo2iM,3423
|
|
43
43
|
kafka/metrics/metrics.py,sha256=EAuMd-OLeSX3IS16NvC3w2tpIEwvCPedPwQ1gyM0C7E,10383
|
|
44
|
-
kafka/metrics/metrics_reporter.py,sha256=
|
|
44
|
+
kafka/metrics/metrics_reporter.py,sha256=hxAs01C5Gj_orStdgHUOYSs4-kOI4xfu0MOkYyuX28s,1437
|
|
45
45
|
kafka/metrics/quota.py,sha256=sMtSym9p0P9qz-YD0pACaEC_CpX__25cuL0edAroNBY,1132
|
|
46
|
-
kafka/metrics/stat.py,sha256=
|
|
46
|
+
kafka/metrics/stat.py,sha256=eos8xrmz7vgBnIk-8LyqpbEsBbyqEEdJ_CrDzEVGEaU,667
|
|
47
47
|
kafka/metrics/stats/__init__.py,sha256=sHcT6pvQCt-s_aow5_QRy9Z5bRV4ShBCZlin51f--Ro,629
|
|
48
48
|
kafka/metrics/stats/avg.py,sha256=WdyAFz37aQhvzIqkvbP4SGUDz9gZ-eua_Urhygjc2xU,678
|
|
49
49
|
kafka/metrics/stats/count.py,sha256=dy5sdPVLOwsiVcfOawEx7EOyjSTXxUKqsJl84sjVZbQ,487
|
|
@@ -53,7 +53,7 @@ kafka/metrics/stats/min_stat.py,sha256=gI0d7RUJB5En7PS_TT3WZ_gJl8tOZi81o-F0JD3S_
|
|
|
53
53
|
kafka/metrics/stats/percentile.py,sha256=ZQoda6vpS9v5LopQJL64SyCWO9160SVELxQ9S1KKit8,342
|
|
54
54
|
kafka/metrics/stats/percentiles.py,sha256=n4Uqt7qyRUkrkOWZvymfKx-7ANvopDXgLeXH1QRC_rk,2901
|
|
55
55
|
kafka/metrics/stats/rate.py,sha256=-zkYp8kZrhy01hDaPCYcKqvRycY1esyNPRQrqK_JH5s,4533
|
|
56
|
-
kafka/metrics/stats/sampled_stat.py,sha256=
|
|
56
|
+
kafka/metrics/stats/sampled_stat.py,sha256=L35kZyCTFwA7rar3aLGkUWE3kx0xoL6JfQvUgvKkpao,3496
|
|
57
57
|
kafka/metrics/stats/sensor.py,sha256=sxX2SxkTOuLA-VPIRu4LyJnJYsqpvEybd6oiVS2Lf5Y,5129
|
|
58
58
|
kafka/metrics/stats/total.py,sha256=tUq8rPW96OVzjVz0aOsBkEe2Ljkv6JaBa5TMCocYydg,418
|
|
59
59
|
kafka/partitioner/__init__.py,sha256=Fks3C5_kokVWYw1Ad5wv0sVVzaaBtOejL-2bIL1yRII,158
|
|
@@ -65,9 +65,9 @@ kafka/producer/kafka.py,sha256=Y6BexiE5G0-3AhvWQaDbeIB2a3PeYiElu67ymgOMllk,39947
|
|
|
65
65
|
kafka/producer/record_accumulator.py,sha256=PtLmcTtmZWikeStmV5tFNH0ABIkUQT3SO2n8zVcZFcc,25155
|
|
66
66
|
kafka/producer/sender.py,sha256=6vKfmPgzcjksFlT53n59so5KCewlqk_wov0i8rL-JWQ,21521
|
|
67
67
|
kafka/protocol/__init__.py,sha256=T1RBBlTH3zze0Cr1RqemPD4Z1b3IUDRmLOBfZTsPgLs,1184
|
|
68
|
-
kafka/protocol/abstract.py,sha256=
|
|
68
|
+
kafka/protocol/abstract.py,sha256=uOnuf6D8OTkL31Tp2QXG3VlzDPHVELGzM_bpSVa-_iw,424
|
|
69
69
|
kafka/protocol/admin.py,sha256=pXcxYADdI8aa9emwl6OYAAoAf6_PFVYvM8neHruo880,30692
|
|
70
|
-
kafka/protocol/api.py,sha256=
|
|
70
|
+
kafka/protocol/api.py,sha256=dPtYU1VPUd5nCzc5AfVgtRSEqZw2ejoTCxcjgjv2TNc,3786
|
|
71
71
|
kafka/protocol/api_versions.py,sha256=guLhFqRbdAcJ4hIjA5o0UtjlaG9dN_9BeOq3uA9xg0I,2246
|
|
72
72
|
kafka/protocol/broker_api_versions.py,sha256=lWOcGgPEVyhxAeVgs5koLQLGCAPo8w5M1xTCsqUzYBA,15778
|
|
73
73
|
kafka/protocol/commit.py,sha256=-COlx8lTVCI6Zg4ZebDnsX4Wy_V69Kjw8V85FRd3Ics,8627
|
|
@@ -88,13 +88,13 @@ kafka/protocol/struct.py,sha256=DxktwrPp1pj4b7Vne2H5n-xWjgx9jpCmf0ydZkeIjoY,2380
|
|
|
88
88
|
kafka/protocol/types.py,sha256=f-lwfCqsJulYnBT1loek_KbMnZZqItN4YRIONjg3kbE,10244
|
|
89
89
|
kafka/record/__init__.py,sha256=Q20hP_R5XX3AEnAlPkpoWzTLShESvxUT2OLXmI-JYEQ,129
|
|
90
90
|
kafka/record/_crc32c.py,sha256=Ok-P62Yvg6D6rMGM9Z56OMjZWQlnps4xBbakg-sdxvI,5761
|
|
91
|
-
kafka/record/abc.py,sha256=
|
|
92
|
-
kafka/record/default_records.py,sha256=
|
|
93
|
-
kafka/record/legacy_records.py,sha256=
|
|
94
|
-
kafka/record/memory_records.py,sha256=
|
|
91
|
+
kafka/record/abc.py,sha256=Hk5yJQO4aMhdJ68DMod5nLK1O8y-EtVf4kerEte66_U,3866
|
|
92
|
+
kafka/record/default_records.py,sha256=mqKaK3U_1PGVP5oCMAF_BB1UdAz5GUSu-_nqa01xV50,22789
|
|
93
|
+
kafka/record/legacy_records.py,sha256=My2tf5AXbSxanZvzd6WPK7p0kAmh4RB4qf0-AV2yKH8,18663
|
|
94
|
+
kafka/record/memory_records.py,sha256=HzpVNGxC4Yxl9RWpFL3nUt2CXyIAU3lsw1tFjKjrU1o,6490
|
|
95
95
|
kafka/record/util.py,sha256=LDajBWdYVetmXts_t9Q76CxEx7njgC9LnjMgz9yPEMM,3556
|
|
96
96
|
kafka/sasl/__init__.py,sha256=wUUGIKRe52J6Qekj7hSypg44vWTrkYsEdVafQC7cX5s,1106
|
|
97
|
-
kafka/sasl/abc.py,sha256=
|
|
97
|
+
kafka/sasl/abc.py,sha256=R0BZOk3AYEGyehiGbbg-LMRvFAlWZsh0fBiESgUpBYw,657
|
|
98
98
|
kafka/sasl/gssapi.py,sha256=joeQjWVDujcG8cdaRdUagSy_KoPhpkzXuVDi8OBqId0,4019
|
|
99
99
|
kafka/sasl/msk.py,sha256=ndUZqPTdgItptiRimVlUAGuFZz1cerQc1KufYMIcPkg,7684
|
|
100
100
|
kafka/sasl/oauth.py,sha256=XuUBxSoU9GCwGkSLFpmKr6-Yw_zDll3BdzO7jnSgng4,2969
|
|
@@ -108,7 +108,7 @@ kafka/vendor/enum34.py,sha256=-u-lxAiJMt6ru4Do7NUDY9OpeWkYJMksb2xengJawFE,31204
|
|
|
108
108
|
kafka/vendor/selectors34.py,sha256=gxejLO4eXf8mRSGXaQiknPig3GdX1rtsZiYOQJVuAy8,20594
|
|
109
109
|
kafka/vendor/six.py,sha256=lLBa9_HrANP5BMZ7twEzg1M3wofwPmXyptuWmHX0brY,34826
|
|
110
110
|
kafka/vendor/socketpair.py,sha256=Fi3PoY1Okkppab720wFk1BhHXyjcw7hi5DwhqrYZH2Y,2737
|
|
111
|
-
kafka_python-2.1.
|
|
112
|
-
kafka_python-2.1.
|
|
113
|
-
kafka_python-2.1.
|
|
114
|
-
kafka_python-2.1.
|
|
111
|
+
kafka_python-2.1.2.dist-info/METADATA,sha256=Fz6e2KUqnM3a4CGyL7r6WXW9YCxLkrF8QVfENC3nygk,9075
|
|
112
|
+
kafka_python-2.1.2.dist-info/WHEEL,sha256=Kol19cahXavY536r5Aj6aAgK_6CmctrOu3bgNJMSNJA,109
|
|
113
|
+
kafka_python-2.1.2.dist-info/top_level.txt,sha256=IivJz7l5WHdLNDT6RIiVAlhjQzYRwGqBBmKHZ7WjPeM,6
|
|
114
|
+
kafka_python-2.1.2.dist-info/RECORD,,
|
|
File without changes
|