From 0b325b2b7de2a6841d4548730ff812dc404121ac Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 11 Dec 2015 18:43:00 -0800 Subject: [PATCH 1/5] API.stream - catch more errors --- homeassistant/components/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 6a66a2a110e..6d2f9e52a7a 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -126,7 +126,9 @@ def _handle_get_api_stream(handler, path_match, data): wfile.write(msg.encode("UTF-8")) wfile.flush() handler.server.sessions.extend_validation(session_id) - except IOError: + except (IOError, ValueError): + # IOError: socket errors + # ValueError: raised when 'I/O operation on closed file' block.set() def forward_events(event): From 5c63862054078224c01409fbb8b1f2df7de6ecb1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 11 Dec 2015 18:45:53 -0800 Subject: [PATCH 2/5] Fix template rounding --- homeassistant/util/template.py | 4 ++-- tests/util/test_template.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py index 107532db776..bc89d053e60 100644 --- a/homeassistant/util/template.py +++ b/homeassistant/util/template.py @@ -66,8 +66,8 @@ class DomainStates(object): def forgiving_round(value, precision=0): """ Rounding method that accepts strings. """ try: - return int(float(value)) if precision == 0 else round(float(value), - precision) + value = round(float(value), precision) + return int(value) if precision == 0 else value except ValueError: # If value can't be converted to float return value diff --git a/tests/util/test_template.py b/tests/util/test_template.py index 5c1dfff1f85..bbb4de31626 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -57,10 +57,10 @@ class TestUtilTemplate(unittest.TestCase): '{{ states.sensor.temperature.state | round(1) }}')) def test_rounding_value2(self): - self.hass.states.set('sensor.temperature', 12.72) + self.hass.states.set('sensor.temperature', 12.78) self.assertEqual( - '127', + '128', template.render( self.hass, '{{ states.sensor.temperature.state | multiply(10) | round }}')) From b1bf6a609e4e187543ac72d592cb3ecdbabff532 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 11 Dec 2015 19:07:03 -0800 Subject: [PATCH 3/5] Catch exceptions when error rendering templates --- homeassistant/exceptions.py | 7 +++++++ homeassistant/util/template.py | 20 ++++++++++++++++---- tests/util/test_template.py | 6 +++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index bd32d356670..510bc9b4e54 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -14,3 +14,10 @@ class InvalidEntityFormatError(HomeAssistantError): class NoEntitySpecifiedError(HomeAssistantError): """ When no entity is specified. """ pass + + +class TemplateError(HomeAssistantError): + """ Error during template rendering. """ + def __init__(self, exception): + super().__init__('{}: {}'.format(exception.__class__.__name__, + exception)) diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py index bc89d053e60..b7fc3197e08 100644 --- a/homeassistant/util/template.py +++ b/homeassistant/util/template.py @@ -6,7 +6,12 @@ Template utility methods for rendering strings with HA data. """ # pylint: disable=too-few-public-methods import json +import logging +import jinja2 from jinja2.sandbox import ImmutableSandboxedEnvironment +from homeassistant.exceptions import TemplateError + +_LOGGER = logging.getLogger(__name__) def render_with_possible_json_value(hass, template, value): @@ -20,7 +25,11 @@ def render_with_possible_json_value(hass, template, value): except ValueError: pass - return render(hass, template, variables) + try: + return render(hass, template, variables) + except TemplateError: + _LOGGER.exception('Error parsing value') + return value def render(hass, template, variables=None, **kwargs): @@ -28,9 +37,12 @@ def render(hass, template, variables=None, **kwargs): if variables is not None: kwargs.update(variables) - return ENV.from_string(template, { - 'states': AllStates(hass) - }).render(kwargs) + try: + return ENV.from_string(template, { + 'states': AllStates(hass) + }).render(kwargs) + except jinja2.TemplateError as err: + raise TemplateError(err) class AllStates(object): diff --git a/tests/util/test_template.py b/tests/util/test_template.py index bbb4de31626..16e1f8b6a04 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -7,7 +7,7 @@ Tests Home Assistant util methods. # pylint: disable=too-many-public-methods import unittest import homeassistant.core as ha - +from homeassistant.exceptions import TemplateError from homeassistant.util import template @@ -84,3 +84,7 @@ class TestUtilTemplate(unittest.TestCase): '', template.render_with_possible_json_value( self.hass, '{{ value_json }}', '{ I AM NOT JSON }')) + + def test_raise_exception_on_error(self): + with self.assertRaises(TemplateError): + template.render(self.hass, '{{ invalid_syntax') From 9eea7a6cde126dbf7c2f76d9708cb7cc93eb34df Mon Sep 17 00:00:00 2001 From: Richard Arends Date: Sat, 12 Dec 2015 16:25:56 +0100 Subject: [PATCH 4/5] Added support for MPD to start playing the current song/playlist --- homeassistant/components/media_player/mpd.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index 8134ce5e743..b6c89cbfc3f 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -21,14 +21,14 @@ from homeassistant.const import ( from homeassistant.components.media_player import ( MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF, - SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, + SUPPORT_TURN_ON, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, MEDIA_TYPE_MUSIC) _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['python-mpd2==0.5.4'] SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK + SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK # pylint: disable=unused-argument @@ -163,9 +163,13 @@ class MpdDevice(MediaPlayerDevice): return SUPPORT_MPD def turn_off(self): - """ Service to exit the running MPD. """ + """ Service to send the MPD the command to stop playing. """ self.client.stop() + def turn_on(self): + """ Service to send the MPD the command to start playing. """ + self.client.play() + def set_volume_level(self, volume): """ Sets volume """ self.client.setvol(int(volume * 100)) From 2b975c8620e0e420df30327d08b6d654ced1dbda Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 12 Dec 2015 10:35:15 -0800 Subject: [PATCH 5/5] Add flexible error value for value template parsing --- homeassistant/util/template.py | 6 ++++-- tests/util/test_template.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py index b7fc3197e08..ad0fabdab53 100644 --- a/homeassistant/util/template.py +++ b/homeassistant/util/template.py @@ -12,9 +12,11 @@ from jinja2.sandbox import ImmutableSandboxedEnvironment from homeassistant.exceptions import TemplateError _LOGGER = logging.getLogger(__name__) +_SENTINEL = object() -def render_with_possible_json_value(hass, template, value): +def render_with_possible_json_value(hass, template, value, + error_value=_SENTINEL): """ Renders template with value exposed. If valid JSON will expose value_json too. """ variables = { @@ -29,7 +31,7 @@ def render_with_possible_json_value(hass, template, value): return render(hass, template, variables) except TemplateError: _LOGGER.exception('Error parsing value') - return value + return value if error_value is _SENTINEL else error_value def render(hass, template, variables=None, **kwargs): diff --git a/tests/util/test_template.py b/tests/util/test_template.py index 16e1f8b6a04..ba354f3e7be 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -85,6 +85,18 @@ class TestUtilTemplate(unittest.TestCase): template.render_with_possible_json_value( self.hass, '{{ value_json }}', '{ I AM NOT JSON }')) + def test_render_with_possible_json_value_with_template_error(self): + self.assertEqual( + 'hello', + template.render_with_possible_json_value( + self.hass, '{{ value_json', 'hello')) + + def test_render_with_possible_json_value_with_template_error_error_value(self): + self.assertEqual( + '-', + template.render_with_possible_json_value( + self.hass, '{{ value_json', 'hello', '-')) + def test_raise_exception_on_error(self): with self.assertRaises(TemplateError): template.render(self.hass, '{{ invalid_syntax')