1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Reinitialize zeroconf discovery flow on unignore (#125753)

* Reinitialize zeroconf discovery flow on unignore

* Adjust tests

* Improve comments

* Fix logic for updating discovery keys

* Add tests

* Use mock_config_flow helper in new config_entries test

* Add discovery_keys attribute to ConfigEntry

* Update zeroconf rediscovery

* Change type of ConfigEntry.discovery_keys

* Update tests

* Fix DiscoveryKey.from_json_dict and add tests

* Fix test

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Erik Montnemery
2024-09-23 16:49:21 +02:00
committed by GitHub
parent f5852b4678
commit b2982c18bb
97 changed files with 987 additions and 26 deletions

View File

@@ -49,6 +49,7 @@ from .exceptions import (
)
from .helpers import device_registry, entity_registry, issue_registry as ir, storage
from .helpers.debounce import Debouncer
from .helpers.discovery_flow import DiscoveryKey
from .helpers.dispatcher import SignalType, async_dispatcher_send_internal
from .helpers.event import (
RANDOM_MICROSECOND_MAX,
@@ -120,7 +121,7 @@ HANDLERS: Registry[str, type[ConfigFlow]] = Registry()
STORAGE_KEY = "core.config_entries"
STORAGE_VERSION = 1
STORAGE_VERSION_MINOR = 3
STORAGE_VERSION_MINOR = 4
SAVE_DELAY = 1
@@ -317,6 +318,7 @@ class ConfigEntry(Generic[_DataT]):
_tries: int
created_at: datetime
modified_at: datetime
discovery_keys: tuple[DiscoveryKey, ...]
def __init__(
self,
@@ -324,6 +326,7 @@ class ConfigEntry(Generic[_DataT]):
created_at: datetime | None = None,
data: Mapping[str, Any],
disabled_by: ConfigEntryDisabler | None = None,
discovery_keys: tuple[DiscoveryKey, ...],
domain: str,
entry_id: str | None = None,
minor_version: int,
@@ -422,6 +425,7 @@ class ConfigEntry(Generic[_DataT]):
_setter(self, "_tries", 0)
_setter(self, "created_at", created_at or utcnow())
_setter(self, "modified_at", modified_at or utcnow())
_setter(self, "discovery_keys", discovery_keys)
def __repr__(self) -> str:
"""Representation of ConfigEntry."""
@@ -951,6 +955,7 @@ class ConfigEntry(Generic[_DataT]):
return {
"created_at": self.created_at.isoformat(),
"data": dict(self.data),
"discovery_keys": self.discovery_keys,
"disabled_by": self.disabled_by,
"domain": self.domain,
"entry_id": self.entry_id,
@@ -1364,6 +1369,30 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]):
ir.async_delete_issue(self.hass, HOMEASSISTANT_DOMAIN, issue_id)
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
# If there's an ignored config entry with a matching unique ID,
# update the discovery key.
if (
(discovery_key := flow.context.get("discovery_key"))
and (unique_id := flow.unique_id) is not None
and (
entry := self.config_entries.async_entry_for_domain_unique_id(
result["handler"], unique_id
)
)
and entry.source == SOURCE_IGNORE
and discovery_key not in (known_discovery_keys := entry.discovery_keys)
):
new_discovery_keys = tuple([*known_discovery_keys, discovery_key][-10:])
_LOGGER.debug(
"Updating discovery keys for %s entry %s %s -> %s",
entry.domain,
unique_id,
known_discovery_keys,
new_discovery_keys,
)
self.config_entries.async_update_entry(
entry, discovery_keys=new_discovery_keys
)
return result
# Avoid adding a config entry for a integration
@@ -1420,8 +1449,11 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]):
if existing_entry is not None and existing_entry.state.recoverable:
await self.config_entries.async_unload(existing_entry.entry_id)
discovery_key = flow.context.get("discovery_key")
discovery_keys = (discovery_key,) if discovery_key else ()
entry = ConfigEntry(
data=result["data"],
discovery_keys=discovery_keys,
domain=result["handler"],
minor_version=result["minor_version"],
options=result["options"],
@@ -1649,6 +1681,11 @@ class ConfigEntryStore(storage.Store[dict[str, list[dict[str, Any]]]]):
for entry in data["entries"]:
entry["created_at"] = entry["modified_at"] = created_at
if old_minor_version < 4:
# Version 1.4 adds discovery_keys
for entry in data["entries"]:
entry["discovery_keys"] = []
if old_major_version > 1:
raise NotImplementedError
return data
@@ -1836,6 +1873,9 @@ class ConfigEntries:
created_at=datetime.fromisoformat(entry["created_at"]),
data=entry["data"],
disabled_by=try_parse_enum(ConfigEntryDisabler, entry["disabled_by"]),
discovery_keys=tuple(
DiscoveryKey.from_json_dict(key) for key in entry["discovery_keys"]
),
domain=entry["domain"],
entry_id=entry_id,
minor_version=entry["minor_version"],
@@ -1992,6 +2032,7 @@ class ConfigEntries:
entry: ConfigEntry,
*,
data: Mapping[str, Any] | UndefinedType = UNDEFINED,
discovery_keys: tuple[DiscoveryKey, ...] | UndefinedType = UNDEFINED,
minor_version: int | UndefinedType = UNDEFINED,
options: Mapping[str, Any] | UndefinedType = UNDEFINED,
pref_disable_new_entities: bool | UndefinedType = UNDEFINED,
@@ -2021,6 +2062,7 @@ class ConfigEntries:
changed = True
for attr, value in (
("discovery_keys", discovery_keys),
("minor_version", minor_version),
("pref_disable_new_entities", pref_disable_new_entities),
("pref_disable_polling", pref_disable_polling),
@@ -2451,7 +2493,20 @@ class ConfigFlow(ConfigEntryBaseFlow):
]
async def async_step_ignore(self, user_input: dict[str, Any]) -> ConfigFlowResult:
"""Ignore this config flow."""
"""Ignore this config flow.
Ignoring a config flow works by creating a config entry with source set to
SOURCE_IGNORE.
There will only be a single active discovery flow per device, also when the
integration has multiple discovery sources for the same device. This method
is called when the user ignores a discovered device or service, we then store
the key for the flow being ignored.
Once the ignore config entry is created, ConfigEntriesFlowManager.async_finish_flow
will make sure the discovery key is kept up to date since it may not be stable
unlike the unique id.
"""
await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False)
return self.async_create_entry(title=user_input["title"], data={})