mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add system option to disable polling (#51299)
This commit is contained in:
parent
5d6b6deed4
commit
4821484d2c
@ -31,7 +31,6 @@ async def async_setup(hass):
|
|||||||
hass.components.websocket_api.async_register_command(config_entry_disable)
|
hass.components.websocket_api.async_register_command(config_entry_disable)
|
||||||
hass.components.websocket_api.async_register_command(config_entry_update)
|
hass.components.websocket_api.async_register_command(config_entry_update)
|
||||||
hass.components.websocket_api.async_register_command(config_entries_progress)
|
hass.components.websocket_api.async_register_command(config_entries_progress)
|
||||||
hass.components.websocket_api.async_register_command(system_options_list)
|
|
||||||
hass.components.websocket_api.async_register_command(system_options_update)
|
hass.components.websocket_api.async_register_command(system_options_update)
|
||||||
hass.components.websocket_api.async_register_command(ignore_config_flow)
|
hass.components.websocket_api.async_register_command(ignore_config_flow)
|
||||||
|
|
||||||
@ -231,20 +230,6 @@ def config_entries_progress(hass, connection, msg):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.require_admin
|
|
||||||
@websocket_api.async_response
|
|
||||||
@websocket_api.websocket_command(
|
|
||||||
{"type": "config_entries/system_options/list", "entry_id": str}
|
|
||||||
)
|
|
||||||
async def system_options_list(hass, connection, msg):
|
|
||||||
"""List all system options for a config entry."""
|
|
||||||
entry_id = msg["entry_id"]
|
|
||||||
entry = hass.config_entries.async_get_entry(entry_id)
|
|
||||||
|
|
||||||
if entry:
|
|
||||||
connection.send_result(msg["id"], entry.system_options.as_dict())
|
|
||||||
|
|
||||||
|
|
||||||
def send_entry_not_found(connection, msg_id):
|
def send_entry_not_found(connection, msg_id):
|
||||||
"""Send Config entry not found error."""
|
"""Send Config entry not found error."""
|
||||||
connection.send_error(
|
connection.send_error(
|
||||||
@ -267,6 +252,7 @@ def get_entry(hass, connection, entry_id, msg_id):
|
|||||||
"type": "config_entries/system_options/update",
|
"type": "config_entries/system_options/update",
|
||||||
"entry_id": str,
|
"entry_id": str,
|
||||||
vol.Optional("disable_new_entities"): bool,
|
vol.Optional("disable_new_entities"): bool,
|
||||||
|
vol.Optional("disable_polling"): bool,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
async def system_options_update(hass, connection, msg):
|
async def system_options_update(hass, connection, msg):
|
||||||
@ -280,8 +266,25 @@ async def system_options_update(hass, connection, msg):
|
|||||||
if entry is None:
|
if entry is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
old_disable_polling = entry.system_options.disable_polling
|
||||||
|
|
||||||
hass.config_entries.async_update_entry(entry, system_options=changes)
|
hass.config_entries.async_update_entry(entry, system_options=changes)
|
||||||
connection.send_result(msg["id"], entry.system_options.as_dict())
|
|
||||||
|
result = {
|
||||||
|
"system_options": entry.system_options.as_dict(),
|
||||||
|
"require_restart": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
old_disable_polling != entry.system_options.disable_polling
|
||||||
|
and entry.state is config_entries.ConfigEntryState.LOADED
|
||||||
|
):
|
||||||
|
if not await hass.config_entries.async_reload(entry.entry_id):
|
||||||
|
result["require_restart"] = (
|
||||||
|
entry.state is config_entries.ConfigEntryState.FAILED_UNLOAD
|
||||||
|
)
|
||||||
|
|
||||||
|
connection.send_result(msg["id"], result)
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.require_admin
|
@websocket_api.require_admin
|
||||||
@ -388,6 +391,7 @@ def entry_json(entry: config_entries.ConfigEntry) -> dict:
|
|||||||
"state": entry.state.value,
|
"state": entry.state.value,
|
||||||
"supports_options": supports_options,
|
"supports_options": supports_options,
|
||||||
"supports_unload": entry.supports_unload,
|
"supports_unload": entry.supports_unload,
|
||||||
|
"system_options": entry.system_options.as_dict(),
|
||||||
"disabled_by": entry.disabled_by,
|
"disabled_by": entry.disabled_by,
|
||||||
"reason": entry.reason,
|
"reason": entry.reason,
|
||||||
}
|
}
|
||||||
|
@ -994,12 +994,10 @@ class ConfigEntries:
|
|||||||
changed = True
|
changed = True
|
||||||
entry.options = MappingProxyType(options)
|
entry.options = MappingProxyType(options)
|
||||||
|
|
||||||
if (
|
if system_options is not UNDEFINED:
|
||||||
system_options is not UNDEFINED
|
old_system_options = entry.system_options.as_dict()
|
||||||
and entry.system_options.as_dict() != system_options
|
|
||||||
):
|
|
||||||
changed = True
|
|
||||||
entry.system_options.update(**system_options)
|
entry.system_options.update(**system_options)
|
||||||
|
changed = entry.system_options.as_dict() != old_system_options
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return False
|
return False
|
||||||
@ -1408,14 +1406,27 @@ class SystemOptions:
|
|||||||
"""Config entry system options."""
|
"""Config entry system options."""
|
||||||
|
|
||||||
disable_new_entities: bool = attr.ib(default=False)
|
disable_new_entities: bool = attr.ib(default=False)
|
||||||
|
disable_polling: bool = attr.ib(default=False)
|
||||||
|
|
||||||
def update(self, *, disable_new_entities: bool) -> None:
|
def update(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
disable_new_entities: bool | UndefinedType = UNDEFINED,
|
||||||
|
disable_polling: bool | UndefinedType = UNDEFINED,
|
||||||
|
) -> None:
|
||||||
"""Update properties."""
|
"""Update properties."""
|
||||||
self.disable_new_entities = disable_new_entities
|
if disable_new_entities is not UNDEFINED:
|
||||||
|
self.disable_new_entities = disable_new_entities
|
||||||
|
|
||||||
|
if disable_polling is not UNDEFINED:
|
||||||
|
self.disable_polling = disable_polling
|
||||||
|
|
||||||
def as_dict(self) -> dict[str, Any]:
|
def as_dict(self) -> dict[str, Any]:
|
||||||
"""Return dictionary version of this config entries system options."""
|
"""Return dictionary version of this config entries system options."""
|
||||||
return {"disable_new_entities": self.disable_new_entities}
|
return {
|
||||||
|
"disable_new_entities": self.disable_new_entities,
|
||||||
|
"disable_polling": self.disable_polling,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EntityRegistryDisabledHandler:
|
class EntityRegistryDisabledHandler:
|
||||||
|
@ -214,6 +214,7 @@ class EntityPlatform:
|
|||||||
@callback
|
@callback
|
||||||
def async_create_setup_task() -> Coroutine:
|
def async_create_setup_task() -> Coroutine:
|
||||||
"""Get task to set up platform."""
|
"""Get task to set up platform."""
|
||||||
|
config_entries.current_entry.set(config_entry)
|
||||||
return platform.async_setup_entry( # type: ignore[no-any-return,union-attr]
|
return platform.async_setup_entry( # type: ignore[no-any-return,union-attr]
|
||||||
self.hass, config_entry, self._async_schedule_add_entities
|
self.hass, config_entry, self._async_schedule_add_entities
|
||||||
)
|
)
|
||||||
@ -395,8 +396,10 @@ class EntityPlatform:
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if self._async_unsub_polling is not None or not any(
|
if (
|
||||||
entity.should_poll for entity in self.entities.values()
|
(self.config_entry and self.config_entry.system_options.disable_polling)
|
||||||
|
or self._async_unsub_polling is not None
|
||||||
|
or not any(entity.should_poll for entity in self.entities.values())
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ class DataUpdateCoordinator(Generic[T]):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.update_method = update_method
|
self.update_method = update_method
|
||||||
self.update_interval = update_interval
|
self.update_interval = update_interval
|
||||||
|
self.config_entry = config_entries.current_entry.get()
|
||||||
|
|
||||||
# It's None before the first successful update.
|
# It's None before the first successful update.
|
||||||
# Components should call async_config_entry_first_refresh
|
# Components should call async_config_entry_first_refresh
|
||||||
@ -110,6 +111,9 @@ class DataUpdateCoordinator(Generic[T]):
|
|||||||
if self.update_interval is None:
|
if self.update_interval is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.config_entry and self.config_entry.system_options.disable_polling:
|
||||||
|
return
|
||||||
|
|
||||||
if self._unsub_refresh:
|
if self._unsub_refresh:
|
||||||
self._unsub_refresh()
|
self._unsub_refresh()
|
||||||
self._unsub_refresh = None
|
self._unsub_refresh = None
|
||||||
@ -229,9 +233,8 @@ class DataUpdateCoordinator(Generic[T]):
|
|||||||
if raise_on_auth_failed:
|
if raise_on_auth_failed:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
config_entry = config_entries.current_entry.get()
|
if self.config_entry:
|
||||||
if config_entry:
|
self.config_entry.async_start_reauth(self.hass)
|
||||||
config_entry.async_start_reauth(self.hass)
|
|
||||||
except NotImplementedError as err:
|
except NotImplementedError as err:
|
||||||
self.last_exception = err
|
self.last_exception = err
|
||||||
raise err
|
raise err
|
||||||
|
@ -87,6 +87,10 @@ async def test_get_entries(hass, client):
|
|||||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||||
"supports_options": True,
|
"supports_options": True,
|
||||||
"supports_unload": True,
|
"supports_unload": True,
|
||||||
|
"system_options": {
|
||||||
|
"disable_new_entities": False,
|
||||||
|
"disable_polling": False,
|
||||||
|
},
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"reason": None,
|
"reason": None,
|
||||||
},
|
},
|
||||||
@ -97,6 +101,10 @@ async def test_get_entries(hass, client):
|
|||||||
"state": core_ce.ConfigEntryState.SETUP_ERROR.value,
|
"state": core_ce.ConfigEntryState.SETUP_ERROR.value,
|
||||||
"supports_options": False,
|
"supports_options": False,
|
||||||
"supports_unload": False,
|
"supports_unload": False,
|
||||||
|
"system_options": {
|
||||||
|
"disable_new_entities": False,
|
||||||
|
"disable_polling": False,
|
||||||
|
},
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"reason": "Unsupported API",
|
"reason": "Unsupported API",
|
||||||
},
|
},
|
||||||
@ -107,6 +115,10 @@ async def test_get_entries(hass, client):
|
|||||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||||
"supports_options": False,
|
"supports_options": False,
|
||||||
"supports_unload": False,
|
"supports_unload": False,
|
||||||
|
"system_options": {
|
||||||
|
"disable_new_entities": False,
|
||||||
|
"disable_polling": False,
|
||||||
|
},
|
||||||
"disabled_by": core_ce.DISABLED_USER,
|
"disabled_by": core_ce.DISABLED_USER,
|
||||||
"reason": None,
|
"reason": None,
|
||||||
},
|
},
|
||||||
@ -328,6 +340,10 @@ async def test_create_account(hass, client, enable_custom_integrations):
|
|||||||
"state": core_ce.ConfigEntryState.LOADED.value,
|
"state": core_ce.ConfigEntryState.LOADED.value,
|
||||||
"supports_options": False,
|
"supports_options": False,
|
||||||
"supports_unload": False,
|
"supports_unload": False,
|
||||||
|
"system_options": {
|
||||||
|
"disable_new_entities": False,
|
||||||
|
"disable_polling": False,
|
||||||
|
},
|
||||||
"title": "Test Entry",
|
"title": "Test Entry",
|
||||||
"reason": None,
|
"reason": None,
|
||||||
},
|
},
|
||||||
@ -399,6 +415,10 @@ async def test_two_step_flow(hass, client, enable_custom_integrations):
|
|||||||
"state": core_ce.ConfigEntryState.LOADED.value,
|
"state": core_ce.ConfigEntryState.LOADED.value,
|
||||||
"supports_options": False,
|
"supports_options": False,
|
||||||
"supports_unload": False,
|
"supports_unload": False,
|
||||||
|
"system_options": {
|
||||||
|
"disable_new_entities": False,
|
||||||
|
"disable_polling": False,
|
||||||
|
},
|
||||||
"title": "user-title",
|
"title": "user-title",
|
||||||
"reason": None,
|
"reason": None,
|
||||||
},
|
},
|
||||||
@ -678,35 +698,17 @@ async def test_two_step_options_flow(hass, client):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_list_system_options(hass, hass_ws_client):
|
|
||||||
"""Test that we can list an entries system options."""
|
|
||||||
assert await async_setup_component(hass, "config", {})
|
|
||||||
ws_client = await hass_ws_client(hass)
|
|
||||||
|
|
||||||
entry = MockConfigEntry(domain="demo")
|
|
||||||
entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
await ws_client.send_json(
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "config_entries/system_options/list",
|
|
||||||
"entry_id": entry.entry_id,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await ws_client.receive_json()
|
|
||||||
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == {"disable_new_entities": False}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_update_system_options(hass, hass_ws_client):
|
async def test_update_system_options(hass, hass_ws_client):
|
||||||
"""Test that we can update system options."""
|
"""Test that we can update system options."""
|
||||||
assert await async_setup_component(hass, "config", {})
|
assert await async_setup_component(hass, "config", {})
|
||||||
ws_client = await hass_ws_client(hass)
|
ws_client = await hass_ws_client(hass)
|
||||||
|
|
||||||
entry = MockConfigEntry(domain="demo")
|
entry = MockConfigEntry(domain="demo", state=core_ce.ConfigEntryState.LOADED)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert entry.system_options.disable_new_entities is False
|
||||||
|
assert entry.system_options.disable_polling is False
|
||||||
|
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
@ -718,8 +720,31 @@ async def test_update_system_options(hass, hass_ws_client):
|
|||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
assert response["result"]["disable_new_entities"]
|
assert response["result"] == {
|
||||||
assert entry.system_options.disable_new_entities
|
"require_restart": False,
|
||||||
|
"system_options": {"disable_new_entities": True, "disable_polling": False},
|
||||||
|
}
|
||||||
|
assert entry.system_options.disable_new_entities is True
|
||||||
|
assert entry.system_options.disable_polling is False
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"type": "config_entries/system_options/update",
|
||||||
|
"entry_id": entry.entry_id,
|
||||||
|
"disable_new_entities": False,
|
||||||
|
"disable_polling": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {
|
||||||
|
"require_restart": True,
|
||||||
|
"system_options": {"disable_new_entities": False, "disable_polling": True},
|
||||||
|
}
|
||||||
|
assert entry.system_options.disable_new_entities is False
|
||||||
|
assert entry.system_options.disable_polling is True
|
||||||
|
|
||||||
|
|
||||||
async def test_update_system_options_nonexisting(hass, hass_ws_client):
|
async def test_update_system_options_nonexisting(hass, hass_ws_client):
|
||||||
|
@ -57,6 +57,19 @@ async def test_polling_only_updates_entities_it_should_poll(hass):
|
|||||||
assert poll_ent.async_update.called
|
assert poll_ent.async_update.called
|
||||||
|
|
||||||
|
|
||||||
|
async def test_polling_disabled_by_config_entry(hass):
|
||||||
|
"""Test the polling of only updated entities."""
|
||||||
|
entity_platform = MockEntityPlatform(hass)
|
||||||
|
entity_platform.config_entry = MockConfigEntry(
|
||||||
|
system_options={"disable_polling": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
poll_ent = MockEntity(should_poll=True)
|
||||||
|
|
||||||
|
await entity_platform.async_add_entities([poll_ent])
|
||||||
|
assert entity_platform._async_unsub_polling is None
|
||||||
|
|
||||||
|
|
||||||
async def test_polling_updates_entities_with_exception(hass):
|
async def test_polling_updates_entities_with_exception(hass):
|
||||||
"""Test the updated entities that not break with an exception."""
|
"""Test the updated entities that not break with an exception."""
|
||||||
component = EntityComponent(_LOGGER, DOMAIN, hass, timedelta(seconds=20))
|
component = EntityComponent(_LOGGER, DOMAIN, hass, timedelta(seconds=20))
|
||||||
|
@ -9,13 +9,14 @@ import aiohttp
|
|||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import CoreState
|
from homeassistant.core import CoreState
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import update_coordinator
|
from homeassistant.helpers import update_coordinator
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -371,3 +372,12 @@ async def test_async_config_entry_first_refresh_success(crd, caplog):
|
|||||||
await crd.async_config_entry_first_refresh()
|
await crd.async_config_entry_first_refresh()
|
||||||
|
|
||||||
assert crd.last_update_success is True
|
assert crd.last_update_success is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_not_schedule_refresh_if_system_option_disable_polling(hass):
|
||||||
|
"""Test we do not schedule a refresh if disable polling in config entry."""
|
||||||
|
entry = MockConfigEntry(system_options={"disable_polling": True})
|
||||||
|
config_entries.current_entry.set(entry)
|
||||||
|
crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL)
|
||||||
|
crd.async_add_listener(lambda: None)
|
||||||
|
assert crd._unsub_refresh is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user