Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-kafka-python for 
openSUSE:Factory checked in at 2026-08-22 21:35:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-kafka-python (Old)
 and      /work/SRC/openSUSE:Factory/.python-kafka-python.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-kafka-python"

Sat Aug 22 21:35:41 2026 rev:17 rq:1373030 version:3.0.11

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-kafka-python/python-kafka-python.changes  
2026-07-15 17:01:39.815234631 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-kafka-python.new.1258/python-kafka-python.changes
        2026-08-22 21:37:49.582829241 +0200
@@ -1,0 +2,11 @@
+Sat Aug 22 09:19:56 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 3.0.11:
+  * Fix JsonSerializer returning None and raising TypeError on
+    deserialize
+  * Admin: Fix `create_partitions` sending empty assignments
+    array instead of null
+  * Consumer: fix stuck partitions retained after rebalance with
+    listener
+
+-------------------------------------------------------------------

Old:
----
  python-kafka-python-3.0.8.tar.gz

New:
----
  python-kafka-python-3.0.11.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-kafka-python.spec ++++++
--- /var/tmp/diff_new_pack.M3RPQz/_old  2026-08-22 21:37:50.187850875 +0200
+++ /var/tmp/diff_new_pack.M3RPQz/_new  2026-08-22 21:37:50.189850946 +0200
@@ -23,7 +23,7 @@
 %endif
 
 Name:           python-kafka-python
-Version:        3.0.8
+Version:        3.0.11
 Release:        0
 Summary:        Pure Python client for Apache Kafka
 License:        Apache-2.0

++++++ python-kafka-python-3.0.8.tar.gz -> python-kafka-python-3.0.11.tar.gz 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/CHANGES.md 
new/kafka-python-3.0.11/CHANGES.md
--- old/kafka-python-3.0.8/CHANGES.md   2026-07-09 20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/CHANGES.md  2026-08-16 18:29:39.000000000 +0200
@@ -1,3 +1,18 @@
+# 3.0.11 (Aug 16, 2026)
+
+### Fixes
+* Fix JsonSerializer returning None and raising TypeError on deserialize 
(#3150)
+
+# 3.0.10 (Aug 4, 2026)
+
+### Fixes
+* Admin: Fix `create_partitions` sending empty assignments array instead of 
null (#3148)
+
+# 3.0.9 (Jul 21, 2026)
+
+### Fixes
+* Consumer: fix stuck partitions retained after rebalance with listener (#3132)
+
 # 3.0.8 (Jul 9, 2026)
 
 ### Fixes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/docs/changelog.rst 
new/kafka-python-3.0.11/docs/changelog.rst
--- old/kafka-python-3.0.8/docs/changelog.rst   2026-07-09 20:43:02.000000000 
+0200
+++ new/kafka-python-3.0.11/docs/changelog.rst  2026-08-16 18:29:39.000000000 
+0200
@@ -1,6 +1,30 @@
 Changelog
 =========
 
+3.0.11 (Aug 16, 2026)
+#####################
+
+Fixes
+-----
+* Fix JsonSerializer returning None and raising TypeError on deserialize 
(#3150)
+
+
+3.0.10 (Aug 4, 2026)
+####################
+
+Fixes
+-----
+* Admin: Fix `create_partitions` sending empty assignments array instead of 
null (#3148)
+
+
+3.0.9 (Jul 21, 2026)
+####################
+
+Fixes
+-----
+* Consumer: fix stuck partitions retained after rebalance with listener (#3132)
+
+
 3.0.8 (Jul 9, 2026)
 ###################
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/kafka/admin/_partitions.py 
new/kafka-python-3.0.11/kafka/admin/_partitions.py
--- old/kafka-python-3.0.8/kafka/admin/_partitions.py   2026-07-09 
20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/kafka/admin/_partitions.py  2026-08-16 
18:29:39.000000000 +0200
@@ -54,12 +54,17 @@
                         assignments=[_Assignment(broker_ids=broker_ids)
                                      for broker_ids in count['assignments']]))
             else:
+                # Deprecated dict[topic, NewPartitions]
+                if count.new_assignments is not None:
+                    new_assignments = [_Assignment(broker_ids=broker_ids)
+                                       for broker_ids in count.new_assignments]
+                else:
+                    new_assignments = None
                 topics.append(
                     _Topic(
                         name=topic,
                         count=count.total_count,
-                        assignments=[_Assignment(broker_ids=broker_ids)
-                                     for broker_ids in count.new_assignments]))
+                        assignments=new_assignments))
         return topics
 
     def create_partitions(self, topic_partitions, timeout_ms=None, 
validate_only=False, raise_errors=True):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/kafka/admin/_topics.py 
new/kafka-python-3.0.11/kafka/admin/_topics.py
--- old/kafka-python-3.0.8/kafka/admin/_topics.py       2026-07-09 
20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/kafka/admin/_topics.py      2026-08-16 
18:29:39.000000000 +0200
@@ -12,7 +12,7 @@
 
 import kafka.errors as Errors
 from kafka.errors import IncompatibleBrokerVersion
-from kafka.protocol.admin import CreateTopicsRequest, DeleteTopicsRequest, 
CreatePartitionsRequest
+from kafka.protocol.admin import CreateTopicsRequest, DeleteTopicsRequest
 from ._configs import ConfigResourceType
 
 if TYPE_CHECKING:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kafka-python-3.0.8/kafka/consumer/subscription_state.py 
new/kafka-python-3.0.11/kafka/consumer/subscription_state.py
--- old/kafka-python-3.0.8/kafka/consumer/subscription_state.py 2026-07-09 
20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/kafka/consumer/subscription_state.py        
2026-08-16 18:29:39.000000000 +0200
@@ -290,6 +290,9 @@
         for tp in list(self.assignment.keys()):
             if tp not in new_set:
                 del self.assignment[tp]
+            else:
+                # Retained partitions are no longer pending_revocation
+                self.assignment[tp].mark_pending_revocation(False)
         # Add new partitions; kept partitions retain their existing
         # TopicPartitionState (positions, paused flag, KIP-392 cache,
         # etc.).
@@ -651,12 +654,12 @@
     def resume(self):
         self.paused = False
 
-    def mark_pending_revocation(self):
+    def mark_pending_revocation(self, val=True):
         """KIP-429: gate fetches while an on_partitions_revoked /
         on_partitions_lost listener is in progress for this partition.
         Single-shot: the surrounding ``assign_from_subscribed`` drops
         the state object once the listener returns."""
-        self._pending_revocation = True
+        self._pending_revocation = val
 
     def is_fetchable(self):
         return (not self.paused
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/kafka/protocol/admin/topics.py 
new/kafka-python-3.0.11/kafka/protocol/admin/topics.py
--- old/kafka-python-3.0.8/kafka/protocol/admin/topics.py       2026-07-09 
20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/kafka/protocol/admin/topics.py      2026-08-16 
18:29:39.000000000 +0200
@@ -17,7 +17,12 @@
 
 class DeleteTopicsResponse(ApiMessage): pass
 
-class CreatePartitionsRequest(ApiMessage): pass
+class CreatePartitionsRequest(ApiMessage):
+    @classmethod
+    def json_patch(cls, json):
+        json['fields'][0]['fields'][2]['default'] = 'null'
+        return json
+
 class CreatePartitionsResponse(ApiMessage): pass
 
 class AlterPartitionReassignmentsRequest(ApiMessage): pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/kafka/serializer/json.py 
new/kafka-python-3.0.11/kafka/serializer/json.py
--- old/kafka-python-3.0.8/kafka/serializer/json.py     2026-07-09 
20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/kafka/serializer/json.py    2026-08-16 
18:29:39.000000000 +0200
@@ -1,9 +1,9 @@
 import json
 
-from .abstract import Serializer, Deserializer
+from .default import DefaultSerializer
 
 
-class JsonSerializer(Serializer, Deserializer):
+class JsonSerializer(DefaultSerializer):
     def serialize(self, topic, headers, data):
         if data is None:
             return None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/kafka/version.py 
new/kafka-python-3.0.11/kafka/version.py
--- old/kafka-python-3.0.8/kafka/version.py     2026-07-09 20:43:02.000000000 
+0200
+++ new/kafka-python-3.0.11/kafka/version.py    2026-08-16 18:29:39.000000000 
+0200
@@ -1 +1 @@
-__version__ = '3.0.8'
+__version__ = '3.0.11'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/test/admin/test_admin_topics.py 
new/kafka-python-3.0.11/test/admin/test_admin_topics.py
--- old/kafka-python-3.0.8/test/admin/test_admin_topics.py      2026-07-09 
20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/test/admin/test_admin_topics.py     2026-08-16 
18:29:39.000000000 +0200
@@ -22,6 +22,40 @@
     assert good_partitions.new_assignments == [[1, 2, 3]]
 
 
+def test_process_create_partitions_input_auto_assign_is_null():
+    # When the caller does not supply manual replica assignments, the request's
+    # `assignments` field must be None so it serialises to null (broker
+    # auto-assigns). An empty list [] serialises to a present-but-empty array,
+    # which the broker reads as "manual assignment" and rejects with
+    # InvalidReplicationAssignmentError. See issue #3146.
+    proc = KafkaAdminClient._process_create_partitions_input
+
+    # int total-count -> auto-assign
+    topics = proc({'foo': 6})
+    assert len(topics) == 1
+    assert topics[0].name == 'foo'
+    assert topics[0].count == 6
+    assert topics[0].assignments is None
+
+    # bare NewPartitions with no manual assignments -> auto-assign
+    topics = proc({'foo': NewPartitions(6)})
+    assert topics[0].assignments is None
+
+
+def test_process_create_partitions_input_manual_assignments():
+    # Explicit manual assignments must be preserved, for both the dict form
+    # and the (deprecated) NewPartitions form.
+    proc = KafkaAdminClient._process_create_partitions_input
+
+    topics = proc({'foo': {'count': 7, 'assignments': [[1, 2, 3]]}})
+    assert topics[0].count == 7
+    assert [a.broker_ids for a in topics[0].assignments] == [[1, 2, 3]]
+
+    topics = proc({'foo': NewPartitions(7, [[1, 2, 3]])})
+    assert topics[0].count == 7
+    assert [a.broker_ids for a in topics[0].assignments] == [[1, 2, 3]]
+
+
 def test_new_topic():
     good_topic = NewTopic('foo')
     assert good_topic.name == 'foo'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kafka-python-3.0.8/test/consumer/test_subscription_state.py 
new/kafka-python-3.0.11/test/consumer/test_subscription_state.py
--- old/kafka-python-3.0.8/test/consumer/test_subscription_state.py     
2026-07-09 20:43:02.000000000 +0200
+++ new/kafka-python-3.0.11/test/consumer/test_subscription_state.py    
2026-08-16 18:29:39.000000000 +0200
@@ -141,6 +141,49 @@
         # Still gates the assigned one.
         assert s.assignment[TopicPartition('foo', 0)]._pending_revocation is 
True
 
+    def 
test_pending_revocation_cleared_for_partition_retained_across_rebalance(self):
+        """Regression test for #3131.
+
+        Under the EAGER rebalance protocol the coordinator marks *every*
+        currently-owned partition pending-revocation before JoinGroup (see
+        ConsumerCoordinator._on_join_prepare_async). When the rebalance
+        completes, ``assign_from_subscribed`` preserves the
+        ``TopicPartitionState`` for any partition the member is given back,
+        so a *retained* partition carries a stale ``_pending_revocation``
+        flag. Because the flag is only ever cleared in
+        ``TopicPartitionState.__init__``, such a partition reports
+        not-fetchable forever - the fetcher positions it (ListOffsets) but
+        never issues a FetchRequest - until a later rebalance drops it and
+        creates fresh state.
+
+        A partition retained across a rebalance must be fetchable again.
+        """
+        s = self._subscribed()
+        foo0 = TopicPartition('foo', 0)
+        foo1 = TopicPartition('foo', 1)
+        s.assign_from_subscribed([foo0, foo1])
+        for tp in (foo0, foo1):
+            s.assignment[tp].seek(
+                OffsetAndMetadata(offset=0, metadata='', leader_epoch=-1))
+        assert s.is_fetchable(foo0)
+        assert s.is_fetchable(foo1)
+
+        # EAGER pre-rebalance: mark the full current assignment for revocation
+        # (mirrors mark_pending_revocation(assigned_partitions())).
+        s.mark_pending_revocation({foo0, foo1})
+        assert not s.is_fetchable(foo0)
+        assert not s.is_fetchable(foo1)
+
+        # Rebalance completes and the member is assigned the same partitions
+        # back. Their state (and valid position) is preserved.
+        s.assign_from_subscribed([foo0, foo1])
+        assert s.assignment[foo0].has_valid_position
+        assert s.assignment[foo1].has_valid_position
+
+        # They must be fetchable again - the revocation window is over.
+        assert s.is_fetchable(foo0)
+        assert s.is_fetchable(foo1)
+
     def test_preserves_preferred_read_replica_on_kept_partition(self):
         s = self._subscribed()
         s.assign_from_subscribed([TopicPartition('foo', 0)])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kafka-python-3.0.8/test/test_serializer.py 
new/kafka-python-3.0.11/test/test_serializer.py
--- old/kafka-python-3.0.8/test/test_serializer.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/kafka-python-3.0.11/test/test_serializer.py     2026-08-16 
18:29:39.000000000 +0200
@@ -0,0 +1,47 @@
+# pylint: skip-file
+
+import pytest
+
+from kafka.serializer import DefaultSerializer, JsonSerializer
+
+
[email protected]('encoding', ['utf-8', 'utf-16'])
+def test_default_serializer_roundtrip(encoding):
+    ser = DefaultSerializer(encoding)
+    data = 'h\u00e9llo w\u00f6rld'
+    encoded = ser.serialize('topic', [], data)
+    assert isinstance(encoded, bytes)
+    assert ser.deserialize('topic', [], encoded) == data
+
+
[email protected]('data', [b'raw', bytearray(b'raw'), 
memoryview(b'raw'), None])
+def test_default_serializer_passthrough_bytes_like(data):
+    assert DefaultSerializer().serialize('topic', [], data) == data
+
+
+def test_default_serializer_deserialize_none():
+    assert DefaultSerializer().deserialize('topic', [], None) is None
+
+
+def test_default_serializer_rejects_non_bytes_str():
+    with pytest.raises(AttributeError):
+        DefaultSerializer().serialize('topic', [], 42)
+
+
+def test_json_serializer_roundtrip():
+    ser = JsonSerializer()
+    data = {'key': 'value', 'n': 42, 'lst': [1, 2]}
+    encoded = ser.serialize('topic', [], data)
+    assert isinstance(encoded, bytes)
+    assert ser.deserialize('topic', [], encoded) == data
+
+
+def test_json_serializer_none():
+    ser = JsonSerializer()
+    assert ser.serialize('topic', [], None) is None
+    assert ser.deserialize('topic', [], None) is None
+
+
+def test_json_serializer_deserializes_plain_json():
+    ser = JsonSerializer()
+    assert ser.deserialize('topic', [], b'{"a": 1}') == {'a': 1}

Reply via email to