Package: asyncio-mqtt
Idiomatic asyncio MQTT Client 🙌Write code like this:SubscriberasyncwithClient("test.mosquitto.org")asclient:asyncwithclient.messages()asmessages:awaitclient.subscribe("humidity/#")asyncformessageinmessages:print(message.payload)PublisherasyncwithClient("test.mosquitto.org")asclient:awaitclient.publish("humidity/outside",payload=0.38)asyncio-mqttcombines the stability of the time-provenpaho-mqttlibrary with a modern, asyncio-based interface.No more callbacks! 👍No more return codes (welcome to theMqttError)Graceful disconnection (forget abouton_unsubscribe,on_disconnect, etc.)Compatible withasynccodeFully type-hintedDid we mention no more callbacks?The whole thing is less than700 lines of code.Contents 🔍InstallationNote for Windows usersAdvanced usageConfiguring the clientFiltering messagesSharing the connectionSide by side with web frameworksWhy can't Iconnect/disconnectmanually?Listening without blockingReconnectingTLSProxyingLicenseVersioningChangelogRelated projectsInstallation 📚asyncio-mqttcan be installed viapip install asyncio-mqtt. It requires Python 3.7+ to run. The only dependency ispaho-mqtt.If you can't wait for the latest version and want to install directly from GitHub, use:pip install git+https://github.com/sbtinstruments/asyncio-mqttNote for Windows usersSince Python 3.8, the default asyncio event loop is theProactorEventLoop. Said loopdoesn't support theadd_readermethodthat is required byasyncio-mqtt. Please switch to an event loop that supports theadd_readermethod such as the built-inSelectorEventLoop:# Change to the "Selector" event loopasyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())# Run your async application as usualasyncio.run(main())Advanced usage ⚡Let's make the example from before more interesting:Configuring the clientYou can configure quite a few things when initializing the client. These are all the possible parameters together with their default values. Seepaho-mqtt's documentationfor more information about the individual parameters.importasyncio_mqttasaiomqttimportpaho.mqttasmqttaiomqtt.Client(hostname="test.mosquitto.org",# The only non-optional parameterport=1883,username=None,password=None,logger=None,client_id=None,tls_context=None,tls_params=None,proxy=None,protocol=None,will=None,clean_session=None,transport="tcp",keepalive=60,bind_address="",bind_port=0,clean_start=mqtt.client.MQTT_CLEAN_START_FIRST_ONLY,properties=None,message_retry_set=20,socket_options=(),max_concurrent_outgoing_calls=None,websocket_path=None,websocket_headers=None,)Filtering messagesImagine you're measuring temperature and humidity on the outside and inside, and our topics look like this:temperature/outside. You want to receive all types of measurements but handle them differently.asyncio-mqttprovidesTopic.matches()to make this easy:importasyncioimportasyncio_mqttasaiomqttasyncdefmain():asyncwithaiomqtt.Client("test.mosquitto.org")asclient:asyncwithclient.messages()asmessages:awaitclient.subscribe("#")asyncformessageinmessages:ifmessage.topic.matches("humidity/outside"):print(f"[humidity/outside]{message.payload}")ifmessage.topic.matches("+/inside"):print(f"[+/inside]{message.payload}")ifmessage.topic.matches("temperature/#"):print(f"[temperature/#]{message.payload}")asyncio.run(main())Note that in our example, messages totemperature/insideare handled twice!Sharing the connectionIn many cases, you'll want to send and receive messages in different locations in your code. You could create a new client each time, butthis is not very performant, andyou'll use a lot more network bandwidth.You can share the connection by passing theClientinstance to all functions that need it:importasyncioimportasyncio_mqttasaiomqttasyncdefpublish_humidity(client):awaitclient.publish("humidity/outside",payload=0.38)asyncdefpublish_temperature(client):awaitclient.publish("temperature/outside",payload=28.3)asyncdefmain():asyncwithaiomqtt.Client("test.mosquitto.org")asclient:awaitpublish_humidity(client)awaitpublish_temperature(client)asyncio.run(main())Side by side with web frameworksMost web frameworks take control over the "main" function, which makes it difficult to figure out where to create and connect theClientand how to share this connection.Some frameworks likeStarlettedirectly support lifespan context managers, with which you can safely set up a global client instance that you can then pass to functions that need it, just like before:importasyncioimportasyncio_mqttasaiomqttimportstarlette.applicationsimportcontextlibclient=None@contextlib.asynccontextmanagerasyncdeflifespan(app):globalclientasyncwithaiomqtt.Client("test.mosquitto.org")asc:client=cyieldapp=starlette.applications.Starlette(routes=[],lifespan=lifespan,)FastAPI (which is built upon Starlette) doesn't expose that API yet, but there aremultipleopen PRsto add it. In the meantime, you can work around it via FastAPI's dependency injection.Why can't Iconnect/disconnectmanually?Managing a connection by callingconnectanddisconnectdirectly is a bit tricky. For example, when you're disconnecting the client, you'd have to make sure that there's no other task that still relies on the connection. There are many similar situations where something can easily go wrong.Context managers take care of all connection and disconnection logic for you, in a way that makes it very difficult to shoot yourself in the foot. They are a lot easier and less error-prone to use thanconnect/disconnect.Supporting both context managers and manualconnect/disconnectwould add a lot of complexity toasyncio-mqtt. To keep maintainer burden manageable, we focus only on the preferred option: context managers.Listening without blockingIf you run the basic example for subscribing and listening for messages, you'll notice that the program doesn't finish until you stop it. Waiting for messages blocks the execution of everything that comes afterwards. If you want to run other code after starting your listener (e.g. handling HTTP requests in a web framework) you don't want this.To solve this, you can use asyncio'screate_taskwithoutawaiting the created task. The concept is similar to starting a new thread withoutjoining it in a multithreaded application.importasyncioimportasyncio_mqttasaiomqttasyncdeflisten():asyncwithaiomqtt.Client("test.mosquitto.org")asclient:asyncwithclient.messages()asmessages:awaitclient.subscribe("humidity/#")asyncformessageinmessages:print(message.payload)asyncdefmain():# Wait for messages in (unawaited) asyncio taskloop=asyncio.get_event_loop()task=loop.create_task(listen())# This will still run!print("Magic!")# If you don't await the task here the program will simply finish.# However, if you're using an async web framework you usually don't have to await# the task, as the framework runs in an endless loop.awaittaskasyncio.run(main())ReconnectingYou can reconnect when the connection to the broker is lost by wrapping your code in atry/except-block and listening forMqttErrors.importasyncioimportasyncio_mqttasaiomqttasyncdefmain():reconnect_interval=5# In secondswhileTrue:try:asyncwithaiomqtt.Client("test.mosquitto.org")asclient:asyncwithclient.messages()asmessages:awaitclient.subscribe("humidity/#")asyncformessageinmessages:print(message.payload.decode())exceptaiomqtt.MqttErroraserror:print(f'Error "{error}". Reconnecting in{reconnect_interval}seconds.')awaitasyncio.sleep(reconnect_interval)asyncio.run(main())TLSYou can configure TLS via theTLSParametersclass. The parameters are directly passed through to paho-mqtt'stls_setfunction. Seepaho-mqtt's documentationfor more information about the individual parameters.importasyncioimportasyncio_mqttasaiomqttimportssltls_params=aiomqtt.TLSParameters(ca_certs=None,certfile=None,keyfile=None,cert_reqs=ssl.CERT_REQUIRED,tls_version=ssl.PROTOCOL_TLS,ciphers=None,)asyncdefmain():asyncwithaiomqtt.Client("test.mosquitto.org",tls_params=tls_params)asclient:awaitclient.publish("humidity/outside",payload=0.38)asyncio.run(main())ProxyingYou can configure proxying via theProxySettingsclass. The parameters are directly passed through to paho-mqtt'sproxy_setfunctionality. Both SOCKS and HTTP proxies are supported. Note that proxying is an extra feature (even in paho-mqtt) that requires thePySocksdependency. Seepaho-mqtt's documentationfor more information about the individual parameters.importasyncioimportasyncio_mqttasaiomqttimportsocksproxy_params=aiomqtt.ProxySettings(proxy_type=socks.HTTP,proxy_addr="www.example.com",proxy_rdns=True,proxy_username=None,proxy_password=None,)asyncdefmain():asyncwithaiomqtt.Client("test.mosquitto.org",proxy=proxy_params)asclient:awaitclient.publish("humidity/outside",payload=0.38)asyncio.run(main())License 📋Note that the underlying paho-mqtt library is dual-licensed. One of the licenses is the so-calledEclipse Distribution License v1.0. It is almost word-for-word identical to theBSD 3-clause License. The only differences are:One use of "COPYRIGHT OWNER" (EDL) instead of "COPYRIGHT HOLDER" (BSD)One use of "Eclipse Foundation, Inc." (EDL) instead of "copyright holder" (BSD)Versioning 🎯This project adheres toSemantic Versioning.Expect API changes until we reach version1.0.0. After1.0.0, breaking changes will only occur in major release (e.g.,2.0.0,3.0.0, etc.).Changelog 🚧Please refer to theCHANGELOGdocument. It adheres to the principles ofKeep a Changelog.Related projects 🌟Isasyncio-mqttnot what you are looking for? Try another client:paho-mqtt— Own protocol implementation. Synchronous.gmqtt— Own protocol implementation. Asynchronous.fastapi-mqtt— Asynchronous wrapper around gmqtt. Simplifies integration in your FastAPI application.amqtt— Own protocol implementation. Asynchronous. Includes a broker.mqttools— Own protocol implementation. Asynchronous.trio-paho-mqtt— Asynchronous wrapper around paho-mqtt (similar toasyncio-mqtt). Based on trio instead of asyncio.