From edb7f32c75cb596d5b4b0dd7bdab7664760dcea5 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 10 Mar 2021 18:54:26 -0700 Subject: [PATCH 1/4] Rework DataUpdateCoordinator --- .../mail_and_packages/__init__.py | 48 +++++++++++++------ custom_components/mail_and_packages/sensor.py | 40 ++++++++-------- tests/test_helpers.py | 5 +- 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index c68efa9c..d797b809 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -2,11 +2,11 @@ import logging from datetime import timedelta -import async_timeout +from async_timeout import timeout from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_RESOURCES from homeassistant.core import HomeAssistant -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( CONF_ALLOW_EXTERNAL, @@ -72,18 +72,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b config_entry.options = config_entry.data config = config_entry.data - async def async_update_data(): - """Fetch data """ - async with async_timeout.timeout(config.get(CONF_IMAP_TIMEOUT)): - return await hass.async_add_executor_job(process_emails, hass, config) - - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name=f"Mail and Packages ({config.get(CONF_HOST)})", - update_method=async_update_data, - update_interval=timedelta(minutes=config_entry.data.get(CONF_SCAN_INTERVAL)), - ) + host = config.get(CONF_HOST) + timeout = config.get(CONF_IMAP_TIMEOUT) + interval = config.get(CONF_SCAN_INTERVAL) + coordinator = MailDataUpdateCoordinator(hass, host, timeout, interval, config) # Fetch initial data so we have data when entities subscribe await coordinator.async_refresh() @@ -187,3 +179,31 @@ async def async_migrate_entry(hass, config_entry): _LOGGER.debug("Migration to version %s complete", config_entry.version) return True + + +class MailDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching mail data.""" + + def __init__(self, hass, host, timeout, interval, config): + """Initialize.""" + self.interval = timedelta(minutes=interval) + self.host = host + self.name = f"Mail and Packages ({host})" + self.timeout = timeout + self.config = config + self.hass = hass + + _LOGGER.debug("Data will be update every %s", self.interval) + + super().__init__(hass, _LOGGER, name=self.name, update_interval=self.interval) + + async def _async_update_data(self): + """Fetch data """ + async with timeout(self.timeout): + try: + data = await self.hass.async_add_executor_job( + process_emails, self.hass, self.config + ) + except Exception as error: + raise UpdateFailed(error) from error + return data diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 2d6d8ce1..3439c958 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -99,18 +99,18 @@ def device_state_attributes(self) -> Optional[str]: attr[const.ATTR_TRACKING_NUM] = data[tracking] return attr - async def async_update(self): - """Update the entity. + # async def async_update(self): + # """Update the entity. - Only used by the generic entity update service. - """ - await self.coordinator.async_request_refresh() + # Only used by the generic entity update service. + # """ + # await self.coordinator.async_request_refresh() - async def async_added_to_hass(self): - """When entity is added to hass.""" - self.async_on_remove( - self.coordinator.async_add_listener(self.async_write_ha_state) - ) + # async def async_added_to_hass(self): + # """When entity is added to hass.""" + # self.async_on_remove( + # self.coordinator.async_add_listener(self.async_write_ha_state) + # ) class ImagePathSensors(CoordinatorEntity): @@ -192,15 +192,15 @@ def device_state_attributes(self) -> Optional[str]: attr = {} return attr - async def async_update(self): - """Update the entity. + # async def async_update(self): + # """Update the entity. - Only used by the generic entity update service. - """ - await self.coordinator.async_request_refresh() + # Only used by the generic entity update service. + # """ + # await self.coordinator.async_request_refresh() - async def async_added_to_hass(self): - """When entity is added to hass.""" - self.async_on_remove( - self.coordinator.async_add_listener(self.async_write_ha_state) - ) + # async def async_added_to_hass(self): + # """When entity is added to hass.""" + # self.async_on_remove( + # self.coordinator.async_add_listener(self.async_write_ha_state) + # ) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 94f26528..5ff4b5fd 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -718,10 +718,7 @@ async def test_generate_mp4( async def test_connection_error(caplog): result = login("localhost", 993, "fakeuser", "suchfakemuchpassword") assert not result - assert ( - "Network error while connecting to server: [Errno 111] Connection refused" - in caplog.text - ) + assert "Network error while connecting to server:" in caplog.text async def test_login_error(mock_imap_login_error, caplog): From eade3a3b98657654004042107053443cb2c7e702 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 10 Mar 2021 19:14:56 -0700 Subject: [PATCH 2/4] Remove unused variable --- custom_components/mail_and_packages/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index d797b809..0ca27af1 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -187,7 +187,6 @@ class MailDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass, host, timeout, interval, config): """Initialize.""" self.interval = timedelta(minutes=interval) - self.host = host self.name = f"Mail and Packages ({host})" self.timeout = timeout self.config = config From d67e8b7bb9c2de6f717587e21b71684295aac9a1 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 10 Mar 2021 19:20:17 -0700 Subject: [PATCH 3/4] Remove commented code --- .../mail_and_packages/__init__.py | 3 +++ custom_components/mail_and_packages/sensor.py | 26 ------------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index 0ca27af1..968ef2b2 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -72,9 +72,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b config_entry.options = config_entry.data config = config_entry.data + # Variables for data coordinator host = config.get(CONF_HOST) timeout = config.get(CONF_IMAP_TIMEOUT) interval = config.get(CONF_SCAN_INTERVAL) + + # Setup the data coordinator coordinator = MailDataUpdateCoordinator(hass, host, timeout, interval, config) # Fetch initial data so we have data when entities subscribe diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 3439c958..9121bb68 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -99,19 +99,6 @@ def device_state_attributes(self) -> Optional[str]: attr[const.ATTR_TRACKING_NUM] = data[tracking] return attr - # async def async_update(self): - # """Update the entity. - - # Only used by the generic entity update service. - # """ - # await self.coordinator.async_request_refresh() - - # async def async_added_to_hass(self): - # """When entity is added to hass.""" - # self.async_on_remove( - # self.coordinator.async_add_listener(self.async_write_ha_state) - # ) - class ImagePathSensors(CoordinatorEntity): """ Represntation of a sensor """ @@ -191,16 +178,3 @@ def device_state_attributes(self) -> Optional[str]: """Return device specific state attributes.""" attr = {} return attr - - # async def async_update(self): - # """Update the entity. - - # Only used by the generic entity update service. - # """ - # await self.coordinator.async_request_refresh() - - # async def async_added_to_hass(self): - # """When entity is added to hass.""" - # self.async_on_remove( - # self.coordinator.async_add_listener(self.async_write_ha_state) - # ) From d8582126c6d4d0797905ea2e420443f95069d37b Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 10 Mar 2021 19:38:39 -0700 Subject: [PATCH 4/4] Additional typing --- custom_components/mail_and_packages/helpers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 11d863e6..b274f9f7 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -12,7 +12,7 @@ import uuid from email.header import decode_header from shutil import copyfile, which -from typing import Any, List, Optional, Union +from typing import Any, List, Optional, Type, Union import aiohttp import imageio as io @@ -268,7 +268,9 @@ def fetch(hass: Any, config: Any, account: Any, data: dict, sensor: str) -> int: return count[sensor] -def login(host, port, user, pwd): +def login( + host: str, port: int, user: str, pwd: str +) -> Union[bool, Type[imaplib.IMAP4_SSL]]: """function used to login""" # Catch invalid mail server / host names @@ -289,7 +291,7 @@ def login(host, port, user, pwd): return account -def selectfolder(account, folder) -> None: +def selectfolder(account: Any, folder: str) -> None: """Select folder inside the mailbox""" try: rv, mailboxes = account.list()