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 1 commit
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
Prev Previous commit
Next Next commit
Add datadog component (#7158)
* Add datadog component

* Improve test_invalid_config datadog test

* Use assert_setup_component for test setup
  • Loading branch information
nunofgs authored and nickovs committed Nov 20, 2017
commit 44cc762c0f0ddc70b8c280cfb4e683c38eab595a
120 changes: 120 additions & 0 deletions homeassistant/components/datadog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
A component which allows you to send data to Datadog.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/datadog/
"""
import logging
import voluptuous as vol

from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_PREFIX,
EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED,
STATE_UNKNOWN)
from homeassistant.helpers import state as state_helper
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['datadog==0.15.0']

_LOGGER = logging.getLogger(__name__)

CONF_RATE = 'rate'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8125
DEFAULT_PREFIX = 'hass'
DEFAULT_RATE = 1
DOMAIN = 'datadog'

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string,
vol.Optional(CONF_RATE, default=DEFAULT_RATE):
vol.All(vol.Coerce(int), vol.Range(min=1)),
}),
}, extra=vol.ALLOW_EXTRA)


def setup(hass, config):
"""Setup the Datadog component."""
from datadog import initialize, statsd

conf = config[DOMAIN]
host = conf.get(CONF_HOST)
port = conf.get(CONF_PORT)
sample_rate = conf.get(CONF_RATE)
prefix = conf.get(CONF_PREFIX)

initialize(statsd_host=host, statsd_port=port)

def logbook_entry_listener(event):
"""Listen for logbook entries and send them as events."""
name = event.data.get('name')
message = event.data.get('message')

statsd.event(
title="Home Assistant",
text="%%% \n **{}** {} \n %%%".format(name, message),
tags=[
"entity:{}".format(event.data.get('entity_id')),
"domain:{}".format(event.data.get('domain'))
]
)

_LOGGER.debug('Sent event %s', event.data.get('entity_id'))

def state_changed_listener(event):
"""Listen for new messages on the bus and sends them to Datadog."""
state = event.data.get('new_state')

if state is None or state.state == STATE_UNKNOWN:
return

if state.attributes.get('hidden') is True:
return

states = dict(state.attributes)
metric = "{}.{}".format(prefix, state.domain)
tags = ["entity:{}".format(state.entity_id)]

for key, value in states.items():
if isinstance(value, (float, int)):
attribute = "{}.{}".format(metric, key.replace(' ', '_'))
statsd.gauge(
attribute,
value,
sample_rate=sample_rate,
tags=tags
)

_LOGGER.debug(
'Sent metric %s: %s (tags: %s)',
attribute,
value,
tags
)

try:
value = state_helper.state_as_number(state)
except ValueError:
_LOGGER.debug(
'Error sending %s: %s (tags: %s)',
metric,
state.state,
tags
)
return

statsd.gauge(
metric,
value,
sample_rate=sample_rate,
tags=tags
)

_LOGGER.debug('Sent metric %s: %s (tags: %s)', metric, value, tags)

hass.bus.listen(EVENT_LOGBOOK_ENTRY, logbook_entry_listener)
hass.bus.listen(EVENT_STATE_CHANGED, state_changed_listener)

return True
5 changes: 2 additions & 3 deletions homeassistant/components/logbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED,
STATE_NOT_HOME, STATE_OFF, STATE_ON, ATTR_HIDDEN, HTTP_BAD_REQUEST)
STATE_NOT_HOME, STATE_OFF, STATE_ON, ATTR_HIDDEN, HTTP_BAD_REQUEST,
EVENT_LOGBOOK_ENTRY)
from homeassistant.core import State, split_entity_id, DOMAIN as HA_DOMAIN

DOMAIN = 'logbook'
Expand Down Expand Up @@ -47,8 +48,6 @@
}),
}, extra=vol.ALLOW_EXTRA)

EVENT_LOGBOOK_ENTRY = 'logbook_entry'

GROUP_BY_MINUTES = 15

ATTR_NAME = 'name'
Expand Down
1 change: 1 addition & 0 deletions homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
EVENT_COMPONENT_LOADED = 'component_loaded'
EVENT_SERVICE_REGISTERED = 'service_registered'
EVENT_SERVICE_REMOVED = 'service_removed'
EVENT_LOGBOOK_ENTRY = 'logbook_entry'

# #### STATES ####
STATE_ON = 'on'
Expand Down
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ concord232==0.14
# homeassistant.components.sensor.crimereports
crimereports==1.0.0

# homeassistant.components.datadog
datadog==0.15.0

# homeassistant.components.sensor.metoffice
# homeassistant.components.weather.metoffice
datapoint==0.4.3
Expand Down
179 changes: 179 additions & 0 deletions tests/components/test_datadog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""The tests for the Datadog component."""
from unittest import mock
import unittest

from homeassistant.const import (
EVENT_LOGBOOK_ENTRY,
EVENT_STATE_CHANGED,
STATE_OFF,
STATE_ON
)
from homeassistant.setup import setup_component
import homeassistant.components.datadog as datadog
import homeassistant.core as ha

from tests.common import (assert_setup_component, get_test_home_assistant)


class TestDatadog(unittest.TestCase):
"""Test the Datadog component."""

def setUp(self): # pylint: disable=invalid-name
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()

def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started."""
self.hass.stop()

def test_invalid_config(self):
"""Test invalid configuration."""
with assert_setup_component(0):
assert not setup_component(self.hass, datadog.DOMAIN, {
datadog.DOMAIN: {
'host1': 'host1'
}
})

@mock.patch('datadog.initialize')
def test_datadog_setup_full(self, mock_connection):
"""Test setup with all data."""
self.hass.bus.listen = mock.MagicMock()

assert setup_component(self.hass, datadog.DOMAIN, {
datadog.DOMAIN: {
'host': 'host',
'port': 123,
'rate': 1,
'prefix': 'foo',
}
})

self.assertEqual(mock_connection.call_count, 1)
self.assertEqual(
mock_connection.call_args,
mock.call(statsd_host='host', statsd_port=123)
)

self.assertTrue(self.hass.bus.listen.called)
self.assertEqual(EVENT_LOGBOOK_ENTRY,
self.hass.bus.listen.call_args_list[0][0][0])
self.assertEqual(EVENT_STATE_CHANGED,
self.hass.bus.listen.call_args_list[1][0][0])

@mock.patch('datadog.initialize')
def test_datadog_setup_defaults(self, mock_connection):
"""Test setup with defaults."""
self.hass.bus.listen = mock.MagicMock()

assert setup_component(self.hass, datadog.DOMAIN, {
datadog.DOMAIN: {
'host': 'host',
'port': datadog.DEFAULT_PORT,
'prefix': datadog.DEFAULT_PREFIX,
}
})

self.assertEqual(mock_connection.call_count, 1)
self.assertEqual(
mock_connection.call_args,
mock.call(statsd_host='host', statsd_port=8125)
)
self.assertTrue(self.hass.bus.listen.called)

@mock.patch('datadog.statsd')
def test_logbook_entry(self, mock_client):
"""Test event listener."""
self.hass.bus.listen = mock.MagicMock()

assert setup_component(self.hass, datadog.DOMAIN, {
datadog.DOMAIN: {
'host': 'host',
'rate': datadog.DEFAULT_RATE,
}
})

self.assertTrue(self.hass.bus.listen.called)
handler_method = self.hass.bus.listen.call_args_list[0][0][1]

event = {
'domain': 'automation',
'entity_id': 'sensor.foo.bar',
'message': 'foo bar biz',
'name': 'triggered something'
}
handler_method(mock.MagicMock(data=event))

self.assertEqual(mock_client.event.call_count, 1)
self.assertEqual(
mock_client.event.call_args,
mock.call(
title="Home Assistant",
text="%%% \n **{}** {} \n %%%".format(
event['name'],
event['message']
),
tags=["entity:sensor.foo.bar", "domain:automation"]
)
)

mock_client.event.reset_mock()

@mock.patch('datadog.statsd')
def test_state_changed(self, mock_client):
"""Test event listener."""
self.hass.bus.listen = mock.MagicMock()

assert setup_component(self.hass, datadog.DOMAIN, {
datadog.DOMAIN: {
'host': 'host',
'prefix': 'ha',
'rate': datadog.DEFAULT_RATE,
}
})

self.assertTrue(self.hass.bus.listen.called)
handler_method = self.hass.bus.listen.call_args_list[1][0][1]

valid = {
'1': 1,
'1.0': 1.0,
STATE_ON: 1,
STATE_OFF: 0
}

attributes = {
'elevation': 3.2,
'temperature': 5.0
}

for in_, out in valid.items():
state = mock.MagicMock(domain="sensor", entity_id="sensor.foo.bar",
state=in_, attributes=attributes)
handler_method(mock.MagicMock(data={'new_state': state}))

self.assertEqual(mock_client.gauge.call_count, 3)

for attribute, value in attributes.items():
mock_client.gauge.assert_has_calls([
mock.call(
"ha.sensor.{}".format(attribute),
value,
sample_rate=1,
tags=["entity:{}".format(state.entity_id)]
)
])

self.assertEqual(
mock_client.gauge.call_args,
mock.call("ha.sensor", out, sample_rate=1, tags=[
"entity:{}".format(state.entity_id)
])
)

mock_client.gauge.reset_mock()

for invalid in ('foo', '', object):
handler_method(mock.MagicMock(data={
'new_state': ha.State('domain.test', invalid, {})}))
self.assertFalse(mock_client.gauge.called)