diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index a5bbf805d42..e17c9ee1b1e 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -16,8 +16,7 @@ import voluptuous as vol from homeassistant.const import ( EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE) -from homeassistant.helpers import entityfilter -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import entityfilter, config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt as dt_util from homeassistant.components.alexa import smart_home as alexa_sh @@ -105,12 +104,7 @@ def async_setup(hass, config): ) cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs) - - success = yield from cloud.initialize() - - if not success: - return False - + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start) yield from http_api.async_setup(hass) return True @@ -192,19 +186,6 @@ class Cloud: return self._gactions_config - @asyncio.coroutine - def initialize(self): - """Initialize and load cloud info.""" - jwt_success = yield from self._fetch_jwt_keyset() - - if not jwt_success: - return False - - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, self._start_cloud) - - return True - def path(self, *parts): """Get config path inside cloud dir. @@ -234,19 +215,34 @@ class Cloud: 'refresh_token': self.refresh_token, }, indent=4)) - def _start_cloud(self, event): + @asyncio.coroutine + def async_start(self, _): """Start the cloud component.""" - # Ensure config dir exists - path = self.hass.config.path(CONFIG_DIR) - if not os.path.isdir(path): - os.mkdir(path) + success = yield from self._fetch_jwt_keyset() - user_info = self.user_info_path - if not os.path.isfile(user_info): + # Fetching keyset can fail if internet is not up yet. + if not success: + self.hass.helpers.async_call_later(5, self.async_start) return - with open(user_info, 'rt') as file: - info = json.loads(file.read()) + def load_config(): + """Load config.""" + # Ensure config dir exists + path = self.hass.config.path(CONFIG_DIR) + if not os.path.isdir(path): + os.mkdir(path) + + user_info = self.user_info_path + if not os.path.isfile(user_info): + return None + + with open(user_info, 'rt') as file: + return json.loads(file.read()) + + info = yield from self.hass.async_add_job(load_config) + + if info is None: + return # Validate tokens try: diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index f11b2eacf3a..eab2d583f45 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1,4 +1,5 @@ """Helpers for listening to events.""" +from datetime import timedelta import functools as ft from homeassistant.loader import bind_hass @@ -219,6 +220,14 @@ track_point_in_utc_time = threaded_listener_factory( async_track_point_in_utc_time) +@callback +@bind_hass +def async_call_later(hass, delay, action): + """Add a listener that is called in .""" + return async_track_point_in_utc_time( + hass, action, dt_util.utcnow() + timedelta(seconds=delay)) + + @callback @bind_hass def async_track_time_interval(hass, action, interval): diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 7623b25d401..69cd540e7d5 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -14,8 +14,8 @@ from tests.common import mock_coro @pytest.fixture def cloud_client(hass, test_client): """Fixture that can fetch from the cloud client.""" - with patch('homeassistant.components.cloud.Cloud.initialize', - return_value=mock_coro(True)): + with patch('homeassistant.components.cloud.Cloud.async_start', + return_value=mock_coro()): hass.loop.run_until_complete(async_setup_component(hass, 'cloud', { 'cloud': { 'mode': 'development', diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 7d23d9faad4..70990519a0b 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -87,7 +87,7 @@ def test_initialize_loads_info(mock_os, hass): with patch('homeassistant.components.cloud.open', mopen, create=True), \ patch('homeassistant.components.cloud.Cloud._decode_claims'): - cl._start_cloud(None) + yield from cl.async_start(None) assert cl.id_token == 'test-id-token' assert cl.access_token == 'test-access-token' diff --git a/tests/components/cloud/test_iot.py b/tests/components/cloud/test_iot.py index 529559f56af..53340ecede1 100644 --- a/tests/components/cloud/test_iot.py +++ b/tests/components/cloud/test_iot.py @@ -266,8 +266,8 @@ def test_handler_alexa(hass): hass.states.async_set( 'switch.test2', 'on', {'friendly_name': "Test switch 2"}) - with patch('homeassistant.components.cloud.Cloud.initialize', - return_value=mock_coro(True)): + with patch('homeassistant.components.cloud.Cloud.async_start', + return_value=mock_coro()): setup = yield from async_setup_component(hass, 'cloud', { 'cloud': { 'alexa': { @@ -309,8 +309,8 @@ def test_handler_google_actions(hass): hass.states.async_set( 'switch.test2', 'on', {'friendly_name': "Test switch 2"}) - with patch('homeassistant.components.cloud.Cloud.initialize', - return_value=mock_coro(True)): + with patch('homeassistant.components.cloud.Cloud.async_start', + return_value=mock_coro()): setup = yield from async_setup_component(hass, 'cloud', { 'cloud': { 'google_actions': { diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 7d601c7a78d..73f2b9ff5a4 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -7,10 +7,12 @@ from datetime import datetime, timedelta from astral import Astral import pytest +from homeassistant.core import callback from homeassistant.setup import setup_component import homeassistant.core as ha from homeassistant.const import MATCH_ALL from homeassistant.helpers.event import ( + async_call_later, track_point_in_utc_time, track_point_in_time, track_utc_time_change, @@ -52,7 +54,7 @@ class TestEventHelpers(unittest.TestCase): runs = [] track_point_in_utc_time( - self.hass, lambda x: runs.append(1), birthday_paulus) + self.hass, callback(lambda x: runs.append(1)), birthday_paulus) self._send_time_changed(before_birthday) self.hass.block_till_done() @@ -68,14 +70,14 @@ class TestEventHelpers(unittest.TestCase): self.assertEqual(1, len(runs)) track_point_in_time( - self.hass, lambda x: runs.append(1), birthday_paulus) + self.hass, callback(lambda x: runs.append(1)), birthday_paulus) self._send_time_changed(after_birthday) self.hass.block_till_done() self.assertEqual(2, len(runs)) unsub = track_point_in_time( - self.hass, lambda x: runs.append(1), birthday_paulus) + self.hass, callback(lambda x: runs.append(1)), birthday_paulus) unsub() self._send_time_changed(after_birthday) @@ -642,3 +644,22 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0)) self.hass.block_till_done() self.assertEqual(0, len(specific_runs)) + + +@asyncio.coroutine +def test_async_call_later(hass): + """Test calling an action later.""" + def action(): pass + now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC) + + with patch('homeassistant.helpers.event' + '.async_track_point_in_utc_time') as mock, \ + patch('homeassistant.util.dt.utcnow', return_value=now): + remove = async_call_later(hass, 3, action) + + assert len(mock.mock_calls) == 1 + p_hass, p_action, p_point = mock.mock_calls[0][1] + assert hass is hass + assert p_action is action + assert p_point == now + timedelta(seconds=3) + assert remove is mock()