Repository navigation
Python SDK: async methods return () instead of declared None #4356
Description
Bug description
Several Python SDK methods are declared as collections.abc.Awaitable[None], but their successful futures return Rust unit (()). With the pinned PyO3 0.29.2 and pyo3-async-runtimes 0.29.0, that path resolves to an empty Python tuple rather than None.
This affects callers that check result is None and makes the runtime contract inconsistent with the generated type declarations.
Affected area / component
Python SDK
Versions
Audited checkout: e0c2423a, based on upstream 3a00b5ba1ebcca9ccfaab532b406c9591a862dcb. PyO3 0.29.2 and pyo3-async-runtimes 0.29.0.
Evidence and validation limits
Source inspection at e0c2423a2961ed6728c0cc92bf377c8a06dc251e identified 24 IggyClient methods and 3 IggyConsumer methods. This audit did not run runtime tests for those 27 methods. The standalone ping reproduction below is prepared but has not yet been run: the available Windows environment lacks the build tools and a usable local Linux/server environment.
The same conversion was observed in the initial delete_segments implementation: two real-server tests failed with assert () is None in this CI run. That new method was separately corrected for #4328. Its successful rerun does not establish runtime coverage for the other methods listed here.
Minimal reproduction to run
Build/install the Python SDK from the audited checkout and start an Iggy server from the same checkout, then run:
import asyncio
from apache_iggy import IggyClient
async def main():
client = IggyClient("127.0.0.1:8090")
await client.connect()
try:
result = await client.ping()
print(repr(result), type(result).__name__)
assert result is None
finally:
await client.shutdown()
asyncio.run(main())Expected: None. Source-predicted result before a fix: () (tuple). The reproduction only opens a connection and pings the server; it does not create or delete resources.
Affected methods from source inspection
IggyClient:ping,login_user,update_user,delete_user,update_permissions,change_password,logout_user,connect,disconnect,shutdown,create_stream,update_stream,delete_stream,purge_stream,create_topic,update_topic,delete_topic,purge_topic,create_partitions,delete_partitions,create_consumer_group,delete_consumer_group,join_consumer_group,leave_consumer_groupIggyConsumer:store_offset,delete_offset,consume_messages
IggyClient.delete_segments and IggyProducer.shutdown explicitly return Python None and are excluded.
Conversion path
Source references: client bindings, consumer bindings, and type declarations.
- The wrappers declare
Awaitable[None]using a stub override, which does not change runtime conversion. pyo3-async-runtimes0.29.0 converts the future's successful value withval.into_py_any(py)(src/generic.rs:655).- PyO3 0.29.2 implements
IntoPyObject for ()usingPyTuple::empty(py)(src/conversion.rs:561-572).
Related work and scope
#4166 proposes returning resource details from create_stream, create_topic, and create_consumer_group. #4167 proposes returning identity details from login_user. Those four methods should be coordinated with that work rather than assigned a new final return contract here.
For methods intended to retain a None contract, an explicit Python None conversion and a return-value regression test would align runtime behavior with the type declaration. The approach should be agreed before a broad fix. This report is separate from #4328.
Activity
slbotbm commentedon Sep 30, 2026
@FenjuFu are you working on this?
hubcio commentedon Sep 30, 2026
i checked in code and you're right. pyo3 turns
()into an empty tuple, and no test checks these return values.let's return
Nonelike the stubs say. fix 26 of them, including the three from #4166 (it can change them later).login_useris already covered by #4276. use one helper for all of them (andIggyProducer.shutdown) and add one test that calls each method and checks the resultis None.you're on #4328 with no PR yet, so open that first (one open pr at a time). after it's merged, grab this one if you want. test and fix in one PR is fine.
do you want to work on this @FenjuFu ?
FenjuFu commentedon Oct 8, 2026
Yes, I'd like to take this, following hubcio's plan (one helper for all 26 methods plus
IggyProducer.shutdown, and one test that checks each returnsNone). Since I can only have one open PR as a new contributor, I'll start right after #4357 is merged. If someone wants it sooner, feel free to take it.hubcio commentedon Oct 8, 2026
fyi
purge_streamandpurge_topicare going away (we're removing purge, removal pr goes up today), so whichever pr lands second just drops them. no need to change your plan.FenjuFu commentedon Oct 14, 2026
#4357 is merged, so I'm free to pick this up. Could you assign it to me? I'll follow the plan above: one helper for the
Nonereturns, and one test that checks each method.purge_stream/purge_topicare already gone from master, so they're not part of it.