@@ -195,11 +195,15 @@ async def async_update_options(
195
195
async def async_migrate_integration (hass : HomeAssistant ) -> None :
196
196
"""Migrate integration entry structure."""
197
197
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
+ )
199
203
if not any (entry .version == 1 for entry in entries ):
200
204
return
201
205
202
- api_keys_entries : dict [str , ConfigEntry ] = {}
206
+ api_keys_entries : dict [str , tuple [ ConfigEntry , bool ] ] = {}
203
207
entity_registry = er .async_get (hass )
204
208
device_registry = dr .async_get (hass )
205
209
@@ -213,9 +217,14 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
213
217
)
214
218
if entry .data [CONF_API_KEY ] not in api_keys_entries :
215
219
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 )
217
226
218
- parent_entry = api_keys_entries [entry .data [CONF_API_KEY ]]
227
+ parent_entry , all_disabled = api_keys_entries [entry .data [CONF_API_KEY ]]
219
228
220
229
hass .config_entries .async_add_subentry (parent_entry , subentry )
221
230
if use_existing :
@@ -228,25 +237,51 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
228
237
unique_id = None ,
229
238
),
230
239
)
231
- conversation_entity = entity_registry .async_get_entity_id (
240
+ conversation_entity_id = entity_registry .async_get_entity_id (
232
241
"conversation" ,
233
242
DOMAIN ,
234
243
entry .entry_id ,
235
244
)
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
+ )
237
264
entity_registry .async_update_entity (
238
- conversation_entity ,
265
+ conversation_entity_id ,
239
266
config_entry_id = parent_entry .entry_id ,
240
267
config_subentry_id = subentry .subentry_id ,
268
+ disabled_by = entity_disabled_by ,
241
269
new_unique_id = subentry .subentry_id ,
242
270
)
243
271
244
- device = device_registry .async_get_device (
245
- identifiers = {(DOMAIN , entry .entry_id )}
246
- )
247
272
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
248
282
device_registry .async_update_device (
249
283
device .id ,
284
+ disabled_by = device_disabled_by ,
250
285
new_identifiers = {(DOMAIN , subentry .subentry_id )},
251
286
add_config_subentry_id = subentry .subentry_id ,
252
287
add_config_entry_id = parent_entry .entry_id ,
@@ -266,12 +301,13 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
266
301
if not use_existing :
267
302
await hass .config_entries .async_remove (entry .entry_id )
268
303
else :
304
+ _add_ai_task_subentry (hass , entry )
269
305
hass .config_entries .async_update_entry (
270
306
entry ,
271
307
title = DEFAULT_TITLE ,
272
308
options = {},
273
309
version = 2 ,
274
- minor_version = 2 ,
310
+ minor_version = 4 ,
275
311
)
276
312
277
313
@@ -315,19 +351,58 @@ async def async_migrate_entry(
315
351
316
352
if entry .version == 2 and entry .minor_version == 2 :
317
353
# 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 )
327
355
hass .config_entries .async_update_entry (entry , minor_version = 3 )
328
356
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
+
329
389
LOGGER .debug (
330
390
"Migration to version %s:%s successful" , entry .version , entry .minor_version
331
391
)
332
392
333
393
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
+ )
0 commit comments