diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py new file mode 100644 index 00000000000..5ab3f149174 --- /dev/null +++ b/homeassistant/util/template.py @@ -0,0 +1,58 @@ +""" +homeassistant.util.template +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Template utility methods for rendering strings with HA data. +""" +# pylint: disable=too-few-public-methods +from jinja2.sandbox import SandboxedEnvironment + +ENV = SandboxedEnvironment() + + +def forgiving_round(value, precision): + """ Rounding method that accepts strings. """ + try: + return round(float(value), precision) + except ValueError: + # If value can't be converted to float + return value + + +ENV.filters['round'] = forgiving_round + + +def render(hass, template): + """ Render given template. """ + return ENV.from_string(template).render( + states=AllStates(hass)) + + +class AllStates(object): + """ Class to expose all HA states as attributes. """ + def __init__(self, hass): + self._hass = hass + + def __getattr__(self, name): + return DomainStates(self._hass, name) + + def __iter__(self): + return iter(sorted(self._hass.states.all(), + key=lambda state: state.entity_id)) + + +class DomainStates(object): + """ Class to expose a specific HA domain as attributes. """ + + def __init__(self, hass, domain): + self._hass = hass + self._domain = domain + + def __getattr__(self, name): + return self._hass.states.get('{}.{}'.format(self._domain, name)) + + def __iter__(self): + return iter(sorted( + (state for state in self._hass.states.all() + if state.domain == self._domain), + key=lambda state: state.entity_id)) diff --git a/tests/util/test_template.py b/tests/util/test_template.py new file mode 100644 index 00000000000..081a93ff867 --- /dev/null +++ b/tests/util/test_template.py @@ -0,0 +1,57 @@ +""" +tests.test_util +~~~~~~~~~~~~~~~~~ + +Tests Home Assistant util methods. +""" +# pylint: disable=too-many-public-methods +import unittest +import homeassistant.core as ha + +from homeassistant.util import template + + +class TestUtilTemplate(unittest.TestCase): + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_referring_states_by_entity_id(self): + self.hass.states.set('test.object', 'happy') + self.assertEqual( + 'happy', + template.render(self.hass, '{{ states.test.object.state }}')) + + def test_iterating_all_states(self): + self.hass.states.set('test.object', 'happy') + self.hass.states.set('sensor.temperature', 10) + + self.assertEqual( + '10happy', + template.render( + self.hass, + '{% for state in states %}{{ state.state }}{% endfor %}')) + + def test_iterating_domain_states(self): + self.hass.states.set('test.object', 'happy') + self.hass.states.set('sensor.back_door', 'open') + self.hass.states.set('sensor.temperature', 10) + + self.assertEqual( + 'open10', + template.render( + self.hass, + '{% for state in states.sensor %}{{ state.state }}{% endfor %}')) + + def test_rounding_value(self): + self.hass.states.set('sensor.temperature', 12.34) + + self.assertEqual( + '12.3', + template.render( + self.hass, + '{{ states.sensor.temperature.state | round(1) }}'))