Home Assistant Unofficial Reference 2024.12.1
config_entry_flow.py
Go to the documentation of this file.
1 """Helpers for data entry flows for config entries."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Awaitable, Callable
6 import logging
7 from typing import TYPE_CHECKING, Any, cast
8 
9 from homeassistant import config_entries
10 from homeassistant.components import onboarding
11 from homeassistant.core import HomeAssistant
12 
13 from .typing import DiscoveryInfoType
14 
15 if TYPE_CHECKING:
16  import asyncio
17 
18  from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
19  from homeassistant.components.dhcp import DhcpServiceInfo
20  from homeassistant.components.ssdp import SsdpServiceInfo
21  from homeassistant.components.zeroconf import ZeroconfServiceInfo
22 
23  from .service_info.mqtt import MqttServiceInfo
24 
25 type DiscoveryFunctionType[_R] = Callable[[HomeAssistant], _R]
26 
27 _LOGGER = logging.getLogger(__name__)
28 
29 
30 class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow):
31  """Handle a discovery config flow."""
32 
33  VERSION = 1
34 
35  def __init__(
36  self,
37  domain: str,
38  title: str,
39  discovery_function: DiscoveryFunctionType[_R],
40  ) -> None:
41  """Initialize the discovery config flow."""
42  self._domain = domain
43  self._title = title
44  self._discovery_function = discovery_function
45 
46  async def async_step_user(
47  self, user_input: dict[str, Any] | None = None
49  """Handle a flow initialized by the user."""
50  if self._async_current_entries():
51  return self.async_abort(reason="single_instance_allowed")
52 
53  await self.async_set_unique_id(self._domain, raise_on_progress=False)
54 
55  return await self.async_step_confirm()
56 
57  async def async_step_confirm(
58  self, user_input: dict[str, Any] | None = None
60  """Confirm setup."""
61  if user_input is None and onboarding.async_is_onboarded(self.hass):
62  self._set_confirm_only()
63  return self.async_show_form(step_id="confirm")
64 
65  if self.source == config_entries.SOURCE_USER:
66  # Get current discovered entries.
67  in_progress = self._async_in_progress()
68 
69  if not (has_devices := bool(in_progress)):
70  has_devices = await cast(
71  "asyncio.Future[bool]", self._discovery_function(self.hass)
72  )
73 
74  if not has_devices:
75  return self.async_abort(reason="no_devices_found")
76 
77  # Cancel the discovered one.
78  for flow in in_progress:
79  self.hass.config_entries.flow.async_abort(flow["flow_id"])
80 
81  if self._async_current_entries():
82  return self.async_abort(reason="single_instance_allowed")
83 
84  return self.async_create_entry(title=self._title, data={})
85 
87  self, discovery_info: DiscoveryInfoType
89  """Handle a flow initialized by discovery."""
90  if self._async_in_progress() or self._async_current_entries():
91  return self.async_abort(reason="single_instance_allowed")
92 
93  await self.async_set_unique_id(self._domain)
94 
95  return await self.async_step_confirm()
96 
98  self, discovery_info: BluetoothServiceInfoBleak
100  """Handle a flow initialized by bluetooth discovery."""
101  if self._async_in_progress() or self._async_current_entries():
102  return self.async_abort(reason="single_instance_allowed")
103 
104  await self.async_set_unique_id(self._domain)
105 
106  return await self.async_step_confirm()
107 
108  async def async_step_dhcp(
109  self, discovery_info: DhcpServiceInfo
111  """Handle a flow initialized by dhcp discovery."""
112  if self._async_in_progress() or self._async_current_entries():
113  return self.async_abort(reason="single_instance_allowed")
114 
115  await self.async_set_unique_id(self._domain)
116 
117  return await self.async_step_confirm()
118 
120  self, discovery_info: ZeroconfServiceInfo
122  """Handle a flow initialized by Homekit discovery."""
123  if self._async_in_progress() or self._async_current_entries():
124  return self.async_abort(reason="single_instance_allowed")
125 
126  await self.async_set_unique_id(self._domain)
127 
128  return await self.async_step_confirm()
129 
130  async def async_step_mqtt(
131  self, discovery_info: MqttServiceInfo
133  """Handle a flow initialized by mqtt discovery."""
134  if self._async_in_progress() or self._async_current_entries():
135  return self.async_abort(reason="single_instance_allowed")
136 
137  await self.async_set_unique_id(self._domain)
138 
139  return await self.async_step_confirm()
140 
142  self, discovery_info: ZeroconfServiceInfo
144  """Handle a flow initialized by Zeroconf discovery."""
145  if self._async_in_progress() or self._async_current_entries():
146  return self.async_abort(reason="single_instance_allowed")
147 
148  await self.async_set_unique_id(self._domain)
149 
150  return await self.async_step_confirm()
151 
152  async def async_step_ssdp(
153  self, discovery_info: SsdpServiceInfo
155  """Handle a flow initialized by Ssdp discovery."""
156  if self._async_in_progress() or self._async_current_entries():
157  return self.async_abort(reason="single_instance_allowed")
158 
159  await self.async_set_unique_id(self._domain)
160 
161  return await self.async_step_confirm()
162 
163  async def async_step_import(
164  self, _: dict[str, Any] | None
166  """Handle a flow initialized by import."""
167  if self._async_current_entries():
168  return self.async_abort(reason="single_instance_allowed")
169 
170  # Cancel other flows.
171  in_progress = self._async_in_progress()
172  for flow in in_progress:
173  self.hass.config_entries.flow.async_abort(flow["flow_id"])
174 
175  return self.async_create_entry(title=self._title, data={})
176 
177 
179  domain: str,
180  title: str,
181  discovery_function: DiscoveryFunctionType[Awaitable[bool] | bool],
182 ) -> None:
183  """Register flow for discovered integrations that not require auth."""
184 
185  class DiscoveryFlow(DiscoveryFlowHandler[Awaitable[bool] | bool]):
186  """Discovery flow handler."""
187 
188  def __init__(self) -> None:
189  super().__init__(domain, title, discovery_function)
190 
191  config_entries.HANDLERS.register(domain)(DiscoveryFlow)
192 
193 
195  """Handle a webhook config flow."""
196 
197  VERSION = 1
198 
199  def __init__(
200  self,
201  domain: str,
202  title: str,
203  description_placeholder: dict,
204  allow_multiple: bool,
205  ) -> None:
206  """Initialize the discovery config flow."""
207  self._domain_domain = domain
208  self._title_title = title
209  self._description_placeholder_description_placeholder = description_placeholder
210  self._allow_multiple_allow_multiple = allow_multiple
211 
212  async def async_step_user(
213  self, user_input: dict[str, Any] | None = None
215  """Handle a user initiated set up flow to create a webhook."""
216  if not self._allow_multiple_allow_multiple and self._async_current_entries_async_current_entries():
217  return self.async_abortasync_abortasync_abort(reason="single_instance_allowed")
218 
219  if user_input is None:
220  return self.async_show_formasync_show_formasync_show_form(step_id="user")
221 
222  # Local import to be sure cloud is loaded and setup
223  # pylint: disable-next=import-outside-toplevel
224  from homeassistant.components.cloud import (
225  async_active_subscription,
226  async_create_cloudhook,
227  async_is_connected,
228  )
229 
230  # Local import to be sure webhook is loaded and setup
231  # pylint: disable-next=import-outside-toplevel
233  async_generate_id,
234  async_generate_url,
235  )
236 
237  webhook_id = async_generate_id()
238 
239  if "cloud" in self.hass.config.components and async_active_subscription(
240  self.hass
241  ):
242  if not async_is_connected(self.hass):
243  return self.async_abortasync_abortasync_abort(reason="cloud_not_connected")
244 
245  webhook_url = await async_create_cloudhook(self.hass, webhook_id)
246  cloudhook = True
247  else:
248  webhook_url = async_generate_url(self.hass, webhook_id)
249  cloudhook = False
250 
251  self._description_placeholder_description_placeholder["webhook_url"] = webhook_url
252 
253  return self.async_create_entryasync_create_entryasync_create_entry(
254  title=self._title_title,
255  data={"webhook_id": webhook_id, "cloudhook": cloudhook},
256  description_placeholders=self._description_placeholder_description_placeholder,
257  )
258 
259 
261  domain: str, title: str, description_placeholder: dict, allow_multiple: bool = False
262 ) -> None:
263  """Register flow for webhook integrations."""
264 
265  class WebhookFlow(WebhookFlowHandler):
266  """Webhook flow handler."""
267 
268  def __init__(self) -> None:
269  super().__init__(domain, title, description_placeholder, allow_multiple)
270 
271  config_entries.HANDLERS.register(domain)(WebhookFlow)
272 
273 
275  hass: HomeAssistant, entry: config_entries.ConfigEntry
276 ) -> None:
277  """Remove a webhook config entry."""
278  if not entry.data.get("cloudhook") or "cloud" not in hass.config.components:
279  return
280 
281  # Local import to be sure cloud is loaded and setup
282  # pylint: disable-next=import-outside-toplevel
283  from homeassistant.components.cloud import async_delete_cloudhook
284 
285  await async_delete_cloudhook(hass, entry.data["webhook_id"])
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)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=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)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
_title
None __init__(self, str domain, str title, dict description_placeholder, bool allow_multiple)
_allow_multiple
_description_placeholder
_domain
config_entries.ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
None async_delete_cloudhook(HomeAssistant hass, str webhook_id)
Definition: __init__.py:217
str async_create_cloudhook(HomeAssistant hass, str webhook_id)
Definition: __init__.py:202
bool async_active_subscription(HomeAssistant hass)
Definition: __init__.py:180
bool async_is_connected(HomeAssistant hass)
Definition: __init__.py:164
str async_generate_url(HomeAssistant hass, str webhook_id, bool allow_internal=True, bool allow_external=True, bool|None allow_ip=None, bool|None prefer_external=True)
Definition: __init__.py:97
config_entries.ConfigFlowResult async_step_discovery(self, DiscoveryInfoType discovery_info)
config_entries.ConfigFlowResult async_step_confirm(self, dict[str, Any]|None user_input=None)
config_entries.ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
config_entries.ConfigFlowResult async_step_bluetooth(self, BluetoothServiceInfoBleak discovery_info)
config_entries.ConfigFlowResult async_step_import(self, dict[str, Any]|None _)
None webhook_async_remove_entry(HomeAssistant hass, config_entries.ConfigEntry entry)
None register_webhook_flow(str domain, str title, dict description_placeholder, bool allow_multiple=False)
config_entries.ConfigFlowResult async_step_ssdp(self, SsdpServiceInfo discovery_info)
None __init__(self, str domain, str title, DiscoveryFunctionType[_R] discovery_function)
config_entries.ConfigFlowResult async_step_mqtt(self, MqttServiceInfo discovery_info)
config_entries.ConfigFlowResult async_step_homekit(self, ZeroconfServiceInfo discovery_info)
config_entries.ConfigFlowResult async_step_dhcp(self, DhcpServiceInfo discovery_info)
None register_discovery_flow(str domain, str title, DiscoveryFunctionType[Awaitable[bool]|bool] discovery_function)
config_entries.ConfigFlowResult async_step_zeroconf(self, ZeroconfServiceInfo discovery_info)