Skip to content

Commit b605879

Browse files
committed
Improve holidays config form and naming
The holidays library is improving over time, let's make use of their data for a more user-friendly experience.
1 parent 7e24b35 commit b605879

File tree

2 files changed

+100
-19
lines changed

2 files changed

+100
-19
lines changed

homeassistant/components/holiday/config_flow.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from homeassistant.helpers.selector import (
2020
CountrySelector,
2121
CountrySelectorConfig,
22+
SelectOptionDict,
2223
SelectSelector,
2324
SelectSelectorConfig,
2425
SelectSelectorMode,
@@ -30,6 +31,37 @@
3031
SUPPORTED_COUNTRIES = list_supported_countries(include_aliases=False)
3132

3233

34+
def get_optional_provinces(country: str) -> list[Any]:
35+
"""Return the country provinces (territories).
36+
37+
Some territories can have extra or different holidays
38+
from another within the same country.
39+
Some territories can have different names.
40+
"""
41+
province_options: list[Any] = []
42+
43+
if provinces := SUPPORTED_COUNTRIES[country]:
44+
country_data = country_holidays(country, years=dt_util.utcnow().year)
45+
if country_data.subdivisions_aliases and (
46+
subdiv_aliases := country_data.get_subdivision_aliases()
47+
):
48+
# Remember the user choice among all of the aliases available
49+
province_options = [
50+
SelectOptionDict(value=alias, label=alias)
51+
for aliases in subdiv_aliases.values()
52+
for alias in aliases
53+
]
54+
# Let the ISO codes still be a valid choice
55+
province_options += [
56+
SelectOptionDict(value=subdiv, label=subdiv)
57+
for subdiv in country_data.subdivisions
58+
]
59+
else:
60+
province_options = provinces
61+
62+
return province_options
63+
64+
3365
def get_optional_categories(country: str) -> list[str]:
3466
"""Return the country categories.
3567
@@ -45,7 +77,7 @@ def get_optional_categories(country: str) -> list[str]:
4577
def get_options_schema(country: str) -> vol.Schema:
4678
"""Return the options schema."""
4779
schema = {}
48-
if provinces := SUPPORTED_COUNTRIES[country]:
80+
if provinces := get_optional_provinces(country):
4981
schema[vol.Optional(CONF_PROVINCE)] = SelectSelector(
5082
SelectSelectorConfig(
5183
options=provinces,
@@ -64,6 +96,12 @@ def get_options_schema(country: str) -> vol.Schema:
6496
return vol.Schema(schema)
6597

6698

99+
def get_province_code(country: str, province: str) -> str:
100+
"""Return the ISO 3166 code for a given province alias, if any."""
101+
country_data = country_holidays(country, province, years=dt_util.utcnow().year)
102+
return country_data.subdivisions_aliases.get(province, province)
103+
104+
67105
class HolidayConfigFlow(ConfigFlow, domain=DOMAIN):
68106
"""Handle a config flow for Holiday."""
69107

@@ -128,7 +166,11 @@ async def async_step_options(
128166
data = {CONF_COUNTRY: country}
129167
options: dict[str, Any] | None = None
130168
if province := user_input.get(CONF_PROVINCE):
131-
data[CONF_PROVINCE] = province
169+
# Don't store the user alias but the stable ISO 3166 code
170+
province_code = await self.hass.async_add_executor_job(
171+
get_province_code, country, province
172+
)
173+
data[CONF_PROVINCE] = province_code
132174
if categories := user_input.get(CONF_CATEGORIES):
133175
options = {CONF_CATEGORIES: categories}
134176

@@ -165,7 +207,11 @@ async def async_step_reconfigure(
165207
data = {CONF_COUNTRY: country}
166208
options: dict[str, Any] | None = None
167209
if province := user_input.get(CONF_PROVINCE):
168-
data[CONF_PROVINCE] = province
210+
# Don't store the user alias but the stable ISO 3166 code
211+
province_code = await self.hass.async_add_executor_job(
212+
get_province_code, country, province
213+
)
214+
data[CONF_PROVINCE] = province_code
169215
if categories := user_input.get(CONF_CATEGORIES):
170216
options = {CONF_CATEGORIES: categories}
171217

tests/components/holiday/test_config_flow.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
4141
result3 = await hass.config_entries.flow.async_configure(
4242
result2["flow_id"],
4343
{
44-
CONF_PROVINCE: "BW",
44+
CONF_PROVINCE: "Baden-Württemberg",
4545
},
4646
)
4747
await hass.async_block_till_done()
4848

4949
assert result3["type"] is FlowResultType.CREATE_ENTRY
50-
assert result3["title"] == "Germany, BW"
50+
assert result3["title"] == "Germany, Baden-Württemberg"
5151
assert result3["data"] == {
5252
"country": "DE",
5353
"province": "BW",
@@ -172,13 +172,13 @@ async def test_form_babel_unresolved_language(hass: HomeAssistant) -> None:
172172
result = await hass.config_entries.flow.async_configure(
173173
result["flow_id"],
174174
{
175-
CONF_PROVINCE: "BW",
175+
CONF_PROVINCE: "Baden-Württemberg",
176176
},
177177
)
178178
await hass.async_block_till_done()
179179

180180
assert result["type"] is FlowResultType.CREATE_ENTRY
181-
assert result["title"] == "Germany, BW"
181+
assert result["title"] == "Germany, Baden-Württemberg"
182182
assert result["data"] == {
183183
"country": "DE",
184184
"province": "BW",
@@ -219,13 +219,13 @@ async def test_form_babel_replace_dash_with_underscore(hass: HomeAssistant) -> N
219219
result = await hass.config_entries.flow.async_configure(
220220
result["flow_id"],
221221
{
222-
CONF_PROVINCE: "BW",
222+
CONF_PROVINCE: "Baden-Württemberg",
223223
},
224224
)
225225
await hass.async_block_till_done()
226226

227227
assert result["type"] is FlowResultType.CREATE_ENTRY
228-
assert result["title"] == "Germany, BW"
228+
assert result["title"] == "Germany, Baden-Württemberg"
229229
assert result["data"] == {
230230
"country": "DE",
231231
"province": "BW",
@@ -237,7 +237,7 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
237237
"""Test reconfigure flow."""
238238
entry = MockConfigEntry(
239239
domain=DOMAIN,
240-
title="Germany, BW",
240+
title="Germany, Baden-Württemberg",
241241
data={"country": "DE", "province": "BW"},
242242
)
243243
entry.add_to_hass(hass)
@@ -248,15 +248,15 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
248248
result = await hass.config_entries.flow.async_configure(
249249
result["flow_id"],
250250
{
251-
CONF_PROVINCE: "NW",
251+
CONF_PROVINCE: "Nordrhein-Westfalen",
252252
},
253253
)
254254
await hass.async_block_till_done()
255255

256256
assert result["type"] is FlowResultType.ABORT
257257
assert result["reason"] == "reconfigure_successful"
258258
entry = hass.config_entries.async_get_entry(entry.entry_id)
259-
assert entry.title == "Germany, NW"
259+
assert entry.title == "Germany, Nordrhein-Westfalen"
260260
assert entry.data == {"country": "DE", "province": "NW"}
261261

262262

@@ -297,7 +297,7 @@ async def test_reconfigure_incorrect_language(hass: HomeAssistant) -> None:
297297

298298
entry = MockConfigEntry(
299299
domain=DOMAIN,
300-
title="Germany, BW",
300+
title="Germany, Baden-Württemberg",
301301
data={"country": "DE", "province": "BW"},
302302
)
303303
entry.add_to_hass(hass)
@@ -308,15 +308,15 @@ async def test_reconfigure_incorrect_language(hass: HomeAssistant) -> None:
308308
result = await hass.config_entries.flow.async_configure(
309309
result["flow_id"],
310310
{
311-
CONF_PROVINCE: "NW",
311+
CONF_PROVINCE: "Nordrhein-Westfalen",
312312
},
313313
)
314314
await hass.async_block_till_done()
315315

316316
assert result["type"] is FlowResultType.ABORT
317317
assert result["reason"] == "reconfigure_successful"
318318
entry = hass.config_entries.async_get_entry(entry.entry_id)
319-
assert entry.title == "Germany, NW"
319+
assert entry.title == "Germany, Nordrhein-Westfalen"
320320
assert entry.data == {"country": "DE", "province": "NW"}
321321

322322

@@ -325,13 +325,13 @@ async def test_reconfigure_entry_exists(hass: HomeAssistant) -> None:
325325
"""Test reconfigure flow stops if other entry already exist."""
326326
entry = MockConfigEntry(
327327
domain=DOMAIN,
328-
title="Germany, BW",
328+
title="Germany, Baden-Württemberg",
329329
data={"country": "DE", "province": "BW"},
330330
)
331331
entry.add_to_hass(hass)
332332
entry2 = MockConfigEntry(
333333
domain=DOMAIN,
334-
title="Germany, NW",
334+
title="Germany, Nordrhein-Westfalen",
335335
data={"country": "DE", "province": "NW"},
336336
)
337337
entry2.add_to_hass(hass)
@@ -342,15 +342,15 @@ async def test_reconfigure_entry_exists(hass: HomeAssistant) -> None:
342342
result = await hass.config_entries.flow.async_configure(
343343
result["flow_id"],
344344
{
345-
CONF_PROVINCE: "NW",
345+
CONF_PROVINCE: "Nordrhein-Westfalen",
346346
},
347347
)
348348
await hass.async_block_till_done()
349349

350350
assert result["type"] is FlowResultType.ABORT
351351
assert result["reason"] == "already_configured"
352352
entry = hass.config_entries.async_get_entry(entry.entry_id)
353-
assert entry.title == "Germany, BW"
353+
assert entry.title == "Germany, Baden-Württemberg"
354354
assert entry.data == {"country": "DE", "province": "BW"}
355355

356356

@@ -425,6 +425,41 @@ async def test_form_with_options(
425425
assert state.state == STATE_OFF
426426

427427

428+
async def test_form_with_subdivision_aliases(hass: HomeAssistant) -> None:
429+
"""Test the flow with several aliases using the same subdivision code."""
430+
await hass.config.async_set_time_zone("Europe/Paris")
431+
432+
result = await hass.config_entries.flow.async_init(
433+
DOMAIN, context={"source": config_entries.SOURCE_USER}
434+
)
435+
assert result["type"] is FlowResultType.FORM
436+
437+
result = await hass.config_entries.flow.async_configure(
438+
result["flow_id"],
439+
{
440+
CONF_COUNTRY: "FR",
441+
},
442+
)
443+
await hass.async_block_till_done()
444+
445+
assert result["type"] is FlowResultType.FORM
446+
447+
result = await hass.config_entries.flow.async_configure(
448+
result["flow_id"],
449+
{
450+
CONF_PROVINCE: "Alsace",
451+
},
452+
)
453+
await hass.async_block_till_done(wait_background_tasks=True)
454+
455+
assert result["type"] is FlowResultType.CREATE_ENTRY
456+
assert result["title"] == "France, Alsace"
457+
assert result["data"] == {
458+
CONF_COUNTRY: "FR",
459+
CONF_PROVINCE: "GES",
460+
}
461+
462+
428463
@pytest.mark.usefixtures("mock_setup_entry")
429464
async def test_options_abort_no_categories(hass: HomeAssistant) -> None:
430465
"""Test the options flow abort if no categories to select."""

0 commit comments

Comments
 (0)