From 6f746c4375e013e200be7661ca8035eec23cb06e Mon Sep 17 00:00:00 2001 From: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:16:13 +0100 Subject: [PATCH] Add common entity_entry_as_dict util to diagnostics (#165692) --- .../components/diagnostics/__init__.py | 4 +- homeassistant/components/diagnostics/util.py | 16 ++++++ .../components/enphase_envoy/diagnostics.py | 5 +- .../components/hassio/diagnostics.py | 5 +- .../hunterdouglas_powerview/diagnostics.py | 4 +- .../components/version/diagnostics.py | 5 +- tests/components/diagnostics/test_util.py | 49 ++++++++++++++++++- 7 files changed, 78 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index 0cb2eddb199..a19f3c888e5 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -38,9 +38,9 @@ from homeassistant.util.hass_dict import HassKey from homeassistant.util.json import format_unserializable_data from .const import DOMAIN, REDACTED, DiagnosticsSubType, DiagnosticsType -from .util import async_redact_data +from .util import async_redact_data, entity_entry_as_dict -__all__ = ["REDACTED", "async_redact_data"] +__all__ = ["REDACTED", "async_redact_data", "entity_entry_as_dict"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py index 0ca85c9a584..ca43a56dde1 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -5,7 +5,10 @@ from __future__ import annotations from collections.abc import Iterable, Mapping from typing import Any, cast, overload +import attr + from homeassistant.core import callback +from homeassistant.helpers.entity_registry import RegistryEntry from .const import REDACTED @@ -42,3 +45,16 @@ def async_redact_data[_T](data: _T, to_redact: Iterable[Any]) -> _T: redacted[key] = [async_redact_data(item, to_redact) for item in value] return cast(_T, redacted) + + +def _entity_entry_filter(a: attr.Attribute, _: Any) -> bool: + return a.name != "_cache" + + +@callback +def entity_entry_as_dict(entry: RegistryEntry) -> dict[str, Any]: + """Convert an entity registry entry to a dict for diagnostics. + + This excludes internal fields that should not be exposed in diagnostics. + """ + return attr.asdict(entry, filter=_entity_entry_filter) diff --git a/homeassistant/components/enphase_envoy/diagnostics.py b/homeassistant/components/enphase_envoy/diagnostics.py index e3d86e7fa23..1517d2a1d67 100644 --- a/homeassistant/components/enphase_envoy/diagnostics.py +++ b/homeassistant/components/enphase_envoy/diagnostics.py @@ -11,7 +11,7 @@ from attr import asdict from pyenphase.envoy import Envoy from pyenphase.exceptions import EnvoyError -from homeassistant.components.diagnostics import async_redact_data +from homeassistant.components.diagnostics import async_redact_data, entity_entry_as_dict from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -111,8 +111,7 @@ async def async_get_config_entry_diagnostics( if state := hass.states.get(entity.entity_id): state_dict = dict(state.as_dict()) state_dict.pop("context", None) - entity_dict = asdict(entity) - entity_dict.pop("_cache", None) + entity_dict = entity_entry_as_dict(entity) entities.append({"entity": entity_dict, "state": state_dict}) device_dict = asdict(device) device_dict.pop("_cache", None) diff --git a/homeassistant/components/hassio/diagnostics.py b/homeassistant/components/hassio/diagnostics.py index 0ef50cedc5a..9002310bfcc 100644 --- a/homeassistant/components/hassio/diagnostics.py +++ b/homeassistant/components/hassio/diagnostics.py @@ -6,6 +6,7 @@ from typing import Any from attr import asdict +from homeassistant.components.diagnostics import entity_entry_as_dict from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -44,7 +45,9 @@ async def async_get_config_entry_diagnostics( state_dict = dict(state.as_dict()) state_dict.pop("context", None) - entities.append({"entry": asdict(entity_entry), "state": state_dict}) + entities.append( + {"entry": entity_entry_as_dict(entity_entry), "state": state_dict} + ) devices.append({"device": asdict(device), "entities": entities}) diff --git a/homeassistant/components/hunterdouglas_powerview/diagnostics.py b/homeassistant/components/hunterdouglas_powerview/diagnostics.py index 7d6908f1936..d7d88a849b4 100644 --- a/homeassistant/components/hunterdouglas_powerview/diagnostics.py +++ b/homeassistant/components/hunterdouglas_powerview/diagnostics.py @@ -7,7 +7,7 @@ from typing import Any import attr -from homeassistant.components.diagnostics import async_redact_data +from homeassistant.components.diagnostics import async_redact_data, entity_entry_as_dict from homeassistant.const import ATTR_CONFIGURATION_URL, CONF_HOST from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -94,7 +94,7 @@ def _async_device_as_dict(hass: HomeAssistant, device: DeviceEntry) -> dict[str, state_dict = dict(state.as_dict()) state_dict.pop("context", None) - entity = attr.asdict(entity_entry) + entity = entity_entry_as_dict(entity_entry) entity["state"] = state_dict entities.append(entity) diff --git a/homeassistant/components/version/diagnostics.py b/homeassistant/components/version/diagnostics.py index bcc94bd8da4..1174c5ad4d3 100644 --- a/homeassistant/components/version/diagnostics.py +++ b/homeassistant/components/version/diagnostics.py @@ -6,6 +6,7 @@ from typing import Any from attr import asdict +from homeassistant.components.diagnostics import entity_entry_as_dict from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -42,7 +43,9 @@ async def async_get_config_entry_diagnostics( state_dict = dict(state.as_dict()) state_dict.pop("context", None) - entities.append({"entry": asdict(entity), "state": state_dict}) + entities.append( + {"entry": entity_entry_as_dict(entity), "state": state_dict} + ) devices.append({"device": asdict(device), "entities": entities}) diff --git a/tests/components/diagnostics/test_util.py b/tests/components/diagnostics/test_util.py index 3781b980e97..6f1c1b2e199 100644 --- a/tests/components/diagnostics/test_util.py +++ b/tests/components/diagnostics/test_util.py @@ -1,6 +1,13 @@ """Test Diagnostics utils.""" -from homeassistant.components.diagnostics import REDACTED, async_redact_data +from datetime import datetime + +from homeassistant.components.diagnostics import ( + REDACTED, + async_redact_data, + entity_entry_as_dict, +) +from homeassistant.helpers.entity_registry import RegistryEntry def test_redact() -> None: @@ -41,3 +48,43 @@ def test_redact() -> None: "key6": "", "key7": REDACTED, } + + +def test_entity_entry_as_dict() -> None: + """Test entity_entry_as_dict.""" + created = datetime.fromisoformat("2024-01-01T00:00:00+00:00") + entry = RegistryEntry( + entity_id="sensor.test_sensor", + unique_id="unique123", + platform="test", + capabilities=None, + config_entry_id=None, + config_subentry_id=None, + created_at=created, + device_id=None, + disabled_by=None, + entity_category=None, + has_entity_name=False, + hidden_by=None, + id=None, + options=None, + original_device_class=None, + original_icon=None, + original_name="Test Sensor", + object_id_base=None, + suggested_object_id=None, + supported_features=0, + translation_key=None, + unit_of_measurement=None, + ) + + result = entity_entry_as_dict(entry) + + assert isinstance(result, dict) + assert "_cache" not in result + assert result["entity_id"] == "sensor.test_sensor" + assert result["unique_id"] == "unique123" + assert result["platform"] == "test" + assert result["original_name"] == "Test Sensor" + assert result["supported_features"] == 0 + assert result["created_at"] == created