Migrate usgs_earthquakes_feed to async library (#68370)

* use new async integration library

* migrate to new async integration library

* updated unit tests

* updated logger

* fix tests and improve test coverage

* fix test

* fix requirements

* time control to fix tests
This commit is contained in:
Malte Franken 2022-06-29 20:13:33 +10:00 committed by GitHub
parent 21e765207c
commit 21d28dd356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 51 deletions

View File

@ -4,9 +4,7 @@ from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import logging import logging
from geojson_client.usgs_earthquake_hazards_program_feed import ( from aio_geojson_usgs_earthquakes import UsgsEarthquakeHazardsProgramFeedManager
UsgsEarthquakeHazardsProgramFeedManager,
)
import voluptuous as vol import voluptuous as vol
from homeassistant.components.geo_location import PLATFORM_SCHEMA, GeolocationEvent from homeassistant.components.geo_location import PLATFORM_SCHEMA, GeolocationEvent
@ -21,10 +19,14 @@ from homeassistant.const import (
LENGTH_KILOMETERS, LENGTH_KILOMETERS,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import aiohttp_client
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -87,10 +89,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
) )
def setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
config: ConfigType, config: ConfigType,
add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None, discovery_info: DiscoveryInfoType | None = None,
) -> None: ) -> None:
"""Set up the USGS Earthquake Hazards Program Feed platform.""" """Set up the USGS Earthquake Hazards Program Feed platform."""
@ -103,21 +105,22 @@ def setup_platform(
radius_in_km = config[CONF_RADIUS] radius_in_km = config[CONF_RADIUS]
minimum_magnitude = config[CONF_MINIMUM_MAGNITUDE] minimum_magnitude = config[CONF_MINIMUM_MAGNITUDE]
# Initialize the entity manager. # Initialize the entity manager.
feed = UsgsEarthquakesFeedEntityManager( manager = UsgsEarthquakesFeedEntityManager(
hass, hass,
add_entities, async_add_entities,
scan_interval, scan_interval,
coordinates, coordinates,
feed_type, feed_type,
radius_in_km, radius_in_km,
minimum_magnitude, minimum_magnitude,
) )
await manager.async_init()
def start_feed_manager(event): async def start_feed_manager(event=None):
"""Start feed manager.""" """Start feed manager."""
feed.startup() await manager.async_update()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_feed_manager) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_feed_manager)
class UsgsEarthquakesFeedEntityManager: class UsgsEarthquakesFeedEntityManager:
@ -126,7 +129,7 @@ class UsgsEarthquakesFeedEntityManager:
def __init__( def __init__(
self, self,
hass, hass,
add_entities, async_add_entities,
scan_interval, scan_interval,
coordinates, coordinates,
feed_type, feed_type,
@ -136,7 +139,9 @@ class UsgsEarthquakesFeedEntityManager:
"""Initialize the Feed Entity Manager.""" """Initialize the Feed Entity Manager."""
self._hass = hass self._hass = hass
websession = aiohttp_client.async_get_clientsession(hass)
self._feed_manager = UsgsEarthquakeHazardsProgramFeedManager( self._feed_manager = UsgsEarthquakeHazardsProgramFeedManager(
websession,
self._generate_entity, self._generate_entity,
self._update_entity, self._update_entity,
self._remove_entity, self._remove_entity,
@ -145,37 +150,42 @@ class UsgsEarthquakesFeedEntityManager:
filter_radius=radius_in_km, filter_radius=radius_in_km,
filter_minimum_magnitude=minimum_magnitude, filter_minimum_magnitude=minimum_magnitude,
) )
self._add_entities = add_entities self._async_add_entities = async_add_entities
self._scan_interval = scan_interval self._scan_interval = scan_interval
def startup(self): async def async_init(self):
"""Start up this manager.""" """Schedule initial and regular updates based on configured time interval."""
self._feed_manager.update()
self._init_regular_updates()
def _init_regular_updates(self): async def update(event_time):
"""Schedule regular updates at the specified interval.""" """Update."""
track_time_interval( await self.async_update()
self._hass, lambda now: self._feed_manager.update(), self._scan_interval
) # Trigger updates at regular intervals.
async_track_time_interval(self._hass, update, self._scan_interval)
_LOGGER.debug("Feed entity manager initialized")
async def async_update(self):
"""Refresh data."""
await self._feed_manager.update()
_LOGGER.debug("Feed entity manager updated")
def get_entry(self, external_id): def get_entry(self, external_id):
"""Get feed entry by external id.""" """Get feed entry by external id."""
return self._feed_manager.feed_entries.get(external_id) return self._feed_manager.feed_entries.get(external_id)
def _generate_entity(self, external_id): async def _generate_entity(self, external_id):
"""Generate new entity.""" """Generate new entity."""
new_entity = UsgsEarthquakesEvent(self, external_id) new_entity = UsgsEarthquakesEvent(self, external_id)
# Add new entities to HA. # Add new entities to HA.
self._add_entities([new_entity], True) self._async_add_entities([new_entity], True)
def _update_entity(self, external_id): async def _update_entity(self, external_id):
"""Update entity.""" """Update entity."""
dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY.format(external_id)) async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY.format(external_id))
def _remove_entity(self, external_id): async def _remove_entity(self, external_id):
"""Remove entity.""" """Remove entity."""
dispatcher_send(self._hass, SIGNAL_DELETE_ENTITY.format(external_id)) async_dispatcher_send(self._hass, SIGNAL_DELETE_ENTITY.format(external_id))
class UsgsEarthquakesEvent(GeolocationEvent): class UsgsEarthquakesEvent(GeolocationEvent):

View File

@ -2,8 +2,8 @@
"domain": "usgs_earthquakes_feed", "domain": "usgs_earthquakes_feed",
"name": "U.S. Geological Survey Earthquake Hazards (USGS)", "name": "U.S. Geological Survey Earthquake Hazards (USGS)",
"documentation": "https://www.home-assistant.io/integrations/usgs_earthquakes_feed", "documentation": "https://www.home-assistant.io/integrations/usgs_earthquakes_feed",
"requirements": ["geojson_client==0.6"], "requirements": ["aio_geojson_usgs_earthquakes==0.1"],
"codeowners": ["@exxamalte"], "codeowners": ["@exxamalte"],
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["geojson_client"] "loggers": ["aio_geojson_usgs_earthquakes"]
} }

View File

@ -106,6 +106,9 @@ aio_geojson_geonetnz_volcano==0.6
# homeassistant.components.nsw_rural_fire_service_feed # homeassistant.components.nsw_rural_fire_service_feed
aio_geojson_nsw_rfs_incidents==0.4 aio_geojson_nsw_rfs_incidents==0.4
# homeassistant.components.usgs_earthquakes_feed
aio_geojson_usgs_earthquakes==0.1
# homeassistant.components.gdacs # homeassistant.components.gdacs
aio_georss_gdacs==0.7 aio_georss_gdacs==0.7
@ -699,9 +702,6 @@ geniushub-client==0.6.30
# homeassistant.components.geocaching # homeassistant.components.geocaching
geocachingapi==0.2.1 geocachingapi==0.2.1
# homeassistant.components.usgs_earthquakes_feed
geojson_client==0.6
# homeassistant.components.aprs # homeassistant.components.aprs
geopy==2.1.0 geopy==2.1.0

View File

@ -93,6 +93,9 @@ aio_geojson_geonetnz_volcano==0.6
# homeassistant.components.nsw_rural_fire_service_feed # homeassistant.components.nsw_rural_fire_service_feed
aio_geojson_nsw_rfs_incidents==0.4 aio_geojson_nsw_rfs_incidents==0.4
# homeassistant.components.usgs_earthquakes_feed
aio_geojson_usgs_earthquakes==0.1
# homeassistant.components.gdacs # homeassistant.components.gdacs
aio_georss_gdacs==0.7 aio_georss_gdacs==0.7
@ -499,9 +502,6 @@ gcal-sync==0.10.0
# homeassistant.components.geocaching # homeassistant.components.geocaching
geocachingapi==0.2.1 geocachingapi==0.2.1
# homeassistant.components.usgs_earthquakes_feed
geojson_client==0.6
# homeassistant.components.aprs # homeassistant.components.aprs
geopy==2.1.0 geopy==2.1.0

View File

@ -1,6 +1,9 @@
"""The tests for the USGS Earthquake Hazards Program Feed platform.""" """The tests for the USGS Earthquake Hazards Program Feed platform."""
import datetime import datetime
from unittest.mock import MagicMock, call, patch from unittest.mock import ANY, MagicMock, call, patch
from aio_geojson_usgs_earthquakes import UsgsEarthquakeHazardsProgramFeed
from freezegun import freeze_time
from homeassistant.components import geo_location from homeassistant.components import geo_location
from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.components.geo_location import ATTR_SOURCE
@ -111,11 +114,10 @@ async def test_setup(hass):
# Patching 'utcnow' to gain more control over the timed update. # Patching 'utcnow' to gain more control over the timed update.
utcnow = dt_util.utcnow() utcnow = dt_util.utcnow()
with patch("homeassistant.util.dt.utcnow", return_value=utcnow), patch( with freeze_time(utcnow), patch(
"geojson_client.usgs_earthquake_hazards_program_feed." "aio_geojson_client.feed.GeoJsonFeed.update"
"UsgsEarthquakeHazardsProgramFeed" ) as mock_feed_update:
) as mock_feed: mock_feed_update.return_value = (
mock_feed.return_value.update.return_value = (
"OK", "OK",
[mock_entry_1, mock_entry_2, mock_entry_3], [mock_entry_1, mock_entry_2, mock_entry_3],
) )
@ -184,9 +186,9 @@ async def test_setup(hass):
} }
assert round(abs(float(state.state) - 25.5), 7) == 0 assert round(abs(float(state.state) - 25.5), 7) == 0
# Simulate an update - one existing, one new entry, # Simulate an update - two existing, one new entry,
# one outdated entry # one outdated entry
mock_feed.return_value.update.return_value = ( mock_feed_update.return_value = (
"OK", "OK",
[mock_entry_1, mock_entry_4, mock_entry_3], [mock_entry_1, mock_entry_4, mock_entry_3],
) )
@ -198,7 +200,7 @@ async def test_setup(hass):
# Simulate an update - empty data, but successful update, # Simulate an update - empty data, but successful update,
# so no changes to entities. # so no changes to entities.
mock_feed.return_value.update.return_value = "OK_NO_DATA", None mock_feed_update.return_value = "OK_NO_DATA", None
async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL) async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -206,7 +208,7 @@ async def test_setup(hass):
assert len(all_states) == 3 assert len(all_states) == 3
# Simulate an update - empty data, removes all entities # Simulate an update - empty data, removes all entities
mock_feed.return_value.update.return_value = "ERROR", None mock_feed_update.return_value = "ERROR", None
async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL) async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -220,10 +222,12 @@ async def test_setup_with_custom_location(hass):
mock_entry_1 = _generate_mock_feed_entry("1234", "Title 1", 20.5, (-31.1, 150.1)) mock_entry_1 = _generate_mock_feed_entry("1234", "Title 1", 20.5, (-31.1, 150.1))
with patch( with patch(
"geojson_client.usgs_earthquake_hazards_program_feed." "aio_geojson_usgs_earthquakes.feed_manager.UsgsEarthquakeHazardsProgramFeed",
"UsgsEarthquakeHazardsProgramFeed" wraps=UsgsEarthquakeHazardsProgramFeed,
) as mock_feed: ) as mock_feed, patch(
mock_feed.return_value.update.return_value = "OK", [mock_entry_1] "aio_geojson_client.feed.GeoJsonFeed.update"
) as mock_feed_update:
mock_feed_update.return_value = "OK", [mock_entry_1]
with assert_setup_component(1, geo_location.DOMAIN): with assert_setup_component(1, geo_location.DOMAIN):
assert await async_setup_component( assert await async_setup_component(
@ -240,6 +244,7 @@ async def test_setup_with_custom_location(hass):
assert len(all_states) == 1 assert len(all_states) == 1
assert mock_feed.call_args == call( assert mock_feed.call_args == call(
ANY,
(15.1, 25.2), (15.1, 25.2),
"past_hour_m25_earthquakes", "past_hour_m25_earthquakes",
filter_minimum_magnitude=0.0, filter_minimum_magnitude=0.0,