Make rest sensor and binary sensor more efficient (#14484)

* create binary sensor even if initial update fails

* fixed broken test assertion

* fixed broken test assertion

* avoid fetching resource twice - manually in the setup_platform and then through add_devices

* raising PlatformNotReady instead of creating the sensor if the initial rest call fails; throttling the update to avoid fetching the same resource again immediately after setting up sensor

* rolled back throttling of the rest update call; can still avoid updating the binary sensor's rest resoure twice; fixed tests

* typo
pull/14150/merge
Malte Franken 2018-09-21 23:24:50 +09:30 committed by Paulus Schoutsen
parent 3e59ffb33a
commit d5813cf167
4 changed files with 129 additions and 103 deletions

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
_LOGGER = logging.getLogger(__name__)
@ -66,13 +67,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()
if rest.data is None:
_LOGGER.error("Unable to fetch REST data from %s", resource)
return False
raise PlatformNotReady
# No need to update the sensor now because it will determine its state
# based in the rest resource that has just been retrieved.
add_entities([RestBinarySensor(
hass, rest, name, device_class, value_template)], True)
hass, rest, name, device_class, value_template)])
class RestBinarySensor(BinarySensorDevice):

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME,
CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, STATE_UNKNOWN)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
@ -76,7 +77,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
auth = None
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()
if rest.data is None:
raise PlatformNotReady
# Must update the sensor now (including fetching the rest resource) to
# ensure it's updating its state.
add_entities([RestSensor(
hass, rest, name, unit, value_template, json_attrs, force_update
)], True)
@ -170,6 +175,7 @@ class RestData:
def update(self):
"""Get the latest data from REST service with provided method."""
_LOGGER.debug("Updating from %s", self._request.url)
try:
with requests.Session() as sess:
response = sess.send(

View File

@ -1,11 +1,13 @@
"""The tests for the REST binary sensor platform."""
import unittest
from pytest import raises
from unittest.mock import patch, Mock
import requests
from requests.exceptions import Timeout, MissingSchema
import requests_mock
from homeassistant.exceptions import PlatformNotReady
from homeassistant.setup import setup_component
import homeassistant.components.binary_sensor as binary_sensor
import homeassistant.components.binary_sensor.rest as rest
@ -18,9 +20,18 @@ from tests.common import get_test_home_assistant, assert_setup_component
class TestRestBinarySensorSetup(unittest.TestCase):
"""Tests for setting up the REST binary sensor platform."""
DEVICES = []
def add_devices(self, devices, update_before_add=False):
"""Mock add devices."""
for device in devices:
self.DEVICES.append(device)
def setUp(self):
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# Reset for this test.
self.DEVICES = []
def tearDown(self):
"""Stop everything that was started."""
@ -45,76 +56,80 @@ class TestRestBinarySensorSetup(unittest.TestCase):
side_effect=requests.exceptions.ConnectionError())
def test_setup_failed_connect(self, mock_req):
"""Test setup when connection error occurs."""
self.assertFalse(rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, lambda devices, update=True: None))
with raises(PlatformNotReady):
rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, self.add_devices, None)
self.assertEqual(len(self.DEVICES), 0)
@patch('requests.Session.send', side_effect=Timeout())
def test_setup_timeout(self, mock_req):
"""Test setup when connection timeout occurs."""
self.assertFalse(rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, lambda devices, update=True: None))
with raises(PlatformNotReady):
rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, self.add_devices, None)
self.assertEqual(len(self.DEVICES), 0)
@requests_mock.Mocker()
def test_setup_minimum(self, mock_req):
"""Test setup with minimum configuration."""
mock_req.get('http://localhost', status_code=200)
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'rest',
'resource': 'http://localhost'
}
}))
self.assertEqual(2, mock_req.call_count)
assert_setup_component(1, 'switch')
with assert_setup_component(1, 'binary_sensor'):
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'rest',
'resource': 'http://localhost'
}
}))
self.assertEqual(1, mock_req.call_count)
@requests_mock.Mocker()
def test_setup_get(self, mock_req):
"""Test setup with valid configuration."""
mock_req.get('http://localhost', status_code=200)
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'GET',
'value_template': '{{ value_json.key }}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
self.assertEqual(2, mock_req.call_count)
assert_setup_component(1, 'binary_sensor')
with assert_setup_component(1, 'binary_sensor'):
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'GET',
'value_template': '{{ value_json.key }}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
self.assertEqual(1, mock_req.call_count)
@requests_mock.Mocker()
def test_setup_post(self, mock_req):
"""Test setup with valid configuration."""
mock_req.post('http://localhost', status_code=200)
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'POST',
'value_template': '{{ value_json.key }}',
'payload': '{ "device": "toaster"}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
self.assertEqual(2, mock_req.call_count)
assert_setup_component(1, 'binary_sensor')
with assert_setup_component(1, 'binary_sensor'):
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'POST',
'value_template': '{{ value_json.key }}',
'payload': '{ "device": "toaster"}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
self.assertEqual(1, mock_req.call_count)
class TestRestBinarySensor(unittest.TestCase):

View File

@ -1,11 +1,13 @@
"""The tests for the REST sensor platform."""
import unittest
from pytest import raises
from unittest.mock import patch, Mock
import requests
from requests.exceptions import Timeout, MissingSchema, RequestException
import requests_mock
from homeassistant.exceptions import PlatformNotReady
from homeassistant.setup import setup_component
import homeassistant.components.sensor as sensor
import homeassistant.components.sensor.rest as rest
@ -45,76 +47,78 @@ class TestRestSensorSetup(unittest.TestCase):
side_effect=requests.exceptions.ConnectionError())
def test_setup_failed_connect(self, mock_req):
"""Test setup when connection error occurs."""
self.assertTrue(rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, lambda devices, update=True: None) is None)
with raises(PlatformNotReady):
rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, lambda devices, update=True: None)
@patch('requests.Session.send', side_effect=Timeout())
def test_setup_timeout(self, mock_req):
"""Test setup when connection timeout occurs."""
self.assertTrue(rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, lambda devices, update=True: None) is None)
with raises(PlatformNotReady):
rest.setup_platform(self.hass, {
'platform': 'rest',
'resource': 'http://localhost',
}, lambda devices, update=True: None)
@requests_mock.Mocker()
def test_setup_minimum(self, mock_req):
"""Test setup with minimum configuration."""
mock_req.get('http://localhost', status_code=200)
self.assertTrue(setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'rest',
'resource': 'http://localhost'
}
}))
with assert_setup_component(1, 'sensor'):
self.assertTrue(setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'rest',
'resource': 'http://localhost'
}
}))
self.assertEqual(2, mock_req.call_count)
assert_setup_component(1, 'switch')
@requests_mock.Mocker()
def test_setup_get(self, mock_req):
"""Test setup with valid configuration."""
mock_req.get('http://localhost', status_code=200)
self.assertTrue(setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'GET',
'value_template': '{{ value_json.key }}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
with assert_setup_component(1, 'sensor'):
self.assertTrue(setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'GET',
'value_template': '{{ value_json.key }}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
self.assertEqual(2, mock_req.call_count)
assert_setup_component(1, 'sensor')
@requests_mock.Mocker()
def test_setup_post(self, mock_req):
"""Test setup with valid configuration."""
mock_req.post('http://localhost', status_code=200)
self.assertTrue(setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'POST',
'value_template': '{{ value_json.key }}',
'payload': '{ "device": "toaster"}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
with assert_setup_component(1, 'sensor'):
self.assertTrue(setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'rest',
'resource': 'http://localhost',
'method': 'POST',
'value_template': '{{ value_json.key }}',
'payload': '{ "device": "toaster"}',
'name': 'foo',
'unit_of_measurement': 'MB',
'verify_ssl': 'true',
'authentication': 'basic',
'username': 'my username',
'password': 'my password',
'headers': {'Accept': 'application/json'}
}
}))
self.assertEqual(2, mock_req.call_count)
assert_setup_component(1, 'sensor')
class TestRestSensor(unittest.TestCase):