Remove YAML configuration from WAQI (#113027)

This commit is contained in:
Joost Lekkerkerker 2024-03-11 11:41:49 +01:00 committed by GitHub
parent 53750acdab
commit 3b0b729557
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 11 additions and 285 deletions

View File

@ -8,7 +8,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.entity_registry as er
from .const import DOMAIN from .const import DOMAIN
from .coordinator import WAQIDataUpdateCoordinator from .coordinator import WAQIDataUpdateCoordinator
@ -19,8 +18,6 @@ PLATFORMS: list[Platform] = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up World Air Quality Index (WAQI) from a config entry.""" """Set up World Air Quality Index (WAQI) from a config entry."""
await _migrate_unique_ids(hass, entry)
client = WAQIClient(session=async_get_clientsession(hass)) client = WAQIClient(session=async_get_clientsession(hass))
client.authenticate(entry.data[CONF_API_KEY]) client.authenticate(entry.data[CONF_API_KEY])
@ -39,16 +36,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok
async def _migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Migrate pre-config flow unique ids."""
entity_registry = er.async_get(hass)
registry_entries = er.async_entries_for_config_entry(
entity_registry, entry.entry_id
)
for reg_entry in registry_entries:
if isinstance(reg_entry.unique_id, int): # type: ignore[unreachable]
entity_registry.async_update_entity( # type: ignore[unreachable]
reg_entry.entity_id, new_unique_id=f"{reg_entry.unique_id}_air_quality"
)

View File

@ -20,20 +20,15 @@ from homeassistant.const import (
CONF_LOCATION, CONF_LOCATION,
CONF_LONGITUDE, CONF_LONGITUDE,
CONF_METHOD, CONF_METHOD,
CONF_NAME,
) )
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.selector import ( from homeassistant.helpers.selector import (
LocationSelector, LocationSelector,
SelectSelector, SelectSelector,
SelectSelectorConfig, SelectSelectorConfig,
) )
from homeassistant.helpers.typing import ConfigType
from .const import CONF_STATION_NUMBER, DOMAIN, ISSUE_PLACEHOLDER from .const import CONF_STATION_NUMBER, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -193,43 +188,3 @@ class WAQIConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_STATION_NUMBER: measuring_station.station_id, CONF_STATION_NUMBER: measuring_station.station_id,
}, },
) )
async def async_step_import(self, import_config: ConfigType) -> ConfigFlowResult:
"""Handle importing from yaml."""
await self.async_set_unique_id(str(import_config[CONF_STATION_NUMBER]))
try:
self._abort_if_unique_id_configured()
except AbortFlow as exc:
async_create_issue(
self.hass,
DOMAIN,
"deprecated_yaml_import_issue_already_configured",
breaks_in_ha_version="2024.4.0",
is_fixable=False,
severity=IssueSeverity.ERROR,
translation_key="deprecated_yaml_import_issue_already_configured",
translation_placeholders=ISSUE_PLACEHOLDER,
)
raise exc
async_create_issue(
self.hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.4.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "World Air Quality Index",
},
)
return self.async_create_entry(
title=import_config[CONF_NAME],
data={
CONF_API_KEY: import_config[CONF_API_KEY],
CONF_STATION_NUMBER: import_config[CONF_STATION_NUMBER],
},
)

View File

@ -7,14 +7,8 @@ from dataclasses import dataclass
import logging import logging
from typing import Any from typing import Any
from aiowaqi import ( from aiowaqi import WAQIAirQuality
WAQIAirQuality,
WAQIAuthenticationError,
WAQIClient,
WAQIConnectionError,
)
from aiowaqi.models import Pollutant from aiowaqi.models import Pollutant
import voluptuous as vol
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -22,28 +16,21 @@ from homeassistant.components.sensor import (
SensorEntityDescription, SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
ATTR_TIME, ATTR_TIME,
CONF_API_KEY,
CONF_NAME,
CONF_TOKEN,
PERCENTAGE, PERCENTAGE,
UnitOfPressure, UnitOfPressure,
UnitOfTemperature, UnitOfTemperature,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import StateType
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONF_STATION_NUMBER, DOMAIN, ISSUE_PLACEHOLDER from .const import DOMAIN
from .coordinator import WAQIDataUpdateCoordinator from .coordinator import WAQIDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -57,102 +44,6 @@ ATTR_PM2_5 = "pm_2_5"
ATTR_PRESSURE = "pressure" ATTR_PRESSURE = "pressure"
ATTR_SULFUR_DIOXIDE = "sulfur_dioxide" ATTR_SULFUR_DIOXIDE = "sulfur_dioxide"
ATTRIBUTION = "Data provided by the World Air Quality Index project"
ATTR_ICON = "mdi:cloud"
CONF_LOCATIONS = "locations"
CONF_STATIONS = "stations"
TIMEOUT = 10
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_STATIONS): cv.ensure_list,
vol.Required(CONF_TOKEN): cv.string,
vol.Required(CONF_LOCATIONS): cv.ensure_list,
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the requested World Air Quality Index locations."""
token = config[CONF_TOKEN]
station_filter = config.get(CONF_STATIONS)
locations = config[CONF_LOCATIONS]
client = WAQIClient(session=async_get_clientsession(hass), request_timeout=TIMEOUT)
client.authenticate(token)
station_count = 0
try:
for location_name in locations:
stations = await client.search(location_name)
_LOGGER.debug("The following stations were returned: %s", stations)
for station in stations:
station_count = station_count + 1
if not station_filter or {
station.station_id,
station.station.external_url,
station.station.name,
} & set(station_filter):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_STATION_NUMBER: station.station_id,
CONF_NAME: station.station.name,
CONF_API_KEY: config[CONF_TOKEN],
},
)
)
except WAQIAuthenticationError as err:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_import_issue_invalid_auth",
breaks_in_ha_version="2024.4.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml_import_issue_invalid_auth",
translation_placeholders=ISSUE_PLACEHOLDER,
)
_LOGGER.exception("Could not authenticate with WAQI")
raise PlatformNotReady from err
except WAQIConnectionError as err:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_import_issue_cannot_connect",
breaks_in_ha_version="2024.4.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml_import_issue_cannot_connect",
translation_placeholders=ISSUE_PLACEHOLDER,
)
_LOGGER.exception("Failed to connect to WAQI servers")
raise PlatformNotReady from err
if station_count == 0:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_import_issue_none_found",
breaks_in_ha_version="2024.4.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml_import_issue_none_found",
translation_placeholders=ISSUE_PLACEHOLDER,
)
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
class WAQISensorEntityDescription(SensorEntityDescription): class WAQISensorEntityDescription(SensorEntityDescription):
@ -301,6 +192,7 @@ class WaqiSensor(CoordinatorEntity[WAQIDataUpdateCoordinator], SensorEntity):
@property @property
def extra_state_attributes(self) -> Mapping[str, Any] | None: def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return old state attributes if the entity is AQI entity.""" """Return old state attributes if the entity is AQI entity."""
# These are deprecated and will be removed in 2024.5
if self.entity_description.key != "air_quality": if self.entity_description.key != "air_quality":
return None return None
attrs: dict[str, Any] = {} attrs: dict[str, Any] = {}

View File

@ -3,125 +3,20 @@
import json import json
from unittest.mock import patch from unittest.mock import patch
from aiowaqi import WAQIAirQuality, WAQIError, WAQISearchResult from aiowaqi import WAQIAirQuality, WAQIError
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.waqi.const import CONF_STATION_NUMBER, DOMAIN from homeassistant.components.waqi.const import DOMAIN
from homeassistant.components.waqi.sensor import CONF_LOCATIONS, CONF_STATIONS, SENSORS from homeassistant.components.waqi.sensor import SENSORS
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
CONF_API_KEY,
CONF_NAME,
CONF_PLATFORM,
CONF_TOKEN,
Platform,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er, issue_registry as ir from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, load_fixture from tests.common import MockConfigEntry, load_fixture
LEGACY_CONFIG = {
Platform.SENSOR: [
{
CONF_PLATFORM: DOMAIN,
CONF_TOKEN: "asd",
CONF_LOCATIONS: ["utrecht"],
CONF_STATIONS: [6332],
}
]
}
async def test_legacy_migration(hass: HomeAssistant) -> None:
"""Test migration from yaml to config flow."""
search_result_json = json.loads(load_fixture("waqi/search_result.json"))
search_results = [
WAQISearchResult.from_dict(search_result)
for search_result in search_result_json
]
with patch(
"aiowaqi.WAQIClient.search",
return_value=search_results,
), patch(
"aiowaqi.WAQIClient.get_by_station_number",
return_value=WAQIAirQuality.from_dict(
json.loads(load_fixture("waqi/air_quality_sensor.json"))
),
):
assert await async_setup_component(hass, Platform.SENSOR, LEGACY_CONFIG)
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.LOADED
issue_registry = ir.async_get(hass)
assert len(issue_registry.issues) == 1
async def test_legacy_migration_already_imported(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test migration from yaml to config flow after already imported."""
mock_config_entry.add_to_hass(hass)
with patch(
"aiowaqi.WAQIClient.get_by_station_number",
return_value=WAQIAirQuality.from_dict(
json.loads(load_fixture("waqi/air_quality_sensor.json"))
),
):
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
state = hass.states.get("sensor.de_jongweg_utrecht_air_quality_index")
assert state.state == "29"
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_STATION_NUMBER: 4584,
CONF_NAME: "xyz",
CONF_API_KEY: "asd",
},
)
)
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.LOADED
issue_registry = ir.async_get(hass)
assert len(issue_registry.issues) == 1
async def test_sensor_id_migration(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test migrating unique id for original sensor."""
mock_config_entry.add_to_hass(hass)
entity_registry = er.async_get(hass)
entity_registry.async_get_or_create(
SENSOR_DOMAIN, DOMAIN, 4584, config_entry=mock_config_entry
)
with patch(
"aiowaqi.WAQIClient.get_by_station_number",
return_value=WAQIAirQuality.from_dict(
json.loads(load_fixture("waqi/air_quality_sensor.json"))
),
):
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
entities = er.async_entries_for_config_entry(
entity_registry, mock_config_entry.entry_id
)
assert len(entities) == 12
assert hass.states.get("sensor.waqi_4584")
assert hass.states.get("sensor.de_jongweg_utrecht_air_quality_index") is None
assert entities[0].unique_id == "4584_air_quality"
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensor( async def test_sensor(