mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
Add location selector to Islamic prayer times (#105911)
* Add location selector to config flow * Simplify entry data * fix abort string * Add migration with minor version * Follow documented migration method
This commit is contained in:
parent
b7661b4d79
commit
c91ac22d3c
@ -1,8 +1,10 @@
|
|||||||
"""The islamic_prayer_times component."""
|
"""The islamic_prayer_times component."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||||
|
|
||||||
@ -13,6 +15,8 @@ PLATFORMS = [Platform.SENSOR]
|
|||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
"""Set up the Islamic Prayer Component."""
|
"""Set up the Islamic Prayer Component."""
|
||||||
@ -41,6 +45,34 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
|
"""Migrate old entry."""
|
||||||
|
_LOGGER.debug("Migrating from version %s", config_entry.version)
|
||||||
|
|
||||||
|
if config_entry.version > 1:
|
||||||
|
# This means the user has downgraded from a future version
|
||||||
|
return False
|
||||||
|
if config_entry.version == 1:
|
||||||
|
new = {**config_entry.data}
|
||||||
|
if config_entry.minor_version < 2:
|
||||||
|
lat = hass.config.latitude
|
||||||
|
lon = hass.config.longitude
|
||||||
|
new = {
|
||||||
|
CONF_LATITUDE: lat,
|
||||||
|
CONF_LONGITUDE: lon,
|
||||||
|
}
|
||||||
|
unique_id = f"{lat}-{lon}"
|
||||||
|
config_entry.version = 1
|
||||||
|
config_entry.minor_version = 2
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
config_entry, data=new, unique_id=unique_id
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER.debug("Migration to version %s successful", config_entry.version)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
"""Unload Islamic Prayer entry from config_entry."""
|
"""Unload Islamic Prayer entry from config_entry."""
|
||||||
if unload_ok := await hass.config_entries.async_unload_platforms(
|
if unload_ok := await hass.config_entries.async_unload_platforms(
|
||||||
|
@ -3,16 +3,22 @@ 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 import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.core import callback
|
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers.selector import (
|
from homeassistant.helpers.selector import (
|
||||||
|
LocationSelector,
|
||||||
SelectSelector,
|
SelectSelector,
|
||||||
SelectSelectorConfig,
|
SelectSelectorConfig,
|
||||||
SelectSelectorMode,
|
SelectSelectorMode,
|
||||||
|
TextSelector,
|
||||||
)
|
)
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CALC_METHODS,
|
CALC_METHODS,
|
||||||
@ -32,10 +38,31 @@ from .const import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_validate_location(
|
||||||
|
hass: HomeAssistant, lon: float, lat: 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(config_entries.ConfigFlow, domain=DOMAIN):
|
class IslamicPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle the Islamic Prayer config flow."""
|
"""Handle the Islamic Prayer config flow."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
MINOR_VERSION = 2
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@callback
|
@callback
|
||||||
@ -49,13 +76,39 @@ class IslamicPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
if self._async_current_entries():
|
errors = {}
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is not None:
|
||||||
return self.async_show_form(step_id="user")
|
lat: float = user_input[CONF_LOCATION][CONF_LATITUDE]
|
||||||
|
lon: float = user_input[CONF_LOCATION][CONF_LONGITUDE]
|
||||||
|
await self.async_set_unique_id(f"{lat}-{lon}")
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
return self.async_create_entry(title=NAME, data=user_input)
|
if not (errors := await async_validate_location(self.hass, lat, lon)):
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=user_input[CONF_NAME],
|
||||||
|
data={
|
||||||
|
CONF_LATITUDE: lat,
|
||||||
|
CONF_LONGITUDE: lon,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
home_location = {
|
||||||
|
CONF_LATITUDE: self.hass.config.latitude,
|
||||||
|
CONF_LONGITUDE: self.hass.config.longitude,
|
||||||
|
}
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_NAME, default=NAME): TextSelector(),
|
||||||
|
vol.Required(
|
||||||
|
CONF_LOCATION, default=home_location
|
||||||
|
): LocationSelector(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IslamicPrayerOptionsFlowHandler(config_entries.OptionsFlow):
|
class IslamicPrayerOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
@ -9,6 +9,7 @@ from prayer_times_calculator import PrayerTimesCalculator, exceptions
|
|||||||
from requests.exceptions import ConnectionError as ConnError
|
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.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_call_later, async_track_point_in_time
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
@ -36,12 +37,14 @@ class IslamicPrayerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, datetim
|
|||||||
|
|
||||||
def __init__(self, hass: HomeAssistant) -> None:
|
def __init__(self, hass: HomeAssistant) -> None:
|
||||||
"""Initialize the Islamic Prayer client."""
|
"""Initialize the Islamic Prayer client."""
|
||||||
self.event_unsub: CALLBACK_TYPE | None = None
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
)
|
)
|
||||||
|
self.latitude = self.config_entry.data[CONF_LATITUDE]
|
||||||
|
self.longitude = self.config_entry.data[CONF_LONGITUDE]
|
||||||
|
self.event_unsub: CALLBACK_TYPE | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def calc_method(self) -> str:
|
def calc_method(self) -> str:
|
||||||
@ -70,8 +73,8 @@ class IslamicPrayerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, datetim
|
|||||||
def get_new_prayer_times(self) -> dict[str, Any]:
|
def get_new_prayer_times(self) -> dict[str, Any]:
|
||||||
"""Fetch prayer times for today."""
|
"""Fetch prayer times for today."""
|
||||||
calc = PrayerTimesCalculator(
|
calc = PrayerTimesCalculator(
|
||||||
latitude=self.hass.config.latitude,
|
latitude=self.latitude,
|
||||||
longitude=self.hass.config.longitude,
|
longitude=self.longitude,
|
||||||
calculation_method=self.calc_method,
|
calculation_method=self.calc_method,
|
||||||
latitudeAdjustmentMethod=self.lat_adj_method,
|
latitudeAdjustmentMethod=self.lat_adj_method,
|
||||||
midnightMode=self.midnight_mode,
|
midnightMode=self.midnight_mode,
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
|
@ -2,8 +2,16 @@
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
MOCK_USER_INPUT = {
|
||||||
|
CONF_NAME: "Home",
|
||||||
|
CONF_LOCATION: {CONF_LATITUDE: 12.34, CONF_LONGITUDE: 23.45},
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_CONFIG = {CONF_LATITUDE: 12.34, CONF_LONGITUDE: 23.45}
|
||||||
|
|
||||||
PRAYER_TIMES = {
|
PRAYER_TIMES = {
|
||||||
"Fajr": "2020-01-01T06:10:00+00:00",
|
"Fajr": "2020-01-01T06:10:00+00:00",
|
||||||
"Sunrise": "2020-01-01T07:25:00+00:00",
|
"Sunrise": "2020-01-01T07:25:00+00:00",
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
"""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, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
from homeassistant.components import islamic_prayer_times
|
from homeassistant.components import islamic_prayer_times
|
||||||
@ -12,6 +16,8 @@ from homeassistant.components.islamic_prayer_times.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import MOCK_CONFIG, MOCK_USER_INPUT
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||||
@ -25,13 +31,47 @@ async def test_flow_works(hass: HomeAssistant) -> None:
|
|||||||
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.islamic_prayer_times.config_flow.async_validate_location",
|
||||||
|
return_value={},
|
||||||
|
):
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"], user_input={}
|
result["flow_id"], user_input=MOCK_USER_INPUT
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == "Islamic Prayer Times"
|
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"] == data_entry_flow.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"] == data_entry_flow.FlowResultType.FORM
|
||||||
|
assert result["errors"]["base"] == error
|
||||||
|
|
||||||
|
|
||||||
async def test_options(hass: HomeAssistant) -> None:
|
async def test_options(hass: HomeAssistant) -> None:
|
||||||
@ -39,7 +79,7 @@ async def test_options(hass: HomeAssistant) -> None:
|
|||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="Islamic Prayer Times",
|
title="Islamic Prayer Times",
|
||||||
data={},
|
data=MOCK_CONFIG,
|
||||||
options={CONF_CALC_METHOD: "isna"},
|
options={CONF_CALC_METHOD: "isna"},
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
@ -68,14 +108,19 @@ async def test_options(hass: HomeAssistant) -> None:
|
|||||||
async def test_integration_already_configured(hass: HomeAssistant) -> None:
|
async def test_integration_already_configured(hass: HomeAssistant) -> None:
|
||||||
"""Test integration is already configured."""
|
"""Test integration is already configured."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN, data=MOCK_CONFIG, options={}, unique_id="12.34-23.45"
|
||||||
data={},
|
|
||||||
options={},
|
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
islamic_prayer_times.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
islamic_prayer_times.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
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"] == data_entry_flow.FlowResultType.ABORT
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||||
assert result["reason"] == "single_instance_allowed"
|
assert result["reason"] == "already_configured"
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant import config_entries
|
|||||||
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.const import STATE_UNAVAILABLE
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, STATE_UNAVAILABLE
|
||||||
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
|
import homeassistant.util.dt as dt_util
|
||||||
@ -185,3 +185,25 @@ async def test_migrate_unique_id(
|
|||||||
entity_migrated = entity_registry.async_get(entity.entity_id)
|
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||||
assert entity_migrated
|
assert entity_migrated
|
||||||
assert entity_migrated.unique_id == f"{entry.entry_id}-{old_unique_id}"
|
assert entity_migrated.unique_id == f"{entry.entry_id}-{old_unique_id}"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migration_from_1_1_to_1_2(hass: HomeAssistant) -> None:
|
||||||
|
"""Test migrating from version 1.1 to 1.2."""
|
||||||
|
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.data == {
|
||||||
|
CONF_LATITUDE: hass.config.latitude,
|
||||||
|
CONF_LONGITUDE: hass.config.longitude,
|
||||||
|
}
|
||||||
|
assert entry.minor_version == 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user