kafka-python 2.1.5__py2.py3-none-any.whl → 2.2.0__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/admin/client.py +6 -6
- kafka/client_async.py +40 -2
- kafka/cluster.py +18 -13
- kafka/conn.py +3 -2
- kafka/consumer/fetcher.py +290 -187
- kafka/consumer/group.py +54 -52
- kafka/consumer/subscription_state.py +79 -35
- kafka/coordinator/base.py +37 -19
- kafka/coordinator/consumer.py +22 -31
- kafka/errors.py +68 -93
- kafka/producer/future.py +3 -3
- kafka/producer/kafka.py +265 -30
- kafka/producer/record_accumulator.py +293 -187
- kafka/producer/sender.py +347 -74
- kafka/producer/transaction_manager.py +981 -0
- kafka/protocol/add_offsets_to_txn.py +59 -0
- kafka/protocol/add_partitions_to_txn.py +63 -0
- kafka/protocol/end_txn.py +58 -0
- kafka/protocol/fetch.py +6 -0
- kafka/protocol/group.py +17 -3
- kafka/protocol/init_producer_id.py +46 -0
- kafka/protocol/txn_offset_commit.py +78 -0
- kafka/record/abc.py +10 -0
- kafka/record/default_records.py +101 -12
- kafka/record/legacy_records.py +12 -3
- kafka/record/memory_records.py +54 -6
- kafka/version.py +1 -1
- {kafka_python-2.1.5.dist-info → kafka_python-2.2.0.dist-info}/METADATA +1 -1
- {kafka_python-2.1.5.dist-info → kafka_python-2.2.0.dist-info}/RECORD +31 -25
- {kafka_python-2.1.5.dist-info → kafka_python-2.2.0.dist-info}/WHEEL +1 -1
- {kafka_python-2.1.5.dist-info → kafka_python-2.2.0.dist-info}/top_level.txt +0 -0
kafka/admin/client.py
CHANGED
|
@@ -15,7 +15,7 @@ from kafka.client_async import KafkaClient, selectors
|
|
|
15
15
|
from kafka.coordinator.protocol import ConsumerProtocolMemberMetadata, ConsumerProtocolMemberAssignment, ConsumerProtocol
|
|
16
16
|
import kafka.errors as Errors
|
|
17
17
|
from kafka.errors import (
|
|
18
|
-
IncompatibleBrokerVersion, KafkaConfigurationError,
|
|
18
|
+
IncompatibleBrokerVersion, KafkaConfigurationError, UnknownTopicOrPartitionError,
|
|
19
19
|
UnrecognizedBrokerVersion, IllegalArgumentError)
|
|
20
20
|
from kafka.metrics import MetricConfig, Metrics
|
|
21
21
|
from kafka.protocol.admin import (
|
|
@@ -411,7 +411,7 @@ class KafkaAdminClient(object):
|
|
|
411
411
|
# extra values (usually the error_message)
|
|
412
412
|
for topic, error_code in map(lambda e: e[:2], topic_error_tuples):
|
|
413
413
|
error_type = Errors.for_code(error_code)
|
|
414
|
-
if tries and error_type is NotControllerError:
|
|
414
|
+
if tries and error_type is Errors.NotControllerError:
|
|
415
415
|
# No need to inspect the rest of the errors for
|
|
416
416
|
# non-retriable errors because NotControllerError should
|
|
417
417
|
# either be thrown for all errors or no errors.
|
|
@@ -431,13 +431,13 @@ class KafkaAdminClient(object):
|
|
|
431
431
|
for topic, partition_results in response.replication_election_results:
|
|
432
432
|
for partition_id, error_code in map(lambda e: e[:2], partition_results):
|
|
433
433
|
error_type = Errors.for_code(error_code)
|
|
434
|
-
if tries and error_type is NotControllerError:
|
|
434
|
+
if tries and error_type is Errors.NotControllerError:
|
|
435
435
|
# No need to inspect the rest of the errors for
|
|
436
436
|
# non-retriable errors because NotControllerError should
|
|
437
437
|
# either be thrown for all errors or no errors.
|
|
438
438
|
self._refresh_controller_id()
|
|
439
439
|
return False
|
|
440
|
-
elif error_type not in
|
|
440
|
+
elif error_type not in (Errors.NoError, Errors.ElectionNotNeededError):
|
|
441
441
|
raise error_type(
|
|
442
442
|
"Request '{}' failed with response '{}'."
|
|
443
443
|
.format(request, response))
|
|
@@ -1460,9 +1460,9 @@ class KafkaAdminClient(object):
|
|
|
1460
1460
|
list: List of tuples of Consumer Groups.
|
|
1461
1461
|
|
|
1462
1462
|
Raises:
|
|
1463
|
-
|
|
1463
|
+
CoordinatorNotAvailableError: The coordinator is not
|
|
1464
1464
|
available, so cannot process requests.
|
|
1465
|
-
|
|
1465
|
+
CoordinatorLoadInProgressError: The coordinator is loading and
|
|
1466
1466
|
hence can't process requests.
|
|
1467
1467
|
"""
|
|
1468
1468
|
# While we return a list, internally use a set to prevent duplicates
|
kafka/client_async.py
CHANGED
|
@@ -27,7 +27,7 @@ from kafka.metrics.stats import Avg, Count, Rate
|
|
|
27
27
|
from kafka.metrics.stats.rate import TimeUnit
|
|
28
28
|
from kafka.protocol.broker_api_versions import BROKER_API_VERSIONS
|
|
29
29
|
from kafka.protocol.metadata import MetadataRequest
|
|
30
|
-
from kafka.util import Dict, WeakMethod, ensure_valid_topic_name
|
|
30
|
+
from kafka.util import Dict, WeakMethod, ensure_valid_topic_name, timeout_ms_fn
|
|
31
31
|
# Although this looks unused, it actually monkey-patches socket.socketpair()
|
|
32
32
|
# and should be left in as long as we're using socket.socketpair() in this file
|
|
33
33
|
from kafka.vendor import socketpair # noqa: F401
|
|
@@ -400,6 +400,11 @@ class KafkaClient(object):
|
|
|
400
400
|
return True
|
|
401
401
|
return False
|
|
402
402
|
|
|
403
|
+
def connection_failed(self, node_id):
|
|
404
|
+
if node_id not in self._conns:
|
|
405
|
+
return False
|
|
406
|
+
return self._conns[node_id].connect_failed()
|
|
407
|
+
|
|
403
408
|
def _should_recycle_connection(self, conn):
|
|
404
409
|
# Never recycle unless disconnected
|
|
405
410
|
if not conn.disconnected():
|
|
@@ -1110,7 +1115,7 @@ class KafkaClient(object):
|
|
|
1110
1115
|
return version
|
|
1111
1116
|
|
|
1112
1117
|
def wakeup(self):
|
|
1113
|
-
if self._waking or self._wake_w is None:
|
|
1118
|
+
if self._closed or self._waking or self._wake_w is None:
|
|
1114
1119
|
return
|
|
1115
1120
|
with self._wake_lock:
|
|
1116
1121
|
try:
|
|
@@ -1157,6 +1162,39 @@ class KafkaClient(object):
|
|
|
1157
1162
|
else:
|
|
1158
1163
|
return False
|
|
1159
1164
|
|
|
1165
|
+
def await_ready(self, node_id, timeout_ms=30000):
|
|
1166
|
+
"""
|
|
1167
|
+
Invokes `poll` to discard pending disconnects, followed by `client.ready` and 0 or more `client.poll`
|
|
1168
|
+
invocations until the connection to `node` is ready, the timeoutMs expires or the connection fails.
|
|
1169
|
+
|
|
1170
|
+
It returns `true` if the call completes normally or `false` if the timeoutMs expires. If the connection fails,
|
|
1171
|
+
an `IOException` is thrown instead. Note that if the `NetworkClient` has been configured with a positive
|
|
1172
|
+
connection timeoutMs, it is possible for this method to raise an `IOException` for a previous connection which
|
|
1173
|
+
has recently disconnected.
|
|
1174
|
+
|
|
1175
|
+
This method is useful for implementing blocking behaviour on top of the non-blocking `NetworkClient`, use it with
|
|
1176
|
+
care.
|
|
1177
|
+
"""
|
|
1178
|
+
inner_timeout_ms = timeout_ms_fn(timeout_ms, None)
|
|
1179
|
+
self.poll(timeout_ms=0)
|
|
1180
|
+
if self.is_ready(node_id):
|
|
1181
|
+
return True
|
|
1182
|
+
|
|
1183
|
+
while not self.is_ready(node_id) and inner_timeout_ms() > 0:
|
|
1184
|
+
if self.connection_failed(node_id):
|
|
1185
|
+
raise Errors.KafkaConnectionError("Connection to %s failed." % (node_id,))
|
|
1186
|
+
self.maybe_connect(node_id)
|
|
1187
|
+
self.poll(timeout_ms=inner_timeout_ms())
|
|
1188
|
+
return self.is_ready(node_id)
|
|
1189
|
+
|
|
1190
|
+
def send_and_receive(self, node_id, request):
|
|
1191
|
+
future = self.send(node_id, request)
|
|
1192
|
+
self.poll(future=future)
|
|
1193
|
+
assert future.is_done
|
|
1194
|
+
if future.failed():
|
|
1195
|
+
raise future.exception
|
|
1196
|
+
return future.value
|
|
1197
|
+
|
|
1160
1198
|
|
|
1161
1199
|
# OrderedDict requires python2.7+
|
|
1162
1200
|
try:
|
kafka/cluster.py
CHANGED
|
@@ -47,7 +47,7 @@ class ClusterMetadata(object):
|
|
|
47
47
|
self._brokers = {} # node_id -> BrokerMetadata
|
|
48
48
|
self._partitions = {} # topic -> partition -> PartitionMetadata
|
|
49
49
|
self._broker_partitions = collections.defaultdict(set) # node_id -> {TopicPartition...}
|
|
50
|
-
self.
|
|
50
|
+
self._coordinators = {} # (coord_type, coord_key) -> node_id
|
|
51
51
|
self._last_refresh_ms = 0
|
|
52
52
|
self._last_successful_refresh_ms = 0
|
|
53
53
|
self._need_update = True
|
|
@@ -167,7 +167,7 @@ class ClusterMetadata(object):
|
|
|
167
167
|
node_id (int or str) for group coordinator, -1 if coordinator unknown
|
|
168
168
|
None if the group does not exist.
|
|
169
169
|
"""
|
|
170
|
-
return self.
|
|
170
|
+
return self._coordinators.get(('group', group))
|
|
171
171
|
|
|
172
172
|
def ttl(self):
|
|
173
173
|
"""Milliseconds until metadata should be refreshed"""
|
|
@@ -202,6 +202,10 @@ class ClusterMetadata(object):
|
|
|
202
202
|
self._future = Future()
|
|
203
203
|
return self._future
|
|
204
204
|
|
|
205
|
+
@property
|
|
206
|
+
def need_update(self):
|
|
207
|
+
return self._need_update
|
|
208
|
+
|
|
205
209
|
def topics(self, exclude_internal_topics=True):
|
|
206
210
|
"""Get set of known topics.
|
|
207
211
|
|
|
@@ -364,24 +368,25 @@ class ClusterMetadata(object):
|
|
|
364
368
|
"""Remove a previously added listener callback"""
|
|
365
369
|
self._listeners.remove(listener)
|
|
366
370
|
|
|
367
|
-
def
|
|
368
|
-
"""Update with metadata for a group coordinator
|
|
371
|
+
def add_coordinator(self, response, coord_type, coord_key):
|
|
372
|
+
"""Update with metadata for a group or txn coordinator
|
|
369
373
|
|
|
370
374
|
Arguments:
|
|
371
|
-
group (str): name of group from FindCoordinatorRequest
|
|
372
375
|
response (FindCoordinatorResponse): broker response
|
|
376
|
+
coord_type (str): 'group' or 'transaction'
|
|
377
|
+
coord_key (str): consumer_group or transactional_id
|
|
373
378
|
|
|
374
379
|
Returns:
|
|
375
380
|
string: coordinator node_id if metadata is updated, None on error
|
|
376
381
|
"""
|
|
377
|
-
log.debug("Updating coordinator for %s: %s",
|
|
382
|
+
log.debug("Updating coordinator for %s/%s: %s", coord_type, coord_key, response)
|
|
378
383
|
error_type = Errors.for_code(response.error_code)
|
|
379
384
|
if error_type is not Errors.NoError:
|
|
380
385
|
log.error("FindCoordinatorResponse error: %s", error_type)
|
|
381
|
-
self.
|
|
386
|
+
self._coordinators[(coord_type, coord_key)] = -1
|
|
382
387
|
return
|
|
383
388
|
|
|
384
|
-
# Use a coordinator-specific node id so that
|
|
389
|
+
# Use a coordinator-specific node id so that requests
|
|
385
390
|
# get a dedicated connection
|
|
386
391
|
node_id = 'coordinator-{}'.format(response.coordinator_id)
|
|
387
392
|
coordinator = BrokerMetadata(
|
|
@@ -390,9 +395,9 @@ class ClusterMetadata(object):
|
|
|
390
395
|
response.port,
|
|
391
396
|
None)
|
|
392
397
|
|
|
393
|
-
log.info("
|
|
398
|
+
log.info("Coordinator for %s/%s is %s", coord_type, coord_key, coordinator)
|
|
394
399
|
self._coordinator_brokers[node_id] = coordinator
|
|
395
|
-
self.
|
|
400
|
+
self._coordinators[(coord_type, coord_key)] = node_id
|
|
396
401
|
return node_id
|
|
397
402
|
|
|
398
403
|
def with_partitions(self, partitions_to_add):
|
|
@@ -401,7 +406,7 @@ class ClusterMetadata(object):
|
|
|
401
406
|
new_metadata._brokers = copy.deepcopy(self._brokers)
|
|
402
407
|
new_metadata._partitions = copy.deepcopy(self._partitions)
|
|
403
408
|
new_metadata._broker_partitions = copy.deepcopy(self._broker_partitions)
|
|
404
|
-
new_metadata.
|
|
409
|
+
new_metadata._coordinators = copy.deepcopy(self._coordinators)
|
|
405
410
|
new_metadata.internal_topics = copy.deepcopy(self.internal_topics)
|
|
406
411
|
new_metadata.unauthorized_topics = copy.deepcopy(self.unauthorized_topics)
|
|
407
412
|
|
|
@@ -415,5 +420,5 @@ class ClusterMetadata(object):
|
|
|
415
420
|
return new_metadata
|
|
416
421
|
|
|
417
422
|
def __str__(self):
|
|
418
|
-
return 'ClusterMetadata(brokers: %d, topics: %d,
|
|
419
|
-
(len(self._brokers), len(self._partitions), len(self.
|
|
423
|
+
return 'ClusterMetadata(brokers: %d, topics: %d, coordinators: %d)' % \
|
|
424
|
+
(len(self._brokers), len(self._partitions), len(self._coordinators))
|
kafka/conn.py
CHANGED
|
@@ -813,7 +813,7 @@ class BrokerConnection(object):
|
|
|
813
813
|
log.info('%s: %s', self, self._sasl_mechanism.auth_details())
|
|
814
814
|
return future.success(True)
|
|
815
815
|
else:
|
|
816
|
-
return future.failure(Errors.
|
|
816
|
+
return future.failure(Errors.SaslAuthenticationFailedError('Failed to authenticate via SASL %s' % self.config['sasl_mechanism']))
|
|
817
817
|
|
|
818
818
|
def blacked_out(self):
|
|
819
819
|
"""
|
|
@@ -934,7 +934,8 @@ class BrokerConnection(object):
|
|
|
934
934
|
if self.state is ConnectionStates.DISCONNECTED:
|
|
935
935
|
return
|
|
936
936
|
log.log(logging.ERROR if error else logging.INFO, '%s: Closing connection. %s', self, error or '')
|
|
937
|
-
|
|
937
|
+
if error:
|
|
938
|
+
self._update_reconnect_backoff()
|
|
938
939
|
self._api_versions_future = None
|
|
939
940
|
self._sasl_auth_future = None
|
|
940
941
|
self._init_sasl_mechanism()
|