From 7b4f393f6f2027cb6a385b301f54bf9a2c67d3c8 Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Tue, 19 May 2026 21:58:13 +0200 Subject: [PATCH 1/2] Fix critical bugs and align with HA standards - Fix coordinator AttributeError: pass config entry to PostNLCoordinator - Fix wrong base class: SensorEntity instead of Entity - Fix None guard on coordinator.data in handle_coordinator_data - Fix AttributeError on ClientError.status: guard with isinstance check - Remove OAuth access token from debug log (security) - Fix async_setup_entry return type annotation: True -> bool - Fix misleading log message in async_unload_entry - Add requests to manifest requirements - Add SensorStateClass.MEASUREMENT to both sensors - Add DeviceEntryType.SERVICE and configuration_url to DeviceInfo - Replace old-style state/unit_of_measurement/icon properties with native_value and _attr_ class variables Co-Authored-By: Claude Sonnet 4.6 --- custom_components/postnl/__init__.py | 8 +++--- custom_components/postnl/coordinator.py | 4 ++- custom_components/postnl/manifest.json | 4 +-- custom_components/postnl/sensor.py | 33 ++++++++++++------------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/custom_components/postnl/__init__.py b/custom_components/postnl/__init__.py index 2584660..0a23f3d 100644 --- a/custom_components/postnl/__init__.py +++ b/custom_components/postnl/__init__.py @@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> True: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up PostNL from config entry.""" _LOGGER.debug("Setup Entry PostNL") @@ -40,8 +40,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> True: 'auth': auth } - _LOGGER.debug('Using access token: %s', auth.access_token) - postnl_login_api = PostNLLoginAPI(auth.access_token) try: @@ -88,7 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> True: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload PostNL config entry.""" - _LOGGER.debug('Reloading PostNL integration') + _LOGGER.debug('Unloading PostNL integration') unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: @@ -126,7 +124,7 @@ async def check_and_refresh_token(self) -> str: except (ClientResponseError, ClientError) as exception: _LOGGER.debug("API error: %s", exception) - if exception.status == 400: + if isinstance(exception, ClientResponseError) and exception.status == 400: self.oauth_session.config_entry.async_start_reauth( self.oauth_session.hass ) diff --git a/custom_components/postnl/coordinator.py b/custom_components/postnl/coordinator.py index 460a55d..f0072bb 100644 --- a/custom_components/postnl/coordinator.py +++ b/custom_components/postnl/coordinator.py @@ -3,6 +3,7 @@ from datetime import timedelta import requests +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import (DataUpdateCoordinator, UpdateFailed) @@ -19,7 +20,7 @@ class PostNLCoordinator(DataUpdateCoordinator): graphq_api: PostNLGraphql jouw_api: PostNLJouwAPI - def __init__(self, hass: HomeAssistant) -> None: + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize PostNL coordinator.""" super().__init__( hass, @@ -27,6 +28,7 @@ def __init__(self, hass: HomeAssistant) -> None: name="PostNL", update_interval=timedelta(seconds=90), ) + self.config_entry = entry _LOGGER.debug("PostNLCoordinator initialized with update interval: %s", self.update_interval) async def _async_update_data(self) -> dict[str, list[Package]]: diff --git a/custom_components/postnl/manifest.json b/custom_components/postnl/manifest.json index cc926d5..eeb8b82 100644 --- a/custom_components/postnl/manifest.json +++ b/custom_components/postnl/manifest.json @@ -8,6 +8,6 @@ "integration_type": "hub", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/arjenbos/ha-postnl/issues", - "requirements": ["gql"], - "version": "2.1.1" + "requirements": ["gql", "requests"], + "version": "2.2.0" } diff --git a/custom_components/postnl/sensor.py b/custom_components/postnl/sensor.py index 8c18b4d..2a8a103 100644 --- a/custom_components/postnl/sensor.py +++ b/custom_components/postnl/sensor.py @@ -1,10 +1,10 @@ """Sensor for PostNL packages.""" import logging +from homeassistant.components.sensor import SensorEntity, SensorStateClass from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry @@ -19,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e """Set up the PostNL sensor platform.""" _LOGGER.debug("Setting up PostNL sensors") - coordinator = PostNLCoordinator(hass) + coordinator = PostNLCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() userinfo = hass.data[DOMAIN][entry.entry_id].get("userinfo", {}) @@ -46,7 +46,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e ]) _LOGGER.debug("PostNL sensors added") -class PostNLDelivery(CoordinatorEntity, Entity): +class PostNLDelivery(CoordinatorEntity, SensorEntity): + _attr_icon = "mdi:package-variant-closed" + _attr_native_unit_of_measurement = "packages" + _attr_state_class = SensorStateClass.MEASUREMENT + def __init__(self, coordinator, postnl_userinfo, unique_id, name, receiver: bool = True): """Initialize the PostNL sensor.""" super().__init__(coordinator, context=name) @@ -75,6 +79,8 @@ def device_info(self) -> DeviceInfo: }, name=self.postnl_userinfo.get('email'), manufacturer="PostNL", + entry_type=DeviceEntryType.SERVICE, + configuration_url="https://jouw.postnl.nl", ) @property @@ -83,25 +89,15 @@ def name(self) -> str: return self._name @property - def state(self): + def native_value(self): """Return the state of the sensor.""" return self._state - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return 'packages' - @property def extra_state_attributes(self): """Return the state attributes.""" return self._attributes - @property - def icon(self): - """Icon to use in the frontend.""" - return "mdi:package-variant-closed" - @callback def _handle_coordinator_update(self) -> None: _LOGGER.debug('Updating sensor %s', self.name) @@ -114,10 +110,13 @@ def handle_coordinator_data(self): self._attributes['delivered'] = [] self._attributes['enroute'] = [] + if not self.coordinator.data: + return + if self.receiver: - coordinator_data = self.coordinator.data['receiver'] + coordinator_data = self.coordinator.data.get('receiver', []) else: - coordinator_data = self.coordinator.data['sender'] + coordinator_data = self.coordinator.data.get('sender', []) for package in coordinator_data: if package.delivered: From 738694c3880cbf0d39d2f2e51fd2472b90d2e373 Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Fri, 26 Jun 2026 18:55:00 +0200 Subject: [PATCH 2/2] Pass config_entry to DataUpdateCoordinator init per HA pattern --- custom_components/postnl/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/postnl/coordinator.py b/custom_components/postnl/coordinator.py index f0072bb..8ecce77 100644 --- a/custom_components/postnl/coordinator.py +++ b/custom_components/postnl/coordinator.py @@ -27,8 +27,8 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: _LOGGER, name="PostNL", update_interval=timedelta(seconds=90), + config_entry=entry, ) - self.config_entry = entry _LOGGER.debug("PostNLCoordinator initialized with update interval: %s", self.update_interval) async def _async_update_data(self) -> dict[str, list[Package]]: