Merge remote-tracking branch 'origin/dev' into lirc

This commit is contained in:
ntouran 2016-05-25 09:21:58 -07:00
commit 262d95b7b1
13 changed files with 167 additions and 91 deletions

View File

@ -4,7 +4,6 @@ from __future__ import print_function
import argparse
import os
import platform
import signal
import subprocess
import sys
import threading
@ -334,29 +333,6 @@ def try_to_restart():
except AssertionError:
sys.stderr.write("Failed to count non-daemonic threads.\n")
# Send terminate signal to all processes in our process group which
# should be any children that have not themselves changed the process
# group id. Don't bother if couldn't even call setpgid.
if hasattr(os, 'setpgid'):
sys.stderr.write("Signalling child processes to terminate...\n")
os.kill(0, signal.SIGTERM)
# wait for child processes to terminate
try:
while True:
time.sleep(1)
if os.waitpid(0, os.WNOHANG) == (0, 0):
break
except OSError:
pass
elif os.name == 'nt':
# Maybe one of the following will work, but how do we indicate which
# processes are our children if there is no process group?
# os.kill(0, signal.CTRL_C_EVENT)
# os.kill(0, signal.CTRL_BREAK_EVENT)
pass
# Try to not leave behind open filedescriptors with the emphasis on try.
try:
max_fd = os.sysconf("SC_OPEN_MAX")
@ -408,13 +384,6 @@ def main():
if args.pid_file:
write_pid(args.pid_file)
# Create new process group if we can
if hasattr(os, 'setpgid'):
try:
os.setpgid(0, 0)
except PermissionError:
pass
exit_code = setup_and_run_hass(config_dir, args)
if exit_code == RESTART_EXIT_CODE and not args.runner:
try_to_restart()

View File

@ -113,14 +113,13 @@ class APIEventStream(HomeAssistantView):
while True:
try:
# Somehow our queue.get takes too long to
# be notified of arrival of object. Probably
# Somehow our queue.get sometimes takes too long to
# be notified of arrival of data. Probably
# because of our spawning on hub in other thread
# hack. Because current goal is to get this out,
# We just timeout every second because it will
# return right away if qsize() > 0.
# So yes, we're basically polling :(
# socket.io anyone?
payload = to_write.get(timeout=1)
if payload is stop_obj:

View File

@ -8,12 +8,12 @@
{
"src": "/static/favicon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"type": "image/png"
},
{
"src": "/static/favicon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"type": "image/png"
}
]
}

View File

@ -15,7 +15,7 @@ from homeassistant.helpers.entity import split_entity_id
import homeassistant.util.dt as dt_util
DOMAIN = "http"
REQUIREMENTS = ("eventlet==0.18.4", "static3==0.7.0", "Werkzeug==0.11.5",)
REQUIREMENTS = ("eventlet==0.19.0", "static3==0.7.0", "Werkzeug==0.11.5",)
CONF_API_PASSWORD = "api_password"
CONF_SERVER_HOST = "server_host"
@ -31,8 +31,29 @@ _FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
_LOGGER = logging.getLogger(__name__)
class HideSensitiveFilter(logging.Filter):
"""Filter API password calls."""
# pylint: disable=too-few-public-methods
def __init__(self, hass):
"""Initialize sensitive data filter."""
super().__init__()
self.hass = hass
def filter(self, record):
"""Hide sensitive data in messages."""
if self.hass.wsgi.api_password is None:
return True
record.msg = record.msg.replace(self.hass.wsgi.api_password, '*******')
return True
def setup(hass, config):
"""Set up the HTTP API and debug interface."""
_LOGGER.addFilter(HideSensitiveFilter(hass))
conf = config.get(DOMAIN, {})
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
@ -202,7 +223,7 @@ class HomeAssistantWSGI(object):
"""Register a redirect with the server.
If given this must be either a string or callable. In case of a
callable its called with the url adapter that triggered the match and
callable it's called with the url adapter that triggered the match and
the values of the URL as keyword arguments and has to return the target
for the redirect, otherwise it has to be a string with placeholders in
rule syntax.
@ -243,9 +264,9 @@ class HomeAssistantWSGI(object):
sock = eventlet.listen((self.server_host, self.server_port))
if self.ssl_certificate:
eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
keyfile=self.ssl_key, server_side=True)
wsgi.server(sock, self)
sock = eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
keyfile=self.ssl_key, server_side=True)
wsgi.server(sock, self, log=_LOGGER)
def dispatch_request(self, request):
"""Handle incoming request."""
@ -318,9 +339,7 @@ class HomeAssistantView(object):
def handle_request(self, request, **values):
"""Handle request to url."""
from werkzeug.exceptions import (
MethodNotAllowed, Unauthorized, BadRequest,
)
from werkzeug.exceptions import MethodNotAllowed, Unauthorized
try:
handler = getattr(self, request.method.lower())
@ -342,18 +361,6 @@ class HomeAssistantView(object):
self.hass.wsgi.api_password):
authenticated = True
else:
# Do we still want to support passing it in as post data?
try:
json_data = request.json
if (json_data is not None and
hmac.compare_digest(
json_data.get(DATA_API_PASSWORD, ''),
self.hass.wsgi.api_password)):
authenticated = True
except BadRequest:
pass
if self.requires_auth and not authenticated:
raise Unauthorized()

View File

@ -16,7 +16,7 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component
DOMAIN = "isy994"
REQUIREMENTS = ['PyISY==1.0.5']
REQUIREMENTS = ['PyISY==1.0.6']
DISCOVER_LIGHTS = "isy994.lights"
DISCOVER_SWITCHES = "isy994.switches"
DISCOVER_SENSORS = "isy994.sensors"

View File

@ -37,7 +37,8 @@ PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): "lg_netcast",
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_ACCESS_TOKEN): vol.All(cv.string, vol.Length(max=6)),
vol.Optional(CONF_ACCESS_TOKEN, default=None):
vol.All(cv.string, vol.Length(max=6)),
})

View File

@ -136,11 +136,12 @@ class GoogleTravelTimeSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
try:
res = self._matrix['rows'][0]['elements'][0]['duration']['value']
return round(res/60)
except KeyError:
return None
_data = self._matrix['rows'][0]['elements'][0]
if 'duration_in_traffic' in _data:
return round(_data['duration_in_traffic']['value']/60)
if 'duration' in _data:
return round(_data['duration']['value']/60)
return None
@property
def name(self):
@ -175,9 +176,15 @@ class GoogleTravelTimeSensor(Entity):
atime = options_copy.get('arrival_time')
if dtime is not None and ':' in dtime:
options_copy['departure_time'] = convert_time_to_utc(dtime)
elif dtime is not None:
options_copy['departure_time'] = dtime
else:
options_copy['departure_time'] = 'now'
if atime is not None and ':' in atime:
options_copy['arrival_time'] = convert_time_to_utc(atime)
elif atime is not None:
options_copy['arrival_time'] = atime
self._matrix = self._client.distance_matrix(self._origin,
self._destination,

View File

@ -93,7 +93,11 @@ class OctoPrintSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
return self._state
sensor_unit = self.unit_of_measurement
if sensor_unit == TEMP_CELSIUS or sensor_unit == "%":
return round(self._state, 2)
else:
return self._state
@property
def unit_of_measurement(self):

View File

@ -15,7 +15,7 @@ from homeassistant.util import dt as dt_util
from homeassistant.util import location as location_util
from homeassistant.const import CONF_ELEVATION
REQUIREMENTS = ['astral==1.0']
REQUIREMENTS = ['astral==1.1']
DOMAIN = "sun"
ENTITY_ID = "sun.sun"

View File

@ -9,7 +9,7 @@ voluptuous==0.8.9
webcolors==1.5
# homeassistant.components.isy994
PyISY==1.0.5
PyISY==1.0.6
# homeassistant.components.arduino
PyMata==2.12
@ -30,7 +30,7 @@ Werkzeug==0.11.5
apcaccess==0.0.4
# homeassistant.components.sun
astral==1.0
astral==1.1
# homeassistant.components.light.blinksticklight
blinkstick==1.1.7
@ -57,7 +57,7 @@ dweepy==0.2.0
eliqonline==1.0.12
# homeassistant.components.http
eventlet==0.18.4
eventlet==0.19.0
# homeassistant.components.thermostat.honeywell
evohomeclient==0.2.5

View File

@ -4,5 +4,6 @@ coveralls>=1.1
pytest>=2.9.1
pytest-cov>=2.2.0
pytest-timeout>=1.0.0
pytest-capturelog>=0.7
betamax==0.5.1
pydocstyle>=1.0.0

View File

@ -1,4 +1,4 @@
"""The tests for the Home Assistant HTTP component."""
"""The tests for the Home Assistant API component."""
# pylint: disable=protected-access,too-many-public-methods
# from contextlib import closing
import json
@ -66,28 +66,6 @@ class TestAPI(unittest.TestCase):
"""Stop everything that was started."""
hass.pool.block_till_done()
# TODO move back to http component and test with use_auth.
def test_access_denied_without_password(self):
"""Test access without password."""
req = requests.get(_url(const.URL_API))
self.assertEqual(401, req.status_code)
def test_access_denied_with_wrong_password(self):
"""Test ascces with wrong password."""
req = requests.get(
_url(const.URL_API),
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
self.assertEqual(401, req.status_code)
def test_access_with_password_in_url(self):
"""Test access with password in URL."""
req = requests.get(
"{}?api_password={}".format(_url(const.URL_API), API_PASSWORD))
self.assertEqual(200, req.status_code)
def test_api_list_state_entities(self):
"""Test if the debug interface allows us to list state entities."""
req = requests.get(_url(const.URL_API_STATES),

View File

@ -0,0 +1,110 @@
"""The tests for the Home Assistant HTTP component."""
# pylint: disable=protected-access,too-many-public-methods
import logging
import eventlet
import requests
from homeassistant import bootstrap, const
import homeassistant.components.http as http
from tests.common import get_test_instance_port, get_test_home_assistant
API_PASSWORD = "test1234"
SERVER_PORT = get_test_instance_port()
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
HA_HEADERS = {
const.HTTP_HEADER_HA_AUTH: API_PASSWORD,
const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON,
}
hass = None
def _url(path=""):
"""Helper method to generate URLs."""
return HTTP_BASE_URL + path
def setUpModule(): # pylint: disable=invalid-name
"""Initialize a Home Assistant server."""
global hass
hass = get_test_home_assistant()
hass.bus.listen('test_event', lambda _: _)
hass.states.set('test.test', 'a_state')
bootstrap.setup_component(
hass, http.DOMAIN,
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
http.CONF_SERVER_PORT: SERVER_PORT}})
bootstrap.setup_component(hass, 'api')
hass.start()
eventlet.sleep(0.05)
def tearDownModule(): # pylint: disable=invalid-name
"""Stop the Home Assistant server."""
hass.stop()
class TestHttp:
"""Test HTTP component."""
def test_access_denied_without_password(self):
"""Test access without password."""
req = requests.get(_url(const.URL_API))
assert req.status_code == 401
def test_access_denied_with_wrong_password_in_header(self):
"""Test ascces with wrong password."""
req = requests.get(
_url(const.URL_API),
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
assert req.status_code == 401
def test_access_with_password_in_header(self, caplog):
"""Test access with password in URL."""
# Hide logging from requests package that we use to test logging
caplog.setLevel(logging.WARNING,
logger='requests.packages.urllib3.connectionpool')
req = requests.get(
_url(const.URL_API),
headers={const.HTTP_HEADER_HA_AUTH: API_PASSWORD})
assert req.status_code == 200
logs = caplog.text()
assert const.URL_API in logs
assert API_PASSWORD not in logs
def test_access_denied_with_wrong_password_in_url(self):
"""Test ascces with wrong password."""
req = requests.get(_url(const.URL_API),
params={'api_password': 'wrongpassword'})
assert req.status_code == 401
def test_access_with_password_in_url(self, caplog):
"""Test access with password in URL."""
# Hide logging from requests package that we use to test logging
caplog.setLevel(logging.WARNING,
logger='requests.packages.urllib3.connectionpool')
req = requests.get(_url(const.URL_API),
params={'api_password': API_PASSWORD})
assert req.status_code == 200
logs = caplog.text()
assert const.URL_API in logs
assert API_PASSWORD not in logs