Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unpacking RESTful sensor JSON results into attributes. #10753

Merged
merged 32 commits into from
Dec 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dbaa626
Added support for extracting JSON attributes from RESTful values
nickovs May 6, 2017
fb19f87
Added support for extracting JSON attributes from RESTful values
nickovs May 6, 2017
0cb9def
Merge branch 'rest-json-attrs' of https://github.com/nickovs/home-ass…
nickovs May 6, 2017
e08f649
Added requirement that RESTful JSON results used as attributes must be
nickovs May 6, 2017
de07ae5
Expanded test coverage to test REFTful JSON attributes with and
nickovs May 6, 2017
227a5ad
Added support for extracting JSON attributes from RESTful values
nickovs May 6, 2017
769c7ff
Added requirement that RESTful JSON results used as attributes must be
nickovs May 6, 2017
0f25f6d
Expanded test coverage to test REFTful JSON attributes with and
nickovs May 6, 2017
a22a6d4
Merge branch 'rest-json-attrs' of github.com:nickovs/home-assistant i…
nickovs May 7, 2017
6bd57cc
sensor.envirophat: add missing requirement (#7451)
imrehg May 5, 2017
d635e26
PyPI Openzwave (#7415)
JshWright May 5, 2017
44cc762
Add datadog component (#7158)
nunofgs May 5, 2017
67e8f52
Fix object type for default KNX port
JshWright May 5, 2017
d23ccce
Added support for extracting JSON attributes from RESTful values
nickovs May 6, 2017
f5bbf6b
Added requirement that RESTful JSON results used as attributes must be
nickovs May 6, 2017
a281132
Expanded test coverage to test REFTful JSON attributes with and
nickovs May 6, 2017
dda455d
Added support for extracting JSON attributes from RESTful values
nickovs May 6, 2017
3a99f4e
Added requirement that RESTful JSON results used as attributes must be
nickovs May 6, 2017
22bc0fc
Expanded test coverage to test REFTful JSON attributes with and
nickovs May 6, 2017
d19a81b
Added support for extracting JSON attributes from RESTful values
nickovs May 6, 2017
4ef54a1
Added requirement that RESTful JSON results used as attributes must be
nickovs May 6, 2017
0b9711c
Expanded test coverage to test REFTful JSON attributes with and
nickovs May 6, 2017
8c5548e
Merge branch 'rest-json-attrs' of github.com:nickovs/home-assistant i…
nickovs Nov 20, 2017
0795823
Brought up to date with most recent upstream master
nickovs Nov 20, 2017
19eab69
Fixed breaks cause by manual upstream merge.
nickovs Nov 22, 2017
9dd2a5a
Added one extra blank line to make PyLint happy.
nickovs Nov 22, 2017
2dbcff5
Switched json_attributes to be a list of keys rather than a boolean.
nickovs Nov 28, 2017
1d36270
Added an explicit default value to the json_attributes config entry.
nickovs Dec 2, 2017
02158b0
Removed self.update() from __init__() body.
nickovs Dec 2, 2017
a537b45
Merge branch 'rest-json-attrs' of github.com:nickovs/home-assistant i…
nickovs Dec 2, 2017
3a9650c
Expended unit tests for error cases of json_attributes processing.
nickovs Dec 3, 2017
0bf5ea3
Align quotes
fabaff Dec 3, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions homeassistant/components/sensor/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
https://home-assistant.io/components/sensor.rest/
"""
import logging
import json

import voluptuous as vol
import requests
Expand All @@ -25,13 +26,15 @@
DEFAULT_NAME = 'REST Sensor'
DEFAULT_VERIFY_SSL = True

CONF_JSON_ATTRS = 'json_attributes'
METHODS = ['POST', 'GET']

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_HEADERS): {cv.string: cv.string},
vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv,
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(METHODS),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
Expand All @@ -55,6 +58,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
headers = config.get(CONF_HEADERS)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template = config.get(CONF_VALUE_TEMPLATE)
json_attrs = config.get(CONF_JSON_ATTRS)

if value_template is not None:
value_template.hass = hass

Expand All @@ -68,20 +73,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()

add_devices([RestSensor(hass, rest, name, unit, value_template)], True)
add_devices([RestSensor(
hass, rest, name, unit, value_template, json_attrs)], True)


class RestSensor(Entity):
"""Implementation of a REST sensor."""

def __init__(self, hass, rest, name, unit_of_measurement, value_template):
def __init__(self, hass, rest, name,
unit_of_measurement, value_template, json_attrs):
"""Initialize the REST sensor."""
self._hass = hass
self.rest = rest
self._name = name
self._state = STATE_UNKNOWN
self._unit_of_measurement = unit_of_measurement
self._value_template = value_template
self._json_attrs = json_attrs
self._attributes = None

@property
def name(self):
Expand All @@ -108,6 +117,20 @@ def update(self):
self.rest.update()
value = self.rest.data

if self._json_attrs:
self._attributes = {}
try:
json_dict = json.loads(value)
if isinstance(json_dict, dict):
attrs = {k: json_dict[k] for k in self._json_attrs
if k in json_dict}
self._attributes = attrs
else:
_LOGGER.warning("JSON result was not a dictionary")
except ValueError:
_LOGGER.warning("REST result could not be parsed as JSON")
_LOGGER.debug("Erroneous JSON: %s", value)

if value is None:
value = STATE_UNKNOWN
elif self._value_template is not None:
Expand All @@ -116,6 +139,11 @@ def update(self):

self._state = value

@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attributes


class RestData(object):
"""Class for handling the data retrieval."""
Expand Down
60 changes: 55 additions & 5 deletions tests/components/sensor/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ def setUp(self):
self.value_template = template('{{ value_json.key }}')
self.value_template.hass = self.hass

self.sensor = rest.RestSensor(
self.hass, self.rest, self.name, self.unit_of_measurement,
self.value_template)
self.sensor = rest.RestSensor(self.hass, self.rest, self.name,
self.unit_of_measurement,
self.value_template, [])

def tearDown(self):
"""Stop everything that was started."""
Expand Down Expand Up @@ -181,12 +181,62 @@ def test_update_with_no_template(self):
self.rest.update = Mock('rest.RestData.update',
side_effect=self.update_side_effect(
'plain_state'))
self.sensor = rest.RestSensor(
self.hass, self.rest, self.name, self.unit_of_measurement, None)
self.sensor = rest.RestSensor(self.hass, self.rest, self.name,
self.unit_of_measurement, None, [])
self.sensor.update()
self.assertEqual('plain_state', self.sensor.state)
self.assertTrue(self.sensor.available)

def test_update_with_json_attrs(self):
"""Test attributes get extracted from a JSON result."""
self.rest.update = Mock('rest.RestData.update',
side_effect=self.update_side_effect(
'{ "key": "some_json_value" }'))
self.sensor = rest.RestSensor(self.hass, self.rest, self.name,
self.unit_of_measurement, None, ['key'])
self.sensor.update()
self.assertEqual('some_json_value',
self.sensor.device_state_attributes['key'])

@patch('homeassistant.components.sensor.rest._LOGGER')
def test_update_with_json_attrs_not_dict(self, mock_logger):
"""Test attributes get extracted from a JSON result."""
self.rest.update = Mock('rest.RestData.update',
side_effect=self.update_side_effect(
'["list", "of", "things"]'))
self.sensor = rest.RestSensor(self.hass, self.rest, self.name,
self.unit_of_measurement, None, ['key'])
self.sensor.update()
self.assertEqual({}, self.sensor.device_state_attributes)
self.assertTrue(mock_logger.warning.called)

@patch('homeassistant.components.sensor.rest._LOGGER')
def test_update_with_json_attrs_bad_JSON(self, mock_logger):
"""Test attributes get extracted from a JSON result."""
self.rest.update = Mock('rest.RestData.update',
side_effect=self.update_side_effect(
'This is text rather than JSON data.'))
self.sensor = rest.RestSensor(self.hass, self.rest, self.name,
self.unit_of_measurement, None, ['key'])
self.sensor.update()
self.assertEqual({}, self.sensor.device_state_attributes)
self.assertTrue(mock_logger.warning.called)
self.assertTrue(mock_logger.debug.called)

def test_update_with_json_attrs_and_template(self):
"""Test attributes get extracted from a JSON result."""
self.rest.update = Mock('rest.RestData.update',
side_effect=self.update_side_effect(
'{ "key": "json_state_updated_value" }'))
self.sensor = rest.RestSensor(self.hass, self.rest, self.name,
self.unit_of_measurement,
self.value_template, ['key'])
self.sensor.update()

self.assertEqual('json_state_updated_value', self.sensor.state)
self.assertEqual('json_state_updated_value',
self.sensor.device_state_attributes['key'])


class TestRestData(unittest.TestCase):
"""Tests for RestData."""
Expand Down