Fix Google Calendar caching when offline

Events from Google Calendar were not firing under the following
circumstances:

1. Start ha as normal with Google Calendar configured as per
   instructions.
2. ha loses network connectivity to Google
3. ha attempts update of Google Calendar
4. calendar/google component throws uncaught Exception causing update
   method to not return
5. (cached) Google Calendar event does not fire, remains "Off"

Catching the Exception and returning False from the update() method
causes the correct behavior (i.e., the calendar component firing the
event as scheduled using cached data).
This commit is contained in:
Philip Rosenberg-Watt
2018-03-21 13:38:48 -06:00
parent 0396725fe9
commit 785f4cc251
3 changed files with 46 additions and 35 deletions

View File

@@ -8,6 +8,8 @@ https://home-assistant.io/components/binary_sensor.google_calendar/
import logging import logging
from datetime import timedelta from datetime import timedelta
from httplib2 import ServerNotFoundError
from homeassistant.components.calendar import CalendarEventDevice from homeassistant.components.calendar import CalendarEventDevice
from homeassistant.components.google import ( from homeassistant.components.google import (
CONF_CAL_ID, CONF_ENTITIES, CONF_TRACK, TOKEN_FILE, CONF_CAL_ID, CONF_ENTITIES, CONF_TRACK, TOKEN_FILE,
@@ -62,7 +64,12 @@ class GoogleCalendarData(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Get the latest data.""" """Get the latest data."""
service = self.calendar_service.get() try:
service = self.calendar_service.get()
except ServerNotFoundError:
_LOGGER.warning("Unable to connect to Google, using cached data")
return False
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS) params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['timeMin'] = dt.now().isoformat('T') params['timeMin'] = dt.now().isoformat('T')
params['calendarId'] = self.calendar_id params['calendarId'] = self.calendar_id

View File

@@ -201,3 +201,6 @@ warrant==0.6.1
# homeassistant.components.sensor.yahoo_finance # homeassistant.components.sensor.yahoo_finance
yahoo-finance==1.4.0 yahoo-finance==1.4.0
# homeassistant.components.calendar.google
httplib2==0.10.3

View File

@@ -2,9 +2,10 @@
# pylint: disable=protected-access # pylint: disable=protected-access
import logging import logging
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch, Mock
import pytest import pytest
from httplib2 import ServerNotFoundError
import homeassistant.components.calendar as calendar_base import homeassistant.components.calendar as calendar_base
import homeassistant.components.calendar.google as calendar import homeassistant.components.calendar.google as calendar
@@ -42,16 +43,16 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
@patch('homeassistant.components.calendar.google.GoogleCalendarData') @patch('homeassistant.components.calendar.google.GoogleCalendarData')
def test_all_day_event(self, mock_next_event): def test_all_day_event(self, mock_next_event):
"""Test that we can create an event trigger on device.""" """Test that we can create an event trigger on device."""
week_from_today = dt_util.dt.date.today() \ week_from_today = dt_util.dt.date.today() + dt_util.dt.timedelta(
+ dt_util.dt.timedelta(days=7) days=7)
event = { event = {
'summary': 'Test All Day Event', 'summary': 'Test All Day Event',
'start': { 'start': {
'date': week_from_today.isoformat() 'date': week_from_today.isoformat()
}, },
'end': { 'end': {
'date': (week_from_today + dt_util.dt.timedelta(days=1)) 'date': (week_from_today + dt_util.dt.timedelta(
.isoformat() days=1)).isoformat()
}, },
'location': 'Test Cases', 'location': 'Test Cases',
'description': 'We\'re just testing that all day events get setup ' 'description': 'We\'re just testing that all day events get setup '
@@ -105,16 +106,14 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
@patch('homeassistant.components.calendar.google.GoogleCalendarData') @patch('homeassistant.components.calendar.google.GoogleCalendarData')
def test_future_event(self, mock_next_event): def test_future_event(self, mock_next_event):
"""Test that we can create an event trigger on device.""" """Test that we can create an event trigger on device."""
one_hour_from_now = dt_util.now() \ one_hour_from_now = dt_util.now() + dt_util.dt.timedelta(minutes=30)
+ dt_util.dt.timedelta(minutes=30)
event = { event = {
'start': { 'start': {
'dateTime': one_hour_from_now.isoformat() 'dateTime': one_hour_from_now.isoformat()
}, },
'end': { 'end': {
'dateTime': (one_hour_from_now 'dateTime': (one_hour_from_now
+ dt_util.dt.timedelta(minutes=60)) + dt_util.dt.timedelta(minutes=60)).isoformat()
.isoformat()
}, },
'summary': 'Test Event in 30 minutes', 'summary': 'Test Event in 30 minutes',
'reminders': {'useDefault': True}, 'reminders': {'useDefault': True},
@@ -157,8 +156,8 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
'offset_reached': False, 'offset_reached': False,
'start_time': one_hour_from_now.strftime(DATE_STR_FORMAT), 'start_time': one_hour_from_now.strftime(DATE_STR_FORMAT),
'end_time': 'end_time':
(one_hour_from_now + dt_util.dt.timedelta(minutes=60)) (one_hour_from_now + dt_util.dt.timedelta(
.strftime(DATE_STR_FORMAT), minutes=60)).strftime(DATE_STR_FORMAT),
'location': '', 'location': '',
'description': '' 'description': ''
}) })
@@ -166,16 +165,14 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
@patch('homeassistant.components.calendar.google.GoogleCalendarData') @patch('homeassistant.components.calendar.google.GoogleCalendarData')
def test_in_progress_event(self, mock_next_event): def test_in_progress_event(self, mock_next_event):
"""Test that we can create an event trigger on device.""" """Test that we can create an event trigger on device."""
middle_of_event = dt_util.now() \ middle_of_event = dt_util.now() - dt_util.dt.timedelta(minutes=30)
- dt_util.dt.timedelta(minutes=30)
event = { event = {
'start': { 'start': {
'dateTime': middle_of_event.isoformat() 'dateTime': middle_of_event.isoformat()
}, },
'end': { 'end': {
'dateTime': (middle_of_event + dt_util.dt 'dateTime': (middle_of_event + dt_util.dt.timedelta(
.timedelta(minutes=60)) minutes=60)).isoformat()
.isoformat()
}, },
'summary': 'Test Event in Progress', 'summary': 'Test Event in Progress',
'reminders': {'useDefault': True}, 'reminders': {'useDefault': True},
@@ -219,8 +216,8 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
'offset_reached': False, 'offset_reached': False,
'start_time': middle_of_event.strftime(DATE_STR_FORMAT), 'start_time': middle_of_event.strftime(DATE_STR_FORMAT),
'end_time': 'end_time':
(middle_of_event + dt_util.dt.timedelta(minutes=60)) (middle_of_event + dt_util.dt.timedelta(minutes=60)).strftime(
.strftime(DATE_STR_FORMAT), DATE_STR_FORMAT),
'location': '', 'location': '',
'description': '' 'description': ''
}) })
@@ -228,17 +225,15 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
@patch('homeassistant.components.calendar.google.GoogleCalendarData') @patch('homeassistant.components.calendar.google.GoogleCalendarData')
def test_offset_in_progress_event(self, mock_next_event): def test_offset_in_progress_event(self, mock_next_event):
"""Test that we can create an event trigger on device.""" """Test that we can create an event trigger on device."""
middle_of_event = dt_util.now() \ middle_of_event = dt_util.now() + dt_util.dt.timedelta(minutes=14)
+ dt_util.dt.timedelta(minutes=14)
event_summary = 'Test Event in Progress' event_summary = 'Test Event in Progress'
event = { event = {
'start': { 'start': {
'dateTime': middle_of_event.isoformat() 'dateTime': middle_of_event.isoformat()
}, },
'end': { 'end': {
'dateTime': (middle_of_event + dt_util.dt 'dateTime': (middle_of_event + dt_util.dt.timedelta(
.timedelta(minutes=60)) minutes=60)).isoformat()
.isoformat()
}, },
'summary': '{} !!-15'.format(event_summary), 'summary': '{} !!-15'.format(event_summary),
'reminders': {'useDefault': True}, 'reminders': {'useDefault': True},
@@ -281,9 +276,8 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
'all_day': False, 'all_day': False,
'offset_reached': True, 'offset_reached': True,
'start_time': middle_of_event.strftime(DATE_STR_FORMAT), 'start_time': middle_of_event.strftime(DATE_STR_FORMAT),
'end_time': 'end_time': (middle_of_event + dt_util.dt.timedelta(
(middle_of_event + dt_util.dt.timedelta(minutes=60)) minutes=60)).strftime(DATE_STR_FORMAT),
.strftime(DATE_STR_FORMAT),
'location': '', 'location': '',
'description': '' 'description': ''
}) })
@@ -292,8 +286,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
@patch('homeassistant.components.calendar.google.GoogleCalendarData') @patch('homeassistant.components.calendar.google.GoogleCalendarData')
def test_all_day_offset_in_progress_event(self, mock_next_event): def test_all_day_offset_in_progress_event(self, mock_next_event):
"""Test that we can create an event trigger on device.""" """Test that we can create an event trigger on device."""
tomorrow = dt_util.dt.date.today() \ tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=1)
+ dt_util.dt.timedelta(days=1)
event_summary = 'Test All Day Event Offset In Progress' event_summary = 'Test All Day Event Offset In Progress'
event = { event = {
@@ -302,8 +295,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
'date': tomorrow.isoformat() 'date': tomorrow.isoformat()
}, },
'end': { 'end': {
'date': (tomorrow + dt_util.dt.timedelta(days=1)) 'date': (tomorrow + dt_util.dt.timedelta(days=1)).isoformat()
.isoformat()
}, },
'location': 'Test Cases', 'location': 'Test Cases',
'description': 'We\'re just testing that all day events get setup ' 'description': 'We\'re just testing that all day events get setup '
@@ -358,8 +350,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
@patch('homeassistant.components.calendar.google.GoogleCalendarData') @patch('homeassistant.components.calendar.google.GoogleCalendarData')
def test_all_day_offset_event(self, mock_next_event): def test_all_day_offset_event(self, mock_next_event):
"""Test that we can create an event trigger on device.""" """Test that we can create an event trigger on device."""
tomorrow = dt_util.dt.date.today() \ tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=2)
+ dt_util.dt.timedelta(days=2)
offset_hours = (1 + dt_util.now().hour) offset_hours = (1 + dt_util.now().hour)
event_summary = 'Test All Day Event Offset' event_summary = 'Test All Day Event Offset'
@@ -369,8 +360,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
'date': tomorrow.isoformat() 'date': tomorrow.isoformat()
}, },
'end': { 'end': {
'date': (tomorrow + dt_util.dt.timedelta(days=1)) 'date': (tomorrow + dt_util.dt.timedelta(days=1)).isoformat()
.isoformat()
}, },
'location': 'Test Cases', 'location': 'Test Cases',
'description': 'We\'re just testing that all day events get setup ' 'description': 'We\'re just testing that all day events get setup '
@@ -421,3 +411,14 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
'location': event['location'], 'location': event['location'],
'description': event['description'] 'description': event['description']
}) })
def test_update_false(self):
"""Test that the update returns False upon Error."""
mock_service = Mock()
mock_service.get = Mock(side_effect=ServerNotFoundError("unit test"))
cal = calendar.GoogleCalendarEventDevice(self.hass, mock_service, None,
{'name': "test"})
result = cal.data.update()
self.assertFalse(result)