Setup fails with TypeError: argument of type 'NoneType' is not a container or iterable when legacy map_objects option is None
Environment
|
|
| Integration version |
v2.0.0b25 (also affects b20–b24) |
| Home Assistant Core |
2026.9.0 |
| Installation type |
Home Assistant OS |
| Device model |
dreame.vacuum.r2211o, firmware 4.3.9_1406 |
| Connection type |
local only (no Mi cloud account on the entry) |
Description
Two of my four config entries fail to set up with setup_error. Both are the ones configured without a cloud account. The two cloud-configured entries (same integration, one of them the exact same model and firmware) load fine.
The failure happens in the map-objects migration in coordinator.py.
Traceback
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config_entries.py", line 803, in __async_setup_with_context
result = await component.async_setup_entry(hass, self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/config/custom_components/dreame_vacuum/__init__.py", line 39, in async_setup_entry
coordinator = DreameVacuumDataUpdateCoordinator(hass, entry=entry)
File "/config/custom_components/dreame_vacuum/coordinator.py", line 143, in __init__
if key not in options[CONF_MAP_OBJECTS] and key != "curtain" and key != "ramp":
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: argument of type 'NoneType' is not a container or iterable
Root cause
coordinator.py (around line 140) guards the migration with a key-existence check instead of a value check:
## Migration: Convert map objects to hidden map objects
if CONF_MAP_OBJECTS in entry.options and CONF_HIDDEN_MAP_OBJECTS not in options:
options[CONF_HIDDEN_MAP_OBJECTS] = []
for key in list(MAP_OBJECTS.keys()):
if key not in options[CONF_MAP_OBJECTS] and key != "curtain" and key != "ramp":
options[CONF_HIDDEN_MAP_OBJECTS].append(key)
del options[CONF_MAP_OBJECTS]
On affected entries the option is present but its value is None:
"options": { "map_objects": null, ... }
so key not in None raises.
Where the None comes from
This is self-inflicted by the config flow. In async_step_options the options dict is built with unconditional .get() calls:
self.options = {
CONF_NOTIFY: user_input[CONF_NOTIFY],
CONF_COLOR_SCHEME: user_input.get(CONF_COLOR_SCHEME),
CONF_ICON_SET: user_input.get(CONF_ICON_SET),
CONF_HIDDEN_MAP_OBJECTS: user_input.get(CONF_HIDDEN_MAP_OBJECTS),
CONF_SQUARE: user_input.get(CONF_SQUARE),
CONF_LOW_RESOLUTION: user_input.get(CONF_LOW_RESOLUTION),
CONF_PREFER_CLOUD: self.prefer_cloud,
}
but the corresponding schema fields are only added for non-local accounts:
if self.account_type != ACCOUNT_TYPE_LOCAL:
data_schema = data_schema.extend({ ... CONF_HIDDEN_MAP_OBJECTS ... })
For a local-only setup those keys are never in user_input, so .get() stores None. Older versions did the same with CONF_MAP_OBJECTS, which is exactly the value that now breaks the migration.
This still happens today with CONF_HIDDEN_MAP_OBJECTS. A freshly created local entry gets "hidden_map_objects": null. It does not crash right now, because the config flow also writes CONF_VERSION, which makes the migration block unreachable. But it is passed straight into the renderer in camera.py:
hidden_map_objects = entry.options.get(CONF_HIDDEN_MAP_OBJECTS, []) # returns None, not []
...
objects = hidden_map_objects
self._renderer = DreameVacuumMapRenderer(color_scheme, icon_set, objects, ...)
The .get(..., []) default does not help when the key exists with value None.
Steps to reproduce
- Add a device using a local connection (host + token, no cloud account) with a version that still wrote
map_objects.
- Update the integration to b20 or later.
- Restart Home Assistant.
- The entry fails with the traceback above; all its entities become
unavailable.
Suggested fix
Check the value, not just the key, in coordinator.py:
-if CONF_MAP_OBJECTS in entry.options and CONF_HIDDEN_MAP_OBJECTS not in options:
+if entry.options.get(CONF_MAP_OBJECTS) and CONF_HIDDEN_MAP_OBJECTS not in options:
I applied this locally and both entries set up correctly again. Since the block also sets CONF_VERSION, the migration is then marked as done and the patch is only needed once.
To fix the underlying cause as well, the config flow should not persist None for fields it never presented, e.g.:
CONF_HIDDEN_MAP_OBJECTS: user_input.get(CONF_HIDDEN_MAP_OBJECTS) or [],
and camera.py would be more robust as:
hidden_map_objects = entry.options.get(CONF_HIDDEN_MAP_OBJECTS) or []
Workaround for other users
Users hitting this can either apply the one-line patch above, or delete and re-create the affected config entries (a newly created entry sets CONF_VERSION, so the migration is skipped entirely).
Setup fails with
TypeError: argument of type 'NoneType' is not a container or iterablewhen legacymap_objectsoption isNoneEnvironment
dreame.vacuum.r2211o, firmware 4.3.9_1406Description
Two of my four config entries fail to set up with
setup_error. Both are the ones configured without a cloud account. The two cloud-configured entries (same integration, one of them the exact same model and firmware) load fine.The failure happens in the map-objects migration in
coordinator.py.Traceback
Root cause
coordinator.py(around line 140) guards the migration with a key-existence check instead of a value check:On affected entries the option is present but its value is
None:so
key not in Noneraises.Where the
Nonecomes fromThis is self-inflicted by the config flow. In
async_step_optionsthe options dict is built with unconditional.get()calls:but the corresponding schema fields are only added for non-local accounts:
For a local-only setup those keys are never in
user_input, so.get()storesNone. Older versions did the same withCONF_MAP_OBJECTS, which is exactly the value that now breaks the migration.This still happens today with
CONF_HIDDEN_MAP_OBJECTS. A freshly created local entry gets"hidden_map_objects": null. It does not crash right now, because the config flow also writesCONF_VERSION, which makes the migration block unreachable. But it is passed straight into the renderer incamera.py:The
.get(..., [])default does not help when the key exists with valueNone.Steps to reproduce
map_objects.unavailable.Suggested fix
Check the value, not just the key, in
coordinator.py:I applied this locally and both entries set up correctly again. Since the block also sets
CONF_VERSION, the migration is then marked as done and the patch is only needed once.To fix the underlying cause as well, the config flow should not persist
Nonefor fields it never presented, e.g.:and
camera.pywould be more robust as:Workaround for other users
Users hitting this can either apply the one-line patch above, or delete and re-create the affected config entries (a newly created entry sets
CONF_VERSION, so the migration is skipped entirely).