Skip to main content

Python

Async (asyncio) bindings generated by UniFFI's built-in Python backend and packaged by maturin. Client and broker are separate wheels. Read the shared concepts first for the option records and semantics.

Install

pip install stem-mqtt-client stem-mqtt-broker    # either one on its own works too

The distribution names are stem-mqtt-client / stem-mqtt-broker (mqtt-client is taken on PyPI), the import names are not:

import mqtt_client
import mqtt_broker

Requirements

  • Python 3.9+ and asyncio. The wheels are py3-none-<platform> (the library is loaded with ctypes), so there is one wheel per OS/arch, not per Python version.
  • Wheels bundle the native library: Linux (manylinux_2_28, x86_64) from the Gitea release; the GitHub release adds macOS and Windows. Other platforms build from source (below).

Quick start

import asyncio
import mqtt_broker
import mqtt_client


def options(port, client_id, username=None):
    return mqtt_client.ConnectOptions(
        host="127.0.0.1", port=port, client_id=client_id,
        version=mqtt_client.MqttVersion.V5, clean_start=True, keep_alive_secs=30,
        username=username, password=None, will=None,
        connect_timeout_secs=10, operation_timeout_secs=5,
        auto_reconnect=False, reconnect_backoff_secs=0, reconnect_max_backoff_secs=0, tls=None,
    )


class Inbox(mqtt_client.MqttMessageListener):
    def __init__(self):
        self.queue = asyncio.Queue()
        self.loop = asyncio.get_running_loop()

    def on_message(self, message):          # called from a Rust thread
        self.loop.call_soon_threadsafe(self.queue.put_nowait, message)

    def on_disconnected(self, reason):
        print("disconnected:", reason)


async def main():
    broker = mqtt_broker.MqttBroker(mqtt_broker.MqttBrokerConfig(
        bind_address="127.0.0.1", port=0, ws_port=None, allow_anonymous=True, max_clients=0,
        max_qos=mqtt_broker.QoS.EXACTLY_ONCE, max_retained_messages=100,
        max_queued_per_client=100, redelivery_interval_secs=0, tls=None,
    ))
    await broker.start()
    port = broker.bound_port()

    inbox = Inbox()
    sub = mqtt_client.MqttClient(options(port, "py-sub"))
    sub.set_message_listener(inbox)
    await sub.connect()
    await sub.subscribe("demo/#", mqtt_client.QoS.AT_LEAST_ONCE)

    pub = mqtt_client.MqttClient(options(port, "py-pub"))
    await pub.connect()
    await pub.publish("demo/hello", b"hello", mqtt_client.QoS.AT_LEAST_ONCE, False)

    message = await asyncio.wait_for(inbox.queue.get(), 5)
    print(message.topic, bytes(message.payload))

    await pub.disconnect(); await sub.disconnect(); await broker.stop()


asyncio.run(main())

max_packet_size, max_outbound_queue, session_expiry_secs, auth_method and auth_data have defaults and can be omitted, as above.

Client

  • Every I/O method is a coroutine: await client.connect(), subscribe(filter, qos), unsubscribe(filter), publish(topic, payload, qos, retain), disconnect(). is_connected(), set_message_listener(), enable_message_queue() and last_disconnect_reason() are plain calls. payload is bytes; received message.payload is bytes-like — wrap it in bytes(...).
  • Enums are UPPER_SNAKE: mqtt_client.QoS.AT_MOST_ONCE | AT_LEAST_ONCE | EXACTLY_ONCE, mqtt_client.MqttVersion.V311 | V5.
  • Listener callbacks run on a Rust thread, not on the event loop. Hand work back with loop.call_soon_threadsafe(...) as above (or a threading.Event). Keep them short: an exception raised in a callback is caught and logged so it cannot take down the I/O task, but it is not propagated to you.
  • Or pull: client.enable_message_queue(16) then await client.next_message(5000) returns a message or None — no threads involved.
polled = mqtt_client.MqttClient(options(port, "py-polled"))
polled.enable_message_queue(16)
await polled.connect()
await polled.subscribe("demo/#", mqtt_client.QoS.AT_LEAST_ONCE)
msg = await polled.next_message(5000)       # None on timeout

Broker

mqtt_broker.MqttBroker(config) — await start(), await stop(), is_running(), bound_port(), bound_ws_port(), bound_tls_port(), client_count(). Use port 0 and read bound_port() for an ephemeral port. The broker owns its own mqtt_broker.QoS; don't pass a mqtt_client.QoS to it.

class Events(mqtt_broker.MqttBrokerEventListener):
    def on_client_connected(self, client_id): print("+", client_id)
    def on_client_disconnected(self, client_id, reason): print("-", client_id, reason)
    def on_message_published(self, client_id, topic, qos): print(client_id, "->", topic)

broker.set_event_listener(Events())

Or poll: broker.enable_event_queue(64) then await broker.next_event(1000) returns a BrokerEvent (ClientConnected, ClientDisconnected, MessagePublished) or None.

Authentication

class Auth(mqtt_broker.MqttAuthProvider):
    def authenticate(self, client_id, username, password):   # password: bytes | None
        return username == "demo" and password == b"demo"

broker.set_auth_provider(Auth())

A refused client raises from await client.connect() (reason 0x86). MQTT 5 enhanced authentication:

class AddOne(mqtt_broker.MqttEnhancedAuthProvider):
    def step(self, client_id, method, data, round):
        if round == 0:
            return mqtt_broker.EnhancedAuthStep(outcome=mqtt_broker.EnhancedAuthOutcome.CONTINUE, data=b"\x07")
        ok = data is not None and bytes(data) == b"\x08"
        return mqtt_broker.EnhancedAuthStep(
            outcome=mqtt_broker.EnhancedAuthOutcome.SUCCESS if ok else mqtt_broker.EnhancedAuthOutcome.FAILURE,
            data=b"welcome" if ok else b"")

class AddOneHandler(mqtt_client.MqttAuthHandler):
    def respond(self, method, challenge):
        return bytes(b + 1 for b in challenge)

broker.set_enhanced_auth_provider(AddOne())
client = mqtt_client.MqttClient(options(port, "py-eauth"))   # with auth_method="X-ADD-ONE", auth_data=b"hello"
client.set_auth_handler(AddOneHandler())

TLS

ca = open("ca.pem", "rb").read()
tls = mqtt_client.TlsOptions(ca_cert_pem=ca, client_cert_pem=None, client_key_pem=None,
                             insecure_skip_certificate_verification=False)
# pass tls=tls in ConnectOptions

broker_tls = mqtt_broker.BrokerTlsConfig(port=8883, cert_pem=open("server.pem", "rb").read(),
                                         key_pem=open("server.key", "rb").read(), client_ca_pem=None)
# pass tls=broker_tls in MqttBrokerConfig

Language notes

  • Optional values are None; byte values are bytes. Errors are exceptions (mqtt_client.MqttError.*, mqtt_broker.MqttBrokerError.*); catch Exception for "refused".
  • A broker started in a script keeps running until await broker.stop(); closing the loop without stopping leaves the listener threads alive until the process exits.

Building from source

pip install maturin
cd crates/mqtt-client && maturin build --release      # wheel in target/wheels/
cd ../mqtt-broker && maturin build --release
pip install ../../target/wheels/*.whl

maturin runs uniffi-bindgen itself; there is no separate generation step. More: packaging/python/README.md. The end-to-end test is tests/bindings/python/smoke.py.

Troubleshooting

  • ImportError: cannot import name 'mqtt_client' — you installed the broker wheel only (or vice versa); install both, or import just the component you have.
  • ModuleNotFoundError after pip install mqtt-client — wrong package; the name is stem-mqtt-client.
  • A callback never fires — it is invoked from another thread; make sure the loop you hand work to is still running and that the listener object is kept referenced (don't let it be garbage collected).
  • no matching distribution — no wheel for your platform yet; build from source.

Metadata

Release files for stem-mqtt-client 0.4.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for stem-mqtt-client 0.4.2
File Interpreter ABI Platform Actions
stem_mqtt_client-0.4.2-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for stem_mqtt_client-0.4.2-py3-none-win_amd64.whl
stem_mqtt_client-0.4.2-py3-none-manylinux_2_34_x86_64.whl Python 3 none Linux glibc 2.34+ x86-64 Details for stem_mqtt_client-0.4.2-py3-none-manylinux_2_34_x86_64.whl
stem_mqtt_client-0.4.2-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for stem_mqtt_client-0.4.2-py3-none-macosx_11_0_arm64.whl

Total release size: 3.7 MB

Release files / stem_mqtt_client-0.4.2-py3-none-win_amd64.whl

Download URL stem_mqtt_client-0.4.2-py3-none-win_amd64.whl
Size 1.2 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
b563a22a6dbbd98a2e991d9244fe7875d5aa9e040bfce63e7d9ec8512309d68d
BLAKE2b-256 checksum
How to use checksums
0419c0eed7cc6e42ce983f4ee7639233f629e704c814fe00ad547ddb610bfb74
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / stem_mqtt_client-0.4.2-py3-none-manylinux_2_34_x86_64.whl

Download URL stem_mqtt_client-0.4.2-py3-none-manylinux_2_34_x86_64.whl
Size 1.3 MB
Tags Linux glibc 2.34+ x86-64 Python 3
SHA-256 checksum
How to use checksums
147ed0239966f29c1d99737736a17be93fb4eb649666e2f2a3ad1bc7170e8993
BLAKE2b-256 checksum
How to use checksums
69d9df17e9a7bc21baf5fab5e58a0e1b19f72475b0933cf9847d6bde53884745
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / stem_mqtt_client-0.4.2-py3-none-macosx_11_0_arm64.whl

Download URL stem_mqtt_client-0.4.2-py3-none-macosx_11_0_arm64.whl
Size 1.2 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
f3909c1838f57fe2c3a06412488d0ea8b89a11dd7777a0b0e9c8a036ef713b6d
BLAKE2b-256 checksum
How to use checksums
2c99373dc96190798c0d2eae0debfe1880cf65abac887bd724d208a3a51bba78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for stem-mqtt-client 0.4.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 3 files in this release have been uploaded with provenance metadata.

Select from 3 files

Attestation for stem_mqtt_client-0.4.2-py3-none-win_amd64.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.4.2 This release

3 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page