Add async lib and DataUpdateCoordinator for environment_canada (#57746)

pull/57322/head
Glenn Waters 2021-10-26 17:23:43 -04:00 committed by GitHub
parent 3715286969
commit de4a4c3ba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 140 additions and 206 deletions

View File

@ -1,14 +1,19 @@
"""The Environment Canada (EC) component.""" """The Environment Canada (EC) component."""
from functools import partial from datetime import timedelta
import logging import logging
import xml.etree.ElementTree as et
from env_canada import ECData, ECRadar from env_canada import ECRadar, ECWeather, ec_exc
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_LANGUAGE, CONF_STATION, DOMAIN from .const import CONF_LANGUAGE, CONF_STATION, DOMAIN
DEFAULT_RADAR_UPDATE_INTERVAL = timedelta(minutes=5)
DEFAULT_WEATHER_UPDATE_INTERVAL = timedelta(minutes=5)
PLATFORMS = ["camera", "sensor", "weather"] PLATFORMS = ["camera", "sensor", "weather"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -21,21 +26,26 @@ async def async_setup_entry(hass, config_entry):
station = config_entry.data.get(CONF_STATION) station = config_entry.data.get(CONF_STATION)
lang = config_entry.data.get(CONF_LANGUAGE, "English") lang = config_entry.data.get(CONF_LANGUAGE, "English")
weather_api = {} coordinators = {}
weather_init = partial( weather_data = ECWeather(
ECData, station_id=station, coordinates=(lat, lon), language=lang.lower() station_id=station,
coordinates=(lat, lon),
language=lang.lower(),
) )
weather_data = await hass.async_add_executor_job(weather_init) coordinators["weather_coordinator"] = ECDataUpdateCoordinator(
weather_api["weather_data"] = weather_data hass, weather_data, "weather", DEFAULT_WEATHER_UPDATE_INTERVAL
)
await coordinators["weather_coordinator"].async_config_entry_first_refresh()
radar_init = partial(ECRadar, coordinates=(lat, lon)) radar_data = ECRadar(coordinates=(lat, lon))
radar_data = await hass.async_add_executor_job(radar_init) coordinators["radar_coordinator"] = ECDataUpdateCoordinator(
weather_api["radar_data"] = radar_data hass, radar_data, "radar", DEFAULT_RADAR_UPDATE_INTERVAL
await hass.async_add_executor_job(radar_data.get_loop) )
await coordinators["radar_coordinator"].async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = weather_api hass.data[DOMAIN][config_entry.entry_id] = coordinators
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
@ -77,3 +87,22 @@ def trigger_import(hass, config):
DOMAIN, context={"source": SOURCE_IMPORT}, data=data DOMAIN, context={"source": SOURCE_IMPORT}, data=data
) )
) )
class ECDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching EC data."""
def __init__(self, hass, ec_data, name, update_interval):
"""Initialize global EC data updater."""
super().__init__(
hass, _LOGGER, name=f"{DOMAIN} {name}", update_interval=update_interval
)
self.ec_data = ec_data
async def _async_update_data(self):
"""Fetch data from EC."""
try:
await self.ec_data.update()
except (et.ParseError, ec_exc.UnknownStationId) as ex:
raise UpdateFailed(f"Error fetching {self.name} data: {ex}") from ex
return self.ec_data

View File

@ -1,33 +1,18 @@
"""Support for the Environment Canada radar imagery.""" """Support for the Environment Canada radar imagery."""
from __future__ import annotations from __future__ import annotations
import datetime
import logging
from env_canada import get_station_coords
from requests.exceptions import ConnectionError as RequestsConnectionError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.const import ( from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
ATTR_ATTRIBUTION,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import trigger_import from . import trigger_import
from .const import CONF_ATTRIBUTION, CONF_STATION, DOMAIN from .const import ATTR_OBSERVATION_TIME, CONF_STATION, DOMAIN
CONF_LOOP = "loop" CONF_LOOP = "loop"
CONF_PRECIP_TYPE = "precip_type" CONF_PRECIP_TYPE = "precip_type"
ATTR_UPDATED = "updated"
MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=10)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
@ -43,13 +28,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Environment Canada camera.""" """Set up the Environment Canada camera."""
if config.get(CONF_STATION): lat = config.get(CONF_LATITUDE, hass.config.latitude)
lat, lon = await hass.async_add_executor_job( lon = config.get(CONF_LONGITUDE, hass.config.longitude)
get_station_coords, config[CONF_STATION]
)
else:
lat = config.get(CONF_LATITUDE, hass.config.latitude)
lon = config.get(CONF_LONGITUDE, hass.config.longitude)
config[CONF_LATITUDE] = lat config[CONF_LATITUDE] = lat
config[CONF_LONGITUDE] = lon config[CONF_LONGITUDE] = lon
@ -59,52 +39,35 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add a weather entity from a config_entry.""" """Add a weather entity from a config_entry."""
radar_data = hass.data[DOMAIN][config_entry.entry_id]["radar_data"] coordinator = hass.data[DOMAIN][config_entry.entry_id]["radar_coordinator"]
async_add_entities([ECCamera(coordinator)])
async_add_entities(
[
ECCamera(
radar_data,
f"{config_entry.title} Radar",
f"{config_entry.unique_id}-radar",
),
]
)
class ECCamera(Camera): class ECCamera(CoordinatorEntity, Camera):
"""Implementation of an Environment Canada radar camera.""" """Implementation of an Environment Canada radar camera."""
def __init__(self, radar_object, camera_name, unique_id): def __init__(self, coordinator):
"""Initialize the camera.""" """Initialize the camera."""
super().__init__() super().__init__(coordinator)
Camera.__init__(self)
self.radar_object = coordinator.ec_data
self._attr_name = f"{coordinator.config_entry.title} Radar"
self._attr_unique_id = f"{coordinator.config_entry.unique_id}-radar"
self._attr_attribution = self.radar_object.metadata["attribution"]
self.radar_object = radar_object
self._attr_name = camera_name
self._attr_unique_id = unique_id
self.content_type = "image/gif" self.content_type = "image/gif"
self.image = None self.image = None
self.timestamp = None self.observation_time = None
def camera_image( def camera_image(
self, width: int | None = None, height: int | None = None self, width: int | None = None, height: int | None = None
) -> bytes | None: ) -> bytes | None:
"""Return bytes of camera image.""" """Return bytes of camera image."""
self.update() self.observation_time = self.radar_object.timestamp
return self.image return self.radar_object.image
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
"""Return the state attributes of the device.""" """Return the state attributes of the device."""
return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_UPDATED: self.timestamp} return {ATTR_OBSERVATION_TIME: self.observation_time}
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update radar image."""
try:
self.image = self.radar_object.get_loop()
except RequestsConnectionError:
_LOGGER.warning("Radar data update failed due to rate limiting")
return
self.timestamp = self.radar_object.timestamp

View File

@ -1,13 +1,12 @@
"""Config flow for Environment Canada integration.""" """Config flow for Environment Canada integration."""
from functools import partial
import logging import logging
import xml.etree.ElementTree as et import xml.etree.ElementTree as et
import aiohttp import aiohttp
from env_canada import ECData from env_canada import ECWeather, ec_exc
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries, exceptions from homeassistant import config_entries
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
@ -16,19 +15,19 @@ from .const import CONF_LANGUAGE, CONF_STATION, CONF_TITLE, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def validate_input(hass, data): async def validate_input(data):
"""Validate the user input allows us to connect.""" """Validate the user input allows us to connect."""
lat = data.get(CONF_LATITUDE) lat = data.get(CONF_LATITUDE)
lon = data.get(CONF_LONGITUDE) lon = data.get(CONF_LONGITUDE)
station = data.get(CONF_STATION) station = data.get(CONF_STATION)
lang = data.get(CONF_LANGUAGE) lang = data.get(CONF_LANGUAGE)
weather_init = partial( weather_data = ECWeather(
ECData, station_id=station, coordinates=(lat, lon), language=lang.lower() station_id=station,
coordinates=(lat, lon),
language=lang.lower(),
) )
weather_data = await hass.async_add_executor_job(weather_init) await weather_data.update()
if weather_data.metadata.get("location") is None:
raise TooManyAttempts
if lat is None or lon is None: if lat is None or lon is None:
lat = weather_data.lat lat = weather_data.lat
@ -52,10 +51,8 @@ class EnvironmentCanadaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {} errors = {}
if user_input is not None: if user_input is not None:
try: try:
info = await validate_input(self.hass, user_input) info = await validate_input(user_input)
except TooManyAttempts: except (et.ParseError, vol.MultipleInvalid, ec_exc.UnknownStationId):
errors["base"] = "too_many_attempts"
except et.ParseError:
errors["base"] = "bad_station_id" errors["base"] = "bad_station_id"
except aiohttp.ClientConnectionError: except aiohttp.ClientConnectionError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
@ -102,7 +99,3 @@ class EnvironmentCanadaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, import_data): async def async_step_import(self, import_data):
"""Import entry from configuration.yaml.""" """Import entry from configuration.yaml."""
return await self.async_step_user(import_data) return await self.async_step_user(import_data)
class TooManyAttempts(exceptions.HomeAssistantError):
"""Error to indicate station ID is missing, invalid, or not in EC database."""

View File

@ -2,7 +2,6 @@
ATTR_OBSERVATION_TIME = "observation_time" ATTR_OBSERVATION_TIME = "observation_time"
ATTR_STATION = "station" ATTR_STATION = "station"
CONF_ATTRIBUTION = "Data provided by Environment Canada"
CONF_LANGUAGE = "language" CONF_LANGUAGE = "language"
CONF_STATION = "station" CONF_STATION = "station"
CONF_TITLE = "title" CONF_TITLE = "title"

View File

@ -2,7 +2,7 @@
"domain": "environment_canada", "domain": "environment_canada",
"name": "Environment Canada", "name": "Environment Canada",
"documentation": "https://www.home-assistant.io/integrations/environment_canada", "documentation": "https://www.home-assistant.io/integrations/environment_canada",
"requirements": ["env_canada==0.2.7"], "requirements": ["env_canada==0.5.14"],
"codeowners": ["@gwww", "@michaeldavie"], "codeowners": ["@gwww", "@michaeldavie"],
"config_flow": true, "config_flow": true,
"iot_class": "cloud_polling" "iot_class": "cloud_polling"

View File

@ -1,5 +1,4 @@
"""Support for the Environment Canada weather service.""" """Support for the Environment Canada weather service."""
from datetime import datetime, timedelta
import logging import logging
import re import re
@ -7,7 +6,6 @@ import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_LOCATION, ATTR_LOCATION,
CONF_LATITUDE, CONF_LATITUDE,
CONF_LONGITUDE, CONF_LONGITUDE,
@ -15,12 +13,17 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import trigger_import from . import trigger_import
from .const import ATTR_STATION, CONF_ATTRIBUTION, CONF_LANGUAGE, CONF_STATION, DOMAIN from .const import (
ATTR_OBSERVATION_TIME,
ATTR_STATION,
CONF_LANGUAGE,
CONF_STATION,
DOMAIN,
)
SCAN_INTERVAL = timedelta(minutes=10)
ATTR_UPDATED = "updated"
ATTR_TIME = "alert time" ATTR_TIME = "alert time"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -31,7 +34,7 @@ def validate_station(station):
if station is None: if station is None:
return None return None
if not re.fullmatch(r"[A-Z]{2}/s0000\d{3}", station): if not re.fullmatch(r"[A-Z]{2}/s0000\d{3}", station):
raise vol.error.Invalid('Station ID must be of the form "XX/s0000###"') raise vol.Invalid('Station ID must be of the form "XX/s0000###"')
return station return station
@ -52,43 +55,40 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add a weather entity from a config_entry.""" """Add a weather entity from a config_entry."""
weather_data = hass.data[DOMAIN][config_entry.entry_id]["weather_data"] coordinator = hass.data[DOMAIN][config_entry.entry_id]["weather_coordinator"]
sensor_list = list(weather_data.conditions) + list(weather_data.alerts) weather_data = coordinator.ec_data
sensors = list(weather_data.conditions)
labels = [weather_data.conditions[sensor]["label"] for sensor in sensors]
alerts_list = list(weather_data.alerts)
labels = labels + [weather_data.alerts[sensor]["label"] for sensor in alerts_list]
sensors = sensors + alerts_list
async_add_entities( async_add_entities(
[ [
ECSensor( ECSensor(coordinator, sensor, label)
sensor_type, for sensor, label in zip(sensors, labels)
f"{config_entry.title} {sensor_type}",
weather_data,
f"{weather_data.metadata['location']}-{sensor_type}",
)
for sensor_type in sensor_list
], ],
True, True,
) )
class ECSensor(SensorEntity): class ECSensor(CoordinatorEntity, SensorEntity):
"""Implementation of an Environment Canada sensor.""" """Implementation of an Environment Canada sensor."""
def __init__(self, sensor_type, name, ec_data, unique_id): def __init__(self, coordinator, sensor, label):
"""Initialize the sensor.""" """Initialize the sensor."""
self.sensor_type = sensor_type super().__init__(coordinator)
self.ec_data = ec_data self.sensor_type = sensor
self.ec_data = coordinator.ec_data
self._attr_unique_id = unique_id self._attr_attribution = self.ec_data.metadata["attribution"]
self._attr_name = name self._attr_name = f"{coordinator.config_entry.title} {label}"
self._state = None self._attr_unique_id = f"{self.ec_data.metadata['location']}-{sensor}"
self._attr = None self._attr = None
self._unit = None self._unit = None
self._device_class = None self._device_class = None
@property
def native_value(self):
"""Return the state of the sensor."""
return self._state
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
"""Return the state attributes of the device.""" """Return the state attributes of the device."""
@ -104,32 +104,31 @@ class ECSensor(SensorEntity):
"""Return the class of this device, from component DEVICE_CLASSES.""" """Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class return self._device_class
def update(self): @property
def native_value(self):
"""Update current conditions.""" """Update current conditions."""
self.ec_data.update()
self.ec_data.conditions.update(self.ec_data.alerts)
conditions = self.ec_data.conditions
metadata = self.ec_data.metadata metadata = self.ec_data.metadata
sensor_data = conditions.get(self.sensor_type) sensor_data = self.ec_data.conditions.get(self.sensor_type)
if not sensor_data:
sensor_data = self.ec_data.alerts.get(self.sensor_type)
self._attr = {} self._attr = {}
value = sensor_data.get("value") value = sensor_data.get("value")
if isinstance(value, list): if isinstance(value, list):
self._state = " | ".join([str(s.get("title")) for s in value])[:255] state = " | ".join([str(s.get("title")) for s in value])[:255]
self._attr.update( self._attr.update(
{ATTR_TIME: " | ".join([str(s.get("date")) for s in value])} {ATTR_TIME: " | ".join([str(s.get("date")) for s in value])}
) )
elif self.sensor_type == "tendency": elif self.sensor_type == "tendency":
self._state = str(value).capitalize() state = str(value).capitalize()
elif value is not None and len(value) > 255: elif isinstance(value, str) and len(value) > 255:
self._state = value[:255] state = value[:255]
_LOGGER.info( _LOGGER.info(
"Value for %s truncated to 255 characters", self._attr_unique_id "Value for %s truncated to 255 characters", self._attr_unique_id
) )
else: else:
self._state = value state = value
if sensor_data.get("unit") == "C" or self.sensor_type in ( if sensor_data.get("unit") == "C" or self.sensor_type in (
"wind_chill", "wind_chill",
@ -140,16 +139,11 @@ class ECSensor(SensorEntity):
else: else:
self._unit = sensor_data.get("unit") self._unit = sensor_data.get("unit")
if timestamp := metadata.get("timestamp"):
updated_utc = datetime.strptime(timestamp, "%Y%m%d%H%M%S").isoformat()
else:
updated_utc = None
self._attr.update( self._attr.update(
{ {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_OBSERVATION_TIME: metadata.get("timestamp"),
ATTR_UPDATED: updated_utc,
ATTR_LOCATION: metadata.get("location"), ATTR_LOCATION: metadata.get("location"),
ATTR_STATION: metadata.get("station"), ATTR_STATION: metadata.get("station"),
} }
) )
return state

View File

@ -1,6 +1,7 @@
"""Platform for retrieving meteorological data from Environment Canada.""" """Platform for retrieving meteorological data from Environment Canada."""
from __future__ import annotations
import datetime import datetime
import logging
import re import re
import voluptuous as vol import voluptuous as vol
@ -28,15 +29,14 @@ from homeassistant.components.weather import (
) )
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt from homeassistant.util import dt
from . import trigger_import from . import trigger_import
from .const import CONF_ATTRIBUTION, CONF_STATION, DOMAIN from .const import CONF_STATION, DOMAIN
CONF_FORECAST = "forecast" CONF_FORECAST = "forecast"
_LOGGER = logging.getLogger(__name__)
def validate_station(station): def validate_station(station):
"""Check that the station ID is well-formed.""" """Check that the station ID is well-formed."""
@ -82,43 +82,25 @@ async def async_setup_platform(hass, config, async_add_entries, discovery_info=N
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add a weather entity from a config_entry.""" """Add a weather entity from a config_entry."""
weather_data = hass.data[DOMAIN][config_entry.entry_id]["weather_data"] coordinator = hass.data[DOMAIN][config_entry.entry_id]["weather_coordinator"]
async_add_entities([ECWeather(coordinator, False), ECWeather(coordinator, True)])
async_add_entities(
[
ECWeather(
weather_data,
f"{config_entry.title}",
config_entry.data,
"daily",
f"{config_entry.unique_id}-daily",
),
ECWeather(
weather_data,
f"{config_entry.title} Hourly",
config_entry.data,
"hourly",
f"{config_entry.unique_id}-hourly",
),
]
)
class ECWeather(WeatherEntity): class ECWeather(CoordinatorEntity, WeatherEntity):
"""Representation of a weather condition.""" """Representation of a weather condition."""
def __init__(self, ec_data, name, config, forecast_type, unique_id): def __init__(self, coordinator, hourly):
"""Initialize Environment Canada weather.""" """Initialize Environment Canada weather."""
self.ec_data = ec_data super().__init__(coordinator)
self.config = config self.ec_data = coordinator.ec_data
self._attr_name = name self._attr_attribution = self.ec_data.metadata["attribution"]
self._attr_unique_id = unique_id self._attr_name = (
self.forecast_type = forecast_type f"{coordinator.config_entry.title}{' Hourly' if hourly else ''}"
)
@property self._attr_unique_id = (
def attribution(self): f"{coordinator.config_entry.unique_id}{'-hourly' if hourly else '-daily'}"
"""Return the attribution.""" )
return CONF_ATTRIBUTION self._hourly = hourly
@property @property
def temperature(self): def temperature(self):
@ -190,18 +172,14 @@ class ECWeather(WeatherEntity):
@property @property
def forecast(self): def forecast(self):
"""Return the forecast array.""" """Return the forecast array."""
return get_forecast(self.ec_data, self.forecast_type) return get_forecast(self.ec_data, self._hourly)
def update(self):
"""Get the latest data from Environment Canada."""
self.ec_data.update()
def get_forecast(ec_data, forecast_type): def get_forecast(ec_data, hourly):
"""Build the forecast array.""" """Build the forecast array."""
forecast_array = [] forecast_array = []
if forecast_type == "daily": if not hourly:
if not (half_days := ec_data.daily_forecasts): if not (half_days := ec_data.daily_forecasts):
return None return None
@ -251,15 +229,11 @@ def get_forecast(ec_data, forecast_type):
} }
) )
elif forecast_type == "hourly": else:
for hour in ec_data.hourly_forecasts: for hour in ec_data.hourly_forecasts:
forecast_array.append( forecast_array.append(
{ {
ATTR_FORECAST_TIME: datetime.datetime.strptime( ATTR_FORECAST_TIME: hour["period"],
hour["period"], "%Y%m%d%H%M%S"
)
.replace(tzinfo=dt.UTC)
.isoformat(),
ATTR_FORECAST_TEMP: int(hour["temperature"]), ATTR_FORECAST_TEMP: int(hour["temperature"]),
ATTR_FORECAST_CONDITION: icon_code_to_condition( ATTR_FORECAST_CONDITION: icon_code_to_condition(
int(hour["icon_code"]) int(hour["icon_code"])

View File

@ -597,7 +597,7 @@ enocean==0.50
enturclient==0.2.2 enturclient==0.2.2
# homeassistant.components.environment_canada # homeassistant.components.environment_canada
env_canada==0.2.7 env_canada==0.5.14
# homeassistant.components.envirophat # homeassistant.components.envirophat
# envirophat==0.0.6 # envirophat==0.0.6

View File

@ -366,7 +366,7 @@ emulated_roku==0.2.1
enocean==0.50 enocean==0.50
# homeassistant.components.environment_canada # homeassistant.components.environment_canada
env_canada==0.2.7 env_canada==0.5.14
# homeassistant.components.enphase_envoy # homeassistant.components.enphase_envoy
envoy_reader==0.20.0 envoy_reader==0.20.0

View File

@ -1,5 +1,5 @@
"""Test the Environment Canada (EC) config flow.""" """Test the Environment Canada (EC) config flow."""
from unittest.mock import MagicMock, Mock, patch from unittest.mock import AsyncMock, MagicMock, Mock, patch
import xml.etree.ElementTree as et import xml.etree.ElementTree as et
import aiohttp import aiohttp
@ -44,10 +44,10 @@ def mocked_ec(
if update: if update:
ec_mock.update = update ec_mock.update = update
else: else:
ec_mock.update = Mock() ec_mock.update = AsyncMock()
return patch( return patch(
"homeassistant.components.environment_canada.config_flow.ECData", "homeassistant.components.environment_canada.config_flow.ECWeather",
return_value=ec_mock, return_value=ec_mock,
) )
@ -94,24 +94,6 @@ async def test_create_same_entry_twice(hass):
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_too_many_attempts(hass):
"""Test hitting rate limit."""
with mocked_ec(metadata={}), patch(
"homeassistant.components.environment_canada.async_setup_entry",
return_value=True,
):
flow = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
flow["flow_id"],
{},
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {"base": "too_many_attempts"}
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error", "error",
[ [
@ -126,7 +108,7 @@ async def test_exception_handling(hass, error):
"""Test exception handling.""" """Test exception handling."""
exc, base_error = error exc, base_error = error
with patch( with patch(
"homeassistant.components.environment_canada.config_flow.ECData", "homeassistant.components.environment_canada.config_flow.ECWeather",
side_effect=exc, side_effect=exc,
): ):
flow = await hass.config_entries.flow.async_init( flow = await hass.config_entries.flow.async_init(