Remove cloud dependency from islamic-prayer-times (#115146)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Collin Fair 2024-04-15 15:42:33 -05:00 committed by GitHub
parent a16d98854a
commit 4aab073bd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 33 additions and 179 deletions

View File

@ -683,8 +683,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/iqvia/ @bachya /homeassistant/components/iqvia/ @bachya
/tests/components/iqvia/ @bachya /tests/components/iqvia/ @bachya
/homeassistant/components/irish_rail_transport/ @ttroy50 /homeassistant/components/irish_rail_transport/ @ttroy50
/homeassistant/components/islamic_prayer_times/ @engrbm87 /homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair
/tests/components/islamic_prayer_times/ @engrbm87 /tests/components/islamic_prayer_times/ @engrbm87 @cpfair
/homeassistant/components/iss/ @DurgNomis-drol /homeassistant/components/iss/ @DurgNomis-drol
/tests/components/iss/ @DurgNomis-drol /tests/components/iss/ @DurgNomis-drol
/homeassistant/components/isy994/ @bdraco @shbatm /homeassistant/components/isy994/ @bdraco @shbatm

View File

@ -4,8 +4,6 @@ from __future__ import annotations
from typing import Any from typing import Any
from prayer_times_calculator import InvalidResponseError, PrayerTimesCalculator
from requests.exceptions import ConnectionError as ConnError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ( from homeassistant.config_entries import (
@ -15,7 +13,7 @@ from homeassistant.config_entries import (
OptionsFlow, OptionsFlow,
) )
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant, callback from homeassistant.core import callback
from homeassistant.helpers.selector import ( from homeassistant.helpers.selector import (
LocationSelector, LocationSelector,
SelectSelector, SelectSelector,
@ -23,7 +21,6 @@ from homeassistant.helpers.selector import (
SelectSelectorMode, SelectSelectorMode,
TextSelector, TextSelector,
) )
import homeassistant.util.dt as dt_util
from .const import ( from .const import (
CALC_METHODS, CALC_METHODS,
@ -43,26 +40,6 @@ from .const import (
) )
async def async_validate_location(
hass: HomeAssistant, lat: float, lon: float
) -> dict[str, str]:
"""Check if the selected location is valid."""
errors = {}
calc = PrayerTimesCalculator(
latitude=lat,
longitude=lon,
calculation_method=DEFAULT_CALC_METHOD,
date=str(dt_util.now().date()),
)
try:
await hass.async_add_executor_job(calc.fetch_prayer_times)
except InvalidResponseError:
errors["base"] = "invalid_location"
except ConnError:
errors["base"] = "conn_error"
return errors
class IslamicPrayerFlowHandler(ConfigFlow, domain=DOMAIN): class IslamicPrayerFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle the Islamic Prayer config flow.""" """Handle the Islamic Prayer config flow."""
@ -81,7 +58,6 @@ class IslamicPrayerFlowHandler(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Handle a flow initialized by the user.""" """Handle a flow initialized by the user."""
errors = {}
if user_input is not None: if user_input is not None:
lat: float = user_input[CONF_LOCATION][CONF_LATITUDE] lat: float = user_input[CONF_LOCATION][CONF_LATITUDE]
@ -89,14 +65,13 @@ class IslamicPrayerFlowHandler(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(f"{lat}-{lon}") await self.async_set_unique_id(f"{lat}-{lon}")
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
if not (errors := await async_validate_location(self.hass, lat, lon)): return self.async_create_entry(
return self.async_create_entry( title=user_input[CONF_NAME],
title=user_input[CONF_NAME], data={
data={ CONF_LATITUDE: lat,
CONF_LATITUDE: lat, CONF_LONGITUDE: lon,
CONF_LONGITUDE: lon, },
}, )
)
home_location = { home_location = {
CONF_LATITUDE: self.hass.config.latitude, CONF_LATITUDE: self.hass.config.latitude,
@ -112,7 +87,6 @@ class IslamicPrayerFlowHandler(ConfigFlow, domain=DOMAIN):
): LocationSelector(), ): LocationSelector(),
} }
), ),
errors=errors,
) )

View File

@ -6,14 +6,13 @@ from datetime import datetime, timedelta
import logging import logging
from typing import Any, cast from typing import Any, cast
from prayer_times_calculator import PrayerTimesCalculator, exceptions from prayer_times_calculator_offline import PrayerTimesCalculator
from requests.exceptions import ConnectionError as ConnError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.event import async_call_later, async_track_point_in_time from homeassistant.helpers.event import async_track_point_in_time
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from .const import ( from .const import (
@ -142,13 +141,7 @@ class IslamicPrayerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, datetim
async def _async_update_data(self) -> dict[str, datetime]: async def _async_update_data(self) -> dict[str, datetime]:
"""Update sensors with new prayer times.""" """Update sensors with new prayer times."""
try: prayer_times = self.get_new_prayer_times()
prayer_times = await self.hass.async_add_executor_job(
self.get_new_prayer_times
)
except (exceptions.InvalidResponseError, ConnError) as err:
async_call_later(self.hass, 60, self.async_request_update)
raise UpdateFailed from err
# introduced in prayer-times-calculator 0.0.8 # introduced in prayer-times-calculator 0.0.8
prayer_times.pop("date", None) prayer_times.pop("date", None)

View File

@ -1,10 +1,10 @@
{ {
"domain": "islamic_prayer_times", "domain": "islamic_prayer_times",
"name": "Islamic Prayer Times", "name": "Islamic Prayer Times",
"codeowners": ["@engrbm87"], "codeowners": ["@engrbm87", "@cpfair"],
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/islamic_prayer_times", "documentation": "https://www.home-assistant.io/integrations/islamic_prayer_times",
"iot_class": "cloud_polling", "iot_class": "calculated",
"loggers": ["prayer_times_calculator"], "loggers": ["prayer_times_calculator"],
"requirements": ["prayer-times-calculator==0.0.12"] "requirements": ["prayer-times-calculator-offline==1.0.3"]
} }

View File

@ -2879,7 +2879,7 @@
"islamic_prayer_times": { "islamic_prayer_times": {
"integration_type": "hub", "integration_type": "hub",
"config_flow": true, "config_flow": true,
"iot_class": "cloud_polling" "iot_class": "calculated"
}, },
"ismartwindow": { "ismartwindow": {
"name": "iSmartWindow", "name": "iSmartWindow",

View File

@ -1556,7 +1556,7 @@ poolsense==0.0.8
praw==7.5.0 praw==7.5.0
# homeassistant.components.islamic_prayer_times # homeassistant.components.islamic_prayer_times
prayer-times-calculator==0.0.12 prayer-times-calculator-offline==1.0.3
# homeassistant.components.proliphix # homeassistant.components.proliphix
proliphix==0.4.1 proliphix==0.4.1

View File

@ -1227,7 +1227,7 @@ poolsense==0.0.8
praw==7.5.0 praw==7.5.0
# homeassistant.components.islamic_prayer_times # homeassistant.components.islamic_prayer_times
prayer-times-calculator==0.0.12 prayer-times-calculator-offline==1.0.3
# homeassistant.components.prometheus # homeassistant.components.prometheus
prometheus-client==0.17.1 prometheus-client==0.17.1

View File

@ -22,14 +22,4 @@ PRAYER_TIMES = {
"Midnight": "2020-01-01T00:45:00+00:00", "Midnight": "2020-01-01T00:45:00+00:00",
} }
NEW_PRAYER_TIMES = {
"Fajr": "2020-01-02T06:00:00+00:00",
"Sunrise": "2020-01-02T07:25:00+00:00",
"Dhuhr": "2020-01-02T12:30:00+00:00",
"Asr": "2020-01-02T15:32:00+00:00",
"Maghrib": "2020-01-02T17:45:00+00:00",
"Isha": "2020-01-02T18:53:00+00:00",
"Midnight": "2020-01-02T00:43:00+00:00",
}
NOW = datetime(2020, 1, 1, 00, 00, 0, tzinfo=dt_util.UTC) NOW = datetime(2020, 1, 1, 00, 00, 0, tzinfo=dt_util.UTC)

View File

@ -1,10 +1,6 @@
"""Tests for Islamic Prayer Times config flow.""" """Tests for Islamic Prayer Times config flow."""
from unittest.mock import patch
from prayer_times_calculator import InvalidResponseError
import pytest import pytest
from requests.exceptions import ConnectionError as ConnError
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import islamic_prayer_times from homeassistant.components import islamic_prayer_times
@ -33,49 +29,15 @@ async def test_flow_works(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.islamic_prayer_times.config_flow.async_validate_location", result["flow_id"], user_input=MOCK_USER_INPUT
return_value={}, )
): await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_INPUT
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "Home" assert result["title"] == "Home"
@pytest.mark.parametrize(
("exception", "error"),
[
(InvalidResponseError, "invalid_location"),
(ConnError, "conn_error"),
],
)
async def test_flow_error(
hass: HomeAssistant, exception: Exception, error: str
) -> None:
"""Test flow errors."""
result = await hass.config_entries.flow.async_init(
islamic_prayer_times.DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
with patch(
"homeassistant.components.islamic_prayer_times.config_flow.PrayerTimesCalculator.fetch_prayer_times",
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_INPUT
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM
assert result["errors"]["base"] == error
async def test_options(hass: HomeAssistant) -> None: async def test_options(hass: HomeAssistant) -> None:
"""Test updating options.""" """Test updating options."""
entry = MockConfigEntry( entry = MockConfigEntry(

View File

@ -1,24 +1,21 @@
"""Tests for Islamic Prayer Times init.""" """Tests for Islamic Prayer Times init."""
from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from freezegun import freeze_time from freezegun import freeze_time
from prayer_times_calculator.exceptions import InvalidResponseError
import pytest import pytest
from homeassistant.components import islamic_prayer_times from homeassistant.components import islamic_prayer_times
from homeassistant.components.islamic_prayer_times.const import CONF_CALC_METHOD from homeassistant.components.islamic_prayer_times.const import CONF_CALC_METHOD
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, STATE_UNAVAILABLE from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
import homeassistant.util.dt as dt_util
from . import NEW_PRAYER_TIMES, NOW, PRAYER_TIMES from . import NOW, PRAYER_TIMES
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -37,7 +34,7 @@ async def test_successful_config_entry(hass: HomeAssistant) -> None:
entry.add_to_hass(hass) entry.add_to_hass(hass)
with patch( with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", "prayer_times_calculator_offline.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES, return_value=PRAYER_TIMES,
): ):
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
@ -46,25 +43,6 @@ async def test_successful_config_entry(hass: HomeAssistant) -> None:
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
async def test_setup_failed(hass: HomeAssistant) -> None:
"""Test Islamic Prayer Times failed due to an error."""
entry = MockConfigEntry(
domain=islamic_prayer_times.DOMAIN,
data={},
)
entry.add_to_hass(hass)
# test request error raising ConfigEntryNotReady
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
side_effect=InvalidResponseError(),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_RETRY
async def test_unload_entry(hass: HomeAssistant) -> None: async def test_unload_entry(hass: HomeAssistant) -> None:
"""Test removing Islamic Prayer Times.""" """Test removing Islamic Prayer Times."""
entry = MockConfigEntry( entry = MockConfigEntry(
@ -74,7 +52,7 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
entry.add_to_hass(hass) entry.add_to_hass(hass)
with patch( with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", "prayer_times_calculator_offline.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES, return_value=PRAYER_TIMES,
): ):
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
@ -91,7 +69,7 @@ async def test_options_listener(hass: HomeAssistant) -> None:
with ( with (
patch( patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", "prayer_times_calculator_offline.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES, return_value=PRAYER_TIMES,
) as mock_fetch_prayer_times, ) as mock_fetch_prayer_times,
freeze_time(NOW), freeze_time(NOW),
@ -107,49 +85,6 @@ async def test_options_listener(hass: HomeAssistant) -> None:
assert mock_fetch_prayer_times.call_count == 2 assert mock_fetch_prayer_times.call_count == 2
async def test_update_failed(hass: HomeAssistant) -> None:
"""Test integrations tries to update after 1 min if update fails."""
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={})
entry.add_to_hass(hass)
with (
patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES,
),
freeze_time(NOW),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times"
) as FetchPrayerTimes:
FetchPrayerTimes.side_effect = [
InvalidResponseError,
NEW_PRAYER_TIMES,
]
midnight_time = dt_util.parse_datetime(PRAYER_TIMES["Midnight"])
assert midnight_time
future = midnight_time + timedelta(days=1, minutes=1)
with freeze_time(future):
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
state = hass.states.get("sensor.islamic_prayer_times_fajr_prayer")
assert state.state == STATE_UNAVAILABLE
# coordinator tries to update after 1 minute
future = future + timedelta(minutes=1)
with freeze_time(future):
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
state = hass.states.get("sensor.islamic_prayer_times_fajr_prayer")
assert state.state == "2020-01-02T06:00:00+00:00"
@pytest.mark.parametrize( @pytest.mark.parametrize(
("object_id", "old_unique_id"), ("object_id", "old_unique_id"),
[ [
@ -184,7 +119,7 @@ async def test_migrate_unique_id(
with ( with (
patch( patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", "prayer_times_calculator_offline.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES, return_value=PRAYER_TIMES,
), ),
freeze_time(NOW), freeze_time(NOW),
@ -207,7 +142,7 @@ async def test_migration_from_1_1_to_1_2(hass: HomeAssistant) -> None:
with ( with (
patch( patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", "prayer_times_calculator_offline.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES, return_value=PRAYER_TIMES,
), ),
freeze_time(NOW), freeze_time(NOW),

View File

@ -40,7 +40,7 @@ async def test_islamic_prayer_times_sensors(
with ( with (
patch( patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", "prayer_times_calculator_offline.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES, return_value=PRAYER_TIMES,
), ),
freeze_time(NOW), freeze_time(NOW),