Skip to content

Commit 334d5f0

Browse files
Create Google Generative AI sub entries for an enabled entry (home-assistant#148161)
Co-authored-by: Erik Montnemery <[email protected]>
1 parent 9f3d890 commit 334d5f0

File tree

3 files changed

+517
-27
lines changed

3 files changed

+517
-27
lines changed

homeassistant/components/google_generative_ai_conversation/__init__.py

Lines changed: 95 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,15 @@ async def async_update_options(
195195
async def async_migrate_integration(hass: HomeAssistant) -> None:
196196
"""Migrate integration entry structure."""
197197

198-
entries = hass.config_entries.async_entries(DOMAIN)
198+
# Make sure we get enabled config entries first
199+
entries = sorted(
200+
hass.config_entries.async_entries(DOMAIN),
201+
key=lambda e: e.disabled_by is not None,
202+
)
199203
if not any(entry.version == 1 for entry in entries):
200204
return
201205

202-
api_keys_entries: dict[str, ConfigEntry] = {}
206+
api_keys_entries: dict[str, tuple[ConfigEntry, bool]] = {}
203207
entity_registry = er.async_get(hass)
204208
device_registry = dr.async_get(hass)
205209

@@ -213,9 +217,14 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
213217
)
214218
if entry.data[CONF_API_KEY] not in api_keys_entries:
215219
use_existing = True
216-
api_keys_entries[entry.data[CONF_API_KEY]] = entry
220+
all_disabled = all(
221+
e.disabled_by is not None
222+
for e in entries
223+
if e.data[CONF_API_KEY] == entry.data[CONF_API_KEY]
224+
)
225+
api_keys_entries[entry.data[CONF_API_KEY]] = (entry, all_disabled)
217226

218-
parent_entry = api_keys_entries[entry.data[CONF_API_KEY]]
227+
parent_entry, all_disabled = api_keys_entries[entry.data[CONF_API_KEY]]
219228

220229
hass.config_entries.async_add_subentry(parent_entry, subentry)
221230
if use_existing:
@@ -228,25 +237,51 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
228237
unique_id=None,
229238
),
230239
)
231-
conversation_entity = entity_registry.async_get_entity_id(
240+
conversation_entity_id = entity_registry.async_get_entity_id(
232241
"conversation",
233242
DOMAIN,
234243
entry.entry_id,
235244
)
236-
if conversation_entity is not None:
245+
device = device_registry.async_get_device(
246+
identifiers={(DOMAIN, entry.entry_id)}
247+
)
248+
249+
if conversation_entity_id is not None:
250+
conversation_entity_entry = entity_registry.entities[conversation_entity_id]
251+
entity_disabled_by = conversation_entity_entry.disabled_by
252+
if (
253+
entity_disabled_by is er.RegistryEntryDisabler.CONFIG_ENTRY
254+
and not all_disabled
255+
):
256+
# Device and entity registries don't update the disabled_by flag
257+
# when moving a device or entity from one config entry to another,
258+
# so we need to do it manually.
259+
entity_disabled_by = (
260+
er.RegistryEntryDisabler.DEVICE
261+
if device
262+
else er.RegistryEntryDisabler.USER
263+
)
237264
entity_registry.async_update_entity(
238-
conversation_entity,
265+
conversation_entity_id,
239266
config_entry_id=parent_entry.entry_id,
240267
config_subentry_id=subentry.subentry_id,
268+
disabled_by=entity_disabled_by,
241269
new_unique_id=subentry.subentry_id,
242270
)
243271

244-
device = device_registry.async_get_device(
245-
identifiers={(DOMAIN, entry.entry_id)}
246-
)
247272
if device is not None:
273+
# Device and entity registries don't update the disabled_by flag when
274+
# moving a device or entity from one config entry to another, so we
275+
# need to do it manually.
276+
device_disabled_by = device.disabled_by
277+
if (
278+
device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY
279+
and not all_disabled
280+
):
281+
device_disabled_by = dr.DeviceEntryDisabler.USER
248282
device_registry.async_update_device(
249283
device.id,
284+
disabled_by=device_disabled_by,
250285
new_identifiers={(DOMAIN, subentry.subentry_id)},
251286
add_config_subentry_id=subentry.subentry_id,
252287
add_config_entry_id=parent_entry.entry_id,
@@ -266,12 +301,13 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
266301
if not use_existing:
267302
await hass.config_entries.async_remove(entry.entry_id)
268303
else:
304+
_add_ai_task_subentry(hass, entry)
269305
hass.config_entries.async_update_entry(
270306
entry,
271307
title=DEFAULT_TITLE,
272308
options={},
273309
version=2,
274-
minor_version=2,
310+
minor_version=4,
275311
)
276312

277313

@@ -315,19 +351,58 @@ async def async_migrate_entry(
315351

316352
if entry.version == 2 and entry.minor_version == 2:
317353
# Add AI Task subentry with default options
318-
hass.config_entries.async_add_subentry(
319-
entry,
320-
ConfigSubentry(
321-
data=MappingProxyType(RECOMMENDED_AI_TASK_OPTIONS),
322-
subentry_type="ai_task_data",
323-
title=DEFAULT_AI_TASK_NAME,
324-
unique_id=None,
325-
),
326-
)
354+
_add_ai_task_subentry(hass, entry)
327355
hass.config_entries.async_update_entry(entry, minor_version=3)
328356

357+
if entry.version == 2 and entry.minor_version == 3:
358+
# Fix migration where the disabled_by flag was not set correctly.
359+
# We can currently only correct this for enabled config entries,
360+
# because migration does not run for disabled config entries. This
361+
# is asserted in tests, and if that behavior is changed, we should
362+
# correct also disabled config entries.
363+
device_registry = dr.async_get(hass)
364+
entity_registry = er.async_get(hass)
365+
devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
366+
entity_entries = er.async_entries_for_config_entry(
367+
entity_registry, entry.entry_id
368+
)
369+
if entry.disabled_by is None:
370+
# If the config entry is not disabled, we need to set the disabled_by
371+
# flag on devices to USER, and on entities to DEVICE, if they are set
372+
# to CONFIG_ENTRY.
373+
for device in devices:
374+
if device.disabled_by is not dr.DeviceEntryDisabler.CONFIG_ENTRY:
375+
continue
376+
device_registry.async_update_device(
377+
device.id,
378+
disabled_by=dr.DeviceEntryDisabler.USER,
379+
)
380+
for entity in entity_entries:
381+
if entity.disabled_by is not er.RegistryEntryDisabler.CONFIG_ENTRY:
382+
continue
383+
entity_registry.async_update_entity(
384+
entity.entity_id,
385+
disabled_by=er.RegistryEntryDisabler.DEVICE,
386+
)
387+
hass.config_entries.async_update_entry(entry, minor_version=4)
388+
329389
LOGGER.debug(
330390
"Migration to version %s:%s successful", entry.version, entry.minor_version
331391
)
332392

333393
return True
394+
395+
396+
def _add_ai_task_subentry(
397+
hass: HomeAssistant, entry: GoogleGenerativeAIConfigEntry
398+
) -> None:
399+
"""Add AI Task subentry to the config entry."""
400+
hass.config_entries.async_add_subentry(
401+
entry,
402+
ConfigSubentry(
403+
data=MappingProxyType(RECOMMENDED_AI_TASK_OPTIONS),
404+
subentry_type="ai_task_data",
405+
title=DEFAULT_AI_TASK_NAME,
406+
unique_id=None,
407+
),
408+
)

homeassistant/components/google_generative_ai_conversation/config_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class GoogleGenerativeAIConfigFlow(ConfigFlow, domain=DOMAIN):
9797
"""Handle a config flow for Google Generative AI Conversation."""
9898

9999
VERSION = 2
100-
MINOR_VERSION = 3
100+
MINOR_VERSION = 4
101101

102102
async def async_step_api(
103103
self, user_input: dict[str, Any] | None = None

0 commit comments

Comments
 (0)