mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
EntityComponent to retry platforms that are not ready yet (#8209)
* Add PlatformNotReady Exception * lint * Remove cap, adjust algorithm
This commit is contained in:
parent
f02d169864
commit
d73b695e73
@ -240,7 +240,7 @@ class HomeAssistant(object):
|
|||||||
target: target to call.
|
target: target to call.
|
||||||
args: parameters for method to call.
|
args: parameters for method to call.
|
||||||
"""
|
"""
|
||||||
if is_callback(target):
|
if not asyncio.iscoroutine(target) and is_callback(target):
|
||||||
target(*args)
|
target(*args)
|
||||||
else:
|
else:
|
||||||
self.async_add_job(target, *args)
|
self.async_add_job(target, *args)
|
||||||
|
@ -26,3 +26,9 @@ class TemplateError(HomeAssistantError):
|
|||||||
"""Init the error."""
|
"""Init the error."""
|
||||||
super().__init__('{}: {}'.format(exception.__class__.__name__,
|
super().__init__('{}: {}'.format(exception.__class__.__name__,
|
||||||
exception))
|
exception))
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformNotReady(HomeAssistantError):
|
||||||
|
"""Error to indicate that platform is not ready."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
@ -8,19 +8,22 @@ from homeassistant.const import (
|
|||||||
ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE,
|
ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE,
|
||||||
DEVICE_DEFAULT_NAME)
|
DEVICE_DEFAULT_NAME)
|
||||||
from homeassistant.core import callback, valid_entity_id
|
from homeassistant.core import callback, valid_entity_id
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import config_per_platform, discovery
|
from homeassistant.helpers import config_per_platform, discovery
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
from homeassistant.helpers.entity import async_generate_entity_id
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import (
|
||||||
|
async_track_time_interval, async_track_point_in_time)
|
||||||
from homeassistant.helpers.service import extract_entity_ids
|
from homeassistant.helpers.service import extract_entity_ids
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
from homeassistant.util.async import (
|
from homeassistant.util.async import (
|
||||||
run_callback_threadsafe, run_coroutine_threadsafe)
|
run_callback_threadsafe, run_coroutine_threadsafe)
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=15)
|
DEFAULT_SCAN_INTERVAL = timedelta(seconds=15)
|
||||||
SLOW_SETUP_WARNING = 10
|
SLOW_SETUP_WARNING = 10
|
||||||
SLOW_SETUP_MAX_WAIT = 60
|
SLOW_SETUP_MAX_WAIT = 60
|
||||||
|
PLATFORM_NOT_READY_RETRIES = 10
|
||||||
|
|
||||||
|
|
||||||
class EntityComponent(object):
|
class EntityComponent(object):
|
||||||
@ -113,7 +116,7 @@ class EntityComponent(object):
|
|||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _async_setup_platform(self, platform_type, platform_config,
|
def _async_setup_platform(self, platform_type, platform_config,
|
||||||
discovery_info=None):
|
discovery_info=None, tries=0):
|
||||||
"""Set up a platform for this component.
|
"""Set up a platform for this component.
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
@ -162,6 +165,16 @@ class EntityComponent(object):
|
|||||||
yield from entity_platform.async_block_entities_done()
|
yield from entity_platform.async_block_entities_done()
|
||||||
self.hass.config.components.add(
|
self.hass.config.components.add(
|
||||||
'{}.{}'.format(self.domain, platform_type))
|
'{}.{}'.format(self.domain, platform_type))
|
||||||
|
except PlatformNotReady:
|
||||||
|
tries += 1
|
||||||
|
wait_time = min(tries, 6) * 30
|
||||||
|
self.logger.warning(
|
||||||
|
'Platform %s not ready yet. Retrying in %d seconds.',
|
||||||
|
platform_type, wait_time)
|
||||||
|
async_track_point_in_time(
|
||||||
|
self.hass, self._async_setup_platform(
|
||||||
|
platform_type, platform_config, discovery_info, tries),
|
||||||
|
dt_util.utcnow() + timedelta(seconds=wait_time))
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
"Setup of platform %s is taking longer than %s seconds."
|
"Setup of platform %s is taking longer than %s seconds."
|
||||||
|
@ -9,6 +9,7 @@ from datetime import timedelta
|
|||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.components import group
|
from homeassistant.components import group
|
||||||
from homeassistant.helpers.entity import Entity, generate_entity_id
|
from homeassistant.helpers.entity import Entity, generate_entity_id
|
||||||
from homeassistant.helpers.entity_component import (
|
from homeassistant.helpers.entity_component import (
|
||||||
@ -21,7 +22,7 @@ import homeassistant.util.dt as dt_util
|
|||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
get_test_home_assistant, MockPlatform, MockModule, fire_time_changed,
|
get_test_home_assistant, MockPlatform, MockModule, fire_time_changed,
|
||||||
mock_coro)
|
mock_coro, async_fire_time_changed)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
DOMAIN = "test_domain"
|
DOMAIN = "test_domain"
|
||||||
@ -533,3 +534,47 @@ def test_extract_from_service_available_device(hass):
|
|||||||
assert ['test_domain.test_3'] == \
|
assert ['test_domain.test_3'] == \
|
||||||
sorted(ent.entity_id for ent in
|
sorted(ent.entity_id for ent in
|
||||||
component.async_extract_from_service(call_2))
|
component.async_extract_from_service(call_2))
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_platform_not_ready(hass):
|
||||||
|
"""Test that we retry when platform not ready."""
|
||||||
|
platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady,
|
||||||
|
None])
|
||||||
|
loader.set_component('test_domain.mod1', MockPlatform(platform1_setup))
|
||||||
|
|
||||||
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||||
|
|
||||||
|
yield from component.async_setup({
|
||||||
|
DOMAIN: {
|
||||||
|
'platform': 'mod1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert len(platform1_setup.mock_calls) == 1
|
||||||
|
assert 'test_domain.mod1' not in hass.config.components
|
||||||
|
|
||||||
|
utcnow = dt_util.utcnow()
|
||||||
|
|
||||||
|
with patch('homeassistant.util.dt.utcnow', return_value=utcnow):
|
||||||
|
# Should not trigger attempt 2
|
||||||
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=29))
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(platform1_setup.mock_calls) == 1
|
||||||
|
|
||||||
|
# Should trigger attempt 2
|
||||||
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=30))
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(platform1_setup.mock_calls) == 2
|
||||||
|
assert 'test_domain.mod1' not in hass.config.components
|
||||||
|
|
||||||
|
# This should not trigger attempt 3
|
||||||
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=59))
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(platform1_setup.mock_calls) == 2
|
||||||
|
|
||||||
|
# Trigger attempt 3, which succeeds
|
||||||
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=60))
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(platform1_setup.mock_calls) == 3
|
||||||
|
assert 'test_domain.mod1' in hass.config.components
|
||||||
|
Loading…
x
Reference in New Issue
Block a user