kafka-python 2.1.5__tar.gz → 2.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kafka_python-2.1.5 → kafka_python-2.2.1}/CHANGES.md +55 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/PKG-INFO +24 -1
- {kafka_python-2.1.5 → kafka_python-2.2.1}/README.rst +23 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/admin/client.py +6 -6
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/client_async.py +40 -2
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/cluster.py +18 -13
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/conn.py +15 -5
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/consumer/fetcher.py +290 -187
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/consumer/group.py +56 -54
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/consumer/subscription_state.py +79 -35
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/base.py +37 -19
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/consumer.py +22 -31
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/errors.py +68 -93
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/producer/future.py +3 -3
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/producer/kafka.py +265 -30
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/producer/record_accumulator.py +293 -187
- kafka_python-2.2.1/kafka/producer/sender.py +762 -0
- kafka_python-2.2.1/kafka/producer/transaction_manager.py +981 -0
- kafka_python-2.2.1/kafka/protocol/add_offsets_to_txn.py +59 -0
- kafka_python-2.2.1/kafka/protocol/add_partitions_to_txn.py +63 -0
- kafka_python-2.2.1/kafka/protocol/end_txn.py +58 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/fetch.py +6 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/group.py +17 -3
- kafka_python-2.2.1/kafka/protocol/init_producer_id.py +46 -0
- kafka_python-2.2.1/kafka/protocol/txn_offset_commit.py +78 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/record/abc.py +10 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/record/default_records.py +101 -12
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/record/legacy_records.py +12 -3
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/record/memory_records.py +54 -6
- kafka_python-2.2.1/kafka/version.py +1 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka_python.egg-info/PKG-INFO +24 -1
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka_python.egg-info/SOURCES.txt +16 -5
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka_python.egg-info/top_level.txt +1 -0
- kafka_python-2.2.1/test/integration/__init__.py +0 -0
- kafka_python-2.2.1/test/integration/conftest.py +168 -0
- kafka_python-2.2.1/test/integration/fixtures.py +765 -0
- {kafka_python-2.1.5/test → kafka_python-2.2.1/test/integration}/test_admin_integration.py +2 -2
- {kafka_python-2.1.5/test → kafka_python-2.2.1/test/integration}/test_consumer_integration.py +3 -2
- kafka_python-2.1.5/test/test_producer.py → kafka_python-2.2.1/test/integration/test_producer_integration.py +78 -18
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_client_async.py +135 -136
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_coordinator.py +21 -23
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_fetcher.py +211 -67
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_metrics.py +2 -18
- kafka_python-2.2.1/test/test_producer.py +35 -0
- kafka_python-2.2.1/test/test_record_accumulator.py +266 -0
- kafka_python-2.2.1/test/test_sender.py +242 -0
- kafka_python-2.1.5/kafka/producer/sender.py +0 -489
- kafka_python-2.1.5/kafka/version.py +0 -1
- kafka_python-2.1.5/test/test_sender.py +0 -39
- {kafka_python-2.1.5 → kafka_python-2.2.1}/AUTHORS.md +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/LICENSE +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/MANIFEST.in +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/benchmarks/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/benchmarks/consumer_performance.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/benchmarks/load_example.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/benchmarks/producer_performance.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/benchmarks/record_batch_compose.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/benchmarks/record_batch_read.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/benchmarks/varint_speed.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/codec.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/future.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/metric_name.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/metrics.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/quota.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/admin.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/api.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/api_versions.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/broker_api_versions.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/commit.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/find_coordinator.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/list_offsets.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/message.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/metadata.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/offset_for_leader_epoch.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/parser.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/produce.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/sasl_authenticate.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/sasl_handshake.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/protocol/types.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/record/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/record/util.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/abc.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/gssapi.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/msk.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/oauth.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/plain.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/scram.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/sasl/sspi.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/socks5_wrapper.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/structs.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/util.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/vendor/six.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/kafka_python.egg-info/requires.txt +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/pyproject.toml +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/setup.cfg +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/setup.py +0 -0
- {kafka_python-2.1.5/test → kafka_python-2.2.1/test/integration}/test_consumer_group.py +0 -0
- {kafka_python-2.1.5/test → kafka_python-2.2.1/test/integration}/test_sasl_integration.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_admin.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_assignors.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_cluster.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_codec.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_conn.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_consumer.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_object_conversion.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_package.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_partition_movements.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_partitioner.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_protocol.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_subscription_state.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/test_util.py +0 -0
- {kafka_python-2.1.5 → kafka_python-2.2.1}/test/testutil.py +0 -0
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
# 2.2.1 (Apr 29, 2025)
|
|
2
|
+
|
|
3
|
+
Fixes
|
|
4
|
+
* Always try ApiVersionsRequest v0, even on broker disconnect (#2603)
|
|
5
|
+
* Fix SubscriptionState AttributeError in KafkaConsumer (#2599)
|
|
6
|
+
|
|
7
|
+
Documentation
|
|
8
|
+
* Add transactional examples to docs
|
|
9
|
+
|
|
10
|
+
# 2.2.0 (Apr 28, 2025)
|
|
11
|
+
|
|
12
|
+
KafkaProducer
|
|
13
|
+
* KIP-98: Add idempotent producer support (#2569)
|
|
14
|
+
* KIP-98: Transactional Producer (#2587)
|
|
15
|
+
* KIP-98: Add offsets support to transactional KafkaProducer (#2590)
|
|
16
|
+
* Prefix producer logs w/ client id and transactional id (#2591)
|
|
17
|
+
* KAFKA-5429: Ignore produce response if batch was previously aborted
|
|
18
|
+
* KIP-91: KafkaProducer `delivery_timeout_ms`
|
|
19
|
+
* Default retries -> infinite
|
|
20
|
+
* Expand KafkaProducer docstring w/ idempotent and transactional notes
|
|
21
|
+
* RecordAccumulator: Use helper method to get/set `_tp_locks`; get dq with lock in reenqueue()
|
|
22
|
+
|
|
23
|
+
KafkaConsumer
|
|
24
|
+
* KIP-98: Add Consumer support for `READ_COMMITTED` (#2582)
|
|
25
|
+
* KIP-394: handle `MEMBER_ID_REQUIRED` error w/ second join group request (#2598)
|
|
26
|
+
* KAFKA-5078: Defer fetch record exception if iterator has already moved across a valid record
|
|
27
|
+
* KAFKA-5075: Defer consumer fetcher exception if fetch position has already increased
|
|
28
|
+
* KAFKA-4937: Batch offset fetches in the Consumer
|
|
29
|
+
* KAFKA-4547: Avoid resetting paused partitions to committed offsets
|
|
30
|
+
* KAFKA-6397: Consumer should not block setting positions of unavailable partitions (#2593)
|
|
31
|
+
|
|
32
|
+
Potentially Breaking Changes (internal)
|
|
33
|
+
* Rename CorruptRecordException -> CorruptRecordError
|
|
34
|
+
* Rename Coordinator errors to generic not group (#2585)
|
|
35
|
+
* Rename `ClusterMetadata.add_group_coordinator` -> `add_coordinator` + support txn type
|
|
36
|
+
* Use SaslAuthenticationFailedError in kafka.conn connection failure; Drop unused AuthenticationFailedError
|
|
37
|
+
* Remove old/unused errors; reorder; KafkaTimeout -> retriable
|
|
38
|
+
* Drop `log_start_offset` from producer RecordMetadata
|
|
39
|
+
|
|
40
|
+
Internal
|
|
41
|
+
* MemoryRecords iterator; MemoryRecordsBuilder records() helper
|
|
42
|
+
* Convert `DefaultRecordsBuilder.size_in_bytes` to classmethod
|
|
43
|
+
|
|
44
|
+
Fixes
|
|
45
|
+
* Resolve datetime deprecation warnings (#2589)
|
|
46
|
+
* Avoid self refcount in log messages; test thread close on all pythons
|
|
47
|
+
* Fix client.wakeup() race from producer/sender close
|
|
48
|
+
* Fix ElectionNotNeededError handling in admin client
|
|
49
|
+
|
|
50
|
+
Tests
|
|
51
|
+
* Move integration tests and fixtures to test/integration/; simplify unit fixtures (#2588)
|
|
52
|
+
* Expand Sender test coverage (#2586)
|
|
53
|
+
* py2 test fixups
|
|
54
|
+
* Drop unused KafkaClient import from `test_fetcher`
|
|
55
|
+
|
|
1
56
|
# 2.1.5 (Apr 4, 2025)
|
|
2
57
|
|
|
3
58
|
Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kafka-python
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
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
|
|
@@ -138,6 +138,14 @@ that expose basic message attributes: topic, partition, offset, key, and value:
|
|
|
138
138
|
for msg in consumer:
|
|
139
139
|
print (msg.headers)
|
|
140
140
|
|
|
141
|
+
.. code-block:: python
|
|
142
|
+
|
|
143
|
+
# Read only committed messages from transactional topic
|
|
144
|
+
consumer = KafkaConsumer(isolation_level='read_committed')
|
|
145
|
+
consumer.subscribe(['txn_topic'])
|
|
146
|
+
for msg in consumer:
|
|
147
|
+
print(msg)
|
|
148
|
+
|
|
141
149
|
.. code-block:: python
|
|
142
150
|
|
|
143
151
|
# Get consumer metrics
|
|
@@ -197,6 +205,21 @@ for more details.
|
|
|
197
205
|
for i in range(1000):
|
|
198
206
|
producer.send('foobar', b'msg %d' % i)
|
|
199
207
|
|
|
208
|
+
.. code-block:: python
|
|
209
|
+
|
|
210
|
+
# Use transactions
|
|
211
|
+
producer = KafkaProducer(transactional_id='fizzbuzz')
|
|
212
|
+
producer.init_transactions()
|
|
213
|
+
producer.begin_transaction()
|
|
214
|
+
future = producer.send('txn_topic', value=b'yes')
|
|
215
|
+
future.get() # wait for successful produce
|
|
216
|
+
producer.commit_transaction() # commit the transaction
|
|
217
|
+
|
|
218
|
+
producer.begin_transaction()
|
|
219
|
+
future = producer.send('txn_topic', value=b'no')
|
|
220
|
+
future.get() # wait for successful produce
|
|
221
|
+
producer.abort_transaction() # abort the transaction
|
|
222
|
+
|
|
200
223
|
.. code-block:: python
|
|
201
224
|
|
|
202
225
|
# Include record headers. The format is list of tuples with string key
|
|
@@ -94,6 +94,14 @@ that expose basic message attributes: topic, partition, offset, key, and value:
|
|
|
94
94
|
for msg in consumer:
|
|
95
95
|
print (msg.headers)
|
|
96
96
|
|
|
97
|
+
.. code-block:: python
|
|
98
|
+
|
|
99
|
+
# Read only committed messages from transactional topic
|
|
100
|
+
consumer = KafkaConsumer(isolation_level='read_committed')
|
|
101
|
+
consumer.subscribe(['txn_topic'])
|
|
102
|
+
for msg in consumer:
|
|
103
|
+
print(msg)
|
|
104
|
+
|
|
97
105
|
.. code-block:: python
|
|
98
106
|
|
|
99
107
|
# Get consumer metrics
|
|
@@ -153,6 +161,21 @@ for more details.
|
|
|
153
161
|
for i in range(1000):
|
|
154
162
|
producer.send('foobar', b'msg %d' % i)
|
|
155
163
|
|
|
164
|
+
.. code-block:: python
|
|
165
|
+
|
|
166
|
+
# Use transactions
|
|
167
|
+
producer = KafkaProducer(transactional_id='fizzbuzz')
|
|
168
|
+
producer.init_transactions()
|
|
169
|
+
producer.begin_transaction()
|
|
170
|
+
future = producer.send('txn_topic', value=b'yes')
|
|
171
|
+
future.get() # wait for successful produce
|
|
172
|
+
producer.commit_transaction() # commit the transaction
|
|
173
|
+
|
|
174
|
+
producer.begin_transaction()
|
|
175
|
+
future = producer.send('txn_topic', value=b'no')
|
|
176
|
+
future.get() # wait for successful produce
|
|
177
|
+
producer.abort_transaction() # abort the transaction
|
|
178
|
+
|
|
156
179
|
.. code-block:: python
|
|
157
180
|
|
|
158
181
|
# Include record headers. The format is list of tuples with string key
|
|
@@ -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
|
|
@@ -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:
|
|
@@ -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))
|
|
@@ -301,6 +301,7 @@ class BrokerConnection(object):
|
|
|
301
301
|
if self.config['ssl_context'] is not None:
|
|
302
302
|
self._ssl_context = self.config['ssl_context']
|
|
303
303
|
self._api_versions_future = None
|
|
304
|
+
self._api_versions_check_timeout = self.config['api_version_auto_timeout_ms']
|
|
304
305
|
self._sasl_auth_future = None
|
|
305
306
|
self.last_attempt = 0
|
|
306
307
|
self._gai = []
|
|
@@ -557,7 +558,8 @@ class BrokerConnection(object):
|
|
|
557
558
|
else:
|
|
558
559
|
request = ApiVersionsRequest[version]()
|
|
559
560
|
future = Future()
|
|
560
|
-
|
|
561
|
+
self._api_versions_check_timeout /= 2
|
|
562
|
+
response = self._send(request, blocking=True, request_timeout_ms=self._api_versions_check_timeout)
|
|
561
563
|
response.add_callback(self._handle_api_versions_response, future)
|
|
562
564
|
response.add_errback(self._handle_api_versions_failure, future)
|
|
563
565
|
self._api_versions_future = future
|
|
@@ -566,7 +568,8 @@ class BrokerConnection(object):
|
|
|
566
568
|
elif self._check_version_idx < len(self.VERSION_CHECKS):
|
|
567
569
|
version, request = self.VERSION_CHECKS[self._check_version_idx]
|
|
568
570
|
future = Future()
|
|
569
|
-
|
|
571
|
+
self._api_versions_check_timeout /= 2
|
|
572
|
+
response = self._send(request, blocking=True, request_timeout_ms=self._api_versions_check_timeout)
|
|
570
573
|
response.add_callback(self._handle_check_version_response, future, version)
|
|
571
574
|
response.add_errback(self._handle_check_version_failure, future)
|
|
572
575
|
self._api_versions_future = future
|
|
@@ -618,7 +621,13 @@ class BrokerConnection(object):
|
|
|
618
621
|
|
|
619
622
|
def _handle_api_versions_failure(self, future, ex):
|
|
620
623
|
future.failure(ex)
|
|
621
|
-
|
|
624
|
+
# Modern brokers should not disconnect on unrecognized api-versions request,
|
|
625
|
+
# but in case they do we always want to try v0 as a fallback
|
|
626
|
+
# otherwise switch to check_version probe.
|
|
627
|
+
if self._api_versions_idx > 0:
|
|
628
|
+
self._api_versions_idx = 0
|
|
629
|
+
else:
|
|
630
|
+
self._check_version_idx = 0
|
|
622
631
|
# after failure connection is closed, so state should already be DISCONNECTED
|
|
623
632
|
|
|
624
633
|
def _handle_check_version_response(self, future, version, _response):
|
|
@@ -813,7 +822,7 @@ class BrokerConnection(object):
|
|
|
813
822
|
log.info('%s: %s', self, self._sasl_mechanism.auth_details())
|
|
814
823
|
return future.success(True)
|
|
815
824
|
else:
|
|
816
|
-
return future.failure(Errors.
|
|
825
|
+
return future.failure(Errors.SaslAuthenticationFailedError('Failed to authenticate via SASL %s' % self.config['sasl_mechanism']))
|
|
817
826
|
|
|
818
827
|
def blacked_out(self):
|
|
819
828
|
"""
|
|
@@ -934,7 +943,8 @@ class BrokerConnection(object):
|
|
|
934
943
|
if self.state is ConnectionStates.DISCONNECTED:
|
|
935
944
|
return
|
|
936
945
|
log.log(logging.ERROR if error else logging.INFO, '%s: Closing connection. %s', self, error or '')
|
|
937
|
-
|
|
946
|
+
if error:
|
|
947
|
+
self._update_reconnect_backoff()
|
|
938
948
|
self._api_versions_future = None
|
|
939
949
|
self._sasl_auth_future = None
|
|
940
950
|
self._init_sasl_mechanism()
|