diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 61df9dc190d..a30d65f5982 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -304,7 +304,8 @@ async def config_entry_update(hass, connection, msg): "type": "config_entries/disable", "entry_id": str, # We only allow setting disabled_by user via API. - "disabled_by": vol.Any(config_entries.DISABLED_USER, None), + # No Enum support like this in voluptuous, use .value + "disabled_by": vol.Any(config_entries.ConfigEntryDisabler.USER.value, None), } ) async def config_entry_disable(hass, connection, msg): diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index cdea9da2540..0dd9cbee52b 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, cast import weakref from homeassistant import data_entry_flow, loader +from homeassistant.backports.enum import StrEnum from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP from homeassistant.core import CALLBACK_TYPE, CoreState, HomeAssistant, callback from homeassistant.exceptions import ( @@ -22,6 +23,7 @@ from homeassistant.exceptions import ( ) from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.event import Event +from homeassistant.helpers.frame import report from homeassistant.helpers.typing import ( UNDEFINED, ConfigType, @@ -128,7 +130,15 @@ RECONFIGURE_NOTIFICATION_ID = "config_entry_reconfigure" EVENT_FLOW_DISCOVERED = "config_entry_discovered" -DISABLED_USER = "user" + +class ConfigEntryDisabler(StrEnum): + """What disabled a config entry.""" + + USER = "user" + + +# DISABLED_* is deprecated, to be removed in 2022.3 +DISABLED_USER = ConfigEntryDisabler.USER.value RELOAD_AFTER_UPDATE_DELAY = 30 @@ -195,7 +205,7 @@ class ConfigEntry: unique_id: str | None = None, entry_id: str | None = None, state: ConfigEntryState = ConfigEntryState.NOT_LOADED, - disabled_by: str | None = None, + disabled_by: ConfigEntryDisabler | None = None, ) -> None: """Initialize a config entry.""" # Unique id of the config entry @@ -237,6 +247,16 @@ class ConfigEntry: self.unique_id = unique_id # Config entry is disabled + if isinstance(disabled_by, str) and not isinstance( + disabled_by, ConfigEntryDisabler + ): + report( # type: ignore[unreachable] + "uses str for config entry disabled_by. This is deprecated and will " + "stop working in Home Assistant 2022.3, it should be updated to use " + "ConfigEntryDisabler instead", + error_if_core=False, + ) + disabled_by = ConfigEntryDisabler(disabled_by) self.disabled_by = disabled_by # Supports unload @@ -924,7 +944,9 @@ class ConfigEntries: # New in 0.104 unique_id=entry.get("unique_id"), # New in 2021.3 - disabled_by=entry.get("disabled_by"), + disabled_by=ConfigEntryDisabler(entry["disabled_by"]) + if entry.get("disabled_by") + else None, # New in 2021.6 pref_disable_new_entities=pref_disable_new_entities, pref_disable_polling=entry.get("pref_disable_polling"), @@ -985,7 +1007,7 @@ class ConfigEntries: return await self.async_setup(entry_id) async def async_set_disabled_by( - self, entry_id: str, disabled_by: str | None + self, entry_id: str, disabled_by: ConfigEntryDisabler | None ) -> bool: """Disable an entry. @@ -994,7 +1016,18 @@ class ConfigEntries: if (entry := self.async_get_entry(entry_id)) is None: raise UnknownEntry - if entry.disabled_by == disabled_by: + if isinstance(disabled_by, str) and not isinstance( + disabled_by, ConfigEntryDisabler + ): + report( # type: ignore[unreachable] + "uses str for config entry disabled_by. This is deprecated and will " + "stop working in Home Assistant 2022.3, it should be updated to use " + "ConfigEntryDisabler instead", + error_if_core=False, + ) + disabled_by = ConfigEntryDisabler(disabled_by) + + if entry.disabled_by is disabled_by: return True entry.disabled_by = disabled_by diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 20a19495597..7f88d9b482b 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -79,7 +79,7 @@ async def test_get_entries(hass, client): domain="comp3", title="Test 3", source="bla3", - disabled_by=core_ce.DISABLED_USER, + disabled_by=core_ce.ConfigEntryDisabler.USER, ).add_to_hass(hass) resp = await client.get("/api/config/config_entries/entry") @@ -121,7 +121,7 @@ async def test_get_entries(hass, client): "supports_unload": False, "pref_disable_new_entities": False, "pref_disable_polling": False, - "disabled_by": core_ce.DISABLED_USER, + "disabled_by": core_ce.ConfigEntryDisabler.USER, "reason": None, }, ] @@ -877,14 +877,14 @@ async def test_disable_entry(hass, hass_ws_client): "id": 5, "type": "config_entries/disable", "entry_id": entry.entry_id, - "disabled_by": core_ce.DISABLED_USER, + "disabled_by": core_ce.ConfigEntryDisabler.USER, } ) response = await ws_client.receive_json() assert response["success"] assert response["result"] == {"require_restart": True} - assert entry.disabled_by == core_ce.DISABLED_USER + assert entry.disabled_by is core_ce.ConfigEntryDisabler.USER assert entry.state is core_ce.ConfigEntryState.FAILED_UNLOAD # Enable @@ -930,7 +930,7 @@ async def test_disable_entry_nonexisting(hass, hass_ws_client): "id": 5, "type": "config_entries/disable", "entry_id": "non_existing", - "disabled_by": core_ce.DISABLED_USER, + "disabled_by": core_ce.ConfigEntryDisabler.USER, } ) response = await ws_client.receive_json() diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index b2cb7bc808e..d9a1695f60f 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -10,7 +10,7 @@ from zwave_js_server.model.node import Node from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.components.zwave_js.const import DOMAIN from homeassistant.components.zwave_js.helpers import get_device_id -from homeassistant.config_entries import DISABLED_USER, ConfigEntryState +from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -554,7 +554,9 @@ async def test_stop_addon( assert entry.state is ConfigEntryState.LOADED - await hass.config_entries.async_set_disabled_by(entry.entry_id, DISABLED_USER) + await hass.config_entries.async_set_disabled_by( + entry.entry_id, ConfigEntryDisabler.USER + ) await hass.async_block_till_done() assert entry.state == entry_state diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 455c90b8f65..571fa0c097e 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1342,7 +1342,7 @@ async def test_disable_config_entry_disables_devices(hass, registry): assert entry2.disabled await hass.config_entries.async_set_disabled_by( - config_entry.entry_id, config_entries.DISABLED_USER + config_entry.entry_id, config_entries.ConfigEntryDisabler.USER ) await hass.async_block_till_done() @@ -1382,7 +1382,7 @@ async def test_only_disable_device_if_all_config_entries_are_disabled(hass, regi assert not entry1.disabled await hass.config_entries.async_set_disabled_by( - config_entry1.entry_id, config_entries.DISABLED_USER + config_entry1.entry_id, config_entries.ConfigEntryDisabler.USER ) await hass.async_block_till_done() @@ -1390,7 +1390,7 @@ async def test_only_disable_device_if_all_config_entries_are_disabled(hass, regi assert not entry1.disabled await hass.config_entries.async_set_disabled_by( - config_entry2.entry_id, config_entries.DISABLED_USER + config_entry2.entry_id, config_entries.ConfigEntryDisabler.USER ) await hass.async_block_till_done() diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index e34e33db005..50c2ea364c5 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -919,7 +919,7 @@ async def test_disable_config_entry_disables_entities(hass, registry): assert entry3.disabled await hass.config_entries.async_set_disabled_by( - config_entry.entry_id, config_entries.DISABLED_USER + config_entry.entry_id, config_entries.ConfigEntryDisabler.USER ) await hass.async_block_till_done() diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 0e743fda91e..80f2f086377 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -529,7 +529,7 @@ async def test_domains_gets_domains_excludes_ignore_and_disabled(manager): ).add_to_manager(manager) MockConfigEntry(domain="test3").add_to_manager(manager) MockConfigEntry( - domain="disabled", disabled_by=config_entries.DISABLED_USER + domain="disabled", disabled_by=config_entries.ConfigEntryDisabler.USER ).add_to_manager(manager) assert manager.async_domains() == ["test", "test2", "test3"] assert manager.async_domains(include_ignore=False) == ["test", "test2", "test3"] @@ -1323,7 +1323,7 @@ async def test_entry_disable_succeed(hass, manager): # Disable assert await manager.async_set_disabled_by( - entry.entry_id, config_entries.DISABLED_USER + entry.entry_id, config_entries.ConfigEntryDisabler.USER ) assert len(async_unload_entry.mock_calls) == 1 assert len(async_setup.mock_calls) == 0 @@ -1358,7 +1358,7 @@ async def test_entry_disable_without_reload_support(hass, manager): # Disable assert not await manager.async_set_disabled_by( - entry.entry_id, config_entries.DISABLED_USER + entry.entry_id, config_entries.ConfigEntryDisabler.USER ) assert len(async_setup.mock_calls) == 0 assert len(async_setup_entry.mock_calls) == 0 @@ -1374,7 +1374,9 @@ async def test_entry_disable_without_reload_support(hass, manager): async def test_entry_enable_without_reload_support(hass, manager): """Test that we can disable an entry without reload support.""" - entry = MockConfigEntry(domain="comp", disabled_by=config_entries.DISABLED_USER) + entry = MockConfigEntry( + domain="comp", disabled_by=config_entries.ConfigEntryDisabler.USER + ) entry.add_to_hass(hass) async_setup = AsyncMock(return_value=True) @@ -1398,7 +1400,7 @@ async def test_entry_enable_without_reload_support(hass, manager): # Disable assert not await manager.async_set_disabled_by( - entry.entry_id, config_entries.DISABLED_USER + entry.entry_id, config_entries.ConfigEntryDisabler.USER ) assert len(async_setup.mock_calls) == 1 assert len(async_setup_entry.mock_calls) == 1 @@ -2966,3 +2968,21 @@ async def test_loading_old_data(hass, hass_storage): assert entry.title == "Mock title" assert entry.data == {"my": "data"} assert entry.pref_disable_new_entities is True + + +async def test_deprecated_disabled_by_str_ctor(hass, caplog): + """Test deprecated str disabled_by constructor enumizes and logs a warning.""" + entry = MockConfigEntry(disabled_by=config_entries.ConfigEntryDisabler.USER.value) + assert entry.disabled_by is config_entries.ConfigEntryDisabler.USER + assert " str for config entry disabled_by. This is deprecated " in caplog.text + + +async def test_deprecated_disabled_by_str_set(hass, manager, caplog): + """Test deprecated str set disabled_by enumizes and logs a warning.""" + entry = MockConfigEntry() + entry.add_to_manager(manager) + assert await manager.async_set_disabled_by( + entry.entry_id, config_entries.ConfigEntryDisabler.USER.value + ) + assert entry.disabled_by is config_entries.ConfigEntryDisabler.USER + assert " str for config entry disabled_by. This is deprecated " in caplog.text