Skip to content

Commit 2cf5bad

Browse files
joostlekfrenck
authored andcommitted
Enable disabled Ollama config entries after entry migration (home-assistant#150105)
1 parent 0478f43 commit 2cf5bad

File tree

3 files changed

+513
-42
lines changed

3 files changed

+513
-42
lines changed

homeassistant/components/ollama/__init__.py

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,15 @@ async def async_update_options(hass: HomeAssistant, entry: OllamaConfigEntry) ->
9292
async def async_migrate_integration(hass: HomeAssistant) -> None:
9393
"""Migrate integration entry structure."""
9494

95-
entries = hass.config_entries.async_entries(DOMAIN)
95+
# Make sure we get enabled config entries first
96+
entries = sorted(
97+
hass.config_entries.async_entries(DOMAIN),
98+
key=lambda e: e.disabled_by is not None,
99+
)
96100
if not any(entry.version == 1 for entry in entries):
97101
return
98102

99-
api_keys_entries: dict[str, ConfigEntry] = {}
103+
url_entries: dict[str, tuple[ConfigEntry, bool]] = {}
100104
entity_registry = er.async_get(hass)
101105
device_registry = dr.async_get(hass)
102106

@@ -112,33 +116,64 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
112116
title=entry.title,
113117
unique_id=None,
114118
)
115-
if entry.data[CONF_URL] not in api_keys_entries:
119+
if entry.data[CONF_URL] not in url_entries:
116120
use_existing = True
117-
api_keys_entries[entry.data[CONF_URL]] = entry
121+
all_disabled = all(
122+
e.disabled_by is not None
123+
for e in entries
124+
if e.data[CONF_URL] == entry.data[CONF_URL]
125+
)
126+
url_entries[entry.data[CONF_URL]] = (entry, all_disabled)
118127

119-
parent_entry = api_keys_entries[entry.data[CONF_URL]]
128+
parent_entry, all_disabled = url_entries[entry.data[CONF_URL]]
120129

121130
hass.config_entries.async_add_subentry(parent_entry, subentry)
122131

123-
conversation_entity = entity_registry.async_get_entity_id(
132+
conversation_entity_id = entity_registry.async_get_entity_id(
124133
"conversation",
125134
DOMAIN,
126135
entry.entry_id,
127136
)
128-
if conversation_entity is not None:
137+
device = device_registry.async_get_device(
138+
identifiers={(DOMAIN, entry.entry_id)}
139+
)
140+
141+
if conversation_entity_id is not None:
142+
conversation_entity_entry = entity_registry.entities[conversation_entity_id]
143+
entity_disabled_by = conversation_entity_entry.disabled_by
144+
if (
145+
entity_disabled_by is er.RegistryEntryDisabler.CONFIG_ENTRY
146+
and not all_disabled
147+
):
148+
# Device and entity registries don't update the disabled_by flag
149+
# when moving a device or entity from one config entry to another,
150+
# so we need to do it manually.
151+
entity_disabled_by = (
152+
er.RegistryEntryDisabler.DEVICE
153+
if device
154+
else er.RegistryEntryDisabler.USER
155+
)
129156
entity_registry.async_update_entity(
130-
conversation_entity,
157+
conversation_entity_id,
131158
config_entry_id=parent_entry.entry_id,
132159
config_subentry_id=subentry.subentry_id,
160+
disabled_by=entity_disabled_by,
133161
new_unique_id=subentry.subentry_id,
134162
)
135163

136-
device = device_registry.async_get_device(
137-
identifiers={(DOMAIN, entry.entry_id)}
138-
)
139164
if device is not None:
165+
# Device and entity registries don't update the disabled_by flag when
166+
# moving a device or entity from one config entry to another, so we
167+
# need to do it manually.
168+
device_disabled_by = device.disabled_by
169+
if (
170+
device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY
171+
and not all_disabled
172+
):
173+
device_disabled_by = dr.DeviceEntryDisabler.USER
140174
device_registry.async_update_device(
141175
device.id,
176+
disabled_by=device_disabled_by,
142177
new_identifiers={(DOMAIN, subentry.subentry_id)},
143178
add_config_subentry_id=subentry.subentry_id,
144179
add_config_entry_id=parent_entry.entry_id,
@@ -158,14 +193,15 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
158193
if not use_existing:
159194
await hass.config_entries.async_remove(entry.entry_id)
160195
else:
196+
_add_ai_task_subentry(hass, entry)
161197
hass.config_entries.async_update_entry(
162198
entry,
163199
title=DEFAULT_NAME,
164200
# Update parent entry to only keep URL, remove model
165201
data={CONF_URL: entry.data[CONF_URL]},
166202
options={},
167203
version=3,
168-
minor_version=1,
204+
minor_version=3,
169205
)
170206

171207

@@ -211,32 +247,69 @@ async def async_migrate_entry(hass: HomeAssistant, entry: OllamaConfigEntry) ->
211247
)
212248

213249
if entry.version == 3 and entry.minor_version == 1:
214-
# Add AI Task subentry with default options. We can only create a new
215-
# subentry if we can find an existing model in the entry. The model
216-
# was removed in the previous migration step, so we need to
217-
# check the subentries for an existing model.
218-
existing_model = next(
219-
iter(
220-
model
221-
for subentry in entry.subentries.values()
222-
if (model := subentry.data.get(CONF_MODEL)) is not None
223-
),
224-
None,
225-
)
226-
if existing_model:
227-
hass.config_entries.async_add_subentry(
228-
entry,
229-
ConfigSubentry(
230-
data=MappingProxyType({CONF_MODEL: existing_model}),
231-
subentry_type="ai_task_data",
232-
title=DEFAULT_AI_TASK_NAME,
233-
unique_id=None,
234-
),
235-
)
250+
_add_ai_task_subentry(hass, entry)
236251
hass.config_entries.async_update_entry(entry, minor_version=2)
237252

253+
if entry.version == 3 and entry.minor_version == 2:
254+
# Fix migration where the disabled_by flag was not set correctly.
255+
# We can currently only correct this for enabled config entries,
256+
# because migration does not run for disabled config entries. This
257+
# is asserted in tests, and if that behavior is changed, we should
258+
# correct also disabled config entries.
259+
device_registry = dr.async_get(hass)
260+
entity_registry = er.async_get(hass)
261+
devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
262+
entity_entries = er.async_entries_for_config_entry(
263+
entity_registry, entry.entry_id
264+
)
265+
if entry.disabled_by is None:
266+
# If the config entry is not disabled, we need to set the disabled_by
267+
# flag on devices to USER, and on entities to DEVICE, if they are set
268+
# to CONFIG_ENTRY.
269+
for device in devices:
270+
if device.disabled_by is not dr.DeviceEntryDisabler.CONFIG_ENTRY:
271+
continue
272+
device_registry.async_update_device(
273+
device.id,
274+
disabled_by=dr.DeviceEntryDisabler.USER,
275+
)
276+
for entity in entity_entries:
277+
if entity.disabled_by is not er.RegistryEntryDisabler.CONFIG_ENTRY:
278+
continue
279+
entity_registry.async_update_entity(
280+
entity.entity_id,
281+
disabled_by=er.RegistryEntryDisabler.DEVICE,
282+
)
283+
hass.config_entries.async_update_entry(entry, minor_version=3)
284+
238285
_LOGGER.debug(
239286
"Migration to version %s:%s successful", entry.version, entry.minor_version
240287
)
241288

242289
return True
290+
291+
292+
def _add_ai_task_subentry(hass: HomeAssistant, entry: OllamaConfigEntry) -> None:
293+
"""Add AI Task subentry to the config entry."""
294+
# Add AI Task subentry with default options. We can only create a new
295+
# subentry if we can find an existing model in the entry. The model
296+
# was removed in the previous migration step, so we need to
297+
# check the subentries for an existing model.
298+
existing_model = next(
299+
iter(
300+
model
301+
for subentry in entry.subentries.values()
302+
if (model := subentry.data.get(CONF_MODEL)) is not None
303+
),
304+
None,
305+
)
306+
if existing_model:
307+
hass.config_entries.async_add_subentry(
308+
entry,
309+
ConfigSubentry(
310+
data=MappingProxyType({CONF_MODEL: existing_model}),
311+
subentry_type="ai_task_data",
312+
title=DEFAULT_AI_TASK_NAME,
313+
unique_id=None,
314+
),
315+
)

homeassistant/components/ollama/config_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class OllamaConfigFlow(ConfigFlow, domain=DOMAIN):
7676
"""Handle a config flow for Ollama."""
7777

7878
VERSION = 3
79-
MINOR_VERSION = 2
79+
MINOR_VERSION = 3
8080

8181
def __init__(self) -> None:
8282
"""Initialize config flow."""

0 commit comments

Comments
 (0)