diff --git a/.coveragerc b/.coveragerc index 96936655c51..2d268742a34 100644 --- a/.coveragerc +++ b/.coveragerc @@ -532,7 +532,6 @@ omit = homeassistant/components/sensor/haveibeenpwned.py homeassistant/components/sensor/hp_ilo.py homeassistant/components/sensor/htu21d.py - homeassistant/components/sensor/hydroquebec.py homeassistant/components/sensor/imap.py homeassistant/components/sensor/imap_email_content.py homeassistant/components/sensor/influxdb.py diff --git a/homeassistant/components/sensor/hydroquebec.py b/homeassistant/components/sensor/hydroquebec.py index d857ce57fce..d4dea54514a 100644 --- a/homeassistant/components/sensor/hydroquebec.py +++ b/homeassistant/components/sensor/hydroquebec.py @@ -7,10 +7,10 @@ https://www.hydroquebec.com/portail/en/group/clientele/portrait-de-consommation For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.hydroquebec/ """ +import asyncio import logging from datetime import timedelta -import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -21,7 +21,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyhydroquebec==1.3.1'] +REQUIREMENTS = ['pyhydroquebec==2.0.2'] _LOGGER = logging.getLogger(__name__) @@ -93,7 +93,8 @@ DAILY_MAP = (('yesterday_total_consumption', 'consoTotalQuot'), ('yesterday_higher_price_consumption', 'consoHautQuot')) -def setup_platform(hass, config, add_devices, discovery_info=None): +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the HydroQuebec sensor.""" # Create a data fetcher to support all of the configured sensors. Then make # the first call to init the data. @@ -102,13 +103,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config.get(CONF_PASSWORD) contract = config.get(CONF_CONTRACT) - try: - hydroquebec_data = HydroquebecData(username, password, contract) - _LOGGER.info("Contract list: %s", - ", ".join(hydroquebec_data.get_contract_list())) - except requests.exceptions.HTTPError as error: - _LOGGER.error("Failt login: %s", error) - return False + hydroquebec_data = HydroquebecData(username, password, contract) + contracts = yield from hydroquebec_data.get_contract_list() + _LOGGER.info("Contract list: %s", + ", ".join(contracts)) name = config.get(CONF_NAME) @@ -116,7 +114,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for variable in config[CONF_MONITORED_VARIABLES]: sensors.append(HydroQuebecSensor(hydroquebec_data, variable, name)) - add_devices(sensors) + async_add_devices(sensors, True) class HydroQuebecSensor(Entity): @@ -152,10 +150,11 @@ class HydroQuebecSensor(Entity): """Icon to use in the frontend, if any.""" return self._icon - def update(self): + @asyncio.coroutine + def async_update(self): """Get the latest data from Hydroquebec and update the state.""" - self.hydroquebec_data.update() - if self.type in self.hydroquebec_data.data: + yield from self.hydroquebec_data.async_update() + if self.hydroquebec_data.data.get(self.type) is not None: self._state = round(self.hydroquebec_data.data[self.type], 2) @@ -170,23 +169,24 @@ class HydroquebecData(object): self._contract = contract self.data = {} + @asyncio.coroutine def get_contract_list(self): """Return the contract list.""" # Fetch data - self._fetch_data() + yield from self._fetch_data() return self.client.get_contracts() + @asyncio.coroutine + @Throttle(MIN_TIME_BETWEEN_UPDATES) def _fetch_data(self): """Fetch latest data from HydroQuebec.""" - from pyhydroquebec.client import PyHydroQuebecError try: - self.client.fetch_data() - except PyHydroQuebecError as exp: + yield from self.client.fetch_data() + except BaseException as exp: _LOGGER.error("Error on receive last Hydroquebec data: %s", exp) - return - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): + @asyncio.coroutine + def async_update(self): """Return the latest collected data from HydroQuebec.""" - self._fetch_data() + yield from self._fetch_data() self.data = self.client.get_data(self._contract)[self._contract] diff --git a/requirements_all.txt b/requirements_all.txt index 02a53b9c26e..e5667b240c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -698,7 +698,7 @@ pyhiveapi==0.2.5 pyhomematic==0.1.36 # homeassistant.components.sensor.hydroquebec -pyhydroquebec==1.3.1 +pyhydroquebec==2.0.2 # homeassistant.components.alarm_control_panel.ialarm pyialarm==0.2 diff --git a/tests/components/sensor/test_hydroquebec.py b/tests/components/sensor/test_hydroquebec.py new file mode 100644 index 00000000000..f2ca97313d3 --- /dev/null +++ b/tests/components/sensor/test_hydroquebec.py @@ -0,0 +1,89 @@ +"""The test for the hydroquebec sensor platform.""" +import asyncio +import logging +import sys +from unittest.mock import MagicMock + +from homeassistant.bootstrap import async_setup_component +from homeassistant.components.sensor import hydroquebec +from tests.common import assert_setup_component + + +CONTRACT = "123456789" + + +class HydroQuebecClientMock(): + """Fake Hydroquebec client.""" + + def __init__(self, username, password, contract=None): + """Fake Hydroquebec client init.""" + pass + + def get_data(self, contract): + """Return fake hydroquebec data.""" + return {CONTRACT: {"balance": 160.12}} + + def get_contracts(self): + """Return fake hydroquebec contracts.""" + return [CONTRACT] + + @asyncio.coroutine + def fetch_data(self): + """Return fake fetching data.""" + pass + + +class HydroQuebecClientMockError(HydroQuebecClientMock): + """Fake Hydroquebec client error.""" + + @asyncio.coroutine + def fetch_data(self): + """Return fake fetching data.""" + raise hydroquebec.PyHydroQuebecError("Fake Error") + + +class PyHydroQuebecErrorMock(BaseException): + """Fake PyHydroquebec Error.""" + + +@asyncio.coroutine +def test_hydroquebec_sensor(loop, hass): + """Test the Hydroquebec number sensor.""" + sys.modules['pyhydroquebec'] = MagicMock() + sys.modules['pyhydroquebec.client'] = MagicMock() + sys.modules['pyhydroquebec.client.PyHydroQuebecError'] = \ + PyHydroQuebecErrorMock + import pyhydroquebec.client + pyhydroquebec.HydroQuebecClient = HydroQuebecClientMock + pyhydroquebec.client.PyHydroQuebecError = PyHydroQuebecErrorMock + config = { + 'sensor': { + 'platform': 'hydroquebec', + 'name': 'hydro', + 'contract': CONTRACT, + 'username': 'myusername', + 'password': 'password', + 'monitored_variables': [ + 'balance', + ], + } + } + with assert_setup_component(1): + yield from async_setup_component(hass, 'sensor', config) + state = hass.states.get('sensor.hydro_balance') + assert state.state == "160.12" + assert state.attributes.get('unit_of_measurement') == "CAD" + + +@asyncio.coroutine +def test_error(hass, caplog): + """Test the Hydroquebec sensor errors.""" + caplog.set_level(logging.ERROR) + sys.modules['pyhydroquebec'] = MagicMock() + sys.modules['pyhydroquebec.client'] = MagicMock() + import pyhydroquebec.client + pyhydroquebec.HydroQuebecClient = HydroQuebecClientMockError + pyhydroquebec.client.PyHydroQuebecError = BaseException + hydro_data = hydroquebec.HydroquebecData('username', 'password') + yield from hydro_data._fetch_data() + assert "Error on receive last Hydroquebec data: " in caplog.text