mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
commit
651f3ab55c
@ -19,6 +19,49 @@ from homeassistant.const import (
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
|
||||
def monkey_patch_asyncio():
|
||||
"""Replace weakref.WeakSet to address Python 3 bug.
|
||||
|
||||
Under heavy threading operations that schedule calls into
|
||||
the asyncio event loop, Task objects are created. Due to
|
||||
a bug in Python, GC may have an issue when switching between
|
||||
the threads and objects with __del__ (which various components
|
||||
in HASS have).
|
||||
|
||||
This monkey-patch removes the weakref.Weakset, and replaces it
|
||||
with an object that ignores the only call utilizing it (the
|
||||
Task.__init__ which calls _all_tasks.add(self)). It also removes
|
||||
the __del__ which could trigger the future objects __del__ at
|
||||
unpredictable times.
|
||||
|
||||
The side-effect of this manipulation of the Task is that
|
||||
Task.all_tasks() is no longer accurate, and there will be no
|
||||
warning emitted if a Task is GC'd while in use.
|
||||
|
||||
On Python 3.6, after the bug is fixed, this monkey-patch can be
|
||||
disabled.
|
||||
|
||||
See https://bugs.python.org/issue26617 for details of the Python
|
||||
bug.
|
||||
"""
|
||||
# pylint: disable=no-self-use, too-few-public-methods, protected-access
|
||||
# pylint: disable=bare-except
|
||||
import asyncio.tasks
|
||||
|
||||
class IgnoreCalls:
|
||||
"""Ignore add calls."""
|
||||
|
||||
def add(self, other):
|
||||
"""No-op add."""
|
||||
return
|
||||
|
||||
asyncio.tasks.Task._all_tasks = IgnoreCalls()
|
||||
try:
|
||||
del asyncio.tasks.Task.__del__
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def validate_python() -> None:
|
||||
"""Validate we're running the right Python version."""
|
||||
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
|
||||
@ -308,6 +351,8 @@ def try_to_restart() -> None:
|
||||
|
||||
def main() -> int:
|
||||
"""Start Home Assistant."""
|
||||
monkey_patch_asyncio()
|
||||
|
||||
validate_python()
|
||||
|
||||
args = get_arguments()
|
||||
|
@ -2,7 +2,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 29
|
||||
PATCH_VERSION = '5'
|
||||
PATCH_VERSION = '6'
|
||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||
REQUIRED_PYTHON_VER = (3, 4, 2)
|
||||
|
@ -62,22 +62,18 @@ def call_from_config(hass, config, blocking=False, variables=None,
|
||||
domain, service_name = domain_service.split('.', 1)
|
||||
service_data = dict(config.get(CONF_SERVICE_DATA, {}))
|
||||
|
||||
def _data_template_creator(value):
|
||||
"""Recursive template creator helper function."""
|
||||
if isinstance(value, list):
|
||||
for idx, element in enumerate(value):
|
||||
value[idx] = _data_template_creator(element)
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
for key, element in value.items():
|
||||
value[key] = _data_template_creator(element)
|
||||
return value
|
||||
value.hass = hass
|
||||
return value.render(variables)
|
||||
|
||||
if CONF_SERVICE_DATA_TEMPLATE in config:
|
||||
for key, value in config[CONF_SERVICE_DATA_TEMPLATE].items():
|
||||
service_data[key] = _data_template_creator(value)
|
||||
def _data_template_creator(value):
|
||||
"""Recursive template creator helper function."""
|
||||
if isinstance(value, list):
|
||||
return [_data_template_creator(item) for item in value]
|
||||
elif isinstance(value, dict):
|
||||
return {key: _data_template_creator(item)
|
||||
for key, item in value.items()}
|
||||
value.hass = hass
|
||||
return value.render(variables)
|
||||
service_data.update(_data_template_creator(
|
||||
config[CONF_SERVICE_DATA_TEMPLATE]))
|
||||
|
||||
if CONF_SERVICE_ENTITY_ID in config:
|
||||
service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID]
|
||||
|
@ -159,6 +159,12 @@ class Template(object):
|
||||
|
||||
return self._compiled
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Compare template with another."""
|
||||
return (self.__class__ == other.__class__ and
|
||||
self.template == other.template and
|
||||
self.hass == other.hass)
|
||||
|
||||
|
||||
class AllStates(object):
|
||||
"""Class to expose all HA states as attributes."""
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Test service helpers."""
|
||||
from copy import deepcopy
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -6,7 +7,8 @@ from unittest.mock import patch
|
||||
import homeassistant.components # noqa
|
||||
from homeassistant import core as ha, loader
|
||||
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ENTITY_ID
|
||||
from homeassistant.helpers import service
|
||||
from homeassistant.helpers import service, template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from tests.common import get_test_home_assistant, mock_service
|
||||
|
||||
@ -97,22 +99,25 @@ class TestServiceHelpers(unittest.TestCase):
|
||||
|
||||
def test_not_mutate_input(self):
|
||||
"""Test for immutable input."""
|
||||
orig = {
|
||||
config = cv.SERVICE_SCHEMA({
|
||||
'service': 'test_domain.test_service',
|
||||
'entity_id': 'hello.world, sensor.beer',
|
||||
'data': {
|
||||
'hello': 1,
|
||||
},
|
||||
}
|
||||
service.call_from_config(self.hass, orig)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual({
|
||||
'service': 'test_domain.test_service',
|
||||
'entity_id': 'hello.world, sensor.beer',
|
||||
'data': {
|
||||
'hello': 1,
|
||||
},
|
||||
}, orig)
|
||||
'data_template': {
|
||||
'nested': {
|
||||
'value': '{{ 1 + 1 }}'
|
||||
}
|
||||
}
|
||||
})
|
||||
orig = deepcopy(config)
|
||||
|
||||
# Only change after call is each template getting hass attached
|
||||
template.attach(self.hass, orig)
|
||||
|
||||
service.call_from_config(self.hass, config, validate_config=False)
|
||||
assert orig == config
|
||||
|
||||
@patch('homeassistant.helpers.service._LOGGER.error')
|
||||
def test_fail_silently_if_no_service(self, mock_log):
|
||||
|
Loading…
x
Reference in New Issue
Block a user