Skip to content

Commit 9820956

Browse files
joostlekfrenck
authored andcommitted
Enable disabled OpenAI config entries after entry migration (home-assistant#150099)
1 parent 1693299 commit 9820956

File tree

3 files changed

+501
-27
lines changed

3 files changed

+501
-27
lines changed

homeassistant/components/openai_conversation/__init__.py

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,15 @@ async def async_update_options(hass: HomeAssistant, entry: OpenAIConfigEntry) ->
272272
async def async_migrate_integration(hass: HomeAssistant) -> None:
273273
"""Migrate integration entry structure."""
274274

275-
entries = hass.config_entries.async_entries(DOMAIN)
275+
# Make sure we get enabled config entries first
276+
entries = sorted(
277+
hass.config_entries.async_entries(DOMAIN),
278+
key=lambda e: e.disabled_by is not None,
279+
)
276280
if not any(entry.version == 1 for entry in entries):
277281
return
278282

279-
api_keys_entries: dict[str, ConfigEntry] = {}
283+
api_keys_entries: dict[str, tuple[ConfigEntry, bool]] = {}
280284
entity_registry = er.async_get(hass)
281285
device_registry = dr.async_get(hass)
282286

@@ -290,30 +294,61 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
290294
)
291295
if entry.data[CONF_API_KEY] not in api_keys_entries:
292296
use_existing = True
293-
api_keys_entries[entry.data[CONF_API_KEY]] = entry
297+
all_disabled = all(
298+
e.disabled_by is not None
299+
for e in entries
300+
if e.data[CONF_API_KEY] == entry.data[CONF_API_KEY]
301+
)
302+
api_keys_entries[entry.data[CONF_API_KEY]] = (entry, all_disabled)
294303

295-
parent_entry = api_keys_entries[entry.data[CONF_API_KEY]]
304+
parent_entry, all_disabled = api_keys_entries[entry.data[CONF_API_KEY]]
296305

297306
hass.config_entries.async_add_subentry(parent_entry, subentry)
298-
conversation_entity = entity_registry.async_get_entity_id(
307+
conversation_entity_id = entity_registry.async_get_entity_id(
299308
"conversation",
300309
DOMAIN,
301310
entry.entry_id,
302311
)
303-
if conversation_entity is not None:
312+
device = device_registry.async_get_device(
313+
identifiers={(DOMAIN, entry.entry_id)}
314+
)
315+
316+
if conversation_entity_id is not None:
317+
conversation_entity_entry = entity_registry.entities[conversation_entity_id]
318+
entity_disabled_by = conversation_entity_entry.disabled_by
319+
if (
320+
entity_disabled_by is er.RegistryEntryDisabler.CONFIG_ENTRY
321+
and not all_disabled
322+
):
323+
# Device and entity registries don't update the disabled_by flag
324+
# when moving a device or entity from one config entry to another,
325+
# so we need to do it manually.
326+
entity_disabled_by = (
327+
er.RegistryEntryDisabler.DEVICE
328+
if device
329+
else er.RegistryEntryDisabler.USER
330+
)
304331
entity_registry.async_update_entity(
305-
conversation_entity,
332+
conversation_entity_id,
306333
config_entry_id=parent_entry.entry_id,
307334
config_subentry_id=subentry.subentry_id,
335+
disabled_by=entity_disabled_by,
308336
new_unique_id=subentry.subentry_id,
309337
)
310338

311-
device = device_registry.async_get_device(
312-
identifiers={(DOMAIN, entry.entry_id)}
313-
)
314339
if device is not None:
340+
# Device and entity registries don't update the disabled_by flag when
341+
# moving a device or entity from one config entry to another, so we
342+
# need to do it manually.
343+
device_disabled_by = device.disabled_by
344+
if (
345+
device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY
346+
and not all_disabled
347+
):
348+
device_disabled_by = dr.DeviceEntryDisabler.USER
315349
device_registry.async_update_device(
316350
device.id,
351+
disabled_by=device_disabled_by,
317352
new_identifiers={(DOMAIN, subentry.subentry_id)},
318353
add_config_subentry_id=subentry.subentry_id,
319354
add_config_entry_id=parent_entry.entry_id,
@@ -333,12 +368,13 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
333368
if not use_existing:
334369
await hass.config_entries.async_remove(entry.entry_id)
335370
else:
371+
_add_ai_task_subentry(hass, entry)
336372
hass.config_entries.async_update_entry(
337373
entry,
338374
title=DEFAULT_NAME,
339375
options={},
340376
version=2,
341-
minor_version=2,
377+
minor_version=4,
342378
)
343379

344380

@@ -365,19 +401,56 @@ async def async_migrate_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) ->
365401
hass.config_entries.async_update_entry(entry, minor_version=2)
366402

367403
if entry.version == 2 and entry.minor_version == 2:
368-
hass.config_entries.async_add_subentry(
369-
entry,
370-
ConfigSubentry(
371-
data=MappingProxyType(RECOMMENDED_AI_TASK_OPTIONS),
372-
subentry_type="ai_task_data",
373-
title=DEFAULT_AI_TASK_NAME,
374-
unique_id=None,
375-
),
376-
)
404+
_add_ai_task_subentry(hass, entry)
377405
hass.config_entries.async_update_entry(entry, minor_version=3)
378406

407+
if entry.version == 2 and entry.minor_version == 3:
408+
# Fix migration where the disabled_by flag was not set correctly.
409+
# We can currently only correct this for enabled config entries,
410+
# because migration does not run for disabled config entries. This
411+
# is asserted in tests, and if that behavior is changed, we should
412+
# correct also disabled config entries.
413+
device_registry = dr.async_get(hass)
414+
entity_registry = er.async_get(hass)
415+
devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
416+
entity_entries = er.async_entries_for_config_entry(
417+
entity_registry, entry.entry_id
418+
)
419+
if entry.disabled_by is None:
420+
# If the config entry is not disabled, we need to set the disabled_by
421+
# flag on devices to USER, and on entities to DEVICE, if they are set
422+
# to CONFIG_ENTRY.
423+
for device in devices:
424+
if device.disabled_by is not dr.DeviceEntryDisabler.CONFIG_ENTRY:
425+
continue
426+
device_registry.async_update_device(
427+
device.id,
428+
disabled_by=dr.DeviceEntryDisabler.USER,
429+
)
430+
for entity in entity_entries:
431+
if entity.disabled_by is not er.RegistryEntryDisabler.CONFIG_ENTRY:
432+
continue
433+
entity_registry.async_update_entity(
434+
entity.entity_id,
435+
disabled_by=er.RegistryEntryDisabler.DEVICE,
436+
)
437+
hass.config_entries.async_update_entry(entry, minor_version=4)
438+
379439
LOGGER.debug(
380440
"Migration to version %s:%s successful", entry.version, entry.minor_version
381441
)
382442

383443
return True
444+
445+
446+
def _add_ai_task_subentry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> None:
447+
"""Add AI Task subentry to the config entry."""
448+
hass.config_entries.async_add_subentry(
449+
entry,
450+
ConfigSubentry(
451+
data=MappingProxyType(RECOMMENDED_AI_TASK_OPTIONS),
452+
subentry_type="ai_task_data",
453+
title=DEFAULT_AI_TASK_NAME,
454+
unique_id=None,
455+
),
456+
)

homeassistant/components/openai_conversation/config_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class OpenAIConfigFlow(ConfigFlow, domain=DOMAIN):
9898
"""Handle a config flow for OpenAI Conversation."""
9999

100100
VERSION = 2
101-
MINOR_VERSION = 3
101+
MINOR_VERSION = 4
102102

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

0 commit comments

Comments
 (0)