Skip to content

Commit f9b2330

Browse files
committed
Fix Proxmox VE sensor crashes due to missing API fields
Update all sensor definitions (Node, VM, Container, and Storage) to safely handle missing keys in the Proxmox API response by using dict.get(). This prevents KeyError crashes for entities where certain fields (like used_fraction, cpu, or memory) may be omitted. Added tests/components/proxmoxve/test_sensor_missing_data.py to verify robust handling of incomplete API data.
1 parent ed9c261 commit f9b2330

File tree

3 files changed

+273
-46
lines changed

3 files changed

+273
-46
lines changed

homeassistant/components/proxmoxve/binary_sensor.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,43 +39,48 @@ class ProxmoxContainerBinarySensorEntityDescription(BinarySensorEntityDescriptio
3939
"""Class to hold Proxmox container binary sensor description."""
4040

4141
state_fn: Callable[[dict[str, Any]], bool | None]
42+
exists_fn: Callable[[dict[str, Any]], bool] = lambda _: True
4243

4344

4445
@dataclass(frozen=True, kw_only=True)
4546
class ProxmoxVMBinarySensorEntityDescription(BinarySensorEntityDescription):
4647
"""Class to hold Proxmox endpoint binary sensor description."""
4748

4849
state_fn: Callable[[dict[str, Any]], bool | None]
50+
exists_fn: Callable[[dict[str, Any]], bool] = lambda _: True
4951

5052

5153
@dataclass(frozen=True, kw_only=True)
5254
class ProxmoxNodeBinarySensorEntityDescription(BinarySensorEntityDescription):
5355
"""Class to hold Proxmox node binary sensor description."""
5456

5557
state_fn: Callable[[ProxmoxNodeData], bool | None]
58+
exists_fn: Callable[[ProxmoxNodeData], bool] = lambda _: True
5659

5760

5861
@dataclass(frozen=True, kw_only=True)
5962
class ProxmoxStorageBinarySensorEntityDescription(BinarySensorEntityDescription):
6063
"""Class to hold Proxmox storage binary sensor description."""
6164

6265
state_fn: Callable[[dict[str, Any]], bool | None]
66+
exists_fn: Callable[[dict[str, Any]], bool] = lambda _: True
6367

6468

6569
NODE_SENSORS: tuple[ProxmoxNodeBinarySensorEntityDescription, ...] = (
6670
ProxmoxNodeBinarySensorEntityDescription(
6771
key="status",
6872
translation_key="status",
69-
state_fn=lambda data: data.node["status"] == NODE_ONLINE,
73+
state_fn=lambda data: data.node.get("status") == NODE_ONLINE,
7074
device_class=BinarySensorDeviceClass.RUNNING,
7175
entity_category=EntityCategory.DIAGNOSTIC,
7276
),
7377
ProxmoxNodeBinarySensorEntityDescription(
7478
key="node_backup_status",
7579
translation_key="node_backup_status",
7680
state_fn=lambda data: bool(
77-
data.backups and data.backups[0]["status"] != STATUS_OK
81+
data.backups and data.backups[0].get("status") != STATUS_OK
7882
),
83+
exists_fn=lambda data: bool(data.backups),
7984
device_class=BinarySensorDeviceClass.PROBLEM,
8085
entity_category=EntityCategory.DIAGNOSTIC,
8186
),
@@ -85,7 +90,7 @@ class ProxmoxStorageBinarySensorEntityDescription(BinarySensorEntityDescription)
8590
ProxmoxContainerBinarySensorEntityDescription(
8691
key="status",
8792
translation_key="status",
88-
state_fn=lambda data: data["status"] == VM_CONTAINER_RUNNING,
93+
state_fn=lambda data: data.get("status") == VM_CONTAINER_RUNNING,
8994
device_class=BinarySensorDeviceClass.RUNNING,
9095
entity_category=EntityCategory.DIAGNOSTIC,
9196
),
@@ -95,7 +100,7 @@ class ProxmoxStorageBinarySensorEntityDescription(BinarySensorEntityDescription)
95100
ProxmoxVMBinarySensorEntityDescription(
96101
key="status",
97102
translation_key="status",
98-
state_fn=lambda data: data["status"] == VM_CONTAINER_RUNNING,
103+
state_fn=lambda data: data.get("status") == VM_CONTAINER_RUNNING,
99104
device_class=BinarySensorDeviceClass.RUNNING,
100105
entity_category=EntityCategory.DIAGNOSTIC,
101106
),
@@ -105,19 +110,22 @@ class ProxmoxStorageBinarySensorEntityDescription(BinarySensorEntityDescription)
105110
ProxmoxStorageBinarySensorEntityDescription(
106111
key="storage_active",
107112
translation_key="storage_active",
108-
state_fn=lambda data: data["active"] == STORAGE_ACTIVE,
113+
state_fn=lambda data: data.get("active") == STORAGE_ACTIVE,
114+
exists_fn=lambda data: "active" in data,
109115
entity_category=EntityCategory.DIAGNOSTIC,
110116
),
111117
ProxmoxStorageBinarySensorEntityDescription(
112118
key="storage_enabled",
113119
translation_key="storage_enabled",
114-
state_fn=lambda data: data["enabled"] == STORAGE_ENABLED,
120+
state_fn=lambda data: data.get("enabled") == STORAGE_ENABLED,
121+
exists_fn=lambda data: "enabled" in data,
115122
entity_category=EntityCategory.DIAGNOSTIC,
116123
),
117124
ProxmoxStorageBinarySensorEntityDescription(
118125
key="storage_shared",
119126
translation_key="storage_shared",
120-
state_fn=lambda data: data["shared"] == STORAGE_SHARED,
127+
state_fn=lambda data: data.get("shared") == STORAGE_SHARED,
128+
exists_fn=lambda data: "shared" in data,
121129
entity_category=EntityCategory.DIAGNOSTIC,
122130
),
123131
)
@@ -137,6 +145,7 @@ def _async_add_new_nodes(nodes: list[ProxmoxNodeData]) -> None:
137145
ProxmoxNodeBinarySensor(coordinator, entity_description, node)
138146
for node in nodes
139147
for entity_description in NODE_SENSORS
148+
if entity_description.exists_fn(node)
140149
)
141150

142151
def _async_add_new_vms(
@@ -147,6 +156,7 @@ def _async_add_new_vms(
147156
ProxmoxVMBinarySensor(coordinator, entity_description, vm, node_data)
148157
for (node_data, vm) in vms
149158
for entity_description in VM_SENSORS
159+
if entity_description.exists_fn(vm)
150160
)
151161

152162
def _async_add_new_containers(
@@ -159,6 +169,7 @@ def _async_add_new_containers(
159169
)
160170
for (node_data, container) in containers
161171
for entity_description in CONTAINER_SENSORS
172+
if entity_description.exists_fn(container)
162173
)
163174

164175
def _async_add_new_storages(
@@ -171,6 +182,7 @@ def _async_add_new_storages(
171182
)
172183
for (node_data, storage) in storages
173184
for entity_description in STORAGE_SENSORS
185+
if entity_description.exists_fn(storage)
174186
)
175187

176188
coordinator.new_nodes_callbacks.append(_async_add_new_nodes)

0 commit comments

Comments
 (0)