Add system option to disable polling (#51299)

This commit is contained in:
Paulus Schoutsen 2021-05-31 15:36:40 -07:00 committed by GitHub
parent 5d6b6deed4
commit 4821484d2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 54 deletions

View File

@ -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_update)
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(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):
"""Send Config entry not found error."""
connection.send_error(
@ -267,6 +252,7 @@ def get_entry(hass, connection, entry_id, msg_id):
"type": "config_entries/system_options/update",
"entry_id": str,
vol.Optional("disable_new_entities"): bool,
vol.Optional("disable_polling"): bool,
}
)
async def system_options_update(hass, connection, msg):
@ -280,8 +266,25 @@ async def system_options_update(hass, connection, msg):
if entry is None:
return
old_disable_polling = entry.system_options.disable_polling
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
@ -388,6 +391,7 @@ def entry_json(entry: config_entries.ConfigEntry) -> dict:
"state": entry.state.value,
"supports_options": supports_options,
"supports_unload": entry.supports_unload,
"system_options": entry.system_options.as_dict(),
"disabled_by": entry.disabled_by,
"reason": entry.reason,
}

View File

@ -994,12 +994,10 @@ class ConfigEntries:
changed = True
entry.options = MappingProxyType(options)
if (
system_options is not UNDEFINED
and entry.system_options.as_dict() != system_options
):
changed = True
if system_options is not UNDEFINED:
old_system_options = entry.system_options.as_dict()
entry.system_options.update(**system_options)
changed = entry.system_options.as_dict() != old_system_options
if not changed:
return False
@ -1408,14 +1406,27 @@ class SystemOptions:
"""Config entry system options."""
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."""
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]:
"""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:

View File

@ -214,6 +214,7 @@ class EntityPlatform:
@callback
def async_create_setup_task() -> Coroutine:
"""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]
self.hass, config_entry, self._async_schedule_add_entities
)
@ -395,8 +396,10 @@ class EntityPlatform:
)
raise
if self._async_unsub_polling is not None or not any(
entity.should_poll for entity in self.entities.values()
if (
(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

View File

@ -49,6 +49,7 @@ class DataUpdateCoordinator(Generic[T]):
self.name = name
self.update_method = update_method
self.update_interval = update_interval
self.config_entry = config_entries.current_entry.get()
# It's None before the first successful update.
# Components should call async_config_entry_first_refresh
@ -110,6 +111,9 @@ class DataUpdateCoordinator(Generic[T]):
if self.update_interval is None:
return
if self.config_entry and self.config_entry.system_options.disable_polling:
return
if self._unsub_refresh:
self._unsub_refresh()
self._unsub_refresh = None
@ -229,9 +233,8 @@ class DataUpdateCoordinator(Generic[T]):
if raise_on_auth_failed:
raise
config_entry = config_entries.current_entry.get()
if config_entry:
config_entry.async_start_reauth(self.hass)
if self.config_entry:
self.config_entry.async_start_reauth(self.hass)
except NotImplementedError as err:
self.last_exception = err
raise err

View File

@ -87,6 +87,10 @@ async def test_get_entries(hass, client):
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": True,
"supports_unload": True,
"system_options": {
"disable_new_entities": False,
"disable_polling": False,
},
"disabled_by": None,
"reason": None,
},
@ -97,6 +101,10 @@ async def test_get_entries(hass, client):
"state": core_ce.ConfigEntryState.SETUP_ERROR.value,
"supports_options": False,
"supports_unload": False,
"system_options": {
"disable_new_entities": False,
"disable_polling": False,
},
"disabled_by": None,
"reason": "Unsupported API",
},
@ -107,6 +115,10 @@ async def test_get_entries(hass, client):
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": False,
"supports_unload": False,
"system_options": {
"disable_new_entities": False,
"disable_polling": False,
},
"disabled_by": core_ce.DISABLED_USER,
"reason": None,
},
@ -328,6 +340,10 @@ async def test_create_account(hass, client, enable_custom_integrations):
"state": core_ce.ConfigEntryState.LOADED.value,
"supports_options": False,
"supports_unload": False,
"system_options": {
"disable_new_entities": False,
"disable_polling": False,
},
"title": "Test Entry",
"reason": None,
},
@ -399,6 +415,10 @@ async def test_two_step_flow(hass, client, enable_custom_integrations):
"state": core_ce.ConfigEntryState.LOADED.value,
"supports_options": False,
"supports_unload": False,
"system_options": {
"disable_new_entities": False,
"disable_polling": False,
},
"title": "user-title",
"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):
"""Test that we can update system options."""
assert await async_setup_component(hass, "config", {})
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)
assert entry.system_options.disable_new_entities is False
assert entry.system_options.disable_polling is False
await ws_client.send_json(
{
"id": 5,
@ -718,8 +720,31 @@ async def test_update_system_options(hass, hass_ws_client):
response = await ws_client.receive_json()
assert response["success"]
assert response["result"]["disable_new_entities"]
assert entry.system_options.disable_new_entities
assert response["result"] == {
"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):

View File

@ -57,6 +57,19 @@ async def test_polling_only_updates_entities_it_should_poll(hass):
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):
"""Test the updated entities that not break with an exception."""
component = EntityComponent(_LOGGER, DOMAIN, hass, timedelta(seconds=20))

View File

@ -9,13 +9,14 @@ import aiohttp
import pytest
import requests
from homeassistant import config_entries
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import CoreState
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import update_coordinator
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__)
@ -371,3 +372,12 @@ async def test_async_config_entry_first_refresh_success(crd, caplog):
await crd.async_config_entry_first_refresh()
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