From f307e838fae40b8c40aff702e5f639d906883080 Mon Sep 17 00:00:00 2001 From: Donald Clark Jackson Date: Sat, 3 Oct 2026 22:52:36 -0700 Subject: [PATCH] fix: Device.as_dict() raised TypeError for any device with a node (#87) It built each node entry as a set literal, {node_id, node.as_dict()}, which hashes the node's dict. Adds a regression test with a node; a device with no nodes returned normally, which is why nothing caught it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01T32pvgqCzp1qCz7K5gS9Wm --- CHANGELOG.md | 4 ++++ src/ebus_sdk/homie.py | 2 +- tests/test_homie_device.py | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 508ee2f..183d98f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to `ebus-sdk` are recorded here. Format follows [Keep a Chan ## [Unreleased] +### Fixed + +- `homie.Device.as_dict()` no longer raises `TypeError: unhashable type: 'dict'` for a device with a node: it built each node entry as a set literal, `{node_id, node.as_dict()}`, instead of a dict entry ([#87](https://github.com/electrification-bus/python-sdk/issues/87)). + ### Changed - `ebus-mqtt-client` floor raised to 0.6.0. It holds every publish issued while the link is down and flushes it before the SDK's on-connect republish; 0.5.0 left QoS 1 and 2 publishes to paho, which replayed them after that republish and past mosquitto's 20-message receive quota, and mosquitto discarded the excess QoS 2 messages. A root built with `mqtt_cfg=` publishes during construction, before CONNACK, so `simple-device` and `simple-tree-device` ended with the root's retained `$state` on `init` instead of `ready` ([electrification-bus/ebus-mqtt-client#20](https://github.com/electrification-bus/ebus-mqtt-client/issues/20)). diff --git a/src/ebus_sdk/homie.py b/src/ebus_sdk/homie.py index 15dd210..99aef40 100644 --- a/src/ebus_sdk/homie.py +++ b/src/ebus_sdk/homie.py @@ -1687,7 +1687,7 @@ def __init__( def as_dict(self) -> dict: nodes = {} for node_id, node in self.nodes().items(): - nodes.update({node_id, node.as_dict()}) + nodes[node_id] = node.as_dict() return { "id": self.id(), "name": self.name(), diff --git a/tests/test_homie_device.py b/tests/test_homie_device.py index 4f5d9ce..6e3ba09 100644 --- a/tests/test_homie_device.py +++ b/tests/test_homie_device.py @@ -1144,6 +1144,15 @@ def test_as_dict(self): assert d["name"] == "Core" assert "temp" in d["properties"] + def test_device_as_dict_keys_nodes_by_id(self): + # GH #87: a set literal here raised TypeError for any device with a node. + dev = Device(id="d1", name="D", type="t", mqttc=_mock_mqtt_client()) + dev.add_node(Node(id="core", name="Core", type="sensor")) + d = dev.as_dict() + assert d["id"] == "d1" + assert list(d["nodes"]) == ["core"] + assert d["nodes"]["core"]["id"] == "core" + class TestNodeClearAllProperties: def test_clear_all_properties(self):