Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Nina integration."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from pynina import ApiError, Nina
8 import voluptuous as vol
9 
10 from homeassistant.config_entries import (
11  ConfigEntry,
12  ConfigFlow,
13  ConfigFlowResult,
14  OptionsFlow,
15 )
16 from homeassistant.core import callback
17 from homeassistant.helpers import entity_registry as er
18 from homeassistant.helpers.aiohttp_client import async_get_clientsession
20 from homeassistant.helpers.typing import VolDictType
21 
22 from .const import (
23  _LOGGER,
24  CONF_AREA_FILTER,
25  CONF_HEADLINE_FILTER,
26  CONF_MESSAGE_SLOTS,
27  CONF_REGIONS,
28  CONST_REGION_MAPPING,
29  CONST_REGIONS,
30  DOMAIN,
31  NO_MATCH_REGEX,
32 )
33 
34 
35 def swap_key_value(dict_to_sort: dict[str, str]) -> dict[str, str]:
36  """Swap keys and values in dict."""
37  all_region_codes_swaped: dict[str, str] = {}
38 
39  for key, value in dict_to_sort.items():
40  if value not in all_region_codes_swaped:
41  all_region_codes_swaped[value] = key
42  else:
43  for i in range(len(dict_to_sort)):
44  tmp_value: str = f"{value}_{i}"
45  if tmp_value not in all_region_codes_swaped:
46  all_region_codes_swaped[tmp_value] = key
47  break
48 
49  return dict(sorted(all_region_codes_swaped.items(), key=lambda ele: ele[1]))
50 
51 
53  _all_region_codes_sorted: dict[str, str], regions: dict[str, dict[str, Any]]
54 ) -> dict[str, dict[str, Any]]:
55  """Split regions alphabetical."""
56  for index, name in _all_region_codes_sorted.items():
57  for region_name, grouping_letters in CONST_REGION_MAPPING.items():
58  if name[0] in grouping_letters:
59  regions[region_name][index] = name
60  break
61  return regions
62 
63 
65  user_input: dict[str, Any], _all_region_codes_sorted: dict[str, str]
66 ) -> dict[str, Any]:
67  """Prepare the user inputs."""
68  tmp: dict[str, Any] = {}
69 
70  for reg in user_input[CONF_REGIONS]:
71  tmp[_all_region_codes_sorted[reg]] = reg.split("_", 1)[0]
72 
73  compact: dict[str, Any] = {}
74 
75  for key, val in tmp.items():
76  if val in compact:
77  # Abenberg, St + Abenberger Wald
78  compact[val] = f"{compact[val]} + {key}"
79  break
80  compact[val] = key
81 
82  user_input[CONF_REGIONS] = compact
83 
84  return user_input
85 
86 
87 class NinaConfigFlow(ConfigFlow, domain=DOMAIN):
88  """Handle a config flow for NINA."""
89 
90  VERSION: int = 1
91 
92  def __init__(self) -> None:
93  """Initialize."""
94  super().__init__()
95  self._all_region_codes_sorted_all_region_codes_sorted: dict[str, str] = {}
96  self.regionsregions: dict[str, dict[str, Any]] = {}
97 
98  for name in CONST_REGIONS:
99  self.regionsregions[name] = {}
100 
101  async def async_step_user(
102  self,
103  user_input: dict[str, Any] | None = None,
104  ) -> ConfigFlowResult:
105  """Handle the initial step."""
106  errors: dict[str, Any] = {}
107 
108  if not self._all_region_codes_sorted_all_region_codes_sorted:
109  nina: Nina = Nina(async_get_clientsession(self.hass))
110 
111  try:
112  self._all_region_codes_sorted_all_region_codes_sorted = swap_key_value(
113  await nina.getAllRegionalCodes()
114  )
115  except ApiError:
116  errors["base"] = "cannot_connect"
117  except Exception as err: # noqa: BLE001
118  _LOGGER.exception("Unexpected exception: %s", err)
119  errors["base"] = "unknown"
120 
121  self.regionsregions = split_regions(self._all_region_codes_sorted_all_region_codes_sorted, self.regionsregions)
122 
123  if user_input is not None and not errors:
124  user_input[CONF_REGIONS] = []
125 
126  for group in CONST_REGIONS:
127  if group_input := user_input.get(group):
128  user_input[CONF_REGIONS] += group_input
129 
130  if not user_input[CONF_HEADLINE_FILTER]:
131  user_input[CONF_HEADLINE_FILTER] = NO_MATCH_REGEX
132 
133  if user_input[CONF_REGIONS]:
134  return self.async_create_entryasync_create_entryasync_create_entry(
135  title="NINA",
136  data=prepare_user_input(user_input, self._all_region_codes_sorted_all_region_codes_sorted),
137  )
138 
139  errors["base"] = "no_selection"
140 
141  regions_schema: VolDictType = {
142  vol.Optional(region): cv.multi_select(self.regionsregions[region])
143  for region in CONST_REGIONS
144  }
145 
146  return self.async_show_formasync_show_formasync_show_form(
147  step_id="user",
148  data_schema=vol.Schema(
149  {
150  **regions_schema,
151  vol.Required(CONF_MESSAGE_SLOTS, default=5): vol.All(
152  int, vol.Range(min=1, max=20)
153  ),
154  vol.Optional(CONF_HEADLINE_FILTER, default=""): cv.string,
155  }
156  ),
157  errors=errors,
158  )
159 
160  @staticmethod
161  @callback
163  config_entry: ConfigEntry,
164  ) -> OptionsFlowHandler:
165  """Get the options flow for this handler."""
166  return OptionsFlowHandler(config_entry)
167 
168 
170  """Handle a option flow for nut."""
171 
172  def __init__(self, config_entry: ConfigEntry) -> None:
173  """Initialize options flow."""
174  self.datadata = dict(config_entry.data)
175 
176  self._all_region_codes_sorted_all_region_codes_sorted: dict[str, str] = {}
177  self.regionsregions: dict[str, dict[str, Any]] = {}
178 
179  for name in CONST_REGIONS:
180  self.regionsregions[name] = {}
181  if name not in self.datadata:
182  self.datadata[name] = []
183 
184  async def async_step_init(
185  self, user_input: dict[str, Any] | None = None
186  ) -> ConfigFlowResult:
187  """Handle options flow."""
188  errors: dict[str, str] = {}
189 
190  if not self._all_region_codes_sorted_all_region_codes_sorted:
191  nina: Nina = Nina(async_get_clientsession(self.hass))
192 
193  try:
194  self._all_region_codes_sorted_all_region_codes_sorted = swap_key_value(
195  await nina.getAllRegionalCodes()
196  )
197  except ApiError:
198  errors["base"] = "cannot_connect"
199  except Exception as err: # noqa: BLE001
200  _LOGGER.exception("Unexpected exception: %s", err)
201  errors["base"] = "unknown"
202 
203  self.regionsregions = split_regions(self._all_region_codes_sorted_all_region_codes_sorted, self.regionsregions)
204 
205  if user_input is not None and not errors:
206  user_input[CONF_REGIONS] = []
207 
208  for group in CONST_REGIONS:
209  if group_input := user_input.get(group):
210  user_input[CONF_REGIONS] += group_input
211 
212  if user_input[CONF_REGIONS]:
213  user_input = prepare_user_input(
214  user_input, self._all_region_codes_sorted_all_region_codes_sorted
215  )
216 
217  entity_registry = er.async_get(self.hass)
218 
219  entries = er.async_entries_for_config_entry(
220  entity_registry, self.config_entryconfig_entryconfig_entry.entry_id
221  )
222 
223  removed_entities_slots = [
224  f"{region}-{slot_id}"
225  for region in self.datadata[CONF_REGIONS]
226  for slot_id in range(self.datadata[CONF_MESSAGE_SLOTS] + 1)
227  if slot_id > user_input[CONF_MESSAGE_SLOTS]
228  ]
229 
230  removed_entites_area = [
231  f"{cfg_region}-{slot_id}"
232  for slot_id in range(1, self.datadata[CONF_MESSAGE_SLOTS] + 1)
233  for cfg_region in self.datadata[CONF_REGIONS]
234  if cfg_region not in user_input[CONF_REGIONS]
235  ]
236 
237  for entry in entries:
238  for entity_uid in list(
239  set(removed_entities_slots + removed_entites_area)
240  ):
241  if entry.unique_id == entity_uid:
242  entity_registry.async_remove(entry.entity_id)
243 
244  self.hass.config_entries.async_update_entry(
245  self.config_entryconfig_entryconfig_entry, data=user_input
246  )
247 
248  return self.async_create_entryasync_create_entry(title="", data={})
249 
250  errors["base"] = "no_selection"
251 
252  schema: VolDictType = {
253  **{
254  vol.Optional(region, default=self.datadata[region]): cv.multi_select(
255  self.regionsregions[region]
256  )
257  for region in CONST_REGIONS
258  },
259  vol.Required(
260  CONF_MESSAGE_SLOTS,
261  default=self.datadata[CONF_MESSAGE_SLOTS],
262  ): vol.All(int, vol.Range(min=1, max=20)),
263  vol.Optional(
264  CONF_HEADLINE_FILTER,
265  default=self.datadata[CONF_HEADLINE_FILTER],
266  ): cv.string,
267  vol.Optional(
268  CONF_AREA_FILTER,
269  default=self.datadata[CONF_AREA_FILTER],
270  ): cv.string,
271  }
272 
273  return self.async_show_formasync_show_form(
274  step_id="init",
275  data_schema=vol.Schema(schema),
276  errors=errors,
277  )
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:164
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:104
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:186
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
None config_entry(self, ConfigEntry value)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
dict[str, str] swap_key_value(dict[str, str] dict_to_sort)
Definition: config_flow.py:35
dict[str, dict[str, Any]] split_regions(dict[str, str] _all_region_codes_sorted, dict[str, dict[str, Any]] regions)
Definition: config_flow.py:54
dict[str, Any] prepare_user_input(dict[str, Any] user_input, dict[str, str] _all_region_codes_sorted)
Definition: config_flow.py:66
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)