Skip to content

Python SDK: async methods return () instead of declared None #4356

Description

@FenjuFu

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_group
  • IggyConsumer: 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-runtimes 0.29.0 converts the future's successful value with val.into_py_any(py) (src/generic.rs:655).
  • PyO3 0.29.2 implements IntoPyObject for () using PyTuple::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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpythonPull requests that update Python code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions