Skip to main content

zmqtt

PyPI version PyPI downloads PyPI requires python

Tests License Telegram members Telegram members Coverage

Docs · PyPI · Changelog · Contributing

Pure asyncio MQTT 3.1.1 and 5.0 for Python 3.10+, with deterministic subscription routing, bounded queues, and correlation-safe request/response.

What is MQTT?

MQTT (Message Queuing Telemetry Transport) is a lightweight publish/subscribe messaging protocol designed for devices with limited resources and networks with low bandwidth or unstable connectivity. It became the de facto standard in the IoT world thanks to its minimal packet overhead: a fixed header takes as little as 2 bytes, which makes it cheap to send data even from small embedded devices over slow or unreliable networks.

Official site: mqtt.org

Why zmqtt?

  • Application-facing subscriptions. Each Subscription owns its filters, bounded queue, acknowledgement policy, and async iterator.
  • Deterministic routing. Wildcards, shared subscriptions, broker decorator prefixes, and MQTT 5 subscription identifiers are handled by the built-in router. One incoming PUBLISH is delivered once to the selected subscription.
  • High-level QoS. Packet identifiers and the QoS 1/2 protocol handshakes are managed by the client. Manual acknowledgement is opt-in per subscription.
  • Safe MQTT 5 request/response. Concurrent requests are matched by both response topic and correlation data and delivered exclusively to one pending request.
  • Bounded by default. Subscription queues and pending request futures apply backpressure instead of growing without limit.
  • Self-contained MQTT stack. The packet codec and protocol engine live in zmqtt; there is no Paho or other MQTT client dependency.
  • One API for MQTT 3.1.1 and 5.0. create_client() returns a version-typed client, so a type checker can reject MQTT 5-only calls on a 3.1.1 connection.

zmqtt and aiomqtt 3

aiomqtt 3 is no longer a Paho wrapper. It is also pure asyncio, fully typed, and has MQTT 5 flow control and opt-in automatic reconnection, but its API deliberately exposes more low-level MQTT 5 mechanics. zmqtt provides a higher-level application API and supports both current protocol versions.

zmqtt aiomqtt 3
MQTT versions 3.1.1 and 5.0 5.0 only
Receive model Subscription-local queues and iterators Client-wide messages() stream
Message routing Built in: wildcards, shared/decorated filters, subscription identifiers Left to the application
QoS 1/2 publish Publish packet IDs and the complete handshake are managed by publish() Caller supplies publish packet IDs and completes QoS 2 with pubrel()
Reconnection Enabled by default; active Subscription objects are restored Opt-in; no automatic resubscription—requires a persistent broker session or application-managed recovery
Request/response request() with correlation routing, cleanup, and backpressure MQTT properties exposed; correlation and response routing left to the application
MQTT protocol dependency Built-in codec and state machines External mqtt5 package

The comparison targets the aiomqtt 3.0 alpha API, where application code supplies packet identifiers for QoS 1/2 publishes, drives the remaining QoS 2 steps, and implements message routing and response correlation. zmqtt keeps the decisions applications need explicit — QoS, acknowledgement timing, MQTT 5 properties, subscription lifecycle, and reconnect policy — while handling that protocol machinery in the client. The result is less application code, fewer protocol edge cases, and one typed API for both MQTT 3.1.1 and 5.0.

Installation

pip install zmqtt

Quick start

import asyncio

from zmqtt import QoS, create_client


async def main() -> None:
    async with create_client("localhost") as client:
        async with client.subscribe(
            "sensors/#",
            qos=QoS.AT_LEAST_ONCE,
        ) as messages:
            await client.publish(
                "sensors/temperature",
                "23.5",
                qos=QoS.AT_LEAST_ONCE,
            )
            msg = await messages.get_message()
            print(msg.topic, msg.payload.decode())


asyncio.run(main())

create_client() defaults to MQTT 3.1.1. Pass version="5.0" for the MQTT 5 API.

Publish

from zmqtt import QoS

await client.publish("events/online", b"device-42")
await client.publish(
    "commands/restart",
    b"device-42",
    qos=QoS.AT_LEAST_ONCE,
    retain=False,
)

Payloads may be bytes or str; strings are encoded as UTF-8. QoS 1 waits for PUBACK. QoS 2 completes the PUBREC/PUBREL/PUBCOMP handshake before returning.

Subscribe and route

async with client.subscribe(
    "sensors/+/temperature",
    "sensors/#",
    receive_buffer_size=100,
) as messages:
    async for msg in messages:
        print(msg.topic, msg.payload)

If one PUBLISH matches multiple filters in the same subscription, zmqtt selects the most specific filter (literal before + before #) and enqueues the message once. MQTT 5 subscription identifiers are used when the broker supplies them, which disambiguates overlapping subscriptions reliably.

Shared subscriptions and broker decorator prefixes work with the same API:

async with client.subscribe("$share/workers/jobs/#") as jobs:
    async for job in jobs:
        await process(job)

The public topic_matches() helper follows the same matching rules, including $share, $queue, $exclusive, and configured stripped prefixes.

Manual acknowledgement

Manual acknowledgement only has an effect at QoS 1 or 2:

from zmqtt import QoS, create_client

client = create_client(
    "localhost",
    client_id="orders-worker-1",
    clean_session=False,
)

async with client:
    async with client.subscribe(
        "orders/#",
        qos=QoS.AT_LEAST_ONCE,
        auto_ack=False,
    ) as messages:
        async for msg in messages:
            await save_to_database(msg)
            await msg.ack()

A stable client ID and persistent broker session make an unacknowledged message eligible for redelivery after reconnect. They do not make application processing exactly once, so handlers should still be idempotent. MQTT 5 clients also need a positive session_expiry_interval for a session to survive disconnection.

MQTT 5 request/response

from zmqtt import create_client

async with create_client("localhost", version="5.0") as client:
    reply = await client.request(
        "services/echo",
        b"hello",
        timeout=5.0,
    )
    print(reply.payload)

zmqtt subscribes to the response topic before publishing, generates a response topic and correlation data when omitted, and matches the reply by the exact (response_topic, correlation_data) pair. Concurrent requests may share a response topic. A matching reply is delivered only to its pending request(); it is not also enqueued for an ordinary subscription. Unmatched or late replies fall through to normal routing, which selects at most one subscription. The request dispatcher itself does not retain them.

The responder must copy the request's correlation data unchanged:

from zmqtt import PublishProperties

async with client.subscribe("services/echo") as requests:
    async for request in requests:
        assert request.properties is not None
        assert request.properties.response_topic is not None
        assert request.properties.correlation_data is not None
        await client.publish(
            request.properties.response_topic,
            request.payload,
            properties=PublishProperties(
                correlation_data=request.properties.correlation_data,
            ),
        )

The timeout argument bounds the reply wait after the response subscription is ready and the request has been published. Use asyncio.wait_for() around the whole coroutine when setup and publishing must be included in the cancellation budget; cancellation cleanup can still extend the wall-clock completion time.

Reconnection

Automatic reconnection is enabled by default. Active subscriptions are re-registered after a successful reconnect while their application queues stay alive. The default policy makes at most five connection attempts:

from zmqtt import ReconnectConfig, create_client

client = create_client(
    "localhost",
    reconnect=ReconnectConfig(max_attempts=None),  # retry indefinitely
)

Broker refusals such as invalid credentials are not retried. See Reconnection for the complete failure semantics.

MQTT 5 properties

from zmqtt import PublishProperties, create_client

async with create_client("localhost", version="5.0") as client:
    await client.publish(
        "events/reading",
        b'{"value": 42}',
        properties=PublishProperties(
            content_type="application/json",
            message_expiry_interval=300,
            user_properties=(("source", "sensor-01"),),
        ),
    )

MQTT 5 also adds session expiry, subscription identifiers, no_local, retain handling, publish properties, and a low-level AUTH packet API.

Learn more

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zmqtt-0.2.2.tar.gz (44.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zmqtt-0.2.2-py3-none-any.whl (55.9 kB view details)

Uploaded Python 3

File details

Details for the file zmqtt-0.2.2.tar.gz.

File metadata

  • Download URL: zmqtt-0.2.2.tar.gz
  • Upload date:
  • Size: 44.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zmqtt-0.2.2.tar.gz
Algorithm Hash digest
SHA256 58bd4b93d581de640504f41f881f68fa0b20192a28561fe14f2dc0024eb989d9
MD5 40ba2bd57fff2920f807fb1e52ef8af2
BLAKE2b-256 bc5e48979a39b03f82dbb9c35f17fba2f685d9f0078ebcb0f12a4abd907fc24c

See more details on using hashes here.

Provenance

The following attestation bundles were made for zmqtt-0.2.2.tar.gz:

Publisher: release.yaml on faststream-community/zMQTT

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zmqtt-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: zmqtt-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 55.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zmqtt-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 44b0f03dc6bf8dcc3a0092ff036b6179dc247c9959c920dc9faf0379a35e8458
MD5 d1997a43935be92f8022dcec643759e3
BLAKE2b-256 0ad67bf9fa71c1a6d4ec56b2037e6026989d9b6aac8d15257a83afde81f4eb0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for zmqtt-0.2.2-py3-none-any.whl:

Publisher: release.yaml on faststream-community/zMQTT

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 files

0.2.1

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

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