diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 9ff2fbd878a..341e6e90233 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -166,7 +166,9 @@ def async_setup(hass, config): for entity in component.async_extract_from_service(service_call): tasks.append(entity.async_trigger( service_call.data.get(ATTR_VARIABLES), True)) - yield from asyncio.wait(tasks, loop=hass.loop) + + if tasks: + yield from asyncio.wait(tasks, loop=hass.loop) @asyncio.coroutine def turn_onoff_service_handler(service_call): @@ -175,7 +177,9 @@ def async_setup(hass, config): method = 'async_{}'.format(service_call.service) for entity in component.async_extract_from_service(service_call): tasks.append(getattr(entity, method)()) - yield from asyncio.wait(tasks, loop=hass.loop) + + if tasks: + yield from asyncio.wait(tasks, loop=hass.loop) @asyncio.coroutine def toggle_service_handler(service_call): @@ -186,7 +190,9 @@ def async_setup(hass, config): tasks.append(entity.async_turn_off()) else: tasks.append(entity.async_turn_on()) - yield from asyncio.wait(tasks, loop=hass.loop) + + if tasks: + yield from asyncio.wait(tasks, loop=hass.loop) @asyncio.coroutine def reload_service_handler(service_call): diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 446b1f5f28b..1bbb3576eed 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -18,7 +18,7 @@ from aiohttp.web_exceptions import HTTPUnauthorized, HTTPMovedPermanently import homeassistant.helpers.config_validation as cv import homeassistant.remote as rem -from homeassistant.util import get_local_ip +import homeassistant.util as hass_util from homeassistant.components import persistent_notification from homeassistant.const import ( SERVER_PORT, CONTENT_TYPE_JSON, ALLOWED_CORS_HEADERS, @@ -41,6 +41,7 @@ REQUIREMENTS = ('aiohttp_cors==0.5.0',) CONF_API_PASSWORD = 'api_password' CONF_SERVER_HOST = 'server_host' CONF_SERVER_PORT = 'server_port' +CONF_BASE_URL = 'base_url' CONF_DEVELOPMENT = 'development' CONF_SSL_CERTIFICATE = 'ssl_certificate' CONF_SSL_KEY = 'ssl_key' @@ -84,6 +85,7 @@ HTTP_SCHEMA = vol.Schema({ vol.Optional(CONF_SERVER_HOST, default=DEFAULT_SERVER_HOST): cv.string, vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT): vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), + vol.Optional(CONF_BASE_URL): cv.string, vol.Optional(CONF_DEVELOPMENT, default=DEFAULT_DEVELOPMENT): cv.string, vol.Optional(CONF_SSL_CERTIFICATE, default=None): cv.isfile, vol.Optional(CONF_SSL_KEY, default=None): cv.isfile, @@ -155,9 +157,17 @@ def async_setup(hass, config): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_server) hass.http = server - hass.config.api = rem.API(server_host if server_host != '0.0.0.0' - else get_local_ip(), - api_password, server_port, + + host = conf.get(CONF_BASE_URL) + + if host: + pass + elif server_host != DEFAULT_SERVER_HOST: + host = server_host + else: + host = hass_util.get_local_ip() + + hass.config.api = rem.API(host, api_password, server_port, ssl_certificate is not None) return True diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index d0faa60684f..32cbbaa265b 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -157,7 +157,8 @@ def async_setup(hass, config): hass.services.async_register( DOMAIN, SERVICE_CLEAR_CACHE, async_clear_cache_handle, - descriptions.get(SERVICE_CLEAR_CACHE), schema=SERVICE_CLEAR_CACHE) + descriptions.get(SERVICE_CLEAR_CACHE), + schema=SCHEMA_SERVICE_CLEAR_CACHE) return True @@ -170,9 +171,9 @@ class SpeechManager(object): self.hass = hass self.providers = {} - self.use_cache = True - self.cache_dir = None - self.time_memory = None + self.use_cache = DEFAULT_CACHE + self.cache_dir = DEFAULT_CACHE_DIR + self.time_memory = DEFAULT_TIME_MEMORY self.file_cache = {} self.mem_cache = {} @@ -229,7 +230,7 @@ class SpeechManager(object): """Remove files from filesystem.""" for _, filename in self.file_cache.items(): try: - os.remove(os.path.join(self.cache_dir), filename) + os.remove(os.path.join(self.cache_dir, filename)) except OSError: pass diff --git a/homeassistant/const.py b/homeassistant/const.py index 0a93f080df7..b4eaf6a5111 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 35 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 4, 2) diff --git a/homeassistant/core.py b/homeassistant/core.py index 7daab159f21..de272beeeea 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -298,9 +298,8 @@ class HomeAssistant(object): # cleanup async layer from python logging if self.data.get(DATA_ASYNCHANDLER): handler = self.data.pop(DATA_ASYNCHANDLER) - logger = logging.getLogger('') - handler.close() - logger.removeHandler(handler) + logging.getLogger('').removeHandler(handler) + yield from handler.async_close(blocking=True) self.loop.stop() diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 1ddec0bc6a1..0a1218f5796 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -49,6 +49,23 @@ class AsyncHandler(object): """Wrap close to handler.""" self.emit(None) + @asyncio.coroutine + def async_close(self, blocking=False): + """Close the handler. + + When blocking=True, will wait till closed. + """ + self.close() + + if blocking: + # Python 3.4.4+ + # pylint: disable=no-member + if hasattr(self._queue, 'join'): + yield from self._queue.join() + else: + while not self._queue.empty(): + yield from asyncio.sleep(0, loop=self.loop) + def emit(self, record): """Process a record.""" ident = self.loop.__dict__.get("_thread_ident") @@ -66,15 +83,23 @@ class AsyncHandler(object): def _process(self): """Process log in a thread.""" + support_join = hasattr(self._queue, 'task_done') + while True: record = run_coroutine_threadsafe( self._queue.get(), self.loop).result() + # pylint: disable=no-member + if record is None: self.handler.close() + if support_join: + self.loop.call_soon_threadsafe(self._queue.task_done) return self.handler.emit(record) + if support_join: + self.loop.call_soon_threadsafe(self._queue.task_done) def createLock(self): """Ignore lock stuff.""" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index f50e1fb9dbf..10793406ea7 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -1,6 +1,7 @@ """The tests for the Home Assistant HTTP component.""" import asyncio import requests +from unittest.mock import MagicMock from homeassistant import bootstrap, const import homeassistant.components.http as http @@ -154,3 +155,49 @@ def test_registering_view_while_running(hass, test_client): text = yield from resp.text() assert text == 'hello' + + +def test_api_base_url(loop): + """Test setting api url.""" + + hass = MagicMock() + hass.loop = loop + + assert loop.run_until_complete( + bootstrap.async_setup_component(hass, 'http', { + 'http': { + 'base_url': 'example.com' + } + }) + ) + + assert hass.config.api.base_url == 'http://example.com:8123' + + assert loop.run_until_complete( + bootstrap.async_setup_component(hass, 'http', { + 'http': { + 'server_host': '1.1.1.1' + } + }) + ) + + assert hass.config.api.base_url == 'http://1.1.1.1:8123' + + assert loop.run_until_complete( + bootstrap.async_setup_component(hass, 'http', { + 'http': { + 'server_host': '1.1.1.1' + } + }) + ) + + assert hass.config.api.base_url == 'http://1.1.1.1:8123' + + assert loop.run_until_complete( + bootstrap.async_setup_component(hass, 'http', { + 'http': { + } + }) + ) + + assert hass.config.api.base_url == 'http://127.0.0.1:8123' diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index fbdbddb8db5..fccd9d66bd7 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -88,35 +88,35 @@ class TestTTS(object): self.default_tts_cache, "265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3")) - def test_setup_component_and_test_service_clear_cache(self): - """Setup the demo platform and call service clear cache.""" - calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) + def test_setup_component_and_test_service_clear_cache(self): + """Setup the demo platform and call service clear cache.""" + calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) - config = { - tts.DOMAIN: { - 'platform': 'demo', - } + config = { + tts.DOMAIN: { + 'platform': 'demo', } + } - with assert_setup_component(1, tts.DOMAIN): - setup_component(self.hass, tts.DOMAIN, config) + with assert_setup_component(1, tts.DOMAIN): + setup_component(self.hass, tts.DOMAIN, config) - self.hass.services.call(tts.DOMAIN, 'demo_say', { - tts.ATTR_MESSAGE: "I person is on front of your door.", - }) - self.hass.block_till_done() + self.hass.services.call(tts.DOMAIN, 'demo_say', { + tts.ATTR_MESSAGE: "I person is on front of your door.", + }) + self.hass.block_till_done() - assert len(calls) == 1 - assert os.path.isfile(os.path.join( - self.default_tts_cache, - "265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3")) + assert len(calls) == 1 + assert os.path.isfile(os.path.join( + self.default_tts_cache, + "265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3")) - self.hass.services.call(tts.DOMAIN, tts.SERVICE_CLEAR_CACHE, {}) - self.hass.block_till_done() + self.hass.services.call(tts.DOMAIN, tts.SERVICE_CLEAR_CACHE, {}) + self.hass.block_till_done() - assert not os.path.isfile(os.path.join( - self.default_tts_cache, - "265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3")) + assert not os.path.isfile(os.path.join( + self.default_tts_cache, + "265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3")) def test_setup_component_and_test_service_with_receive_voice(self): """Setup the demo platform and call service and receive voice."""