O paho-mqtt 2.0 mudou a forma de criar um cliente e o que seus callbacks recebem. Se o seu script quebrou depois de um pip install --upgrade, a correção é quase sempre a mesma: passe mqtt.CallbackAPIVersion.VERSION2 como primeiro argumento de mqtt.Client() e atualize as assinaturas dos callbacks. Este guia mostra os erros exatos, o que mudou e um exemplo completo de antes e depois.
Os erros que você provavelmente está vendo
| Erro | Causa | Correção |
|---|---|---|
ValueError: Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for details | O client ID foi passado como primeiro argumento posicional: mqtt.Client("my-id") | mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="my-id") |
DeprecationWarning: Callback API version 1 is deprecated, update to latest version | mqtt.Client() sem versão, ou com VERSION1 | Passe VERSION2 e atualize os callbacks |
TypeError: on_connect() takes 4 positional arguments but 5 were given | VERSION2 com um on_connect(client, userdata, flags, rc) no estilo 1.x | Adicione o parâmetro properties (veja abaixo) |
TypeError: on_disconnect() takes 3 positional arguments but 5 were given | O mesmo para on_disconnect(client, userdata, rc) | Nova assinatura com disconnect_flags, reason_code, properties |
AttributeError: module 'paho.mqtt.client' has no attribute 'CallbackAPIVersion' | Seu código é para 2.x, mas o ambiente ainda tem o 1.x | pip install "paho-mqtt>=2.0" e confira qual interpretador roda o script |
Confira primeiro a versão instalada — ambientes virtuais e o Python do sistema muitas vezes divergem:
python -c "import paho.mqtt; print(paho.mqtt.__version__)"
pip show paho-mqttO que mudou no 2.0
A versão 1.x tinha um único estilo de callback cujos argumentos dependiam da versão do protocolo: callbacks de MQTT 3.1.1 recebiam um inteiro rc, e os de MQTT 5 recebiam reason_code e properties. Era fácil escrever código que funcionava em um protocolo e quebrava no outro.
O 2.0 introduz versões da API de callbacks. Com CallbackAPIVersion.VERSION2, todo callback tem a mesma assinatura em MQTT 3.1.1 e MQTT 5, reason codes são sempre objetos ReasonCode e properties são sempre passadas (como None quando o protocolo não tem). O construtor agora fica assim:
mqtt.Client(
callback_api_version, # novo, primeiro argumento posicional
client_id="",
clean_session=None,
userdata=None,
protocol=mqtt.MQTTv311,
transport="tcp",
reconnect_on_failure=True,
manual_ack=False,
)Outros pontos importantes: o 2.x exige Python 3.7 ou mais recente, e os helpers publish.single() / subscribe.simple() cuidam da versão para você, então código que só usa esses helpers geralmente continua funcionando.
Assinaturas de callback: 1.x vs VERSION2
| Callback | 1.x (MQTT 3.1.1) | 2.x com VERSION2 |
|---|---|---|
on_connect | (client, userdata, flags, rc) | (client, userdata, flags, reason_code, properties) |
on_disconnect | (client, userdata, rc) | (client, userdata, disconnect_flags, reason_code, properties) |
on_subscribe | (client, userdata, mid, granted_qos) | (client, userdata, mid, reason_code_list, properties) |
on_unsubscribe | (client, userdata, mid) | (client, userdata, mid, reason_code_list, properties) |
on_publish | (client, userdata, mid) | (client, userdata, mid, reason_code, properties) |
on_message | (client, userdata, message) | sem mudanças |
on_log | (client, userdata, level, buf) | sem mudanças |
Na prática, três detalhes importam:
flagsnão é mais um dict. Emon_connectele é um objetoConnectFlags: useflags.session_presentem vez deflags["session present"].disconnect_flags.is_disconnect_packet_from_serverindica se o broker enviou um DISCONNECT (MQTT 5) ou se a própria biblioteca detectou a queda.on_subscriberecebe reason codes, não inteiros de QoS. Para uma assinatura concedida com QoS 1, ovaluedo reason code é1; uma assinatura recusada tem valor0x80ou maior, entãorc.is_failureé verdadeiro.
Reason codes agora são objetos
Com VERSION2, reason_code é um ReasonCode. Ele é igual a um inteiro e ao seu nome na comparação, é impresso como seu nome e tem a propriedade is_failure:
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"falha ao conectar: {reason_code} ({reason_code.value})")
return
print("conectado, sessão presente:", flags.session_present)reason_code == 0 e reason_code == "Success" continuam funcionando para o caso de sucesso, então a verificação mais comum sobrevive à atualização sem mudanças.
Um exemplo completo de antes e depois
Este é um código típico do 1.x — no 2.x ele quebra com o ValueError acima:
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, rc):
if rc == 0:
client.subscribe("testmqtt/paho2/#", qos=1)
else:
print("connect failed, rc =", rc)
def on_disconnect(client, userdata, rc):
print("disconnected, rc =", rc)
def on_message(client, userdata, msg):
print(msg.topic, msg.payload.decode())
client = mqtt.Client("testmqtt-paho2-demo")
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, 60)
client.loop_forever()O mesmo cliente migrado para VERSION2:
import os
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"falha ao conectar: {reason_code}")
return
print("conectado, sessão presente:", flags.session_present)
client.subscribe("testmqtt/paho2/#", qos=1)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
for rc in reason_code_list:
print("assinatura:", "recusada" if rc.is_failure else f"QoS concedido {rc.value}")
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
print(f"desconectado: {reason_code} (do servidor: {disconnect_flags.is_disconnect_packet_from_server})")
def on_message(client, userdata, msg):
print(msg.topic, msg.payload.decode())
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="testmqtt-paho2-demo")
if os.environ.get("MQTT_USER"):
client.username_pw_set(os.environ["MQTT_USER"], os.environ.get("MQTT_PASS"))
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_disconnect = on_disconnect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_forever()Os mesmos callbacks funcionam sem mudanças com protocol=mqtt.MQTTv5 — essa é a principal vantagem do VERSION2. Só os argumentos do construtor mudam: o MQTT 5 não aceita clean_session (gera ValueError: Clean session is not used for MQTT 5.0); use clean_start e a expiração de sessão em connect(). O guia de migração do MQTT 3.1.1 para o MQTT 5 cobre essas opções.
Checklist de migração
- Fixe a versão para a qual você migra, por exemplo
paho-mqtt>=2.1,<3norequirements.txt, para que a próxima versão major não pegue você de surpresa. - Procure
mqtt.Client(no código e passemqtt.CallbackAPIVersion.VERSION2primeiro, comclient_id=como argumento nomeado. - Atualize todos os callbacks para a nova assinatura — inclusive os atribuídos em outros módulos ou bibliotecas.
- Troque comparações de inteiros em
rcporreason_code.is_failureou comparações por nome. - Troque
flags["session present"]porflags.session_present. - Rode com os warnings visíveis (
python -W error::DeprecationWarning app.py) para pegar qualquer cliente que ainda esteja em VERSION1.
Se você vai reescrever bastante código de qualquer forma, avalie se um cliente assíncrono encaixa melhor — o aiomqtt para asyncio é construído sobre o paho-mqtt 2.x. Para outras exceções do paho, veja erros do paho-mqtt em Python.
Teste o cliente migrado
Rode client_v2.py, abra o cliente MQTT online no navegador, conecte-se a broker.connectmq.com e publique em testmqtt/paho2/hello. Seu script deve imprimir o topic e o payload. Para exercitar os caminhos de erro, conecte-se ao seu próprio broker com uma senha errada e confira se on_connect imprime um motivo legível como Bad user name or password em vez de um número solto.
Publique uma mensagem de teste para o cliente migrado
A migração para o paho-mqtt 2.0 é basicamente mecânica: um novo argumento no construtor, cinco assinaturas de callback e algumas verificações de inteiros que viram verificações de reason codes. Faça uma vez com VERSION2, fixe a versão major e seus callbacks vão se comportar igual em MQTT 3.1.1 e MQTT 5. Para um passo a passo completo da biblioteca, veja o guia de MQTT com Python.
Perguntas frequentes
O que significa 'Unsupported callback API version' no paho-mqtt?
Desde o paho-mqtt 2.0, o primeiro argumento de mqtt.Client() é a versão da API de callbacks. Código escrito para 1.x que passa o client ID como primeiro argumento posicional agora gera esse ValueError. Passe mqtt.CallbackAPIVersion.VERSION2 primeiro e o client ID como client_id=.
Posso manter meus callbacks antigos no paho-mqtt 2.x?
Sim, temporariamente. Passe mqtt.CallbackAPIVersion.VERSION1 e as assinaturas de callback do 1.x continuam funcionando, mas a biblioteca emite um DeprecationWarning e a versão 1 deve ser removida em uma futura versão major. Migre para VERSION2 assim que puder.
Por que minha verificação 'if rc == 5' parou de funcionar depois da atualização?
Com VERSION2, on_connect recebe um objeto ReasonCode mesmo em MQTT 3.1.1, e os códigos de retorno do 3.1.1 são convertidos em reason codes do MQTT 5. 'Not authorized' é 135 (0x87), não 5. Use reason_code.is_failure ou compare pelo nome, por exemplo reason_code == 'Not authorized'.

