1 """Manage config entries in Home Assistant."""
3 from __future__
import annotations
6 from collections
import UserDict, defaultdict
7 from collections.abc
import (
16 from contextvars
import ContextVar
17 from copy
import deepcopy
18 from datetime
import datetime
19 from enum
import Enum, StrEnum
21 from functools
import cache
23 from random
import randint
24 from types
import MappingProxyType
25 from typing
import TYPE_CHECKING, Any, Generic, Self, cast
27 from async_interrupt
import interrupt
28 from propcache
import cached_property
29 from typing_extensions
import TypeVar
30 import voluptuous
as vol
32 from .
import data_entry_flow, loader
33 from .components
import persistent_notification
36 EVENT_HOMEASSISTANT_STARTED,
37 EVENT_HOMEASSISTANT_STOP,
42 DOMAIN
as HOMEASSISTANT_DOMAIN,
50 from .data_entry_flow
import FLOW_NOT_COMPLETE_STEPS, FlowContext, FlowResult
51 from .exceptions
import (
52 ConfigEntryAuthFailed,
57 from .helpers
import (
58 device_registry
as dr,
59 entity_registry
as er,
63 from .helpers.debounce
import Debouncer
64 from .helpers.discovery_flow
import DiscoveryKey
65 from .helpers.dispatcher
import SignalType, async_dispatcher_send_internal
66 from .helpers.event
import (
67 RANDOM_MICROSECOND_MAX,
68 RANDOM_MICROSECOND_MIN,
71 from .helpers.frame
import ReportBehavior, report_usage
72 from .helpers.json
import json_bytes, json_bytes_sorted, json_fragment
73 from .helpers.typing
import UNDEFINED, ConfigType, DiscoveryInfoType, UndefinedType
74 from .loader
import async_suggest_report_issue
79 async_process_deps_reqs,
80 async_setup_component,
83 from .util
import ulid
as ulid_util
84 from .util.async_
import create_eager_task
85 from .util.decorator
import Registry
86 from .util.dt
import utc_from_timestamp, utcnow
87 from .util.enum
import try_parse_enum
90 from .components.bluetooth
import BluetoothServiceInfoBleak
91 from .components.dhcp
import DhcpServiceInfo
92 from .components.ssdp
import SsdpServiceInfo
93 from .components.usb
import UsbServiceInfo
94 from .components.zeroconf
import ZeroconfServiceInfo
95 from .helpers.service_info.hassio
import HassioServiceInfo
96 from .helpers.service_info.mqtt
import MqttServiceInfo
99 _LOGGER = logging.getLogger(__name__)
101 SOURCE_BLUETOOTH =
"bluetooth"
103 SOURCE_DISCOVERY =
"discovery"
104 SOURCE_HARDWARE =
"hardware"
105 SOURCE_HASSIO =
"hassio"
106 SOURCE_HOMEKIT =
"homekit"
107 SOURCE_IMPORT =
"import"
108 SOURCE_INTEGRATION_DISCOVERY =
"integration_discovery"
111 SOURCE_SYSTEM =
"system"
114 SOURCE_ZEROCONF =
"zeroconf"
119 SOURCE_IGNORE =
"ignore"
122 SOURCE_REAUTH =
"reauth"
125 SOURCE_RECONFIGURE =
"reconfigure"
127 HANDLERS: Registry[str, type[ConfigFlow]] =
Registry()
129 STORAGE_KEY =
"core.config_entries"
131 STORAGE_VERSION_MINOR = 4
135 DISCOVERY_COOLDOWN = 1
137 ISSUE_UNIQUE_ID_COLLISION =
"config_entry_unique_id_collision"
138 UNIQUE_ID_COLLISION_TITLE_LIMIT = 5
140 _DataT = TypeVar(
"_DataT", default=Any)
144 """Config entry state."""
146 LOADED =
"loaded",
True
147 """The config entry has been set up successfully"""
148 SETUP_ERROR =
"setup_error",
True
149 """There was an error while trying to set up this config entry"""
150 MIGRATION_ERROR =
"migration_error",
False
151 """There was an error while trying to migrate the config entry to a new version"""
152 SETUP_RETRY =
"setup_retry",
True
153 """The config entry was not ready to be set up yet, but might be later"""
154 NOT_LOADED =
"not_loaded",
True
155 """The config entry has not been loaded"""
156 FAILED_UNLOAD =
"failed_unload",
False
157 """An error occurred when trying to unload the entry"""
158 SETUP_IN_PROGRESS =
"setup_in_progress",
False
159 """The config entry is setting up."""
163 def __new__(cls, value: str, recoverable: bool) -> Self:
164 """Create new ConfigEntryState."""
165 obj = object.__new__(cls)
167 obj._recoverable = recoverable
172 """Get if the state is recoverable.
174 If the entry state is recoverable, unloads
175 and reloads are allowed.
177 return self._recoverable
180 DEFAULT_DISCOVERY_UNIQUE_ID =
"default_discovery_unique_id"
181 DISCOVERY_NOTIFICATION_ID =
"config_entry_discovery"
182 DISCOVERY_SOURCES = {
190 SOURCE_INTEGRATION_DISCOVERY,
198 RECONFIGURE_NOTIFICATION_ID =
"config_entry_reconfigure"
200 EVENT_FLOW_DISCOVERED =
"config_entry_discovered"
202 SIGNAL_CONFIG_ENTRY_CHANGED = SignalType[
"ConfigEntryChange",
"ConfigEntry"](
203 "config_entry_changed"
209 discovery_domain: str,
210 ) -> SignalType[ConfigEntry]:
212 return SignalType(f
"{discovery_domain}_discovered_config_entry_removed")
215 NO_RESET_TRIES_STATES = {
216 ConfigEntryState.SETUP_RETRY,
217 ConfigEntryState.SETUP_IN_PROGRESS,
222 """What was changed in a config entry."""
230 """What disabled a config entry."""
236 DISABLED_USER = ConfigEntryDisabler.USER.value
238 RELOAD_AFTER_UPDATE_DELAY = 30
243 CONN_CLASS_CLOUD_PUSH =
"cloud_push"
244 CONN_CLASS_CLOUD_POLL =
"cloud_poll"
245 CONN_CLASS_LOCAL_PUSH =
"local_push"
246 CONN_CLASS_LOCAL_POLL =
"local_poll"
247 CONN_CLASS_ASSUMED =
"assumed"
248 CONN_CLASS_UNKNOWN =
"unknown"
252 """Error while configuring an account."""
255 class UnknownEntry(ConfigError):
256 """Unknown entry specified."""
260 """Raised when a config entry operation is not allowed."""
263 type UpdateListenerType = Callable[
264 [HomeAssistant, ConfigEntry], Coroutine[Any, Any,
None]
270 "error_reason_translation_key",
271 "error_reason_translation_placeholders",
273 FROZEN_CONFIG_ENTRY_ATTRS = {
"entry_id",
"domain", *STATE_KEYS}
274 UPDATE_ENTRY_CONFIG_ENTRY_ATTRS = {
279 "pref_disable_new_entities",
280 "pref_disable_polling",
287 """Typed context dict for config flow."""
289 alternative_domain: str
290 configuration_url: str
292 discovery_key: DiscoveryKey
294 title_placeholders: Mapping[str, str]
295 unique_id: str |
None
299 """Typed result dict for config flow."""
302 options: Mapping[str, Any]
306 def _validate_item(*, disabled_by: ConfigEntryDisabler | Any |
None =
None) ->
None:
307 """Validate config entry item."""
310 if disabled_by
is not None and not isinstance(disabled_by, ConfigEntryDisabler):
312 f
"disabled_by must be a ConfigEntryDisabler value, got {disabled_by}"
317 """Hold a configuration entry."""
322 data: MappingProxyType[str, Any]
324 options: MappingProxyType[str, Any]
325 unique_id: str |
None
326 state: ConfigEntryState
328 error_reason_translation_key: str |
None
329 error_reason_translation_placeholders: dict[str, Any] |
None
330 pref_disable_new_entities: bool
331 pref_disable_polling: bool
335 disabled_by: ConfigEntryDisabler |
None
336 supports_unload: bool |
None
337 supports_remove_device: bool |
None
338 _supports_options: bool |
None
339 _supports_reconfigure: bool |
None
340 update_listeners: list[UpdateListenerType]
341 _async_cancel_retry_setup: Callable[[], Any] |
None
342 _on_unload: list[Callable[[], Coroutine[Any, Any,
None] |
None]] |
None
343 setup_lock: asyncio.Lock
344 _reauth_lock: asyncio.Lock
345 _tasks: set[asyncio.Future[Any]]
346 _background_tasks: set[asyncio.Future[Any]]
350 modified_at: datetime
351 discovery_keys: MappingProxyType[str, tuple[DiscoveryKey, ...]]
356 created_at: datetime |
None =
None,
357 data: Mapping[str, Any],
358 disabled_by: ConfigEntryDisabler |
None =
None,
359 discovery_keys: MappingProxyType[str, tuple[DiscoveryKey, ...]],
361 entry_id: str |
None =
None,
363 modified_at: datetime |
None =
None,
364 options: Mapping[str, Any] |
None,
365 pref_disable_new_entities: bool |
None =
None,
366 pref_disable_polling: bool |
None =
None,
368 state: ConfigEntryState = ConfigEntryState.NOT_LOADED,
370 unique_id: str |
None,
373 """Initialize a config entry."""
374 _setter = object.__setattr__
376 _setter(self,
"entry_id", entry_id
or ulid_util.ulid_now())
379 _setter(self,
"version", version)
380 _setter(self,
"minor_version", minor_version)
383 _setter(self,
"domain", domain)
386 _setter(self,
"title", title)
389 _setter(self,
"data", MappingProxyType(data))
392 _setter(self,
"options", MappingProxyType(options
or {}))
395 if pref_disable_new_entities
is None:
396 pref_disable_new_entities =
False
398 _setter(self,
"pref_disable_new_entities", pref_disable_new_entities)
400 if pref_disable_polling
is None:
401 pref_disable_polling =
False
403 _setter(self,
"pref_disable_polling", pref_disable_polling)
406 _setter(self,
"source", source)
409 _setter(self,
"state", state)
412 _setter(self,
"unique_id", unique_id)
416 _setter(self,
"disabled_by", disabled_by)
419 _setter(self,
"supports_unload",
None)
422 _setter(self,
"supports_remove_device",
None)
425 _setter(self,
"_supports_options",
None)
428 _setter(self,
"_supports_reconfigure",
None)
431 _setter(self,
"update_listeners", [])
434 _setter(self,
"reason",
None)
435 _setter(self,
"error_reason_translation_key",
None)
436 _setter(self,
"error_reason_translation_placeholders",
None)
439 _setter(self,
"_async_cancel_retry_setup",
None)
442 _setter(self,
"_on_unload",
None)
445 _setter(self,
"setup_lock", asyncio.Lock())
447 _setter(self,
"_reauth_lock", asyncio.Lock())
449 _setter(self,
"_tasks", set())
450 _setter(self,
"_background_tasks", set())
452 _setter(self,
"_integration_for_domain",
None)
453 _setter(self,
"_tries", 0)
454 _setter(self,
"created_at", created_at
or utcnow())
455 _setter(self,
"modified_at", modified_at
or utcnow())
456 _setter(self,
"discovery_keys", discovery_keys)
459 """Representation of ConfigEntry."""
461 f
"<ConfigEntry entry_id={self.entry_id} version={self.version} domain={self.domain} "
462 f
"title={self.title} state={self.state} unique_id={self.unique_id}>"
466 """Set an attribute."""
467 if key
in UPDATE_ENTRY_CONFIG_ENTRY_ATTRS:
468 raise AttributeError(
469 f
"{key} cannot be changed directly, use async_update_entry instead"
471 if key
in FROZEN_CONFIG_ENTRY_ATTRS:
472 raise AttributeError(f
"{key} cannot be changed")
480 """Return if entry supports config options."""
481 if self._supports_options
is None and (handler := HANDLERS.get(self.
domaindomain)):
484 self,
"_supports_options", handler.async_supports_options_flow(self)
486 return self._supports_options
or False
490 """Return if entry supports reconfigure step."""
491 if self._supports_reconfigure
is None and (
492 handler := HANDLERS.get(self.
domaindomain)
497 "_supports_reconfigure",
498 hasattr(handler,
"async_step_reconfigure"),
500 return self._supports_reconfigure
or False
503 """Clear cached properties that are included in as_json_fragment."""
504 self.__dict__.pop(
"as_json_fragment",
None)
508 """Return JSON fragment of a config entry that is used for the API."""
510 "created_at": self.created_at.timestamp(),
511 "entry_id": self.entry_id,
512 "domain": self.
domaindomain,
513 "modified_at": self.modified_at.timestamp(),
515 "source": self.
sourcesource,
516 "state": self.
statestate.value,
521 "pref_disable_new_entities": self.pref_disable_new_entities,
522 "pref_disable_polling": self.pref_disable_polling,
523 "disabled_by": self.disabled_by,
524 "reason": self.reason,
525 "error_reason_translation_key": self.error_reason_translation_key,
526 "error_reason_translation_placeholders": self.error_reason_translation_placeholders,
531 """Clear cached properties that are included in as_storage_fragment."""
532 self.__dict__.pop(
"as_storage_fragment",
None)
536 """Return a storage fragment for this entry."""
545 """Set up an entry."""
546 if self.
sourcesource == SOURCE_IGNORE
or self.disabled_by:
549 current_entry.set(self)
553 current_entry.set(
None)
560 """Set up an entry, with current_entry set."""
562 integration = await loader.async_get_integration(hass, self.
domaindomain)
566 if domain_is_integration := self.
domaindomain == integration.domain:
567 if self.
statestate
in (
568 ConfigEntryState.LOADED,
569 ConfigEntryState.SETUP_IN_PROGRESS,
572 f
"The config entry {self.title} ({self.domain}) with entry_id"
573 f
" {self.entry_id} cannot be set up because it is already loaded "
574 f
"in the {self.state} state"
576 if not self.setup_lock.locked():
578 f
"The config entry {self.title} ({self.domain}) with entry_id"
579 f
" {self.entry_id} cannot be set up because it does not hold "
582 self.
_async_set_state_async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS,
None)
591 component = await integration.async_get_component()
592 except ImportError
as err:
594 "Error importing integration %s to set up %s configuration entry: %s",
599 if domain_is_integration:
601 hass, ConfigEntryState.SETUP_ERROR,
"Import error"
605 if domain_is_integration:
607 await integration.async_get_platform(
"config_flow")
608 except ImportError
as err:
611 "Error importing platform config_flow from integration %s to"
612 " set up %s configuration entry: %s"
619 hass, ConfigEntryState.SETUP_ERROR,
"Import error"
625 self.
_async_set_state_async_set_state(hass, ConfigEntryState.MIGRATION_ERROR,
None)
628 setup_phase = SetupPhases.CONFIG_ENTRY_SETUP
630 setup_phase = SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP
633 error_reason_translation_key =
None
634 error_reason_translation_placeholders =
None
638 hass, integration=self.
domaindomain, group=self.entry_id, phase=setup_phase
640 result = await component.async_setup_entry(hass, self)
642 if not isinstance(result, bool):
644 "%s.async_setup_entry did not return boolean", integration.domain
647 except ConfigEntryError
as exc:
648 error_reason =
str(exc)
or "Unknown fatal config entry error"
649 error_reason_translation_key = exc.translation_key
650 error_reason_translation_placeholders = exc.translation_placeholders
652 "Error setting up entry %s for %s: %s",
659 except ConfigEntryAuthFailed
as exc:
661 auth_base_message =
"could not authenticate"
662 error_reason = message
or auth_base_message
663 error_reason_translation_key = exc.translation_key
664 error_reason_translation_placeholders = exc.translation_placeholders
666 f
"{auth_base_message}: {message}" if message
else auth_base_message
669 "Config entry '%s' for %s integration %s",
677 except ConfigEntryNotReady
as exc:
679 error_reason_translation_key = exc.translation_key
680 error_reason_translation_placeholders = exc.translation_placeholders
683 ConfigEntryState.SETUP_RETRY,
685 error_reason_translation_key,
686 error_reason_translation_placeholders,
688 wait_time = 2 **
min(self.
_tries_tries, 4) * 5 + (
689 randint(RANDOM_MICROSECOND_MIN, RANDOM_MICROSECOND_MAX) / 1000000
692 ready_message = f
"ready yet: {message}" if message
else "ready yet"
695 "Config entry '%s' for %s integration not %s; Retrying in %d"
704 if hass.state
is CoreState.running:
710 job_type=HassJobType.Callback,
711 cancel_on_shutdown=
True,
716 EVENT_HOMEASSISTANT_STARTED,
723 except (asyncio.CancelledError, SystemExit, Exception):
725 "Error setting up entry %s for %s", self.title, integration.domain
739 if not domain_is_integration:
749 ConfigEntryState.SETUP_ERROR,
751 error_reason_translation_key,
752 error_reason_translation_placeholders,
757 """Schedule setup again.
759 This method is a callback to ensure that _async_cancel_retry_setup
760 is unset as soon as its callback is called.
765 if not hass.is_stopping:
766 hass.async_create_background_task(
768 f
"config entry retry {self.domain} {self.title}",
775 """Set up while holding the setup lock."""
776 async
with self.setup_lock:
777 if self.
statestate
is ConfigEntryState.LOADED:
782 "Not setting up %s (%s %s) again, already loaded",
788 await self.
async_setupasync_setup(hass, integration=integration)
792 """Call when Home Assistant is stopping."""
797 """Cancel retry setup."""
807 Returns if unload is possible and was successful.
809 if self.
sourcesource == SOURCE_IGNORE:
810 self.
_async_set_state_async_set_state(hass, ConfigEntryState.NOT_LOADED,
None)
813 if self.
statestate == ConfigEntryState.NOT_LOADED:
818 integration = await loader.async_get_integration(hass, self.
domaindomain)
824 self.
_async_set_state_async_set_state(hass, ConfigEntryState.NOT_LOADED,
None)
827 component = await integration.async_get_component()
829 if domain_is_integration := self.
domaindomain == integration.domain:
830 if not self.setup_lock.locked():
832 f
"The config entry {self.title} ({self.domain}) with entry_id"
833 f
" {self.entry_id} cannot be unloaded because it does not hold "
837 if not self.
statestate.recoverable:
840 if self.
statestate
is not ConfigEntryState.LOADED:
842 self.
_async_set_state_async_set_state(hass, ConfigEntryState.NOT_LOADED,
None)
845 supports_unload = hasattr(component,
"async_unload_entry")
847 if not supports_unload:
848 if domain_is_integration:
850 hass, ConfigEntryState.FAILED_UNLOAD,
"Unload not supported"
855 result = await component.async_unload_entry(hass, self)
857 assert isinstance(result, bool)
860 if domain_is_integration
and result:
862 if hasattr(self,
"runtime_data"):
863 object.__delattr__(self,
"runtime_data")
865 self.
_async_set_state_async_set_state(hass, ConfigEntryState.NOT_LOADED,
None)
867 except Exception
as exc:
869 "Error unloading entry %s for %s", self.title, integration.domain
871 if domain_is_integration:
873 hass, ConfigEntryState.FAILED_UNLOAD,
str(exc)
or "Unknown error"
879 """Invoke remove callback on component."""
880 old_modified_at = self.modified_at
881 object.__setattr__(self,
"modified_at",
utcnow())
885 if self.
sourcesource == SOURCE_IGNORE:
888 if not self.setup_lock.locked():
890 f
"The config entry {self.title} ({self.domain}) with entry_id"
891 f
" {self.entry_id} cannot be removed because it does not hold "
897 integration = await loader.async_get_integration(hass, self.
domaindomain)
905 component = await integration.async_get_component()
906 if not hasattr(component,
"async_remove_entry"):
909 await component.async_remove_entry(hass, self)
912 "Error calling entry remove callback %s for %s",
917 object.__setattr__(self,
"modified_at", old_modified_at)
923 state: ConfigEntryState,
925 error_reason_translation_key: str |
None =
None,
926 error_reason_translation_placeholders: dict[str, str] |
None =
None,
928 """Set the state of the config entry."""
929 if state
not in NO_RESET_TRIES_STATES:
931 _setter = object.__setattr__
932 _setter(self,
"state", state)
933 _setter(self,
"reason", reason)
934 _setter(self,
"error_reason_translation_key", error_reason_translation_key)
937 "error_reason_translation_placeholders",
938 error_reason_translation_placeholders,
944 async_dispatcher_send_internal(
945 hass, SIGNAL_CONFIG_ENTRY_CHANGED, ConfigEntryChange.UPDATED, self
951 Returns True if config entry is up-to-date or has been migrated.
953 if (handler := HANDLERS.get(self.
domaindomain))
is None:
955 "Flow handler not found for entry %s for %s", self.title, self.
domaindomain
961 while isinstance(handler, functools.partial):
962 handler = handler.func
964 same_major_version = self.
versionversion == handler.VERSION
965 if same_major_version
and self.
minor_versionminor_version == handler.MINOR_VERSION:
969 integration = await loader.async_get_integration(hass, self.
domaindomain)
970 component = await integration.async_get_component()
971 supports_migrate = hasattr(component,
"async_migrate_entry")
972 if not supports_migrate:
973 if same_major_version:
976 "Migration handler not found for entry %s for %s",
983 result = await component.async_migrate_entry(hass, self)
984 if not isinstance(result, bool):
986 "%s.async_migrate_entry did not return boolean", self.
domaindomain
990 hass.config_entries._async_schedule_save()
993 "Error migrating entry %s for %s", self.title, self.
domaindomain
999 """Listen for when entry is updated.
1001 Returns function to unlisten.
1003 self.update_listeners.append(listener)
1004 return lambda: self.update_listeners.
remove(listener)
1007 """Return dictionary version of this entry."""
1009 "created_at": self.created_at.isoformat(),
1010 "data":
dict(self.data),
1011 "discovery_keys":
dict(self.discovery_keys),
1012 "disabled_by": self.disabled_by,
1013 "domain": self.
domaindomain,
1014 "entry_id": self.entry_id,
1016 "modified_at": self.modified_at.isoformat(),
1017 "options":
dict(self.options),
1018 "pref_disable_new_entities": self.pref_disable_new_entities,
1019 "pref_disable_polling": self.pref_disable_polling,
1020 "source": self.
sourcesource,
1021 "title": self.title,
1022 "unique_id": self.unique_id,
1023 "version": self.
versionversion,
1028 self, func: Callable[[], Coroutine[Any, Any,
None] |
None]
1030 """Add a function to call when config entry is unloaded."""
1036 """Process the on_unload callbacks and wait for pending tasks."""
1040 self.async_create_task(hass, job, eager_start=
True)
1042 if not self._tasks
and not self._background_tasks:
1045 cancel_message = f
"Config entry {self.title} with {self.domain} unloading"
1046 for task
in self._background_tasks:
1047 task.cancel(cancel_message)
1049 _, pending = await asyncio.wait(
1050 [*self._tasks, *self._background_tasks], timeout=10
1053 for task
in pending:
1055 "Unloading %s (%s) config entry. Task %s did not complete in time",
1064 hass: HomeAssistant,
1065 context: ConfigFlowContext |
None =
None,
1066 data: dict[str, Any] |
None =
None,
1068 """Start a reauth flow."""
1074 hass.async_create_task(
1076 f
"config entry reauth {self.title} {self.domain} {self.entry_id}",
1082 hass: HomeAssistant,
1083 context: ConfigFlowContext |
None =
None,
1084 data: dict[str, Any] |
None =
None,
1086 """Start a reauth flow."""
1087 async
with self._reauth_lock:
1093 result = await hass.config_entries.flow.async_init(
1096 source=SOURCE_REAUTH,
1097 entry_id=self.entry_id,
1098 title_placeholders={
"name": self.title},
1099 unique_id=self.unique_id,
1102 data=self.data | (data
or {}),
1104 if result[
"type"]
not in FLOW_NOT_COMPLETE_STEPS:
1108 issue_id = f
"config_entry_reauth_{self.domain}_{self.entry_id}"
1109 ir.async_create_issue(
1111 HOMEASSISTANT_DOMAIN,
1113 data={
"flow_id": result[
"flow_id"]},
1115 issue_domain=self.
domaindomain,
1116 severity=ir.IssueSeverity.ERROR,
1117 translation_key=
"config_entry_reauth",
1118 translation_placeholders={
"name": self.title},
1123 self, hass: HomeAssistant, sources: set[str]
1124 ) -> Generator[ConfigFlowResult]:
1125 """Get any active flows of certain sources for this entry."""
1128 for flow
in hass.config_entries.flow.async_progress_by_handler(
1130 match_context={
"entry_id": self.entry_id},
1131 include_uninitialized=
True,
1133 if flow[
"context"].
get(
"source")
in sources
1137 def async_create_task[_R](
1139 hass: HomeAssistant,
1140 target: Coroutine[Any, Any, _R],
1141 name: str |
None =
None,
1142 eager_start: bool =
True,
1143 ) -> asyncio.Task[_R]:
1144 """Create a task from within the event loop.
1146 This method must be run in the event loop.
1148 target: target to call.
1150 task = hass.async_create_task_internal(
1151 target, f
"{name} {self.title} {self.domain} {self.entry_id}", eager_start
1153 if eager_start
and task.done():
1155 self._tasks.
add(task)
1156 task.add_done_callback(self._tasks.remove)
1161 def async_create_background_task[_R](
1163 hass: HomeAssistant,
1164 target: Coroutine[Any, Any, _R],
1166 eager_start: bool =
True,
1167 ) -> asyncio.Task[_R]:
1168 """Create a background task tied to the config entry lifecycle.
1170 Background tasks are automatically canceled when config entry is unloaded.
1172 A background task is different from a normal task:
1174 - Will not block startup
1175 - Will be automatically cancelled on shutdown
1176 - Calls to async_block_till_done will not wait for completion
1178 This method must be run in the event loop.
1180 task = hass.async_create_background_task(target, name, eager_start)
1183 self._background_tasks.
add(task)
1184 task.add_done_callback(self._background_tasks.remove)
1188 current_entry: ContextVar[ConfigEntry |
None] = ContextVar(
1189 "current_entry", default=
None
1194 """Error to indicate that a flow has been cancelled."""
1198 """Report non awaited platform forwards."""
1200 f
"calls {what} for integration {entry.domain} with "
1201 f
"title: {entry.title} and entry_id: {entry.entry_id}, "
1202 f
"during setup without awaiting {what}, which can cause "
1203 "the setup lock to be released before the setup is done",
1204 core_behavior=ReportBehavior.LOG,
1205 breaks_in_ha_version=
"2025.1",
1212 """Manage all the config entry flows that are in progress."""
1214 _flow_result = ConfigFlowResult
1218 hass: HomeAssistant,
1219 config_entries: ConfigEntries,
1220 hass_config: ConfigType,
1222 """Initialize the config entry flow manager."""
1226 self._pending_import_flows: defaultdict[
1227 str, dict[str, asyncio.Future[
None]]
1228 ] = defaultdict(dict)
1229 self._initialize_futures: defaultdict[str, set[asyncio.Future[
None]]] = (
1235 cooldown=DISCOVERY_COOLDOWN,
1242 """Wait till all import flows in progress are initialized."""
1243 if not (current := self._pending_import_flows.
get(handler)):
1246 await asyncio.wait(current.values())
1250 """Check if there are any other discovery flows in progress."""
1251 for flow
in self._progress.values():
1252 if flow.flow_id != flow_id
and flow.context[
"source"]
in DISCOVERY_SOURCES:
1260 context: ConfigFlowContext |
None =
None,
1262 ) -> ConfigFlowResult:
1263 """Start a configuration flow."""
1264 if not context
or "source" not in context:
1265 raise KeyError(
"Context not set or doesn't have a source set")
1268 if (source := context[
"source"])
in {
1271 }
and "entry_id" not in context:
1274 f
"initialises a {source} flow without a link to the config entry",
1275 breaks_in_ha_version=
"2025.12",
1278 flow_id = ulid_util.ulid_now()
1283 source
not in {SOURCE_IGNORE, SOURCE_REAUTH, SOURCE_RECONFIGURE}
1285 self.
config_entriesconfig_entries.async_has_entries(handler, include_ignore=
False)
1287 self.
config_entriesconfig_entries.async_has_entries(handler, include_ignore=
True)
1288 and source != SOURCE_USER
1294 type=data_entry_flow.FlowResultType.ABORT,
1297 reason=
"single_instance_allowed",
1298 translation_domain=HOMEASSISTANT_DOMAIN,
1301 loop = self.
hasshass.loop
1303 if source == SOURCE_IMPORT:
1304 self._pending_import_flows[handler][flow_id] = loop.create_future()
1306 cancel_init_future = loop.create_future()
1307 handler_init_futures = self._initialize_futures[handler]
1308 handler_init_futures.add(cancel_init_future)
1310 async
with interrupt(
1313 "Config entry initialize canceled: Home Assistant is shutting down",
1315 flow, result = await self.
_async_init_async_init(flow_id, handler, context, data)
1316 except FlowCancelledError
as ex:
1317 raise asyncio.CancelledError
from ex
1319 handler_init_futures.remove(cancel_init_future)
1320 if not handler_init_futures:
1321 del self._initialize_futures[handler]
1322 if handler
in self._pending_import_flows:
1323 self._pending_import_flows[handler].pop(flow_id,
None)
1324 if not self._pending_import_flows[handler]:
1325 del self._pending_import_flows[handler]
1327 if result[
"type"] != data_entry_flow.FlowResultType.ABORT:
1336 context: ConfigFlowContext,
1338 ) -> tuple[ConfigFlow, ConfigFlowResult]:
1339 """Run the init in a task to allow it to be canceled at shutdown."""
1343 flow.hass = self.
hasshass
1344 flow.handler = handler
1345 flow.flow_id = flow_id
1346 flow.context = context
1347 flow.init_data = data
1356 """Set pending import flow as done."""
1358 (handler_import_flows := self._pending_import_flows.
get(flow.handler))
1359 and (init_done := handler_import_flows.get(flow.flow_id))
1360 and not init_done.done()
1362 init_done.set_result(
None)
1366 """Cancel any initializing flows."""
1367 for future_list
in self._initialize_futures.values():
1368 for future
in future_list:
1369 future.set_result(
None)
1374 flow: data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult],
1375 result: ConfigFlowResult,
1376 ) -> ConfigFlowResult:
1377 """Finish a config flow and add an entry.
1379 This method is called when a flow step returns FlowResultType.ABORT or
1380 FlowResultType.CREATE_ENTRY.
1382 flow = cast(ConfigFlow, flow)
1392 persistent_notification.async_dismiss(self.
hasshass, DISCOVERY_NOTIFICATION_ID)
1395 if flow.context[
"source"] == SOURCE_REAUTH:
1396 if (entry_id := flow.context.get(
"entry_id"))
is not None and (
1399 issue_id = f
"config_entry_reauth_{entry.domain}_{entry.entry_id}"
1400 ir.async_delete_issue(self.
hasshass, HOMEASSISTANT_DOMAIN, issue_id)
1402 if result[
"type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
1406 (discovery_key := flow.context.get(
"discovery_key"))
1407 and (unique_id := flow.unique_id)
is not None
1409 entry := self.
config_entriesconfig_entries.async_entry_for_domain_unique_id(
1410 result[
"handler"], unique_id
1415 known_discovery_keys := entry.discovery_keys.get(
1416 discovery_key.domain, ()
1420 new_discovery_keys = MappingProxyType(
1421 entry.discovery_keys
1423 discovery_key.domain:
tuple(
1424 [*known_discovery_keys, discovery_key][-10:]
1429 "Updating discovery keys for %s entry %s %s -> %s",
1432 entry.discovery_keys,
1436 entry, discovery_keys=new_discovery_keys
1443 self.
config_entriesconfig_entries.async_has_entries(flow.handler, include_ignore=
False)
1445 and flow.context[
"source"] != SOURCE_IGNORE
1448 type=data_entry_flow.FlowResultType.ABORT,
1449 flow_id=flow.flow_id,
1450 handler=flow.handler,
1451 reason=
"single_instance_allowed",
1452 translation_domain=HOMEASSISTANT_DOMAIN,
1456 existing_entry =
None
1461 progress_unique_id = progress_flow[
"context"].
get(
"unique_id")
1462 progress_flow_id = progress_flow[
"flow_id"]
1464 if progress_flow_id != flow.flow_id
and (
1465 (flow.unique_id
and progress_unique_id == flow.unique_id)
1466 or progress_unique_id == DEFAULT_DISCOVERY_UNIQUE_ID
1474 progress_flow_id != flow.flow_id
1479 if flow.unique_id
is not None:
1481 if flow.unique_id == DEFAULT_DISCOVERY_UNIQUE_ID:
1482 await flow.async_set_unique_id(
None)
1485 existing_entry = self.
config_entriesconfig_entries.async_entry_for_domain_unique_id(
1486 result[
"handler"], flow.unique_id
1490 if existing_entry
is not None and existing_entry.state.recoverable:
1491 await self.
config_entriesconfig_entries.async_unload(existing_entry.entry_id)
1493 discovery_key = flow.context.get(
"discovery_key")
1495 MappingProxyType({discovery_key.domain: (discovery_key,)})
1497 else MappingProxyType({})
1500 data=result[
"data"],
1501 discovery_keys=discovery_keys,
1502 domain=result[
"handler"],
1503 minor_version=result[
"minor_version"],
1504 options=result[
"options"],
1505 source=flow.context[
"source"],
1506 title=result[
"title"],
1507 unique_id=flow.unique_id,
1508 version=result[
"version"],
1511 if existing_entry
is not None:
1514 await self.
config_entriesconfig_entries._async_remove(existing_entry.entry_id)
1517 if existing_entry
is not None:
1520 self.
config_entriesconfig_entries._async_clean_up(existing_entry)
1522 result[
"result"] = entry
1529 context: ConfigFlowContext |
None =
None,
1532 """Create a flow for specified handler.
1534 Handler key is the domain of the component that we want to set up.
1539 if not context
or "source" not in context:
1540 raise KeyError(
"Context not set or doesn't have a source set")
1543 flow.init_step = context[
"source"]
1548 flow: data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult],
1549 result: ConfigFlowResult,
1551 """After a flow is initialised trigger new flow notifications."""
1552 source = flow.context[
"source"]
1555 if source
in DISCOVERY_SOURCES:
1557 elif source == SOURCE_REAUTH:
1558 persistent_notification.async_create(
1560 title=
"Integration requires reconfiguration",
1562 "At least one of your integrations requires reconfiguration to "
1563 "continue functioning. [Check it out](/config/integrations)."
1565 notification_id=RECONFIGURE_NOTIFICATION_ID,
1570 """Handle discovery."""
1573 self.
hasshass.bus.async_fire_internal(EVENT_FLOW_DISCOVERED)
1574 persistent_notification.async_create(
1576 title=
"New devices discovered",
1578 "We have discovered new devices on your network. "
1579 "[Check it out](/config/integrations)."
1581 notification_id=DISCOVERY_NOTIFICATION_ID,
1586 self, handler: str, match_context: ConfigFlowContext, data: Any
1588 """Check if an existing matching discovery flow is in progress.
1590 A flow with the same handler, context, and data.
1592 If match_context is passed, only return flows with a context that is a
1593 superset of match_context.
1595 if not (flows := self._handler_progress_index.
get(handler)):
1597 match_items = match_context.items()
1598 for progress
in flows:
1599 if match_items <= progress.context.items()
and progress.init_data == data:
1605 """Check if an existing matching flow is in progress."""
1606 if not (flows := self._handler_progress_index.
get(flow.handler)):
1608 for other_flow
in set(flows):
1609 if other_flow
is not flow
and flow.is_matching(other_flow):
1615 """Container for config items, maps config_entry_id -> entry.
1617 Maintains two additional indexes:
1618 - domain -> list[ConfigEntry]
1619 - domain -> unique_id -> ConfigEntry
1623 """Initialize the container."""
1626 self._domain_index: dict[str, list[ConfigEntry]] = {}
1627 self._domain_unique_id_index: dict[str, dict[str, list[ConfigEntry]]] = {}
1630 """Return the underlying values to avoid __iter__ overhead."""
1631 return self.data.
values()
1637 if entry_id
in data:
1640 _LOGGER.error(
"An entry with the id %s already exists", entry_id)
1642 data[entry_id] = entry
1646 """Check config entry unique id.
1648 For a string unique id (this is the correct case): return
1649 For a hashable non string unique id: log warning
1650 For a non-hashable unique id: raise error
1652 if (unique_id := entry.unique_id)
is None:
1654 if isinstance(unique_id, str):
1657 if isinstance(unique_id, Hashable):
1661 self.
_hass_hass, integration_domain=entry.domain
1665 "Config entry '%s' from integration %s has an invalid unique_id"
1666 " '%s' of type %s when a string is expected, please %s"
1671 type(entry.unique_id).__name__,
1678 f
"The entry unique id {unique_id} is not a string."
1682 """Index an entry."""
1684 self._domain_index.setdefault(entry.domain, []).append(entry)
1685 if entry.unique_id
is not None:
1686 self._domain_unique_id_index.setdefault(entry.domain, {}).setdefault(
1691 """Unindex an entry."""
1692 entry = self.data[entry_id]
1693 domain = entry.domain
1694 self._domain_index[domain].
remove(entry)
1695 if not self._domain_index[domain]:
1696 del self._domain_index[domain]
1697 if (unique_id := entry.unique_id)
is not None:
1698 self._domain_unique_id_index[domain][unique_id].
remove(entry)
1699 if not self._domain_unique_id_index[domain][unique_id]:
1700 del self._domain_unique_id_index[domain][unique_id]
1701 if not self._domain_unique_id_index[domain]:
1702 del self._domain_unique_id_index[domain]
1705 """Remove an item."""
1710 """Update unique id for an entry.
1712 This method mutates the entry with the new unique id and updates the indexes.
1714 entry_id = entry.entry_id
1717 object.__setattr__(entry,
"unique_id", new_unique_id)
1719 entry.clear_state_cache()
1720 entry.clear_storage_cache()
1723 """Get entries for a domain."""
1724 return self._domain_index.
get(domain, [])
1727 self, domain: str, unique_id: str
1728 ) -> ConfigEntry |
None:
1729 """Get entry by domain and unique id."""
1730 if unique_id
is None:
1732 if not isinstance(unique_id, Hashable):
1734 f
"The entry unique id {unique_id} is not a string."
1736 entries = self._domain_unique_id_index.
get(domain, {}).
get(unique_id)
1743 """Class to help storing config entry data."""
1746 """Initialize storage class."""
1751 minor_version=STORAGE_VERSION_MINOR,
1756 old_major_version: int,
1757 old_minor_version: int,
1758 old_data: dict[str, Any],
1759 ) -> dict[str, Any]:
1760 """Migrate to the new version."""
1762 if old_major_version == 1:
1763 if old_minor_version < 2:
1765 for entry
in data[
"entries"]:
1768 pref_disable_new_entities = entry.get(
"pref_disable_new_entities")
1769 if pref_disable_new_entities
is None and "system_options" in entry:
1770 pref_disable_new_entities = entry.get(
"system_options", {}).
get(
1771 "disable_new_entities"
1774 entry.setdefault(
"disabled_by", entry.get(
"disabled_by"))
1775 entry.setdefault(
"minor_version", entry.get(
"minor_version", 1))
1776 entry.setdefault(
"options", entry.get(
"options", {}))
1778 "pref_disable_new_entities", pref_disable_new_entities
1781 "pref_disable_polling", entry.get(
"pref_disable_polling")
1783 entry.setdefault(
"unique_id", entry.get(
"unique_id"))
1785 if old_minor_version < 3:
1788 for entry
in data[
"entries"]:
1789 entry[
"created_at"] = entry[
"modified_at"] = created_at
1791 if old_minor_version < 4:
1793 for entry
in data[
"entries"]:
1794 entry[
"discovery_keys"] = {}
1796 if old_major_version > 1:
1797 raise NotImplementedError
1802 """Manage the configuration entries.
1804 An instance of this object is available via `hass.config_entries`.
1807 def __init__(self, hass: HomeAssistant, hass_config: ConfigType) ->
None:
1808 """Initialize the entry manager."""
1819 self, include_ignore: bool =
False, include_disabled: bool =
False
1821 """Return domains for which we have entries."""
1825 for entry
in self.
_entries_entries.values()
1826 if (include_ignore
or entry.source != SOURCE_IGNORE)
1827 and (include_disabled
or not entry.disabled_by)
1833 """Return entry with matching entry_id."""
1834 return self.
_entries_entries.data.get(entry_id)
1838 """Return entry with matching entry_id.
1840 Raises UnknownEntry if entry is not found.
1848 """Return entry ids."""
1853 self, domain: str, include_ignore: bool =
True, include_disabled: bool =
True
1855 """Return if there are entries for a domain."""
1856 entries = self.
_entries_entries.get_entries_for_domain(domain)
1857 if include_ignore
and include_disabled:
1858 return bool(entries)
1859 for entry
in entries:
1860 if (include_ignore
or entry.source != SOURCE_IGNORE)
and (
1861 include_disabled
or not entry.disabled_by
1869 domain: str |
None =
None,
1870 include_ignore: bool =
True,
1871 include_disabled: bool =
True,
1872 ) -> list[ConfigEntry]:
1873 """Return all entries or entries for a specific domain."""
1875 entries: Iterable[ConfigEntry] = self.
_entries_entries.values()
1877 entries = self.
_entries_entries.get_entries_for_domain(domain)
1879 if include_ignore
and include_disabled:
1880 return list(entries)
1884 for entry
in entries
1885 if (include_ignore
or entry.source != SOURCE_IGNORE)
1886 and (include_disabled
or not entry.disabled_by)
1891 """Return loaded entries for a specific domain.
1893 This will exclude ignored or disabled config entruis.
1895 entries = self.
_entries_entries.get_entries_for_domain(domain)
1897 return [entry
for entry
in entries
if entry.state == ConfigEntryState.LOADED]
1901 self, domain: str, unique_id: str
1902 ) -> ConfigEntry |
None:
1903 """Return entry for a domain with a matching unique id."""
1904 return self.
_entries_entries.get_entry_by_domain_and_unique_id(domain, unique_id)
1907 """Add and setup an entry."""
1908 if entry.entry_id
in self.
_entries_entries.data:
1910 f
"An entry with the id {entry.entry_id} already exists."
1913 self.
_entries_entries[entry.entry_id] = entry
1920 """Remove, unload and clean up after an entry."""
1921 unload_success, entry = await self.
_async_remove_async_remove(entry_id)
1924 for discovery_domain
in entry.discovery_keys:
1925 async_dispatcher_send_internal(
1931 return {
"require_restart":
not unload_success}
1934 """Remove and unload an entry."""
1937 async
with entry.setup_lock:
1938 if not entry.state.recoverable:
1939 unload_success = entry.state
is not ConfigEntryState.FAILED_UNLOAD
1941 unload_success = await self.
async_unloadasync_unload(entry_id, _lock=
False)
1943 await entry.async_remove(self.
hasshass)
1945 del self.
_entries_entries[entry.entry_id]
1949 return (unload_success, entry)
1953 """Clean up after an entry."""
1954 entry_id = entry.entry_id
1956 dev_reg = dr.async_get(self.
hasshass)
1957 ent_reg = er.async_get(self.
hasshass)
1959 dev_reg.async_clear_config_entry(entry_id)
1960 ent_reg.async_clear_config_entry(entry_id)
1965 for progress_flow
in self.
hasshass.config_entries.flow.async_progress_by_handler(
1966 entry.domain, match_context={
"entry_id": entry_id,
"source": SOURCE_REAUTH}
1968 if "flow_id" in progress_flow:
1969 self.
hasshass.config_entries.flow.async_abort(progress_flow[
"flow_id"])
1970 issue_id = f
"config_entry_reauth_{entry.domain}_{entry.entry_id}"
1971 ir.async_delete_issue(self.
hasshass, HOMEASSISTANT_DOMAIN, issue_id)
1977 """Call when Home Assistant is stopping."""
1978 for entry
in self.
_entries_entries.values():
1979 entry.async_shutdown()
1980 self.
flowflow.async_shutdown()
1983 """Initialize config entry config."""
1986 self.
hasshass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.
_async_shutdown_async_shutdown)
1993 for entry
in config[
"entries"]:
1994 entry_id = entry[
"entry_id"]
1997 created_at=datetime.fromisoformat(entry[
"created_at"]),
1999 disabled_by=try_parse_enum(ConfigEntryDisabler, entry[
"disabled_by"]),
2000 discovery_keys=MappingProxyType(
2002 domain:
tuple(DiscoveryKey.from_json_dict(key)
for key
in keys)
2003 for domain, keys
in entry[
"discovery_keys"].items()
2006 domain=entry[
"domain"],
2008 minor_version=entry[
"minor_version"],
2009 modified_at=datetime.fromisoformat(entry[
"modified_at"]),
2010 options=entry[
"options"],
2011 pref_disable_new_entities=entry[
"pref_disable_new_entities"],
2012 pref_disable_polling=entry[
"pref_disable_polling"],
2013 source=entry[
"source"],
2014 title=entry[
"title"],
2015 unique_id=entry[
"unique_id"],
2016 version=entry[
"version"],
2018 entries[entry_id] = config_entry
2023 async
def async_setup(self, entry_id: str, _lock: bool =
True) -> bool:
2024 """Set up a config entry.
2026 Return True if entry has been successfully loaded.
2030 if entry.state
is not ConfigEntryState.NOT_LOADED:
2032 f
"The config entry '{entry.title}' ({entry.domain}) with entry_id"
2033 f
" '{entry.entry_id}' cannot be set up because it is in state "
2034 f
"{entry.state}, but needs to be in the {ConfigEntryState.NOT_LOADED} state"
2038 if entry.domain
in self.
hasshass.config.components:
2040 async
with entry.setup_lock:
2041 await entry.async_setup(self.
hasshass)
2043 await entry.async_setup(self.
hasshass)
2054 entry.state
is ConfigEntryState.LOADED
2058 """Unload a config entry."""
2061 if not entry.state.recoverable:
2063 f
"The config entry '{entry.title}' ({entry.domain}) with entry_id"
2064 f
" '{entry.entry_id}' cannot be unloaded because it is in the non"
2065 f
" recoverable state {entry.state}"
2069 async
with entry.setup_lock:
2070 return await entry.async_unload(self.
hasshass)
2072 return await entry.async_unload(self.
hasshass)
2076 """Schedule a config entry to be reloaded."""
2078 entry.async_cancel_retry_setup()
2079 self.
hasshass.async_create_task(
2081 f
"config entry reload {entry.title} {entry.domain} {entry.entry_id}",
2087 When reloading from an integration is is preferable to
2088 call async_schedule_reload instead of this method since
2089 it will cancel setup retry before starting this method
2090 in a task which eliminates a race condition where the
2091 setup retry can fire during the reload.
2093 If an entry was not loaded, will just load.
2100 entry.async_cancel_retry_setup()
2102 if entry.domain
not in self.
hasshass.config.components:
2108 return entry.state
is ConfigEntryState.LOADED
2110 async
with entry.setup_lock:
2111 unload_result = await self.
async_unloadasync_unload(entry_id, _lock=
False)
2113 if not unload_result
or entry.disabled_by:
2114 return unload_result
2116 return await self.
async_setupasync_setup(entry_id, _lock=
False)
2119 self, entry_id: str, disabled_by: ConfigEntryDisabler |
None
2121 """Disable an entry.
2123 If disabled_by is changed, the config entry will be reloaded.
2128 if entry.disabled_by
is disabled_by:
2131 entry.disabled_by = disabled_by
2134 dev_reg = dr.async_get(self.
hasshass)
2135 ent_reg = er.async_get(self.
hasshass)
2137 if not entry.disabled_by:
2139 dr.async_config_entry_disabled_by_changed(dev_reg, entry)
2140 er.async_config_entry_disabled_by_changed(ent_reg, entry)
2143 reload_result = await self.
async_reloadasync_reload(entry_id)
2145 if entry.disabled_by:
2147 dr.async_config_entry_disabled_by_changed(dev_reg, entry)
2148 er.async_config_entry_disabled_by_changed(ent_reg, entry)
2150 return reload_result
2157 data: Mapping[str, Any] | UndefinedType = UNDEFINED,
2158 discovery_keys: MappingProxyType[str, tuple[DiscoveryKey, ...]]
2159 | UndefinedType = UNDEFINED,
2160 minor_version: int | UndefinedType = UNDEFINED,
2161 options: Mapping[str, Any] | UndefinedType = UNDEFINED,
2162 pref_disable_new_entities: bool | UndefinedType = UNDEFINED,
2163 pref_disable_polling: bool | UndefinedType = UNDEFINED,
2164 title: str | UndefinedType = UNDEFINED,
2165 unique_id: str |
None | UndefinedType = UNDEFINED,
2166 version: int | UndefinedType = UNDEFINED,
2168 """Update a config entry.
2170 If the entry was changed, the update_listeners are
2171 fired and this function returns True
2173 If the entry was not changed, the update_listeners are
2174 not fired and this function returns False
2176 if entry.entry_id
not in self.
_entries_entries:
2179 self.
hasshass.verify_event_loop_thread(
"hass.config_entries.async_update_entry")
2181 _setter = object.__setattr__
2183 if unique_id
is not UNDEFINED
and entry.unique_id != unique_id:
2190 entry.domain !=
"flipr"
2191 and unique_id
is not None
2196 self.
hasshass, integration_domain=entry.domain
2200 "Unique id of config entry '%s' from integration %s changed to"
2201 " '%s' which is already in use, please %s"
2213 for attr, value
in (
2214 (
"discovery_keys", discovery_keys),
2215 (
"minor_version", minor_version),
2216 (
"pref_disable_new_entities", pref_disable_new_entities),
2217 (
"pref_disable_polling", pref_disable_polling),
2219 (
"version", version),
2221 if value
is UNDEFINED
or getattr(entry, attr) == value:
2224 _setter(entry, attr, value)
2227 if data
is not UNDEFINED
and entry.data != data:
2229 _setter(entry,
"data", MappingProxyType(data))
2231 if options
is not UNDEFINED
and entry.options != options:
2233 _setter(entry,
"options", MappingProxyType(options))
2238 _setter(entry,
"modified_at",
utcnow())
2240 for listener
in entry.update_listeners:
2241 self.
hasshass.async_create_task(
2242 listener(self.
hasshass, entry),
2243 f
"config entry update listener {entry.title} {entry.domain} {entry.domain}",
2247 entry.clear_state_cache()
2248 entry.clear_storage_cache()
2254 self, change_type: ConfigEntryChange, entry: ConfigEntry
2256 """Dispatch a config entry change."""
2257 async_dispatcher_send_internal(
2258 self.
hasshass, SIGNAL_CONFIG_ENTRY_CHANGED, change_type, entry
2262 self, entry: ConfigEntry, platforms: Iterable[Platform | str]
2264 """Forward the setup of an entry to platforms.
2266 This method should be awaited before async_setup_entry is finished
2267 in each integration. This is to ensure that all platforms are loaded
2268 before the entry is set up. This ensures that the config entry cannot
2269 be unloaded before all platforms are loaded.
2271 This method is more efficient than async_forward_entry_setup as
2272 it can load multiple platforms at once and does not require a separate
2273 import executor job for each platform.
2275 integration = await loader.async_get_integration(self.
hasshass, entry.domain)
2276 if not integration.platforms_are_loaded(platforms):
2278 await integration.async_get_platforms(platforms)
2280 if not entry.setup_lock.locked():
2281 async
with entry.setup_lock:
2282 if entry.state
is not ConfigEntryState.LOADED:
2284 f
"The config entry '{entry.title}' ({entry.domain}) with "
2285 f
"entry_id '{entry.entry_id}' cannot forward setup for "
2286 f
"{platforms} because it is in state {entry.state}, but needs "
2287 f
"to be in the {ConfigEntryState.LOADED} state"
2294 if not entry.setup_lock.locked():
2296 entry,
"async_forward_entry_setups"
2300 self, entry: ConfigEntry, platforms: Iterable[Platform | str]
2302 await asyncio.gather(
2307 f
"config entry forward setup {entry.title} "
2308 f
"{entry.domain} {entry.entry_id} {platform}"
2310 loop=self.
hasshass.loop,
2312 for platform
in platforms
2317 self, entry: ConfigEntry, domain: Platform | str
2319 """Forward the setup of an entry to a different component.
2321 By default an entry is setup with the component it belongs to. If that
2322 component also has related platforms, the component will have to
2323 forward the entry to be setup by that component.
2325 This method is deprecated and will stop working in Home Assistant 2025.6.
2327 Instead, await async_forward_entry_setups as it can load
2328 multiple platforms at once and is more efficient since it
2329 does not require a separate import executor job for each platform.
2332 "calls async_forward_entry_setup for "
2333 f
"integration, {entry.domain} with title: {entry.title} "
2334 f
"and entry_id: {entry.entry_id}, which is deprecated, "
2335 "await async_forward_entry_setups instead",
2336 core_behavior=ReportBehavior.LOG,
2337 breaks_in_ha_version=
"2025.6",
2339 if not entry.setup_lock.locked():
2340 async
with entry.setup_lock:
2341 if entry.state
is not ConfigEntryState.LOADED:
2343 f
"The config entry '{entry.title}' ({entry.domain}) with "
2344 f
"entry_id '{entry.entry_id}' cannot forward setup for "
2345 f
"{domain} because it is in state {entry.state}, but needs "
2346 f
"to be in the {ConfigEntryState.LOADED} state"
2352 if not entry.setup_lock.locked():
2359 domain: Platform | str,
2360 preload_platform: bool,
2362 """Forward the setup of an entry to a different component."""
2364 if domain
not in self.
hasshass.config.components:
2373 if preload_platform:
2377 integration = await loader.async_get_integration(self.
hasshass, entry.domain)
2378 if not integration.platforms_are_loaded((domain,)):
2380 await integration.async_get_platform(domain)
2382 integration = loader.async_get_loaded_integration(self.
hasshass, domain)
2383 await entry.async_setup(self.
hasshass, integration=integration)
2387 self, entry: ConfigEntry, platforms: Iterable[Platform | str]
2389 """Forward the unloading of an entry to platforms."""
2391 await asyncio.gather(
2396 f
"config entry forward unload {entry.title} "
2397 f
"{entry.domain} {entry.entry_id} {platform}"
2399 loop=self.
hasshass.loop,
2401 for platform
in platforms
2407 self, entry: ConfigEntry, domain: Platform | str
2409 """Forward the unloading of an entry to a different component.
2411 Its is preferred to call async_unload_platforms instead
2412 of directly calling this method.
2415 if domain
not in self.
hasshass.config.components:
2418 integration = loader.async_get_loaded_integration(self.
hasshass, domain)
2420 return await entry.async_unload(self.
hasshass, integration=integration)
2424 """Save the entity registry to a file."""
2429 """Return data to save."""
2432 "entries": [entry.as_storage_fragment
for entry
in self.
_entries_entries.values()]
2436 """Wait for an entry's component to load and return if the entry is loaded.
2438 This is primarily intended for existing config entries which are loaded at
2439 startup, awaiting this function will block until the component and all its
2440 config entries are loaded.
2441 Config entries which are created after Home Assistant is started can't be waited
2442 for, the function will just return if the config entry is loaded or not.
2444 setup_done = self.
hasshass.data.get(DATA_SETUP_DONE, {})
2445 if setup_future := setup_done.get(entry.domain):
2448 if entry.domain
not in self.
hasshass.config.components:
2450 return entry.state
is ConfigEntryState.LOADED
2454 """Update unique id collision issues."""
2455 issue_registry = ir.async_get(self.
hasshass)
2456 issues: set[str] = set()
2458 for issue
in issue_registry.issues.values():
2460 issue.domain != HOMEASSISTANT_DOMAIN
2461 or not (issue_data := issue.data)
2462 or issue_data.get(
"issue_type") != ISSUE_UNIQUE_ID_COLLISION
2465 issues.add(issue.issue_id)
2467 for domain, unique_ids
in self.
_entries_entries._domain_unique_id_index.items():
2472 if domain ==
"flipr":
2474 for unique_id, entries
in unique_ids.items():
2477 entries =
list(entries)
2482 for entry
in list(entries):
2483 if entry.source == SOURCE_IGNORE:
2484 entries.remove(entry)
2486 if len(entries) < 2:
2488 issue_id = f
"{ISSUE_UNIQUE_ID_COLLISION}_{domain}_{unique_id}"
2489 issues.discard(issue_id)
2490 titles = [f
"'{entry.title}'" for entry
in entries]
2491 translation_placeholders = {
2493 "configure_url": f
"/config/integrations/integration/{domain}",
2494 "unique_id":
str(unique_id),
2496 if len(titles) <= UNIQUE_ID_COLLISION_TITLE_LIMIT:
2497 translation_key =
"config_entry_unique_id_collision"
2498 translation_placeholders[
"titles"] =
", ".join(titles)
2500 translation_key =
"config_entry_unique_id_collision_many"
2501 translation_placeholders[
"number_of_entries"] =
str(len(titles))
2502 translation_placeholders[
"titles"] =
", ".join(
2503 titles[:UNIQUE_ID_COLLISION_TITLE_LIMIT]
2505 translation_placeholders[
"title_limit"] =
str(
2506 UNIQUE_ID_COLLISION_TITLE_LIMIT
2509 ir.async_create_issue(
2511 HOMEASSISTANT_DOMAIN,
2513 breaks_in_ha_version=
"2025.11.0",
2515 "issue_type": ISSUE_UNIQUE_ID_COLLISION,
2516 "unique_id": unique_id,
2519 issue_domain=domain,
2520 severity=ir.IssueSeverity.ERROR,
2521 translation_key=translation_key,
2522 translation_placeholders=translation_placeholders,
2527 for issue_id
in issues:
2528 ir.async_delete_issue(self.
hasshass, HOMEASSISTANT_DOMAIN, issue_id)
2533 other_entries: list[ConfigEntry], match_dict: dict[str, Any] |
None =
None
2535 """Abort if current entries match all data.
2537 Requires `already_configured` in strings.json in user visible flows.
2539 if match_dict
is None:
2541 for entry
in other_entries:
2542 options_items = entry.options.items()
2543 data_items = entry.data.items()
2544 for kv
in match_dict.items():
2545 if kv
not in options_items
and kv
not in data_items:
2554 """Base class for config and option flows."""
2556 _flow_result = ConfigFlowResult
2560 """Base class for config flows with some helpers."""
2563 """Initialize a subclass, register if possible."""
2565 if domain
is not None:
2566 HANDLERS.register(domain)(cls)
2570 """Return unique ID if available."""
2571 if not self.context:
2574 return self.context.
get(
"unique_id")
2579 """Get the options flow for this handler."""
2585 """Return options flow support for this handler."""
2590 self, match_dict: dict[str, Any] |
None =
None
2592 """Abort if current entries match all data.
2594 Requires `already_configured` in strings.json in user visible flows.
2604 reason: str =
"unique_id_mismatch",
2605 description_placeholders: Mapping[str, str] |
None =
None,
2607 """Abort if the unique ID does not match the reauth/reconfigure context.
2609 Requires strings.json entry corresponding to the `reason` parameter
2610 in user visible flows.
2624 updates: dict[str, Any] |
None =
None,
2625 reload_on_update: bool =
True,
2627 error: str =
"already_configured",
2629 """Abort if the unique ID is already configured.
2631 Requires strings.json entry corresponding to the `error` parameter
2632 in user visible flows.
2638 entry := self.hass.config_entries.async_entry_for_domain_unique_id(
2644 should_reload =
False
2647 and self.hass.config_entries.async_update_entry(
2648 entry, data={**entry.data, **updates}
2650 and reload_on_update
2651 and entry.state
in (ConfigEntryState.LOADED, ConfigEntryState.SETUP_RETRY)
2655 should_reload =
True
2658 and entry.state
is ConfigEntryState.SETUP_RETRY
2662 should_reload =
True
2664 if entry.source == SOURCE_IGNORE
and self.
sourcesourcesource == SOURCE_USER:
2667 self.hass.config_entries.async_schedule_reload(entry.entry_id)
2671 self, unique_id: str |
None =
None, *, raise_on_progress: bool =
True
2672 ) -> ConfigEntry |
None:
2673 """Set a unique ID for the config flow.
2675 Returns optionally existing config entry with same ID.
2677 if unique_id
is None:
2678 self.context[
"unique_id"] =
None
2681 if raise_on_progress:
2683 include_uninitialized=
True, match_context={
"unique_id": unique_id}
2687 self.context[
"unique_id"] = unique_id
2690 if unique_id != DEFAULT_DISCOVERY_UNIQUE_ID:
2692 include_uninitialized=
True,
2693 match_context={
"unique_id": DEFAULT_DISCOVERY_UNIQUE_ID},
2695 self.hass.config_entries.flow.async_abort(progress[
"flow_id"])
2697 return self.hass.config_entries.async_entry_for_domain_unique_id(
2698 self.handler, unique_id
2705 """Mark the config flow as only needing user confirmation to finish flow."""
2706 self.context[
"confirm_only"] =
True
2710 self, include_ignore: bool |
None =
None
2711 ) -> list[ConfigEntry]:
2712 """Return current entries.
2714 If the flow is user initiated, filter out ignored entries,
2715 unless include_ignore is True.
2717 return self.hass.config_entries.async_entries(
2719 include_ignore
or (include_ignore
is None and self.
sourcesourcesource != SOURCE_USER),
2724 """Return current unique IDs."""
2727 for entry
in self.hass.config_entries.async_entries(self.handler)
2728 if include_ignore
or entry.source != SOURCE_IGNORE
2734 include_uninitialized: bool =
False,
2735 match_context: dict[str, Any] |
None =
None,
2736 ) -> list[ConfigFlowResult]:
2737 """Return other in progress flows for current domain."""
2740 for flw
in self.hass.config_entries.flow.async_progress_by_handler(
2742 include_uninitialized=include_uninitialized,
2743 match_context=match_context,
2745 if flw[
"flow_id"] != self.flow_id
2749 """Ignore this config flow.
2751 Ignoring a config flow works by creating a config entry with source set to
2754 There will only be a single active discovery flow per device, also when the
2755 integration has multiple discovery sources for the same device. This method
2756 is called when the user ignores a discovered device or service, we then store
2757 the key for the flow being ignored.
2759 Once the ignore config entry is created, ConfigEntriesFlowManager.async_finish_flow
2760 will make sure the discovery key is kept up to date since it may not be stable
2761 unlike the unique id.
2763 await self.
async_set_unique_idasync_set_unique_id(user_input[
"unique_id"], raise_on_progress=
False)
2767 self, user_input: dict[str, Any] |
None =
None
2768 ) -> ConfigFlowResult:
2769 """Handle a flow initiated by the user."""
2773 """Mark this flow discovered, without a unique identifier.
2775 If a flow initiated by discovery, doesn't have a unique ID, this can
2776 be used alternatively. It will ensure only 1 flow is started and only
2777 when the handler has no existing config entries.
2779 It ensures that the discovery can be ignored by the user.
2781 Requires `already_configured` and `already_in_progress` in strings.json
2782 in user visible flows.
2801 ) -> ConfigFlowResult:
2802 """Handle a flow initialized by discovery."""
2807 self, discovery_info: DiscoveryInfoType
2808 ) -> ConfigFlowResult:
2809 """Handle a flow initialized by discovery."""
2817 description_placeholders: Mapping[str, str] |
None =
None,
2818 ) -> ConfigFlowResult:
2819 """Abort the config flow."""
2821 if self.
sourcesourcesource == SOURCE_REAUTH
and not any(
2822 ent[
"flow_id"] != self.flow_id
2823 for ent
in self.hass.config_entries.flow.async_progress_by_handler(
2824 self.handler, match_context={
"source": SOURCE_REAUTH}
2827 persistent_notification.async_dismiss(
2828 self.hass, RECONFIGURE_NOTIFICATION_ID
2832 reason=reason, description_placeholders=description_placeholders
2836 self, discovery_info: BluetoothServiceInfoBleak
2837 ) -> ConfigFlowResult:
2838 """Handle a flow initialized by Bluetooth discovery."""
2842 self, discovery_info: DhcpServiceInfo
2843 ) -> ConfigFlowResult:
2844 """Handle a flow initialized by DHCP discovery."""
2848 self, discovery_info: HassioServiceInfo
2849 ) -> ConfigFlowResult:
2850 """Handle a flow initialized by HASS IO discovery."""
2854 self, discovery_info: DiscoveryInfoType
2855 ) -> ConfigFlowResult:
2856 """Handle a flow initialized by integration specific discovery."""
2860 self, discovery_info: ZeroconfServiceInfo
2861 ) -> ConfigFlowResult:
2862 """Handle a flow initialized by Homekit discovery."""
2866 self, discovery_info: MqttServiceInfo
2867 ) -> ConfigFlowResult:
2868 """Handle a flow initialized by MQTT discovery."""
2872 self, discovery_info: SsdpServiceInfo
2873 ) -> ConfigFlowResult:
2874 """Handle a flow initialized by SSDP discovery."""
2878 """Handle a flow initialized by USB discovery."""
2882 self, discovery_info: ZeroconfServiceInfo
2883 ) -> ConfigFlowResult:
2884 """Handle a flow initialized by Zeroconf discovery."""
2892 data: Mapping[str, Any],
2893 description: str |
None =
None,
2894 description_placeholders: Mapping[str, str] |
None =
None,
2895 options: Mapping[str, Any] |
None =
None,
2896 ) -> ConfigFlowResult:
2897 """Finish config flow and create a config entry."""
2898 if self.
sourcesourcesource
in {SOURCE_REAUTH, SOURCE_RECONFIGURE}:
2900 f
"creates a new entry in a '{self.source}' flow, "
2901 "when it is expected to update an existing entry and abort",
2902 core_behavior=ReportBehavior.LOG,
2903 breaks_in_ha_version=
"2025.11",
2904 integration_domain=self.handler,
2909 description=description,
2910 description_placeholders=description_placeholders,
2914 result[
"options"] = options
or {}
2915 result[
"version"] = self.
VERSIONVERSION
2924 unique_id: str |
None | UndefinedType = UNDEFINED,
2925 title: str | UndefinedType = UNDEFINED,
2926 data: Mapping[str, Any] | UndefinedType = UNDEFINED,
2927 data_updates: Mapping[str, Any] | UndefinedType = UNDEFINED,
2928 options: Mapping[str, Any] | UndefinedType = UNDEFINED,
2929 reason: str | UndefinedType = UNDEFINED,
2930 reload_even_if_entry_is_unchanged: bool =
True,
2931 ) -> ConfigFlowResult:
2932 """Update config entry, reload config entry and finish config flow.
2934 :param data: replace the entry data with new data
2935 :param data_updates: add items from data_updates to entry data - existing keys
2937 :param options: replace the entry options with new options
2938 :param title: replace the title of the entry
2939 :param unique_id: replace the unique_id of the entry
2941 :param reason: set the reason for the abort, defaults to
2942 `reauth_successful` or `reconfigure_successful` based on flow source
2944 :param reload_even_if_entry_is_unchanged: set this to `False` if the entry
2945 should not be reloaded if it is unchanged
2947 if data_updates
is not UNDEFINED:
2948 if data
is not UNDEFINED:
2949 raise ValueError(
"Cannot set both data and data_updates")
2950 data = entry.data | data_updates
2951 result = self.hass.config_entries.async_update_entry(
2953 unique_id=unique_id,
2958 if reload_even_if_entry_is_unchanged
or result:
2959 self.hass.config_entries.async_schedule_reload(entry.entry_id)
2960 if reason
is UNDEFINED:
2961 reason =
"reauth_successful"
2963 reason =
"reconfigure_successful"
2970 step_id: str |
None =
None,
2971 data_schema: vol.Schema |
None =
None,
2972 errors: dict[str, str] |
None =
None,
2973 description_placeholders: Mapping[str, str] |
None =
None,
2974 last_step: bool |
None =
None,
2975 preview: str |
None =
None,
2976 ) -> ConfigFlowResult:
2977 """Return the definition of a form to gather user input.
2979 The step_id parameter is deprecated and will be removed in a future release.
2981 if self.
sourcesourcesource == SOURCE_REAUTH
and "entry_id" in self.context:
2986 description_placeholders =
dict(description_placeholders
or {})
2987 if description_placeholders.get(CONF_NAME)
is None:
2988 description_placeholders[CONF_NAME] = self.
_get_reauth_entry_get_reauth_entry().title
2991 data_schema=data_schema,
2993 description_placeholders=description_placeholders,
2994 last_step=last_step,
2999 """Return True if other_flow is matching this flow."""
3000 raise NotImplementedError
3004 """Return reauth entry id."""
3006 raise ValueError(f
"Source is {self.source}, expected {SOURCE_REAUTH}")
3007 return self.context[
"entry_id"]
3011 """Return the reauth config entry linked to the current context."""
3012 return self.hass.config_entries.async_get_known_entry(self.
_reauth_entry_id_reauth_entry_id)
3016 """Return reconfigure entry id."""
3018 raise ValueError(f
"Source is {self.source}, expected {SOURCE_RECONFIGURE}")
3019 return self.context[
"entry_id"]
3023 """Return the reconfigure config entry linked to the current context."""
3024 return self.hass.config_entries.async_get_known_entry(
3032 """Flow to set options for a configuration entry."""
3034 _flow_result = ConfigFlowResult
3037 """Return config entry or raise if not found."""
3038 return self.
hasshass.config_entries.async_get_known_entry(config_entry_id)
3044 context: ConfigFlowContext |
None =
None,
3045 data: dict[str, Any] |
None =
None,
3047 """Create an options flow for a config entry.
3049 Entry_id and flow.handler is the same thing to map entry with flow.
3053 return handler.async_get_options_flow(entry)
3057 flow: data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult],
3058 result: ConfigFlowResult,
3059 ) -> ConfigFlowResult:
3060 """Finish an options flow and update options for configuration entry.
3062 This method is called when a flow step returns FlowResultType.ABORT or
3063 FlowResultType.CREATE_ENTRY.
3065 Flow.handler and entry_id is the same thing to map flow with entry.
3067 flow = cast(OptionsFlow, flow)
3069 if result[
"type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
3072 entry = self.
hasshass.config_entries.async_get_known_entry(flow.handler)
3074 if result[
"data"]
is not None:
3075 self.
hasshass.config_entries.async_update_entry(entry, options=result[
"data"])
3077 result[
"result"] =
True
3081 self, flow: data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult]
3083 """Set up preview for an option flow handler."""
3086 if entry.domain
not in self._preview:
3087 self._preview.
add(entry.domain)
3088 await flow.async_setup_preview(self.
hasshass)
3092 """Base class for config options flows."""
3096 _config_entry: ConfigEntry
3097 """For compatibility only - to be removed in 2025.12"""
3101 self, match_dict: dict[str, Any] |
None =
None
3103 """Abort if another current entry matches all data.
3105 Requires `already_configured` in strings.json in user visible flows.
3110 for entry
in self.hass.config_entries.async_entries(
3120 """Return config entry id.
3122 Please note that this is not available inside `__init__` method, and
3123 can only be referenced after initialisation.
3126 if self.handler
is None:
3128 "The config entry id is not available during initialisation"
3134 """Return the config entry linked to the current options flow.
3136 Please note that this is not available inside `__init__` method, and
3137 can only be referenced after initialisation.
3140 if hasattr(self,
"_config_entry"):
3143 if self.hass
is None:
3144 raise ValueError(
"The config entry is not available during initialisation")
3145 return self.hass.config_entries.async_get_known_entry(self.
_config_entry_id_config_entry_id)
3147 @config_entry.setter
3149 """Set the config entry value."""
3151 "sets option flow config_entry explicitly, which is deprecated",
3152 core_behavior=ReportBehavior.ERROR,
3153 core_integration_behavior=ReportBehavior.ERROR,
3154 custom_integration_behavior=ReportBehavior.LOG,
3155 breaks_in_ha_version=
"2025.12",
3161 """Base class for options flows with config entry and options.
3163 This class is being phased out, and should not be referenced in new code.
3164 It is kept only for backward compatibility, and only for custom integrations.
3168 """Initialize options flow."""
3172 "inherits from OptionsFlowWithConfigEntry",
3173 core_behavior=ReportBehavior.ERROR,
3174 core_integration_behavior=ReportBehavior.ERROR,
3175 custom_integration_behavior=ReportBehavior.IGNORE,
3180 """Return a mutable copy of the config entry options."""
3185 """Handler when entities related to config entries updated disabled_by."""
3188 """Initialize the handler."""
3190 self.
registryregistry: er.EntityRegistry |
None =
None
3191 self.
changedchanged: set[str] = set()
3196 """Set up the disable handler."""
3197 self.
hasshass.bus.async_listen(
3198 er.EVENT_ENTITY_REGISTRY_UPDATED,
3200 event_filter=_handle_entry_updated_filter,
3205 self, event: Event[er.EventEntityRegistryUpdatedData]
3207 """Handle entity registry entry update."""
3215 entity_entry
is None
3217 or entity_entry.config_entry_id
is None
3220 or entity_entry.disabled_by
3224 config_entry = self.
hasshass.config_entries.async_get_known_entry(
3225 entity_entry.config_entry_id
3228 if config_entry.entry_id
not in self.
changedchanged
and config_entry.supports_unload:
3229 self.
changedchanged.
add(config_entry.entry_id)
3242 RELOAD_AFTER_UPDATE_DELAY,
3248 """Handle a reload."""
3250 to_reload = self.
changedchanged
3255 "Reloading configuration entries because disabled_by changed in entity"
3258 ", ".join(to_reload),
3260 for entry_id
in to_reload:
3261 self.
hasshass.config_entries.async_schedule_reload(entry_id)
3266 event_data: er.EventEntityRegistryUpdatedData,
3268 """Handle entity registry entry update filter.
3270 Only handle changes to "disabled_by".
3271 If "disabled_by" was CONFIG_ENTRY, reload is not needed.
3274 event_data[
"action"] !=
"update"
3275 or "disabled_by" not in event_data[
"changes"]
3276 or event_data[
"changes"][
"disabled_by"]
is er.RegistryEntryDisabler.CONFIG_ENTRY
3281 """Test if a domain supports entry unloading."""
3282 integration = await loader.async_get_integration(hass, domain)
3283 component = await integration.async_get_component()
3284 return hasattr(component,
"async_unload_entry")
3288 """Test if a domain supports being removed from a device."""
3289 integration = await loader.async_get_integration(hass, domain)
3290 component = await integration.async_get_component()
3291 return hasattr(component,
"async_remove_config_entry_device")
3295 """Test if a domain supports only a single config entry."""
3296 integration = await loader.async_get_integration(hass, domain)
3297 return integration.single_config_entry
3301 hass: HomeAssistant, domain: str, hass_config: ConfigType
3304 integration = await loader.async_get_integration(hass, domain)
3306 _LOGGER.error(
"Cannot find integration %s", domain)
3312 await integration.async_get_platform(
"config_flow")
3313 except ImportError
as err:
3315 "Error occurred loading flow for integration %s: %s",
3323 hass: HomeAssistant, domain: str, hass_config: ConfigType
3324 ) -> type[ConfigFlow]:
3325 """Get a flow handler for specified domain."""
3328 if loader.is_component_module_loaded(hass, f
"{domain}.config_flow")
and (
3329 handler := HANDLERS.get(domain)
3335 if handler := HANDLERS.get(domain):
ConfigFlowResult async_finish_flow(self, data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult] flow, ConfigFlowResult result)
None async_post_init(self, data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult] flow, ConfigFlowResult result)
None _set_pending_import_done(self, ConfigFlow flow)
None __init__(self, HomeAssistant hass, ConfigEntries config_entries, ConfigType hass_config)
None async_wait_import_flow_initialized(self, str handler)
bool _async_has_other_discovery_flows(self, str flow_id)
ConfigFlowResult async_init(self, str handler, *ConfigFlowContext|None context=None, Any data=None)
bool async_has_matching_flow(self, ConfigFlow flow)
tuple[ConfigFlow, ConfigFlowResult] _async_init(self, str flow_id, str handler, ConfigFlowContext context, Any data)
None _async_discovery(self)
ConfigFlow async_create_flow(self, str handler_key, *ConfigFlowContext|None context=None, Any data=None)
bool async_has_matching_discovery_flow(self, str handler, ConfigFlowContext match_context, Any data)
None async_shutdown(self)
dict[str, Any] async_remove(self, str entry_id)
ConfigEntry|None async_entry_for_domain_unique_id(self, str domain, str unique_id)
list[str] async_entry_ids(self)
None _async_forward_entry_setups_locked(self, ConfigEntry entry, Iterable[Platform|str] platforms)
bool async_unload(self, str entry_id, bool _lock=True)
bool async_reload(self, str entry_id)
None _async_schedule_save(self)
list[str] async_domains(self, bool include_ignore=False, bool include_disabled=False)
None async_schedule_reload(self, str entry_id)
ConfigEntry|None async_get_entry(self, str entry_id)
None async_forward_entry_setups(self, ConfigEntry entry, Iterable[Platform|str] platforms)
bool async_setup(self, str entry_id, bool _lock=True)
bool async_has_entries(self, str domain, bool include_ignore=True, bool include_disabled=True)
dict[str, list[dict[str, Any]]] _data_to_save(self)
None _async_clean_up(self, ConfigEntry entry)
bool async_forward_entry_unload(self, ConfigEntry entry, Platform|str domain)
bool async_set_disabled_by(self, str entry_id, ConfigEntryDisabler|None disabled_by)
list[ConfigEntry] async_entries(self, str|None domain=None, bool include_ignore=True, bool include_disabled=True)
tuple[bool, ConfigEntry] _async_remove(self, str entry_id)
None async_initialize(self)
None async_update_issues(self)
None _async_shutdown(self, Event event)
bool async_forward_entry_setup(self, ConfigEntry entry, Platform|str domain)
bool async_wait_component(self, ConfigEntry entry)
None async_add(self, ConfigEntry entry)
ConfigEntry async_get_known_entry(self, str entry_id)
None __init__(self, HomeAssistant hass, ConfigType hass_config)
None _async_dispatch(self, ConfigEntryChange change_type, ConfigEntry entry)
bool async_unload_platforms(self, ConfigEntry entry, Iterable[Platform|str] platforms)
bool async_update_entry(self, ConfigEntry entry, *Mapping[str, Any]|UndefinedType data=UNDEFINED, MappingProxyType[str, tuple[DiscoveryKey,...]]|UndefinedType discovery_keys=UNDEFINED, int|UndefinedType minor_version=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, bool|UndefinedType pref_disable_new_entities=UNDEFINED, bool|UndefinedType pref_disable_polling=UNDEFINED, str|UndefinedType title=UNDEFINED, str|None|UndefinedType unique_id=UNDEFINED, int|UndefinedType version=UNDEFINED)
list[ConfigEntry] async_loaded_entries(self, str domain)
bool _async_forward_entry_setup(self, ConfigEntry entry, Platform|str domain, bool preload_platform)
ConfigEntry|None get_entry_by_domain_and_unique_id(self, str domain, str unique_id)
None update_unique_id(self, ConfigEntry entry, str|None new_unique_id)
None __init__(self, HomeAssistant hass)
None __setitem__(self, str entry_id, ConfigEntry entry)
None __delitem__(self, str entry_id)
None check_unique_id(self, ConfigEntry entry)
list[ConfigEntry] get_entries_for_domain(self, str domain)
None _unindex_entry(self, str entry_id)
ValuesView[ConfigEntry] values(self)
None _index_entry(self, ConfigEntry entry)
Self __new__(cls, str value, bool recoverable)
None __init__(self, HomeAssistant hass)
dict[str, Any] _async_migrate_func(self, int old_major_version, int old_minor_version, dict[str, Any] old_data)
dict[str, Any] as_dict(self)
None async_setup(self, HomeAssistant hass, *loader.Integration|None integration=None)
None async_start_reauth(self, HomeAssistant hass, ConfigFlowContext|None context=None, dict[str, Any]|None data=None)
None async_remove(self, HomeAssistant hass)
CALLBACK_TYPE add_update_listener(self, UpdateListenerType listener)
Generator[ConfigFlowResult] async_get_active_flows(self, HomeAssistant hass, set[str] sources)
None clear_storage_cache(self)
None __init__(self, *datetime|None created_at=None, Mapping[str, Any] data, ConfigEntryDisabler|None disabled_by=None, MappingProxyType[str, tuple[DiscoveryKey,...]] discovery_keys, str domain, str|None entry_id=None, int minor_version, datetime|None modified_at=None, Mapping[str, Any]|None options, bool|None pref_disable_new_entities=None, bool|None pref_disable_polling=None, str source, ConfigEntryState state=ConfigEntryState.NOT_LOADED, str title, str|None unique_id, int version)
None _async_setup_again(self, HomeAssistant hass, *Any _)
bool supports_options(self)
bool async_migrate(self, HomeAssistant hass)
bool supports_reconfigure(self)
bool async_unload(self, HomeAssistant hass, *loader.Integration|None integration=None)
None _async_set_state(self, HomeAssistant hass, ConfigEntryState state, str|None reason, str|None error_reason_translation_key=None, dict[str, str]|None error_reason_translation_placeholders=None)
None async_cancel_retry_setup(self)
None _async_init_reauth(self, HomeAssistant hass, ConfigFlowContext|None context=None, dict[str, Any]|None data=None)
None async_on_unload(self, Callable[[], Coroutine[Any, Any, None]|None] func)
json_fragment as_storage_fragment(self)
_async_cancel_retry_setup
None clear_state_cache(self)
json_fragment as_json_fragment(self)
None async_setup_locked(self, HomeAssistant hass, loader.Integration|None integration=None)
None _async_process_on_unload(self, HomeAssistant hass)
None __async_setup_with_context(self, HomeAssistant hass, loader.Integration|None integration)
None __setattr__(self, str key, Any value)
None async_shutdown(self)
bool async_supports_options_flow(cls, ConfigEntry config_entry)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
None _async_handle_discovery_without_unique_id(self)
ConfigEntry _get_reauth_entry(self)
None _set_confirm_only(self)
ConfigFlowResult async_step_dhcp(self, DhcpServiceInfo discovery_info)
ConfigFlowResult async_step_usb(self, UsbServiceInfo discovery_info)
set[str|None] _async_current_ids(self, bool include_ignore=True)
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_step_ssdp(self, SsdpServiceInfo discovery_info)
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_step_ignore(self, dict[str, Any] user_input)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=None)
str _reauth_entry_id(self)
list[ConfigFlowResult] _async_in_progress(self, bool include_uninitialized=False, dict[str, Any]|None match_context=None)
ConfigFlowResult async_update_reload_and_abort(self, ConfigEntry entry, *str|None|UndefinedType unique_id=UNDEFINED, str|UndefinedType title=UNDEFINED, Mapping[str, Any]|UndefinedType data=UNDEFINED, Mapping[str, Any]|UndefinedType data_updates=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, str|UndefinedType reason=UNDEFINED, bool reload_even_if_entry_is_unchanged=True)
ConfigFlowResult async_step_mqtt(self, MqttServiceInfo discovery_info)
ConfigFlowResult async_step_hassio(self, HassioServiceInfo discovery_info)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _async_step_discovery_without_unique_id(self)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_step_discovery(self, DiscoveryInfoType discovery_info)
ConfigFlowResult async_step_zeroconf(self, ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_integration_discovery(self, DiscoveryInfoType discovery_info)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=None)
bool is_matching(self, Self other_flow)
ConfigFlowResult async_step_bluetooth(self, BluetoothServiceInfoBleak discovery_info)
ConfigFlowResult async_step_homekit(self, ZeroconfServiceInfo discovery_info)
None __init_subclass__(cls, *str|None domain=None, **Any kwargs)
str _reconfigure_entry_id(self)
ConfigEntry _get_reconfigure_entry(self)
None _abort_if_unique_id_mismatch(self, *str reason="unique_id_mismatch", 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)
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
None _async_handle_reload(self, Any _now)
None __init__(self, HomeAssistant hass)
None _handle_entry_updated(self, Event[er.EventEntityRegistryUpdatedData] event)
OptionsFlow async_create_flow(self, str handler_key, *ConfigFlowContext|None context=None, dict[str, Any]|None data=None)
None _async_setup_preview(self, data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult] flow)
ConfigFlowResult async_finish_flow(self, data_entry_flow.FlowHandler[ConfigFlowContext, ConfigFlowResult] flow, ConfigFlowResult result)
ConfigEntry _async_get_config_entry(self, str config_entry_id)
None __init__(self, ConfigEntry config_entry)
dict[str, Any] options(self)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=None)
ConfigEntry config_entry(self)
str _config_entry_id(self)
None config_entry(self, ConfigEntry value)
_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)
list[_FlowResultT] async_progress_by_handler(self, _HandlerT handler, bool include_uninitialized=False, dict[str, Any]|None match_context=None)
FlowHandler[_FlowContextT, _FlowResultT, _HandlerT] async_create_flow(self, _HandlerT handler_key, *_FlowContextT|None context=None, dict[str, Any]|None data=None)
_FlowResultT _async_handle_step(self, FlowHandler[_FlowContextT, _FlowResultT, _HandlerT] flow, str step_id, dict|BaseServiceInfo|None user_input)
None async_post_init(self, FlowHandler[_FlowContextT, _FlowResultT, _HandlerT] flow, _FlowResultT result)
None async_abort(self, str flow_id)
None _async_add_flow_progress(self, FlowHandler[_FlowContextT, _FlowResultT, _HandlerT] flow)
bool add(self, _T matcher)
bool remove(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
dict[str, str]|None update_unique_id(er.RegistryEntry entity_entry, str unique_id)
None async_update_entry(HomeAssistant hass, ConfigEntry entry)
MealieConfigEntry async_get_entry(HomeAssistant hass, str config_entry_id)
bool _support_single_config_entry_only(HomeAssistant hass, str domain)
None _report_non_awaited_platform_forwards(ConfigEntry entry, str what)
None _async_abort_entries_match(list[ConfigEntry] other_entries, dict[str, Any]|None match_dict=None)
bool _handle_entry_updated_filter(er.EventEntityRegistryUpdatedData event_data)
bool support_entry_unload(HomeAssistant hass, str domain)
type[ConfigFlow] _async_get_flow_handler(HomeAssistant hass, str domain, ConfigType hass_config)
None _load_integration(HomeAssistant hass, str domain, ConfigType hass_config)
bool support_remove_from_device(HomeAssistant hass, str domain)
SignalType[ConfigEntry] signal_discovered_config_entry_removed(str discovery_domain)
None _validate_item(*ConfigEntryDisabler|Any|None disabled_by=None)
None async_load(HomeAssistant hass)
AreaRegistry async_get(HomeAssistant hass)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)
None async_delay_save(self, Callable[[], _T] data_func, float delay=0)
str async_suggest_report_issue(HomeAssistant|None hass, *Integration|None integration=None, str|None integration_domain=None, str|None module=None)
Generator[None] async_pause_setup(core.HomeAssistant hass, SetupPhases phase)
Generator[None] async_start_setup(core.HomeAssistant hass, str integration, SetupPhases phase, str|None group=None)
None async_process_deps_reqs(core.HomeAssistant hass, ConfigType config, loader.Integration integration)
bool async_setup_component(core.HomeAssistant hass, str domain, ConfigType config)