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.
Summary
Calling
create_partitions({topic: N})with a plain int total count — the form the docstring recommends — fails against a normal broker withInvalidReplicationAssignmentError(error 39). The int path builds the per-topic struct without anassignmentsvalue, which defaults to an empty list[]rather thanNone. 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
confluentinc/cp-kafka:latest, single node, KRaft modeReproduction
Result:
NewPartitions(total_count=6)is not a usable alternative either — that branch raisesTypeError: 'NoneType' object is not iterablebecause_process_create_partitions_inputiteratescount.new_assignmentsunconditionally, even thoughnew_assignmentsdefaults toNone.Root cause (verified at the byte level)
The int path in
_process_create_partitions_inputbuildsCreatePartitionsTopic(name=topic, count=count)with noassignments, so it defaults to[]. Encoding the request confirms[]andNonediffer exactly at the assignments field:The broker treats
nullas "auto-assign replicas" and a present array as "manual assignment", so the empty array triggers error 39.Workaround
Build the request so
assignmentsisNone:Suggested fix
assignmentstoNone(not[]) when the caller doesn't supply manual assignments.NewPartitionspath: guard fornew_assignments is Nonebefore iterating.