A mudança de callback API version do paho mqtt 2.0 é, de longe, o motivo mais comum para scripts MQTT em Python pararem de funcionar de repente depois de um pip install --upgrade. Este guia explica exatamente o que mudou no paho-mqtt 2.0, traz a correção de uma linha para o erro e, em seguida, mostra como migrar cada callback para as assinaturas VERSION2 recomendadas. Você termina com um exemplo completo e funcional conectado ao broker.connectmq.com, que pode usar como modelo.
Os erros que você provavelmente está vendo
Se você chegou aqui por um buscador, é bem provável que esteja diante de um destes:
ValueError: Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for details
TypeError: Client.__init__() missing 1 required positional argument: 'callback_api_version'
DeprecationWarning: Callback API version 1 is deprecated, update to latest version
TypeError: on_connect() takes 4 positional arguments but 5 were given
Os dois primeiros surgem ao criar o cliente do jeito 1.x. O ValueError aparece quando você passa um client ID como primeiro argumento posicional, como em mqtt.Client("my-device"), porque essa posição agora pertence à callback API version. O terceiro é um aviso de que você escolheu explicitamente a API antiga. O quarto aparece depois que você muda para VERSION2, mas deixa algum callback com o número antigo de parâmetros.
O que mudou no paho-mqtt 2.0
A versão 2.0 do cliente Python do Eclipse Paho, lançada no início de 2024, foi o primeiro grande lançamento em anos. A principal mudança é que os callbacks agora têm versão. No 1.x, os parâmetros passados aos callbacks variavam entre MQTT 3.1.1 e MQTT 5, e alguns usavam inteiros simples enquanto outros usavam objetos de reason code. A versão 2 unifica tudo: cada callback recebe os mesmos parâmetros, independentemente da versão do protocolo, e os códigos de resultado são sempre objetos ReasonCode.
Para não quebrar código silenciosamente com as novas assinaturas, os mantenedores obrigaram você a escolher de forma explícita. O primeiro argumento de Client() agora é callback_api_version, com dois valores:
| Valor | Comportamento | Recomendado? |
|---|---|---|
CallbackAPIVersion.VERSION1 |
Assinaturas antigas do 1.x, emite aviso de depreciação | Apenas como ponte temporária |
CallbackAPIVersion.VERSION2 |
Novas assinaturas unificadas, objetos ReasonCode em todo lugar |
Sim, para todo código novo ou migrado |
Outras mudanças relevantes do 2.0 incluem o fim do suporte ao Python 2.7 e a versões muito antigas do Python 3, além da renomeação da classe ReasonCodes para ReasonCode. A lista completa está nas notas oficiais de migração do paho-mqtt.
Correção 1: a ponte de uma linha (VERSION1)
Se a produção caiu e você precisa dela de volta agora, peça ao paho para manter o comportamento antigo:
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id="my-device")
Todos os seus callbacks existentes continuam funcionando sem alterações. Você verá o aviso de depreciação, que lembra que a VERSION1 será removida em uma futura versão principal. Trate isso como um curativo, não como uma correção.
Outro curativo possível é fixar a biblioteca antiga com pip install "paho-mqtt<2.0". Funciona, mas bloqueia correções de segurança e de bugs e costuma entrar em conflito com outros pacotes que já exigem o 2.x. Prefira a flag VERSION1 a fixar a versão, e planeje a migração de verdade.
Correção 2: migrar para a callback API version 2 do Paho MQTT 2.0
A correção real é passar VERSION2 e atualizar as assinaturas dos callbacks. Aqui está cada callback lado a lado:
| Callback | Assinatura 1.x / VERSION1 | Assinatura 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 alteração |
on_log |
(client, userdata, level, buf) |
sem alteração |
Na VERSION1 com MQTT 5, alguns desses já tinham parâmetros extras; a VERSION2 elimina essa diferença, e esse é justamente o objetivo da mudança.
Checklist de migração
Percorra o seu código nesta ordem:
- Troque cada chamada
mqtt.Client(...)pormqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=..., ...), passando todos os outros argumentos por palavra-chave. - Adicione
propertiesaoon_connecte renomeiercparareason_code. Substituarc == 0pornot reason_code.is_failure(a comparação com inteiros ainda funciona, mas o atributo é mais legível). - Substitua
flags["session present"]porflags.session_present. - Dê ao
on_disconnecto novo parâmetrodisconnect_flagseproperties. - No
on_subscribe, troquegranted_qosporreason_code_liste verifique cada entrada: códigos de sucesso têm valores de 0 a 2 (o QoS concedido), falhas têmis_failuredefinido. - Adicione
reason_codeepropertiesaoon_publishe aoon_unsubscribe. - Procure
ReasonCodesna base de código e troque porReasonCode. - Rode os testes transformando avisos em erros (
python -W error::DeprecationWarning) para provar que não sobrou nenhum cliente VERSION1.
Um exemplo completo em VERSION2
Este script usa todos os callbacks comuns com as novas assinaturas. Troque k7f3q2 por uma string aleatória sua para não compartilhar topics com outros usuários do servidor MQTT público, que é só para testes:
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PREFIX = "connectmq/demo/k7f3q2"
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connect failed: {reason_code}")
return
print(f"Connected (session present: {flags.session_present})")
client.subscribe(f"{PREFIX}/#", qos=1)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
for rc in reason_code_list:
print("Subscribe rejected:" if rc.is_failure else "Granted QoS:", rc)
def on_publish(client, userdata, mid, reason_code, properties):
print(f"Publish {mid} acknowledged: {reason_code}")
def on_message(client, userdata, msg):
print(f"{msg.topic}: {msg.payload.decode()}")
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
print(f"Disconnected: {reason_code}")
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"migrate-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_publish = on_publish
client.on_message = on_message
client.on_disconnect = on_disconnect
client.connect(BROKER, 1883, keepalive=60)
client.loop_start()
time.sleep(1)
for i in range(3):
client.publish(f"{PREFIX}/test", f"hello {i}", qos=1)
time.sleep(1)
client.disconnect()
client.loop_stop()
Execute e você deverá ver a conexão, o QoS concedido, três confirmações de publish e as três mensagens devolvidas pelo servidor MQTT público do ConnectMQ. Troque protocol para mqtt.MQTTv311 e os mesmos callbacks continuam funcionando sem edições, que é exatamente o que a VERSION2 promete. Se você é novo na biblioteca, comece pelo tutorial de Paho MQTT em Python.
Armadilhas depois da migração
Bibliotecas de terceiros presas ao 1.x. Alguns pacotes que encapsulam o paho declararam paho-mqtt<2.0 nos requisitos. Verifique com pip show e pip check. As versões recentes da maioria dos wrappers mantidos suportam o 2.x; atualize-os ou isole as ferramentas legadas em um ambiente virtual próprio.
Argumentos posicionais. Qualquer chamada mqtt.Client("id", False, None, mqtt.MQTTv311) agora fica deslocada em uma posição. Converta todas para argumentos nomeados; isso torna as atualizações futuras indolores.
Callbacks registrados com decorators. Código que usa @client.connect_callback() e decorators semelhantes também precisa atualizar as assinaturas das funções decoradas.
Exceções engolidas. Por padrão, o paho registra em log as exceções lançadas dentro dos callbacks em vez de derrubar o programa. Uma assinatura errada pode, portanto, parecer que "nada acontece". Anexe um logger com client.enable_logger() durante a migração para que o TypeError fique visível.
Lógica de reconexão. Se você comparava inteiros rc no on_disconnect para decidir se reconectava, reescreva essa lógica em torno de reason_code. O guia de reconexão do paho-mqtt mostra padrões robustos, e o guia de TLS com MQTT em Python cobre a conexão criptografada que você deve usar em produção.
Vale considerar o aiomqtt?
Se a sua aplicação é construída sobre asyncio, migrar os callbacks pode ser um bom momento para adotar de vez uma API assíncrona. O aiomqtt encapsula o paho e troca os callbacks por async for message in client.messages. Nosso tutorial de aiomqtt e asyncio mostra o mesmo fluxo de publish/subscribe escrito dessa forma. Para uma comparação mais ampla entre clientes Python e de outras linguagens, veja a visão geral de bibliotecas de cliente MQTT.
Teste o código migrado no ConnectMQ
O ConnectMQ mantém um servidor MQTT público gratuito em broker.connectmq.com, ideal para verificar uma migração (não para dados de produção), e um broker MQTT gerenciado para produção com MQTT 3.1.1 e 5.0, TLS na 8883 e credenciais geradas. O portal inclui um explorador de topics ao vivo para você confirmar que os clientes migrados continuam publicando o esperado. Quando estiver pronto para sair do servidor MQTT público, rode os seus clientes em um broker MQTT na nuvem privado: cadastre-se no plano Serverless gratuito, sem cartão de crédito.
Perguntas frequentes
O que é callback_api_version no paho-mqtt?
É o primeiro argumento de mqtt.Client() desde o paho-mqtt 2.0. Ele seleciona quais assinaturas de callback o cliente usa: VERSION1 para o estilo antigo do 1.x ou VERSION2 para o novo estilo unificado.
Devo usar VERSION1 ou VERSION2?
Use VERSION2. A VERSION1 existe apenas para manter código antigo rodando e exibe um aviso de depreciação, então ela será removida em algum momento.
Por que recebo "Unsupported callback API version" ao passar um client ID?
Você passou o client ID como primeiro argumento posicional, que agora é reservado para a callback API version. Passe mqtt.CallbackAPIVersion.VERSION2 primeiro e o ID como client_id=....
Preciso alterar o on_message na migração?
Não. on_message(client, userdata, message) mantém a mesma assinatura nas duas versões. As mudanças afetam os callbacks de connect, disconnect, subscribe, unsubscribe e publish.



