Skip to content

KafkaAdminClient.create_partitions with int total count fails with InvalidReplicationAssignmentError (sends empty assignments array instead of null) #3146

Description

@ebrard

Summary

Calling create_partitions({topic: N}) with a plain int total count — the form the docstring recommends — fails against a normal broker with InvalidReplicationAssignmentError (error 39). The int path builds the per-topic struct without an assignments value, which defaults to an empty list [] rather than None. On the wire that becomes a present-but-empty assignments array, which the broker interprets as "manual replica assignment supplied" and then rejects because the assignment count doesn't match the number of new partitions.

Environment

  • kafka-python 3.0.9 (latest on PyPI)
  • Python 3.14
  • Broker: confluentinc/cp-kafka:latest, single node, KRaft mode

Reproduction

from kafka.admin import KafkaAdminClient

admin = KafkaAdminClient(bootstrap_servers="localhost:9092")
# demo-topic already exists with 3 partitions
admin.create_partitions({"demo-topic": 6})

Result:

kafka.errors.InvalidReplicationAssignmentError: [Error 39]
  CreatePartitionsTopic(name='demo-topic', count=6, assignments=[])
  error_message='Attempted to add 3 additional partition(s), but only 0 assignment(s) were specified.'

NewPartitions(total_count=6) is not a usable alternative either — that branch raises TypeError: 'NoneType' object is not iterable because _process_create_partitions_input iterates count.new_assignments unconditionally, even though new_assignments defaults to None.

Root cause (verified at the byte level)

The int path in _process_create_partitions_input builds CreatePartitionsTopic(name=topic, count=count) with no assignments, so it defaults to []. Encoding the request confirms [] and None differ exactly at the assignments field:

# v3 (compact array length byte after count=00000006):
[]   -> ...00000006 01 ...   # compact length 1 => 0 elements, present array
None -> ...00000006 00 ...   # 0 => null

# v0 (classic 4-byte array length):
[]   -> ...00000006 00000000 ...   # length 0, present
None -> ...00000006 ffffffff ...   # -1 => null

The broker treats null as "auto-assign replicas" and a present array as "manual assignment", so the empty array triggers error 39.

Workaround

Build the request so assignments is None:

from kafka.admin._partitions import CreatePartitionsRequest
from kafka.errors import for_code

def create_partitions_auto(admin, topic, total_count):
    Topic = CreatePartitionsRequest.CreatePartitionsTopic
    request = CreatePartitionsRequest(
        topics=[Topic(name=topic, count=total_count, assignments=None)],
        timeout_ms=30000, validate_only=False)
    def errs(r):
        for res in r.results:
            yield for_code(res.error_code)
    return admin._manager.run(admin._send_request_to_controller, request, errs, True)

Suggested fix

  • Int path: default assignments to None (not []) when the caller doesn't supply manual assignments.
  • NewPartitions path: guard for new_assignments is None before iterating.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions