Tutoriais

Migração para Paho MQTT 2.0: corrija os erros de CallbackAPIVersion

Migre do paho-mqtt 1.x para o 2.x: corrija o ValueError de CallbackAPIVersion, ajuste on_connect e os demais callbacks e trate os novos objetos ReasonCode.

Atualizado · 8 min de leitura

Migração para Paho MQTT 2.0: corrija os erros de CallbackAPIVersion

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

ErroCausaCorreção
ValueError: Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for detailsO 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 versionmqtt.Client() sem versão, ou com VERSION1Passe VERSION2 e atualize os callbacks
TypeError: on_connect() takes 4 positional arguments but 5 were givenVERSION2 com um on_connect(client, userdata, flags, rc) no estilo 1.xAdicione o parâmetro properties (veja abaixo)
TypeError: on_disconnect() takes 3 positional arguments but 5 were givenO 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.xpip 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:

Qual paho-mqtt está instalado?
python -c "import paho.mqtt; print(paho.mqtt.__version__)"
pip show paho-mqtt

O 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:

python
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

Callback1.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:

  • flags não é mais um dict. Em on_connect ele é um objeto ConnectFlags: use flags.session_present em vez de flags["session present"].
  • disconnect_flags.is_disconnect_packet_from_server indica se o broker enviou um DISCONNECT (MQTT 5) ou se a própria biblioteca detectou a queda.
  • on_subscribe recebe reason codes, não inteiros de QoS. Para uma assinatura concedida com QoS 1, o value do reason code é 1; uma assinatura recusada tem valor 0x80 ou maior, então rc.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:

python
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:

client_v1.py — paho-mqtt 1.x
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:

client_v2.py — paho-mqtt 2.x
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

  1. Fixe a versão para a qual você migra, por exemplo paho-mqtt>=2.1,<3 no requirements.txt, para que a próxima versão major não pegue você de surpresa.
  2. Procure mqtt.Client( no código e passe mqtt.CallbackAPIVersion.VERSION2 primeiro, com client_id= como argumento nomeado.
  3. Atualize todos os callbacks para a nova assinatura — inclusive os atribuídos em outros módulos ou bibliotecas.
  4. Troque comparações de inteiros em rc por reason_code.is_failure ou comparações por nome.
  5. Troque flags["session present"] por flags.session_present.
  6. 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'.