From 7fbf24f4da108e0e84ace6bd6d8d3c2b5d870311 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 10:50:09 +0000 Subject: [PATCH 1/5] Migrate paho-mqtt client to CallbackAPIVersion.VERSION2 Construct mqtt.Client with the non-deprecated v2 callback API and update on_connect / on_disconnect to the VERSION2 signatures. Tests now invoke those callbacks with the v2 arity. Co-authored-by: Stackie Jia --- CHANGELOG.rst | 3 +++ src/libdeye/mqtt_client.py | 14 ++++++++++---- tests/test_mqtt_client.py | 12 +++++++++--- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e8a1c96..ba28cbe 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,9 @@ Changelog Unreleased ========== +- Construct the paho-mqtt client with ``CallbackAPIVersion.VERSION2`` and + update ``on_connect`` / ``on_disconnect`` to the v2 callback signatures. + Version 3.0.2 ============= diff --git a/src/libdeye/mqtt_client.py b/src/libdeye/mqtt_client.py index ba9da1a..14b39f4 100644 --- a/src/libdeye/mqtt_client.py +++ b/src/libdeye/mqtt_client.py @@ -391,7 +391,7 @@ def __init__( self._cloud_api = cloud_api self._endpoint = "" self._topic = "" - self._mqtt = mqtt.Client() + self._mqtt = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) if tls_context is not None: self._mqtt.tls_set_context(tls_context) else: @@ -420,7 +420,11 @@ def disconnect(self) -> None: def _mqtt_on_connect( self, - *args: Any, + _mqtt: mqtt.Client, + _userdata: None, + _connect_flags: mqtt.ConnectFlags, + _reason_code: mqtt.ReasonCode, + _properties: mqtt.Properties | None, ) -> None: for topic, callbacks in self._subscribers.items(): if len(callbacks) > 0: @@ -434,9 +438,11 @@ def _mqtt_on_disconnect( self, _mqtt: mqtt.Client, _userdata: None, - result_code: int, + _disconnect_flags: mqtt.DisconnectFlags, + reason_code: mqtt.ReasonCode, + _properties: mqtt.Properties | None, ) -> None: - if result_code == 0: # User initiated disconnect + if reason_code == 0: # User initiated disconnect return # Update MQTT info and wait for it to complete before reconnecting diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py index d87990f..52ddc16 100644 --- a/tests/test_mqtt_client.py +++ b/tests/test_mqtt_client.py @@ -153,7 +153,9 @@ def test_mqtt_on_connect(self, base_client: MockBaseDeyeMqttClient) -> None: # Call _mqtt_on_connect with patch.object(base_client._mqtt, "subscribe") as mock_subscribe: with patch.object(base_client._mqtt, "publish") as mock_publish: - base_client._mqtt_on_connect(base_client._mqtt, None, {}, 0, {}) + base_client._mqtt_on_connect( + base_client._mqtt, None, mqtt.ConnectFlags(), 0, None + ) mock_subscribe.assert_called_once_with(topic1) mock_publish.assert_called_once_with(pending_topic, pending_command) assert len(base_client._pending_commands) == 0 @@ -163,7 +165,9 @@ def test_mqtt_on_disconnect_user_initiated( ) -> None: """Test _mqtt_on_disconnect method with user initiated disconnect.""" with patch("asyncio.run_coroutine_threadsafe") as mock_run_coroutine_threadsafe: - base_client._mqtt_on_disconnect(base_client._mqtt, None, 0) + base_client._mqtt_on_disconnect( + base_client._mqtt, None, mqtt.DisconnectFlags(), 0, None + ) mock_run_coroutine_threadsafe.assert_not_called() def test_mqtt_on_disconnect_unexpected( @@ -171,7 +175,9 @@ def test_mqtt_on_disconnect_unexpected( ) -> None: """Test _mqtt_on_disconnect method with unexpected disconnect.""" with patch("asyncio.run_coroutine_threadsafe") as mock_run_coroutine_threadsafe: - base_client._mqtt_on_disconnect(base_client._mqtt, None, 1) + base_client._mqtt_on_disconnect( + base_client._mqtt, None, mqtt.DisconnectFlags(), 1, None + ) mock_run_coroutine_threadsafe.assert_called_once() def test_mqtt_on_message(self, base_client: MockBaseDeyeMqttClient) -> None: From 5419abb677ae390c83422c56e69b7f023503da78 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 10:52:05 +0000 Subject: [PATCH 2/5] Fix VERSION2 MQTT callback tests to use paho flag and reason types ConnectFlags/DisconnectFlags require their namedtuple fields, and mypy expects ReasonCode rather than a bare int for the v2 callback arity. Co-authored-by: Stackie Jia --- tests/test_mqtt_client.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py index 52ddc16..0447ab8 100644 --- a/tests/test_mqtt_client.py +++ b/tests/test_mqtt_client.py @@ -154,7 +154,11 @@ def test_mqtt_on_connect(self, base_client: MockBaseDeyeMqttClient) -> None: with patch.object(base_client._mqtt, "subscribe") as mock_subscribe: with patch.object(base_client._mqtt, "publish") as mock_publish: base_client._mqtt_on_connect( - base_client._mqtt, None, mqtt.ConnectFlags(), 0, None + base_client._mqtt, + None, + mqtt.ConnectFlags(False), + mqtt.ReasonCode(mqtt.PacketTypes.CONNACK), + None, ) mock_subscribe.assert_called_once_with(topic1) mock_publish.assert_called_once_with(pending_topic, pending_command) @@ -166,7 +170,11 @@ def test_mqtt_on_disconnect_user_initiated( """Test _mqtt_on_disconnect method with user initiated disconnect.""" with patch("asyncio.run_coroutine_threadsafe") as mock_run_coroutine_threadsafe: base_client._mqtt_on_disconnect( - base_client._mqtt, None, mqtt.DisconnectFlags(), 0, None + base_client._mqtt, + None, + mqtt.DisconnectFlags(False), + mqtt.ReasonCode(mqtt.PacketTypes.DISCONNECT), + None, ) mock_run_coroutine_threadsafe.assert_not_called() @@ -176,7 +184,11 @@ def test_mqtt_on_disconnect_unexpected( """Test _mqtt_on_disconnect method with unexpected disconnect.""" with patch("asyncio.run_coroutine_threadsafe") as mock_run_coroutine_threadsafe: base_client._mqtt_on_disconnect( - base_client._mqtt, None, mqtt.DisconnectFlags(), 1, None + base_client._mqtt, + None, + mqtt.DisconnectFlags(False), + mqtt.ReasonCode(mqtt.PacketTypes.DISCONNECT, "Unspecified error"), + None, ) mock_run_coroutine_threadsafe.assert_called_once() From 1699664b97eea33c47949390ddf4a38b86f64ac9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 11:04:04 +0000 Subject: [PATCH 3/5] fix: parenthesize MQTT message exceptions and type userdata as Any paho-mqtt v2 callbacks receive arbitrary userdata. The JSONDecodeError handler used invalid Python 2 except syntax and could not import. Co-authored-by: Stackie Jia --- src/libdeye/mqtt_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libdeye/mqtt_client.py b/src/libdeye/mqtt_client.py index 14b39f4..e3cda74 100644 --- a/src/libdeye/mqtt_client.py +++ b/src/libdeye/mqtt_client.py @@ -421,7 +421,7 @@ def disconnect(self) -> None: def _mqtt_on_connect( self, _mqtt: mqtt.Client, - _userdata: None, + _userdata: Any, _connect_flags: mqtt.ConnectFlags, _reason_code: mqtt.ReasonCode, _properties: mqtt.Properties | None, @@ -437,7 +437,7 @@ def _mqtt_on_connect( def _mqtt_on_disconnect( self, _mqtt: mqtt.Client, - _userdata: None, + _userdata: Any, _disconnect_flags: mqtt.DisconnectFlags, reason_code: mqtt.ReasonCode, _properties: mqtt.Properties | None, @@ -465,7 +465,7 @@ def _mqtt_on_message( self._loop.call_soon_threadsafe( callback, self._process_message_payload(msg) ) - except json.JSONDecodeError, KeyError: + except (json.JSONDecodeError, KeyError): pass def _subscribe_topic( From 1c6d569edf68f71b7608df0f7b51655011e22da3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 11:04:32 +0000 Subject: [PATCH 4/5] fix: type MQTT on_message userdata as Any Co-authored-by: Stackie Jia --- src/libdeye/mqtt_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libdeye/mqtt_client.py b/src/libdeye/mqtt_client.py index e3cda74..46ecc21 100644 --- a/src/libdeye/mqtt_client.py +++ b/src/libdeye/mqtt_client.py @@ -455,7 +455,7 @@ def _process_message_payload(self, msg: mqtt.MQTTMessage) -> Any: raise NotImplementedError def _mqtt_on_message( - self, _mqtt: mqtt.Client, _userdata: None, msg: mqtt.MQTTMessage + self, _mqtt: mqtt.Client, _userdata: Any, msg: mqtt.MQTTMessage ) -> None: if msg.topic not in self._subscribers: return From 18a8641f07cae162ba69a41eeb85ade3c3752e04 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 11:07:37 +0000 Subject: [PATCH 5/5] style: let ruff unparenthesize PEP 758 except clauses Python 3.14 (required by this package) allows except E1, E2 without parentheses. ruff-format with target-version py314 rewrites the parenthesized form, which failed CI. Co-authored-by: Stackie Jia --- src/libdeye/mqtt_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libdeye/mqtt_client.py b/src/libdeye/mqtt_client.py index 46ecc21..c7b6455 100644 --- a/src/libdeye/mqtt_client.py +++ b/src/libdeye/mqtt_client.py @@ -465,7 +465,7 @@ def _mqtt_on_message( self._loop.call_soon_threadsafe( callback, self._process_message_payload(msg) ) - except (json.JSONDecodeError, KeyError): + except json.JSONDecodeError, KeyError: pass def _subscribe_topic(