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 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
- raise 'Unable to determine broker version.'
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 not self._subscriptions.is_fetchable(tp):
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
- next_offset = part_records[-1].offset + 1
386
- leader_epoch = part_records[-1].leader_epoch
387
-
388
- log.log(0, "Returning fetched records at offset %d for assigned"
389
- " partition %s and update position to %s (leader epoch %s)", position.offset,
390
- tp, next_offset, leader_epoch)
391
-
392
- for record in part_records:
393
- drained[tp].append(record)
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
- self._subscriptions.assignment[tp].position = OffsetAndMetadata(next_offset, '', -1)
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.fetch_offset,
405
+ " the current position is %d", tp, part.next_fetch_offset,
405
406
  position.offset)
406
407
 
407
- part.discard()
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
- unpacked = list(self._unpack_records(tp, records))
894
- parsed_records = self.PartitionRecords(fetch_offset, tp, unpacked)
895
- if unpacked:
896
- last_offset = unpacked[-1].offset
897
- self._sensors.records_fetch_lag.record(highwater - last_offset)
898
- num_bytes = records.valid_bytes()
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.is_retriable:
797
+ elif getattr(error_type, 'retriable', False):
938
798
  log.debug("Retriable error fetching partition %s: %s", tp, error_type())
939
- if error_type.invalid_metadata:
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
- completed_fetch.metric_aggregator.record(tp, num_bytes, records_count)
805
+ if parsed_records is None:
806
+ completed_fetch.metric_aggregator.record(tp, 0, 0)
807
+
808
+ return None
946
809
 
947
- return parsed_records
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, messages):
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.messages = messages
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
- for i, msg in enumerate(messages):
959
- if msg.offset < fetch_offset:
960
- log.debug("Skipping message offset: %s (expecting %s)",
961
- msg.offset, fetch_offset)
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
- self.message_idx = 0
968
- self.messages = None
838
+ return False
969
839
 
970
- # For truthiness evaluation we need to define __len__ or __nonzero__
971
- def __len__(self):
972
- if self.messages is None or self.message_idx >= len(self.messages):
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 discard(self):
977
- self.messages = None
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
- if not len(self):
981
- return []
982
- if n is None or n > len(self):
983
- n = len(self)
984
- next_idx = self.message_idx + n
985
- res = self.messages[self.message_idx:next_idx]
986
- self.message_idx = next_idx
987
- # fetch_offset should be incremented by 1 to parallel the
988
- # subscription position (also incremented by 1)
989
- self.fetch_offset = max(self.fetch_offset, res[-1].offset + 1)
990
- return res
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.total_bytes = 0
1210
- self.total_records = 0
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(six.viewitems(partitions_by_topic), generation)
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.value = value
34
- self.is_done = True
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
- self.exception = e if type(e) is not type else e()
42
- assert isinstance(self.exception, BaseException), (
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.is_done = True
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
- if self.is_done and not self.exception:
52
- self._call_backs('callback', [f], self.value)
53
- else:
54
- self._callbacks.append(f)
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
- if self.is_done and self.exception:
61
- self._call_backs('errback', [f], self.exception)
62
- else:
63
- self._errbacks.append(f)
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):
@@ -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
@@ -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 = []
@@ -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
@@ -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,
@@ -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, _, crc, _, attrs, timestamp = header
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
 
@@ -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 = 0
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.0'
1
+ __version__ = '2.1.2'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: kafka-python
3
- Version: 2.1.0
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=5oAV6zI1PFSkWbDxy09Qm0Tji51sFBuA7-gEN_7iUOw,67712
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=uJJLfKMFsdEHgHSyvCzQe_AXNrToiZE-MynZVNhk9qc,2474
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=LbU43-7hsLmdXWI0wTAl3y6D3Tr7CbJiDOLXwl7clj8,22
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=idTu7IZKsq9vA3YLtq0C1FuDSchk9Q9IEN0ANkP-Smg,61162
20
- kafka/consumer/group.py,sha256=FuXOahsTY6UiSzv8K5KdG1sZPitjlZ62VjuORJ57Or4,57928
21
- kafka/consumer/subscription_state.py,sha256=6Z_UySnvv9ww_UFRGv6GO9781A1rBqyf5R9iaCGEaGc,21737
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=dNEAkrGew1xPxlpZP_CYys4ncjCAuwenjqXC5c-P0Rg,34217
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=CNnP71sNnViUhCDFHimdlXBb8G-PXrbqg6FfSS-SkVc,776
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=NcOQfOieQV8m6mMClDFJDY1ibE-RmIPrth15W5XPDdU,503
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=2qZRLiyOUzB-2ULBtOhXOtjU9phElIlundjPluYYXgE,1398
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=T_YGImowGnUnGgeNZ-r4buk1PdM_7NHG15PzTHieyZo,628
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=rb42q6MIkAm2LJh4H4QoC6OmP_zJ-3jbBrva0kf_0J0,3458
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=LUYVZkjlEnZzvklkgrsfz8iZKNSFhS8cP-Q-N0jqdQo,385
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=JU8YZcrMRbSRUSu75nc1YDFlr3t_xx9j2HgPg4ol9yA,3751
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=14m5X2BfXo1XUhEZh04gZXixhgC7ktGMzBzYsTnzaZY,3465
92
- kafka/record/default_records.py,sha256=As5lyYIWE6whCFG-GlPXWyjkrTWAJ0-90JtxIqkfxk0,22510
93
- kafka/record/legacy_records.py,sha256=uFRdGC8W4NTFpiQyV5TZSaEwjw5AEbbm5G2W-pxvnkA,17820
94
- kafka/record/memory_records.py,sha256=VjUsbLtIU0y5HM9eYlUjjpCFMwTej67uhrIL5CgtYq8,6344
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=2HVLMDNPZFVjdyYz9GtecUTE5aaeQ6OpVKAcxWf403U,618
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.0.dist-info/METADATA,sha256=6LGKaQ4Ccqys0cGCaMT73q-ywqaH1C-D0JrF5MkM024,9025
112
- kafka_python-2.1.0.dist-info/WHEEL,sha256=SrDKpSbFN1G94qcmBqS9nyHcDMp9cUS9OC06hC0G3G0,109
113
- kafka_python-2.1.0.dist-info/top_level.txt,sha256=IivJz7l5WHdLNDT6RIiVAlhjQzYRwGqBBmKHZ7WjPeM,6
114
- kafka_python-2.1.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (76.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any