kafka-python 2.0.2__py2.py3-none-any.whl → 2.0.3__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/__init__.py +1 -1
- kafka/admin/client.py +29 -8
- kafka/codec.py +1 -1
- kafka/conn.py +5 -4
- kafka/consumer/fetcher.py +12 -3
- kafka/consumer/group.py +7 -3
- kafka/coordinator/assignors/sticky/sticky_assignor.py +7 -3
- kafka/coordinator/base.py +6 -1
- kafka/producer/kafka.py +9 -6
- kafka/protocol/__init__.py +3 -0
- kafka/protocol/admin.py +173 -2
- kafka/protocol/api.py +42 -1
- kafka/protocol/parser.py +7 -14
- kafka/protocol/types.py +169 -2
- kafka/record/_crc32c.py +1 -1
- kafka/record/abc.py +1 -1
- kafka/record/legacy_records.py +1 -1
- kafka/vendor/selectors34.py +5 -1
- kafka/vendor/six.py +128 -21
- kafka/vendor/socketpair.py +17 -0
- kafka/version.py +1 -1
- {kafka_python-2.0.2.dist-info → kafka_python-2.0.3.dist-info}/METADATA +135 -75
- {kafka_python-2.0.2.dist-info → kafka_python-2.0.3.dist-info}/RECORD +26 -26
- {kafka_python-2.0.2.dist-info → kafka_python-2.0.3.dist-info}/WHEEL +1 -1
- {kafka_python-2.0.2.dist-info → kafka_python-2.0.3.dist-info}/LICENSE +0 -0
- {kafka_python-2.0.2.dist-info → kafka_python-2.0.3.dist-info}/top_level.txt +0 -0
kafka/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ __title__ = 'kafka'
|
|
|
4
4
|
from kafka.version import __version__
|
|
5
5
|
__author__ = 'Dana Powers'
|
|
6
6
|
__license__ = 'Apache License 2.0'
|
|
7
|
-
__copyright__ = 'Copyright
|
|
7
|
+
__copyright__ = 'Copyright 2025 Dana Powers, David Arthur, and Contributors'
|
|
8
8
|
|
|
9
9
|
# Set default logging handler to avoid "No handler found" warnings.
|
|
10
10
|
import logging
|
kafka/admin/client.py
CHANGED
|
@@ -20,7 +20,7 @@ from kafka.metrics import MetricConfig, Metrics
|
|
|
20
20
|
from kafka.protocol.admin import (
|
|
21
21
|
CreateTopicsRequest, DeleteTopicsRequest, DescribeConfigsRequest, AlterConfigsRequest, CreatePartitionsRequest,
|
|
22
22
|
ListGroupsRequest, DescribeGroupsRequest, DescribeAclsRequest, CreateAclsRequest, DeleteAclsRequest,
|
|
23
|
-
DeleteGroupsRequest
|
|
23
|
+
DeleteGroupsRequest, DescribeLogDirsRequest
|
|
24
24
|
)
|
|
25
25
|
from kafka.protocol.commit import GroupCoordinatorRequest, OffsetFetchRequest
|
|
26
26
|
from kafka.protocol.metadata import MetadataRequest
|
|
@@ -146,6 +146,7 @@ class KafkaAdminClient(object):
|
|
|
146
146
|
sasl mechanism handshake. Default: one of bootstrap servers
|
|
147
147
|
sasl_oauth_token_provider (AbstractTokenProvider): OAuthBearer token provider
|
|
148
148
|
instance. (See kafka.oauth.abstract). Default: None
|
|
149
|
+
kafka_client (callable): Custom class / callable for creating KafkaClient instances
|
|
149
150
|
|
|
150
151
|
"""
|
|
151
152
|
DEFAULT_CONFIG = {
|
|
@@ -186,6 +187,7 @@ class KafkaAdminClient(object):
|
|
|
186
187
|
'metric_reporters': [],
|
|
187
188
|
'metrics_num_samples': 2,
|
|
188
189
|
'metrics_sample_window_ms': 30000,
|
|
190
|
+
'kafka_client': KafkaClient,
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
def __init__(self, **configs):
|
|
@@ -205,9 +207,11 @@ class KafkaAdminClient(object):
|
|
|
205
207
|
reporters = [reporter() for reporter in self.config['metric_reporters']]
|
|
206
208
|
self._metrics = Metrics(metric_config, reporters)
|
|
207
209
|
|
|
208
|
-
self._client =
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
self._client = self.config['kafka_client'](
|
|
211
|
+
metrics=self._metrics,
|
|
212
|
+
metric_group_prefix='admin',
|
|
213
|
+
**self.config
|
|
214
|
+
)
|
|
211
215
|
self._client.check_version(timeout=(self.config['api_version_auto_timeout_ms'] / 1000))
|
|
212
216
|
|
|
213
217
|
# Get auto-discovered version from client if necessary
|
|
@@ -351,13 +355,14 @@ class KafkaAdminClient(object):
|
|
|
351
355
|
}
|
|
352
356
|
return groups_coordinators
|
|
353
357
|
|
|
354
|
-
def _send_request_to_node(self, node_id, request):
|
|
358
|
+
def _send_request_to_node(self, node_id, request, wakeup=True):
|
|
355
359
|
"""Send a Kafka protocol message to a specific broker.
|
|
356
360
|
|
|
357
361
|
Returns a future that may be polled for status and results.
|
|
358
362
|
|
|
359
363
|
:param node_id: The broker id to which to send the message.
|
|
360
364
|
:param request: The message to send.
|
|
365
|
+
:param wakeup: Optional flag to disable thread-wakeup.
|
|
361
366
|
:return: A future object that may be polled for status and results.
|
|
362
367
|
:exception: The exception if the message could not be sent.
|
|
363
368
|
"""
|
|
@@ -365,7 +370,7 @@ class KafkaAdminClient(object):
|
|
|
365
370
|
# poll until the connection to broker is ready, otherwise send()
|
|
366
371
|
# will fail with NodeNotReadyError
|
|
367
372
|
self._client.poll()
|
|
368
|
-
return self._client.send(node_id, request)
|
|
373
|
+
return self._client.send(node_id, request, wakeup)
|
|
369
374
|
|
|
370
375
|
def _send_request_to_controller(self, request):
|
|
371
376
|
"""Send a Kafka protocol message to the cluster controller.
|
|
@@ -1205,7 +1210,7 @@ class KafkaAdminClient(object):
|
|
|
1205
1210
|
|
|
1206
1211
|
:param response: an OffsetFetchResponse.
|
|
1207
1212
|
:return: A dictionary composed of TopicPartition keys and
|
|
1208
|
-
|
|
1213
|
+
OffsetAndMetadata values.
|
|
1209
1214
|
"""
|
|
1210
1215
|
if response.API_VERSION <= 3:
|
|
1211
1216
|
|
|
@@ -1219,7 +1224,7 @@ class KafkaAdminClient(object):
|
|
|
1219
1224
|
.format(response))
|
|
1220
1225
|
|
|
1221
1226
|
# transform response into a dictionary with TopicPartition keys and
|
|
1222
|
-
#
|
|
1227
|
+
# OffsetAndMetadata values--this is what the Java AdminClient returns
|
|
1223
1228
|
offsets = {}
|
|
1224
1229
|
for topic, partitions in response.topics:
|
|
1225
1230
|
for partition, offset, metadata, error_code in partitions:
|
|
@@ -1340,3 +1345,19 @@ class KafkaAdminClient(object):
|
|
|
1340
1345
|
|
|
1341
1346
|
if future.failed():
|
|
1342
1347
|
raise future.exception # pylint: disable-msg=raising-bad-type
|
|
1348
|
+
|
|
1349
|
+
def describe_log_dirs(self):
|
|
1350
|
+
"""Send a DescribeLogDirsRequest request to a broker.
|
|
1351
|
+
|
|
1352
|
+
:return: A message future
|
|
1353
|
+
"""
|
|
1354
|
+
version = self._matching_api_version(DescribeLogDirsRequest)
|
|
1355
|
+
if version <= 0:
|
|
1356
|
+
request = DescribeLogDirsRequest[version]()
|
|
1357
|
+
future = self._send_request_to_node(self._client.least_loaded_node(), request)
|
|
1358
|
+
self._wait_for_futures([future])
|
|
1359
|
+
else:
|
|
1360
|
+
raise NotImplementedError(
|
|
1361
|
+
"Support for DescribeLogDirsRequest_v{} has not yet been added to KafkaAdminClient."
|
|
1362
|
+
.format(version))
|
|
1363
|
+
return future.value
|
kafka/codec.py
CHANGED
|
@@ -187,7 +187,7 @@ def _detect_xerial_stream(payload):
|
|
|
187
187
|
The version is the version of this format as written by xerial,
|
|
188
188
|
in the wild this is currently 1 as such we only support v1.
|
|
189
189
|
|
|
190
|
-
Compat is there to claim the
|
|
190
|
+
Compat is there to claim the minimum supported version that
|
|
191
191
|
can read a xerial block stream, presently in the wild this is
|
|
192
192
|
1.
|
|
193
193
|
"""
|
kafka/conn.py
CHANGED
|
@@ -24,7 +24,7 @@ import kafka.errors as Errors
|
|
|
24
24
|
from kafka.future import Future
|
|
25
25
|
from kafka.metrics.stats import Avg, Count, Max, Rate
|
|
26
26
|
from kafka.oauth.abstract import AbstractTokenProvider
|
|
27
|
-
from kafka.protocol.admin import SaslHandShakeRequest, DescribeAclsRequest_v2
|
|
27
|
+
from kafka.protocol.admin import SaslHandShakeRequest, DescribeAclsRequest_v2, DescribeClientQuotasRequest
|
|
28
28
|
from kafka.protocol.commit import OffsetFetchRequest
|
|
29
29
|
from kafka.protocol.offset import OffsetRequest
|
|
30
30
|
from kafka.protocol.produce import ProduceRequest
|
|
@@ -78,7 +78,7 @@ except ImportError:
|
|
|
78
78
|
try:
|
|
79
79
|
import gssapi
|
|
80
80
|
from gssapi.raw.misc import GSSError
|
|
81
|
-
except ImportError:
|
|
81
|
+
except (ImportError, OSError):
|
|
82
82
|
#no gssapi available, will disable gssapi mechanism
|
|
83
83
|
gssapi = None
|
|
84
84
|
GSSError = None
|
|
@@ -496,7 +496,7 @@ class BrokerConnection(object):
|
|
|
496
496
|
try:
|
|
497
497
|
self._sock = self._ssl_context.wrap_socket(
|
|
498
498
|
self._sock,
|
|
499
|
-
server_hostname=self.host,
|
|
499
|
+
server_hostname=self.host.rstrip("."),
|
|
500
500
|
do_handshake_on_connect=False)
|
|
501
501
|
except ssl.SSLError as e:
|
|
502
502
|
log.exception('%s: Failed to wrap socket in SSLContext!', self)
|
|
@@ -916,7 +916,7 @@ class BrokerConnection(object):
|
|
|
916
916
|
with self._lock:
|
|
917
917
|
if self.state is ConnectionStates.DISCONNECTED:
|
|
918
918
|
return
|
|
919
|
-
log.
|
|
919
|
+
log.log(logging.ERROR if error else logging.INFO, '%s: Closing connection. %s', self, error or '')
|
|
920
920
|
self._update_reconnect_backoff()
|
|
921
921
|
self._sasl_auth_future = None
|
|
922
922
|
self._protocol = KafkaProtocol(
|
|
@@ -1169,6 +1169,7 @@ class BrokerConnection(object):
|
|
|
1169
1169
|
# in reverse order. As soon as we find one that works, return it
|
|
1170
1170
|
test_cases = [
|
|
1171
1171
|
# format (<broker version>, <needed struct>)
|
|
1172
|
+
((2, 6, 0), DescribeClientQuotasRequest[0]),
|
|
1172
1173
|
((2, 5, 0), DescribeAclsRequest_v2),
|
|
1173
1174
|
((2, 4, 0), ProduceRequest[8]),
|
|
1174
1175
|
((2, 3, 0), FetchRequest[11]),
|
kafka/consumer/fetcher.py
CHANGED
|
@@ -125,7 +125,7 @@ class Fetcher(six.Iterator):
|
|
|
125
125
|
log.debug("Sending FetchRequest to node %s", node_id)
|
|
126
126
|
future = self._client.send(node_id, request, wakeup=False)
|
|
127
127
|
future.add_callback(self._handle_fetch_response, request, time.time())
|
|
128
|
-
future.add_errback(
|
|
128
|
+
future.add_errback(self._handle_fetch_error, node_id)
|
|
129
129
|
futures.append(future)
|
|
130
130
|
self._fetch_futures.extend(futures)
|
|
131
131
|
self._clean_done_fetch_futures()
|
|
@@ -778,6 +778,14 @@ class Fetcher(six.Iterator):
|
|
|
778
778
|
self._sensors.fetch_throttle_time_sensor.record(response.throttle_time_ms)
|
|
779
779
|
self._sensors.fetch_latency.record((time.time() - send_time) * 1000)
|
|
780
780
|
|
|
781
|
+
def _handle_fetch_error(self, node_id, exception):
|
|
782
|
+
log.log(
|
|
783
|
+
logging.INFO if isinstance(exception, Errors.Cancelled) else logging.ERROR,
|
|
784
|
+
'Fetch to node %s failed: %s',
|
|
785
|
+
node_id,
|
|
786
|
+
exception
|
|
787
|
+
)
|
|
788
|
+
|
|
781
789
|
def _parse_fetched_data(self, completed_fetch):
|
|
782
790
|
tp = completed_fetch.topic_partition
|
|
783
791
|
fetch_offset = completed_fetch.fetched_offset
|
|
@@ -817,8 +825,9 @@ class Fetcher(six.Iterator):
|
|
|
817
825
|
position)
|
|
818
826
|
unpacked = list(self._unpack_message_set(tp, records))
|
|
819
827
|
parsed_records = self.PartitionRecords(fetch_offset, tp, unpacked)
|
|
820
|
-
|
|
821
|
-
|
|
828
|
+
if unpacked:
|
|
829
|
+
last_offset = unpacked[-1].offset
|
|
830
|
+
self._sensors.records_fetch_lag.record(highwater - last_offset)
|
|
822
831
|
num_bytes = records.valid_bytes()
|
|
823
832
|
records_count = len(unpacked)
|
|
824
833
|
elif records.size_in_bytes() > 0:
|
kafka/consumer/group.py
CHANGED
|
@@ -244,6 +244,7 @@ class KafkaConsumer(six.Iterator):
|
|
|
244
244
|
sasl mechanism handshake. Default: one of bootstrap servers
|
|
245
245
|
sasl_oauth_token_provider (AbstractTokenProvider): OAuthBearer token provider
|
|
246
246
|
instance. (See kafka.oauth.abstract). Default: None
|
|
247
|
+
kafka_client (callable): Custom class / callable for creating KafkaClient instances
|
|
247
248
|
|
|
248
249
|
Note:
|
|
249
250
|
Configuration parameters are described in more detail at
|
|
@@ -306,6 +307,7 @@ class KafkaConsumer(six.Iterator):
|
|
|
306
307
|
'sasl_kerberos_domain_name': None,
|
|
307
308
|
'sasl_oauth_token_provider': None,
|
|
308
309
|
'legacy_iterator': False, # enable to revert to < 1.4.7 iterator
|
|
310
|
+
'kafka_client': KafkaClient,
|
|
309
311
|
}
|
|
310
312
|
DEFAULT_SESSION_TIMEOUT_MS_0_9 = 30000
|
|
311
313
|
|
|
@@ -353,7 +355,7 @@ class KafkaConsumer(six.Iterator):
|
|
|
353
355
|
log.warning('use api_version=%s [tuple] -- "%s" as str is deprecated',
|
|
354
356
|
str(self.config['api_version']), str_version)
|
|
355
357
|
|
|
356
|
-
self._client =
|
|
358
|
+
self._client = self.config['kafka_client'](metrics=self._metrics, **self.config)
|
|
357
359
|
|
|
358
360
|
# Get auto-discovered version from client if necessary
|
|
359
361
|
if self.config['api_version'] is None:
|
|
@@ -651,7 +653,7 @@ class KafkaConsumer(six.Iterator):
|
|
|
651
653
|
# Poll for new data until the timeout expires
|
|
652
654
|
start = time.time()
|
|
653
655
|
remaining = timeout_ms
|
|
654
|
-
while
|
|
656
|
+
while not self._closed:
|
|
655
657
|
records = self._poll_once(remaining, max_records, update_offsets=update_offsets)
|
|
656
658
|
if records:
|
|
657
659
|
return records
|
|
@@ -660,7 +662,9 @@ class KafkaConsumer(six.Iterator):
|
|
|
660
662
|
remaining = timeout_ms - elapsed_ms
|
|
661
663
|
|
|
662
664
|
if remaining <= 0:
|
|
663
|
-
|
|
665
|
+
break
|
|
666
|
+
|
|
667
|
+
return {}
|
|
664
668
|
|
|
665
669
|
def _poll_once(self, timeout_ms, max_records, update_offsets=True):
|
|
666
670
|
"""Do one round of polling. In addition to checking for new data, this does
|
|
@@ -648,15 +648,19 @@ class StickyPartitionAssignor(AbstractPartitionAssignor):
|
|
|
648
648
|
|
|
649
649
|
@classmethod
|
|
650
650
|
def metadata(cls, topics):
|
|
651
|
-
|
|
651
|
+
return cls._metadata(topics, cls.member_assignment, cls.generation)
|
|
652
|
+
|
|
653
|
+
@classmethod
|
|
654
|
+
def _metadata(cls, topics, member_assignment_partitions, generation=-1):
|
|
655
|
+
if member_assignment_partitions is None:
|
|
652
656
|
log.debug("No member assignment available")
|
|
653
657
|
user_data = b''
|
|
654
658
|
else:
|
|
655
659
|
log.debug("Member assignment is available, generating the metadata: generation {}".format(cls.generation))
|
|
656
660
|
partitions_by_topic = defaultdict(list)
|
|
657
|
-
for topic_partition in
|
|
661
|
+
for topic_partition in member_assignment_partitions:
|
|
658
662
|
partitions_by_topic[topic_partition.topic].append(topic_partition.partition)
|
|
659
|
-
data = StickyAssignorUserDataV1(six.
|
|
663
|
+
data = StickyAssignorUserDataV1(six.viewitems(partitions_by_topic), generation)
|
|
660
664
|
user_data = data.encode()
|
|
661
665
|
return ConsumerProtocolMemberMetadata(cls.version, list(topics), user_data)
|
|
662
666
|
|
kafka/coordinator/base.py
CHANGED
|
@@ -952,7 +952,7 @@ class HeartbeatThread(threading.Thread):
|
|
|
952
952
|
# disable here to prevent propagating an exception to this
|
|
953
953
|
# heartbeat thread
|
|
954
954
|
# must get client._lock, or maybe deadlock at heartbeat
|
|
955
|
-
# failure
|
|
955
|
+
# failure callback in consumer poll
|
|
956
956
|
self.coordinator._client.poll(timeout_ms=0)
|
|
957
957
|
|
|
958
958
|
with self.coordinator._lock:
|
|
@@ -990,6 +990,11 @@ class HeartbeatThread(threading.Thread):
|
|
|
990
990
|
# foreground thread has stalled in between calls to
|
|
991
991
|
# poll(), so we explicitly leave the group.
|
|
992
992
|
log.warning('Heartbeat poll expired, leaving group')
|
|
993
|
+
### XXX
|
|
994
|
+
# maybe_leave_group acquires client + coordinator lock;
|
|
995
|
+
# if we hold coordinator lock before calling, we risk deadlock
|
|
996
|
+
# release() is safe here because this is the last code in the current context
|
|
997
|
+
self.coordinator._lock.release()
|
|
993
998
|
self.coordinator.maybe_leave_group()
|
|
994
999
|
|
|
995
1000
|
elif not self.coordinator.heartbeat.should_heartbeat():
|
kafka/producer/kafka.py
CHANGED
|
@@ -233,7 +233,7 @@ class KafkaProducer(object):
|
|
|
233
233
|
should verify that the certificate matches the brokers hostname.
|
|
234
234
|
default: true.
|
|
235
235
|
ssl_cafile (str): optional filename of ca file to use in certificate
|
|
236
|
-
|
|
236
|
+
verification. default: none.
|
|
237
237
|
ssl_certfile (str): optional filename of file in pem format containing
|
|
238
238
|
the client certificate, as well as any ca certificates needed to
|
|
239
239
|
establish the certificate's authenticity. default: none.
|
|
@@ -280,10 +280,11 @@ class KafkaProducer(object):
|
|
|
280
280
|
sasl mechanism handshake. Default: one of bootstrap servers
|
|
281
281
|
sasl_oauth_token_provider (AbstractTokenProvider): OAuthBearer token provider
|
|
282
282
|
instance. (See kafka.oauth.abstract). Default: None
|
|
283
|
+
kafka_client (callable): Custom class / callable for creating KafkaClient instances
|
|
283
284
|
|
|
284
285
|
Note:
|
|
285
286
|
Configuration parameters are described in more detail at
|
|
286
|
-
https://kafka.apache.org/0100/
|
|
287
|
+
https://kafka.apache.org/0100/documentation/#producerconfigs
|
|
287
288
|
"""
|
|
288
289
|
DEFAULT_CONFIG = {
|
|
289
290
|
'bootstrap_servers': 'localhost',
|
|
@@ -332,7 +333,8 @@ class KafkaProducer(object):
|
|
|
332
333
|
'sasl_plain_password': None,
|
|
333
334
|
'sasl_kerberos_service_name': 'kafka',
|
|
334
335
|
'sasl_kerberos_domain_name': None,
|
|
335
|
-
'sasl_oauth_token_provider': None
|
|
336
|
+
'sasl_oauth_token_provider': None,
|
|
337
|
+
'kafka_client': KafkaClient,
|
|
336
338
|
}
|
|
337
339
|
|
|
338
340
|
_COMPRESSORS = {
|
|
@@ -378,9 +380,10 @@ class KafkaProducer(object):
|
|
|
378
380
|
reporters = [reporter() for reporter in self.config['metric_reporters']]
|
|
379
381
|
self._metrics = Metrics(metric_config, reporters)
|
|
380
382
|
|
|
381
|
-
client =
|
|
382
|
-
|
|
383
|
-
|
|
383
|
+
client = self.config['kafka_client'](
|
|
384
|
+
metrics=self._metrics, metric_group_prefix='producer',
|
|
385
|
+
wakeup_timeout_ms=self.config['max_block_ms'],
|
|
386
|
+
**self.config)
|
|
384
387
|
|
|
385
388
|
# Get auto-discovered version from client if necessary
|
|
386
389
|
if self.config['api_version'] is None:
|
kafka/protocol/__init__.py
CHANGED
kafka/protocol/admin.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
2
|
|
|
3
3
|
from kafka.protocol.api import Request, Response
|
|
4
|
-
from kafka.protocol.types import Array, Boolean, Bytes, Int8, Int16, Int32, Int64, Schema, String
|
|
4
|
+
from kafka.protocol.types import Array, Boolean, Bytes, Int8, Int16, Int32, Int64, Schema, String, Float64, CompactString, CompactArray, TaggedFields
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ApiVersionResponse_v0(Response):
|
|
@@ -719,7 +719,7 @@ class DescribeConfigsResponse_v1(Response):
|
|
|
719
719
|
('config_names', String('utf-8')),
|
|
720
720
|
('config_value', String('utf-8')),
|
|
721
721
|
('read_only', Boolean),
|
|
722
|
-
('
|
|
722
|
+
('config_source', Int8),
|
|
723
723
|
('is_sensitive', Boolean),
|
|
724
724
|
('config_synonyms', Array(
|
|
725
725
|
('config_name', String('utf-8')),
|
|
@@ -790,6 +790,48 @@ DescribeConfigsResponse = [
|
|
|
790
790
|
]
|
|
791
791
|
|
|
792
792
|
|
|
793
|
+
class DescribeLogDirsResponse_v0(Response):
|
|
794
|
+
API_KEY = 35
|
|
795
|
+
API_VERSION = 0
|
|
796
|
+
FLEXIBLE_VERSION = True
|
|
797
|
+
SCHEMA = Schema(
|
|
798
|
+
('throttle_time_ms', Int32),
|
|
799
|
+
('log_dirs', Array(
|
|
800
|
+
('error_code', Int16),
|
|
801
|
+
('log_dir', String('utf-8')),
|
|
802
|
+
('topics', Array(
|
|
803
|
+
('name', String('utf-8')),
|
|
804
|
+
('partitions', Array(
|
|
805
|
+
('partition_index', Int32),
|
|
806
|
+
('partition_size', Int64),
|
|
807
|
+
('offset_lag', Int64),
|
|
808
|
+
('is_future_key', Boolean)
|
|
809
|
+
))
|
|
810
|
+
))
|
|
811
|
+
))
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
class DescribeLogDirsRequest_v0(Request):
|
|
816
|
+
API_KEY = 35
|
|
817
|
+
API_VERSION = 0
|
|
818
|
+
RESPONSE_TYPE = DescribeLogDirsResponse_v0
|
|
819
|
+
SCHEMA = Schema(
|
|
820
|
+
('topics', Array(
|
|
821
|
+
('topic', String('utf-8')),
|
|
822
|
+
('partitions', Int32)
|
|
823
|
+
))
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
DescribeLogDirsResponse = [
|
|
828
|
+
DescribeLogDirsResponse_v0,
|
|
829
|
+
]
|
|
830
|
+
DescribeLogDirsRequest = [
|
|
831
|
+
DescribeLogDirsRequest_v0,
|
|
832
|
+
]
|
|
833
|
+
|
|
834
|
+
|
|
793
835
|
class SaslAuthenticateResponse_v0(Response):
|
|
794
836
|
API_KEY = 36
|
|
795
837
|
API_VERSION = 0
|
|
@@ -923,3 +965,132 @@ DeleteGroupsRequest = [
|
|
|
923
965
|
DeleteGroupsResponse = [
|
|
924
966
|
DeleteGroupsResponse_v0, DeleteGroupsResponse_v1
|
|
925
967
|
]
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
class DescribeClientQuotasResponse_v0(Response):
|
|
971
|
+
API_KEY = 48
|
|
972
|
+
API_VERSION = 0
|
|
973
|
+
SCHEMA = Schema(
|
|
974
|
+
('throttle_time_ms', Int32),
|
|
975
|
+
('error_code', Int16),
|
|
976
|
+
('error_message', String('utf-8')),
|
|
977
|
+
('entries', Array(
|
|
978
|
+
('entity', Array(
|
|
979
|
+
('entity_type', String('utf-8')),
|
|
980
|
+
('entity_name', String('utf-8')))),
|
|
981
|
+
('values', Array(
|
|
982
|
+
('name', String('utf-8')),
|
|
983
|
+
('value', Float64))))),
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
class DescribeClientQuotasRequest_v0(Request):
|
|
988
|
+
API_KEY = 48
|
|
989
|
+
API_VERSION = 0
|
|
990
|
+
RESPONSE_TYPE = DescribeClientQuotasResponse_v0
|
|
991
|
+
SCHEMA = Schema(
|
|
992
|
+
('components', Array(
|
|
993
|
+
('entity_type', String('utf-8')),
|
|
994
|
+
('match_type', Int8),
|
|
995
|
+
('match', String('utf-8')),
|
|
996
|
+
)),
|
|
997
|
+
('strict', Boolean)
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
DescribeClientQuotasRequest = [
|
|
1002
|
+
DescribeClientQuotasRequest_v0,
|
|
1003
|
+
]
|
|
1004
|
+
|
|
1005
|
+
DescribeClientQuotasResponse = [
|
|
1006
|
+
DescribeClientQuotasResponse_v0,
|
|
1007
|
+
]
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
class AlterPartitionReassignmentsResponse_v0(Response):
|
|
1011
|
+
API_KEY = 45
|
|
1012
|
+
API_VERSION = 0
|
|
1013
|
+
SCHEMA = Schema(
|
|
1014
|
+
("throttle_time_ms", Int32),
|
|
1015
|
+
("error_code", Int16),
|
|
1016
|
+
("error_message", CompactString("utf-8")),
|
|
1017
|
+
("responses", CompactArray(
|
|
1018
|
+
("name", CompactString("utf-8")),
|
|
1019
|
+
("partitions", CompactArray(
|
|
1020
|
+
("partition_index", Int32),
|
|
1021
|
+
("error_code", Int16),
|
|
1022
|
+
("error_message", CompactString("utf-8")),
|
|
1023
|
+
("tags", TaggedFields)
|
|
1024
|
+
)),
|
|
1025
|
+
("tags", TaggedFields)
|
|
1026
|
+
)),
|
|
1027
|
+
("tags", TaggedFields)
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
class AlterPartitionReassignmentsRequest_v0(Request):
|
|
1032
|
+
FLEXIBLE_VERSION = True
|
|
1033
|
+
API_KEY = 45
|
|
1034
|
+
API_VERSION = 0
|
|
1035
|
+
RESPONSE_TYPE = AlterPartitionReassignmentsResponse_v0
|
|
1036
|
+
SCHEMA = Schema(
|
|
1037
|
+
("timeout_ms", Int32),
|
|
1038
|
+
("topics", CompactArray(
|
|
1039
|
+
("name", CompactString("utf-8")),
|
|
1040
|
+
("partitions", CompactArray(
|
|
1041
|
+
("partition_index", Int32),
|
|
1042
|
+
("replicas", CompactArray(Int32)),
|
|
1043
|
+
("tags", TaggedFields)
|
|
1044
|
+
)),
|
|
1045
|
+
("tags", TaggedFields)
|
|
1046
|
+
)),
|
|
1047
|
+
("tags", TaggedFields)
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
AlterPartitionReassignmentsRequest = [AlterPartitionReassignmentsRequest_v0]
|
|
1052
|
+
|
|
1053
|
+
AlterPartitionReassignmentsResponse = [AlterPartitionReassignmentsResponse_v0]
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
class ListPartitionReassignmentsResponse_v0(Response):
|
|
1057
|
+
API_KEY = 46
|
|
1058
|
+
API_VERSION = 0
|
|
1059
|
+
SCHEMA = Schema(
|
|
1060
|
+
("throttle_time_ms", Int32),
|
|
1061
|
+
("error_code", Int16),
|
|
1062
|
+
("error_message", CompactString("utf-8")),
|
|
1063
|
+
("topics", CompactArray(
|
|
1064
|
+
("name", CompactString("utf-8")),
|
|
1065
|
+
("partitions", CompactArray(
|
|
1066
|
+
("partition_index", Int32),
|
|
1067
|
+
("replicas", CompactArray(Int32)),
|
|
1068
|
+
("adding_replicas", CompactArray(Int32)),
|
|
1069
|
+
("removing_replicas", CompactArray(Int32)),
|
|
1070
|
+
("tags", TaggedFields)
|
|
1071
|
+
)),
|
|
1072
|
+
("tags", TaggedFields)
|
|
1073
|
+
)),
|
|
1074
|
+
("tags", TaggedFields)
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
class ListPartitionReassignmentsRequest_v0(Request):
|
|
1079
|
+
FLEXIBLE_VERSION = True
|
|
1080
|
+
API_KEY = 46
|
|
1081
|
+
API_VERSION = 0
|
|
1082
|
+
RESPONSE_TYPE = ListPartitionReassignmentsResponse_v0
|
|
1083
|
+
SCHEMA = Schema(
|
|
1084
|
+
("timeout_ms", Int32),
|
|
1085
|
+
("topics", CompactArray(
|
|
1086
|
+
("name", CompactString("utf-8")),
|
|
1087
|
+
("partition_index", CompactArray(Int32)),
|
|
1088
|
+
("tags", TaggedFields)
|
|
1089
|
+
)),
|
|
1090
|
+
("tags", TaggedFields)
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
ListPartitionReassignmentsRequest = [ListPartitionReassignmentsRequest_v0]
|
|
1095
|
+
|
|
1096
|
+
ListPartitionReassignmentsResponse = [ListPartitionReassignmentsResponse_v0]
|
kafka/protocol/api.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import absolute_import
|
|
|
3
3
|
import abc
|
|
4
4
|
|
|
5
5
|
from kafka.protocol.struct import Struct
|
|
6
|
-
from kafka.protocol.types import Int16, Int32, String, Schema, Array
|
|
6
|
+
from kafka.protocol.types import Int16, Int32, String, Schema, Array, TaggedFields
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class RequestHeader(Struct):
|
|
@@ -20,9 +20,40 @@ class RequestHeader(Struct):
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
class RequestHeaderV2(Struct):
|
|
24
|
+
# Flexible response / request headers end in field buffer
|
|
25
|
+
SCHEMA = Schema(
|
|
26
|
+
('api_key', Int16),
|
|
27
|
+
('api_version', Int16),
|
|
28
|
+
('correlation_id', Int32),
|
|
29
|
+
('client_id', String('utf-8')),
|
|
30
|
+
('tags', TaggedFields),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def __init__(self, request, correlation_id=0, client_id='kafka-python', tags=None):
|
|
34
|
+
super(RequestHeaderV2, self).__init__(
|
|
35
|
+
request.API_KEY, request.API_VERSION, correlation_id, client_id, tags or {}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ResponseHeader(Struct):
|
|
40
|
+
SCHEMA = Schema(
|
|
41
|
+
('correlation_id', Int32),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ResponseHeaderV2(Struct):
|
|
46
|
+
SCHEMA = Schema(
|
|
47
|
+
('correlation_id', Int32),
|
|
48
|
+
('tags', TaggedFields),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
23
52
|
class Request(Struct):
|
|
24
53
|
__metaclass__ = abc.ABCMeta
|
|
25
54
|
|
|
55
|
+
FLEXIBLE_VERSION = False
|
|
56
|
+
|
|
26
57
|
@abc.abstractproperty
|
|
27
58
|
def API_KEY(self):
|
|
28
59
|
"""Integer identifier for api request"""
|
|
@@ -50,6 +81,16 @@ class Request(Struct):
|
|
|
50
81
|
def to_object(self):
|
|
51
82
|
return _to_object(self.SCHEMA, self)
|
|
52
83
|
|
|
84
|
+
def build_request_header(self, correlation_id, client_id):
|
|
85
|
+
if self.FLEXIBLE_VERSION:
|
|
86
|
+
return RequestHeaderV2(self, correlation_id=correlation_id, client_id=client_id)
|
|
87
|
+
return RequestHeader(self, correlation_id=correlation_id, client_id=client_id)
|
|
88
|
+
|
|
89
|
+
def parse_response_header(self, read_buffer):
|
|
90
|
+
if self.FLEXIBLE_VERSION:
|
|
91
|
+
return ResponseHeaderV2.decode(read_buffer)
|
|
92
|
+
return ResponseHeader.decode(read_buffer)
|
|
93
|
+
|
|
53
94
|
|
|
54
95
|
class Response(Struct):
|
|
55
96
|
__metaclass__ = abc.ABCMeta
|
kafka/protocol/parser.py
CHANGED
|
@@ -4,10 +4,9 @@ import collections
|
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
6
|
import kafka.errors as Errors
|
|
7
|
-
from kafka.protocol.api import RequestHeader
|
|
8
7
|
from kafka.protocol.commit import GroupCoordinatorResponse
|
|
9
8
|
from kafka.protocol.frame import KafkaBytes
|
|
10
|
-
from kafka.protocol.types import Int32
|
|
9
|
+
from kafka.protocol.types import Int32, TaggedFields
|
|
11
10
|
from kafka.version import __version__
|
|
12
11
|
|
|
13
12
|
log = logging.getLogger(__name__)
|
|
@@ -59,9 +58,8 @@ class KafkaProtocol(object):
|
|
|
59
58
|
log.debug('Sending request %s', request)
|
|
60
59
|
if correlation_id is None:
|
|
61
60
|
correlation_id = self._next_correlation_id()
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
client_id=self._client_id)
|
|
61
|
+
|
|
62
|
+
header = request.build_request_header(correlation_id=correlation_id, client_id=self._client_id)
|
|
65
63
|
message = b''.join([header.encode(), request.encode()])
|
|
66
64
|
size = Int32.encode(len(message))
|
|
67
65
|
data = size + message
|
|
@@ -135,17 +133,12 @@ class KafkaProtocol(object):
|
|
|
135
133
|
return responses
|
|
136
134
|
|
|
137
135
|
def _process_response(self, read_buffer):
|
|
138
|
-
recv_correlation_id = Int32.decode(read_buffer)
|
|
139
|
-
log.debug('Received correlation id: %d', recv_correlation_id)
|
|
140
|
-
|
|
141
136
|
if not self.in_flight_requests:
|
|
142
|
-
raise Errors.CorrelationIdError(
|
|
143
|
-
'No in-flight-request found for server response'
|
|
144
|
-
' with correlation ID %d'
|
|
145
|
-
% (recv_correlation_id,))
|
|
146
|
-
|
|
137
|
+
raise Errors.CorrelationIdError('No in-flight-request found for server response')
|
|
147
138
|
(correlation_id, request) = self.in_flight_requests.popleft()
|
|
148
|
-
|
|
139
|
+
response_header = request.parse_response_header(read_buffer)
|
|
140
|
+
recv_correlation_id = response_header.correlation_id
|
|
141
|
+
log.debug('Received correlation id: %d', recv_correlation_id)
|
|
149
142
|
# 0.8.2 quirk
|
|
150
143
|
if (recv_correlation_id == 0 and
|
|
151
144
|
correlation_id != 0 and
|