Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow to configure the Nextbus integration."""
2 
3 from collections import Counter
4 import logging
5 
6 from py_nextbus import NextBusClient
7 import voluptuous as vol
8 
9 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
10 from homeassistant.const import CONF_STOP
12  SelectOptionDict,
13  SelectSelector,
14  SelectSelectorConfig,
15  SelectSelectorMode,
16 )
17 
18 from .const import CONF_AGENCY, CONF_ROUTE, DOMAIN
19 from .util import listify
20 
21 _LOGGER = logging.getLogger(__name__)
22 
23 
24 def _dict_to_select_selector(options: dict[str, str]) -> SelectSelector:
25  return SelectSelector(
27  options=sorted(
28  (
29  SelectOptionDict(value=key, label=value)
30  for key, value in options.items()
31  ),
32  key=lambda o: o["label"],
33  ),
34  mode=SelectSelectorMode.DROPDOWN,
35  )
36  )
37 
38 
39 def _get_agency_tags(client: NextBusClient) -> dict[str, str]:
40  return {a["id"]: a["name"] for a in client.agencies()}
41 
42 
43 def _get_route_tags(client: NextBusClient, agency_tag: str) -> dict[str, str]:
44  return {a["id"]: a["title"] for a in client.routes(agency_tag)}
45 
46 
48  client: NextBusClient, agency_tag: str, route_tag: str
49 ) -> dict[str, str]:
50  route_config = client.route_details(route_tag, agency_tag)
51  stop_ids = {a["id"]: a["name"] for a in route_config["stops"]}
52  title_counts = Counter(stop_ids.values())
53 
54  stop_directions: dict[str, str] = {}
55  for direction in listify(route_config["directions"]):
56  if not direction["useForUi"]:
57  continue
58  for stop in direction["stops"]:
59  stop_directions[stop] = direction["name"]
60 
61  # Append directions for stops with shared titles
62  for stop_id, title in stop_ids.items():
63  if title_counts[title] > 1:
64  stop_ids[stop_id] = f"{title} ({stop_directions.get(stop_id, stop_id)})"
65 
66  return stop_ids
67 
68 
69 def _unique_id_from_data(data: dict[str, str]) -> str:
70  return f"{data[CONF_AGENCY]}_{data[CONF_ROUTE]}_{data[CONF_STOP]}"
71 
72 
73 class NextBusFlowHandler(ConfigFlow, domain=DOMAIN):
74  """Handle Nextbus configuration."""
75 
76  VERSION = 1
77 
78  _agency_tags: dict[str, str]
79  _route_tags: dict[str, str]
80  _stop_tags: dict[str, str]
81 
82  def __init__(self) -> None:
83  """Initialize NextBus config flow."""
84  self.data: dict[str, str] = {}
85  self._client_client = NextBusClient()
86 
87  async def async_step_user(
88  self,
89  user_input: dict[str, str] | None = None,
90  ) -> ConfigFlowResult:
91  """Handle a flow initiated by the user."""
92  return await self.async_step_agencyasync_step_agency(user_input)
93 
94  async def async_step_agency(
95  self,
96  user_input: dict[str, str] | None = None,
97  ) -> ConfigFlowResult:
98  """Select agency."""
99  if user_input is not None:
100  self.data[CONF_AGENCY] = user_input[CONF_AGENCY]
101 
102  return await self.async_step_routeasync_step_route()
103 
104  self._agency_tags_agency_tags = await self.hass.async_add_executor_job(
105  _get_agency_tags, self._client_client
106  )
107 
108  return self.async_show_formasync_show_formasync_show_form(
109  step_id="agency",
110  data_schema=vol.Schema(
111  {
112  vol.Required(CONF_AGENCY): _dict_to_select_selector(
113  self._agency_tags_agency_tags
114  ),
115  }
116  ),
117  )
118 
119  async def async_step_route(
120  self,
121  user_input: dict[str, str] | None = None,
122  ) -> ConfigFlowResult:
123  """Select route."""
124  if user_input is not None:
125  self.data[CONF_ROUTE] = user_input[CONF_ROUTE]
126 
127  return await self.async_step_stopasync_step_stop()
128 
129  self._route_tags_route_tags = await self.hass.async_add_executor_job(
130  _get_route_tags, self._client_client, self.data[CONF_AGENCY]
131  )
132 
133  return self.async_show_formasync_show_formasync_show_form(
134  step_id="route",
135  data_schema=vol.Schema(
136  {
137  vol.Required(CONF_ROUTE): _dict_to_select_selector(
138  self._route_tags_route_tags
139  ),
140  }
141  ),
142  )
143 
144  async def async_step_stop(
145  self,
146  user_input: dict[str, str] | None = None,
147  ) -> ConfigFlowResult:
148  """Select stop."""
149 
150  if user_input is not None:
151  self.data[CONF_STOP] = user_input[CONF_STOP]
152 
153  await self.async_set_unique_idasync_set_unique_id(_unique_id_from_data(self.data))
154  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
155 
156  agency_tag = self.data[CONF_AGENCY]
157  route_tag = self.data[CONF_ROUTE]
158  stop_tag = self.data[CONF_STOP]
159 
160  agency_name = self._agency_tags_agency_tags[agency_tag]
161  route_name = self._route_tags_route_tags[route_tag]
162  stop_name = self._stop_tags_stop_tags[stop_tag]
163 
164  return self.async_create_entryasync_create_entryasync_create_entry(
165  title=f"{agency_name} {route_name} {stop_name}",
166  data=self.data,
167  )
168 
169  self._stop_tags_stop_tags = await self.hass.async_add_executor_job(
170  _get_stop_tags,
171  self._client_client,
172  self.data[CONF_AGENCY],
173  self.data[CONF_ROUTE],
174  )
175 
176  return self.async_show_formasync_show_formasync_show_form(
177  step_id="stop",
178  data_schema=vol.Schema(
179  {
180  vol.Required(CONF_STOP): _dict_to_select_selector(self._stop_tags_stop_tags),
181  }
182  ),
183  )
ConfigFlowResult async_step_stop(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:147
ConfigFlowResult async_step_user(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:90
ConfigFlowResult async_step_agency(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:97
ConfigFlowResult async_step_route(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:122
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
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)
_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] _get_route_tags(NextBusClient client, str agency_tag)
Definition: config_flow.py:43
SelectSelector _dict_to_select_selector(dict[str, str] options)
Definition: config_flow.py:24
str _unique_id_from_data(dict[str, str] data)
Definition: config_flow.py:69
dict[str, str] _get_stop_tags(NextBusClient client, str agency_tag, str route_tag)
Definition: config_flow.py:49
dict[str, str] _get_agency_tags(NextBusClient client)
Definition: config_flow.py:39
list[Any] listify(Any maybe_list)
Definition: util.py:6