mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
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
This commit is contained in:
parent
3e59ffb33a
commit
d5813cf167
@ -18,6 +18,7 @@ from homeassistant.const import (
|
|||||||
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
|
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
|
||||||
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
|
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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 = RestData(method, resource, auth, headers, payload, verify_ssl)
|
||||||
rest.update()
|
rest.update()
|
||||||
|
|
||||||
if rest.data is None:
|
if rest.data is None:
|
||||||
_LOGGER.error("Unable to fetch REST data from %s", resource)
|
raise PlatformNotReady
|
||||||
return False
|
|
||||||
|
|
||||||
|
# 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(
|
add_entities([RestBinarySensor(
|
||||||
hass, rest, name, device_class, value_template)], True)
|
hass, rest, name, device_class, value_template)])
|
||||||
|
|
||||||
|
|
||||||
class RestBinarySensor(BinarySensorDevice):
|
class RestBinarySensor(BinarySensorDevice):
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.const import (
|
|||||||
CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME,
|
CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME,
|
||||||
CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL,
|
CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL,
|
||||||
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, STATE_UNKNOWN)
|
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, STATE_UNKNOWN)
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -76,7 +77,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
auth = None
|
auth = None
|
||||||
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
|
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
|
||||||
rest.update()
|
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(
|
add_entities([RestSensor(
|
||||||
hass, rest, name, unit, value_template, json_attrs, force_update
|
hass, rest, name, unit, value_template, json_attrs, force_update
|
||||||
)], True)
|
)], True)
|
||||||
@ -170,6 +175,7 @@ class RestData:
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data from REST service with provided method."""
|
"""Get the latest data from REST service with provided method."""
|
||||||
|
_LOGGER.debug("Updating from %s", self._request.url)
|
||||||
try:
|
try:
|
||||||
with requests.Session() as sess:
|
with requests.Session() as sess:
|
||||||
response = sess.send(
|
response = sess.send(
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
"""The tests for the REST binary sensor platform."""
|
"""The tests for the REST binary sensor platform."""
|
||||||
import unittest
|
import unittest
|
||||||
|
from pytest import raises
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import Timeout, MissingSchema
|
from requests.exceptions import Timeout, MissingSchema
|
||||||
import requests_mock
|
import requests_mock
|
||||||
|
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import setup_component
|
||||||
import homeassistant.components.binary_sensor as binary_sensor
|
import homeassistant.components.binary_sensor as binary_sensor
|
||||||
import homeassistant.components.binary_sensor.rest as rest
|
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):
|
class TestRestBinarySensorSetup(unittest.TestCase):
|
||||||
"""Tests for setting up the REST binary sensor platform."""
|
"""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):
|
def setUp(self):
|
||||||
"""Set up things to be run when tests are started."""
|
"""Set up things to be run when tests are started."""
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = get_test_home_assistant()
|
||||||
|
# Reset for this test.
|
||||||
|
self.DEVICES = []
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
@ -45,76 +56,80 @@ class TestRestBinarySensorSetup(unittest.TestCase):
|
|||||||
side_effect=requests.exceptions.ConnectionError())
|
side_effect=requests.exceptions.ConnectionError())
|
||||||
def test_setup_failed_connect(self, mock_req):
|
def test_setup_failed_connect(self, mock_req):
|
||||||
"""Test setup when connection error occurs."""
|
"""Test setup when connection error occurs."""
|
||||||
self.assertFalse(rest.setup_platform(self.hass, {
|
with raises(PlatformNotReady):
|
||||||
'platform': 'rest',
|
rest.setup_platform(self.hass, {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
}, lambda devices, update=True: None))
|
'resource': 'http://localhost',
|
||||||
|
}, self.add_devices, None)
|
||||||
|
self.assertEqual(len(self.DEVICES), 0)
|
||||||
|
|
||||||
@patch('requests.Session.send', side_effect=Timeout())
|
@patch('requests.Session.send', side_effect=Timeout())
|
||||||
def test_setup_timeout(self, mock_req):
|
def test_setup_timeout(self, mock_req):
|
||||||
"""Test setup when connection timeout occurs."""
|
"""Test setup when connection timeout occurs."""
|
||||||
self.assertFalse(rest.setup_platform(self.hass, {
|
with raises(PlatformNotReady):
|
||||||
'platform': 'rest',
|
rest.setup_platform(self.hass, {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
}, lambda devices, update=True: None))
|
'resource': 'http://localhost',
|
||||||
|
}, self.add_devices, None)
|
||||||
|
self.assertEqual(len(self.DEVICES), 0)
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_setup_minimum(self, mock_req):
|
def test_setup_minimum(self, mock_req):
|
||||||
"""Test setup with minimum configuration."""
|
"""Test setup with minimum configuration."""
|
||||||
mock_req.get('http://localhost', status_code=200)
|
mock_req.get('http://localhost', status_code=200)
|
||||||
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
|
with assert_setup_component(1, 'binary_sensor'):
|
||||||
'binary_sensor': {
|
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
|
||||||
'platform': 'rest',
|
'binary_sensor': {
|
||||||
'resource': 'http://localhost'
|
'platform': 'rest',
|
||||||
}
|
'resource': 'http://localhost'
|
||||||
}))
|
}
|
||||||
self.assertEqual(2, mock_req.call_count)
|
}))
|
||||||
assert_setup_component(1, 'switch')
|
self.assertEqual(1, mock_req.call_count)
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_setup_get(self, mock_req):
|
def test_setup_get(self, mock_req):
|
||||||
"""Test setup with valid configuration."""
|
"""Test setup with valid configuration."""
|
||||||
mock_req.get('http://localhost', status_code=200)
|
mock_req.get('http://localhost', status_code=200)
|
||||||
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
|
with assert_setup_component(1, 'binary_sensor'):
|
||||||
'binary_sensor': {
|
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
|
||||||
'platform': 'rest',
|
'binary_sensor': {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
'method': 'GET',
|
'resource': 'http://localhost',
|
||||||
'value_template': '{{ value_json.key }}',
|
'method': 'GET',
|
||||||
'name': 'foo',
|
'value_template': '{{ value_json.key }}',
|
||||||
'unit_of_measurement': 'MB',
|
'name': 'foo',
|
||||||
'verify_ssl': 'true',
|
'unit_of_measurement': 'MB',
|
||||||
'authentication': 'basic',
|
'verify_ssl': 'true',
|
||||||
'username': 'my username',
|
'authentication': 'basic',
|
||||||
'password': 'my password',
|
'username': 'my username',
|
||||||
'headers': {'Accept': 'application/json'}
|
'password': 'my password',
|
||||||
}
|
'headers': {'Accept': 'application/json'}
|
||||||
}))
|
}
|
||||||
self.assertEqual(2, mock_req.call_count)
|
}))
|
||||||
assert_setup_component(1, 'binary_sensor')
|
self.assertEqual(1, mock_req.call_count)
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_setup_post(self, mock_req):
|
def test_setup_post(self, mock_req):
|
||||||
"""Test setup with valid configuration."""
|
"""Test setup with valid configuration."""
|
||||||
mock_req.post('http://localhost', status_code=200)
|
mock_req.post('http://localhost', status_code=200)
|
||||||
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
|
with assert_setup_component(1, 'binary_sensor'):
|
||||||
'binary_sensor': {
|
self.assertTrue(setup_component(self.hass, 'binary_sensor', {
|
||||||
'platform': 'rest',
|
'binary_sensor': {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
'method': 'POST',
|
'resource': 'http://localhost',
|
||||||
'value_template': '{{ value_json.key }}',
|
'method': 'POST',
|
||||||
'payload': '{ "device": "toaster"}',
|
'value_template': '{{ value_json.key }}',
|
||||||
'name': 'foo',
|
'payload': '{ "device": "toaster"}',
|
||||||
'unit_of_measurement': 'MB',
|
'name': 'foo',
|
||||||
'verify_ssl': 'true',
|
'unit_of_measurement': 'MB',
|
||||||
'authentication': 'basic',
|
'verify_ssl': 'true',
|
||||||
'username': 'my username',
|
'authentication': 'basic',
|
||||||
'password': 'my password',
|
'username': 'my username',
|
||||||
'headers': {'Accept': 'application/json'}
|
'password': 'my password',
|
||||||
}
|
'headers': {'Accept': 'application/json'}
|
||||||
}))
|
}
|
||||||
self.assertEqual(2, mock_req.call_count)
|
}))
|
||||||
assert_setup_component(1, 'binary_sensor')
|
self.assertEqual(1, mock_req.call_count)
|
||||||
|
|
||||||
|
|
||||||
class TestRestBinarySensor(unittest.TestCase):
|
class TestRestBinarySensor(unittest.TestCase):
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
"""The tests for the REST sensor platform."""
|
"""The tests for the REST sensor platform."""
|
||||||
import unittest
|
import unittest
|
||||||
|
from pytest import raises
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import Timeout, MissingSchema, RequestException
|
from requests.exceptions import Timeout, MissingSchema, RequestException
|
||||||
import requests_mock
|
import requests_mock
|
||||||
|
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import setup_component
|
||||||
import homeassistant.components.sensor as sensor
|
import homeassistant.components.sensor as sensor
|
||||||
import homeassistant.components.sensor.rest as rest
|
import homeassistant.components.sensor.rest as rest
|
||||||
@ -45,76 +47,78 @@ class TestRestSensorSetup(unittest.TestCase):
|
|||||||
side_effect=requests.exceptions.ConnectionError())
|
side_effect=requests.exceptions.ConnectionError())
|
||||||
def test_setup_failed_connect(self, mock_req):
|
def test_setup_failed_connect(self, mock_req):
|
||||||
"""Test setup when connection error occurs."""
|
"""Test setup when connection error occurs."""
|
||||||
self.assertTrue(rest.setup_platform(self.hass, {
|
with raises(PlatformNotReady):
|
||||||
'platform': 'rest',
|
rest.setup_platform(self.hass, {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
}, lambda devices, update=True: None) is None)
|
'resource': 'http://localhost',
|
||||||
|
}, lambda devices, update=True: None)
|
||||||
|
|
||||||
@patch('requests.Session.send', side_effect=Timeout())
|
@patch('requests.Session.send', side_effect=Timeout())
|
||||||
def test_setup_timeout(self, mock_req):
|
def test_setup_timeout(self, mock_req):
|
||||||
"""Test setup when connection timeout occurs."""
|
"""Test setup when connection timeout occurs."""
|
||||||
self.assertTrue(rest.setup_platform(self.hass, {
|
with raises(PlatformNotReady):
|
||||||
'platform': 'rest',
|
rest.setup_platform(self.hass, {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
}, lambda devices, update=True: None) is None)
|
'resource': 'http://localhost',
|
||||||
|
}, lambda devices, update=True: None)
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_setup_minimum(self, mock_req):
|
def test_setup_minimum(self, mock_req):
|
||||||
"""Test setup with minimum configuration."""
|
"""Test setup with minimum configuration."""
|
||||||
mock_req.get('http://localhost', status_code=200)
|
mock_req.get('http://localhost', status_code=200)
|
||||||
self.assertTrue(setup_component(self.hass, 'sensor', {
|
with assert_setup_component(1, 'sensor'):
|
||||||
'sensor': {
|
self.assertTrue(setup_component(self.hass, 'sensor', {
|
||||||
'platform': 'rest',
|
'sensor': {
|
||||||
'resource': 'http://localhost'
|
'platform': 'rest',
|
||||||
}
|
'resource': 'http://localhost'
|
||||||
}))
|
}
|
||||||
|
}))
|
||||||
self.assertEqual(2, mock_req.call_count)
|
self.assertEqual(2, mock_req.call_count)
|
||||||
assert_setup_component(1, 'switch')
|
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_setup_get(self, mock_req):
|
def test_setup_get(self, mock_req):
|
||||||
"""Test setup with valid configuration."""
|
"""Test setup with valid configuration."""
|
||||||
mock_req.get('http://localhost', status_code=200)
|
mock_req.get('http://localhost', status_code=200)
|
||||||
self.assertTrue(setup_component(self.hass, 'sensor', {
|
with assert_setup_component(1, 'sensor'):
|
||||||
'sensor': {
|
self.assertTrue(setup_component(self.hass, 'sensor', {
|
||||||
'platform': 'rest',
|
'sensor': {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
'method': 'GET',
|
'resource': 'http://localhost',
|
||||||
'value_template': '{{ value_json.key }}',
|
'method': 'GET',
|
||||||
'name': 'foo',
|
'value_template': '{{ value_json.key }}',
|
||||||
'unit_of_measurement': 'MB',
|
'name': 'foo',
|
||||||
'verify_ssl': 'true',
|
'unit_of_measurement': 'MB',
|
||||||
'authentication': 'basic',
|
'verify_ssl': 'true',
|
||||||
'username': 'my username',
|
'authentication': 'basic',
|
||||||
'password': 'my password',
|
'username': 'my username',
|
||||||
'headers': {'Accept': 'application/json'}
|
'password': 'my password',
|
||||||
}
|
'headers': {'Accept': 'application/json'}
|
||||||
}))
|
}
|
||||||
|
}))
|
||||||
self.assertEqual(2, mock_req.call_count)
|
self.assertEqual(2, mock_req.call_count)
|
||||||
assert_setup_component(1, 'sensor')
|
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_setup_post(self, mock_req):
|
def test_setup_post(self, mock_req):
|
||||||
"""Test setup with valid configuration."""
|
"""Test setup with valid configuration."""
|
||||||
mock_req.post('http://localhost', status_code=200)
|
mock_req.post('http://localhost', status_code=200)
|
||||||
self.assertTrue(setup_component(self.hass, 'sensor', {
|
with assert_setup_component(1, 'sensor'):
|
||||||
'sensor': {
|
self.assertTrue(setup_component(self.hass, 'sensor', {
|
||||||
'platform': 'rest',
|
'sensor': {
|
||||||
'resource': 'http://localhost',
|
'platform': 'rest',
|
||||||
'method': 'POST',
|
'resource': 'http://localhost',
|
||||||
'value_template': '{{ value_json.key }}',
|
'method': 'POST',
|
||||||
'payload': '{ "device": "toaster"}',
|
'value_template': '{{ value_json.key }}',
|
||||||
'name': 'foo',
|
'payload': '{ "device": "toaster"}',
|
||||||
'unit_of_measurement': 'MB',
|
'name': 'foo',
|
||||||
'verify_ssl': 'true',
|
'unit_of_measurement': 'MB',
|
||||||
'authentication': 'basic',
|
'verify_ssl': 'true',
|
||||||
'username': 'my username',
|
'authentication': 'basic',
|
||||||
'password': 'my password',
|
'username': 'my username',
|
||||||
'headers': {'Accept': 'application/json'}
|
'password': 'my password',
|
||||||
}
|
'headers': {'Accept': 'application/json'}
|
||||||
}))
|
}
|
||||||
|
}))
|
||||||
self.assertEqual(2, mock_req.call_count)
|
self.assertEqual(2, mock_req.call_count)
|
||||||
assert_setup_component(1, 'sensor')
|
|
||||||
|
|
||||||
|
|
||||||
class TestRestSensor(unittest.TestCase):
|
class TestRestSensor(unittest.TestCase):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user