* Upgrade aiohttp2

* Fix resource caching

* Fix helpers.aiohttp_client

* Lint

* Use static path for api error_log

* Fix ClientErrors import

* Remove not needed DisconnectError

* Remove releasing responses code

* Add timeout if stream starts non responding

* More async_aiohttp_proxy_stream cleanup

* Fix references to ClientError

* Fix fingerprinting

* Fix aiohttp stream tests

* Rename aiohttp_proxy_stream

* Remove impossible darksky test

* Fix sleepiq requests escaping mocker

* Lint

* Remove deprecated parameter

* Break up aiohttp_proxy_stream in 2 methods

* Lint

* Upgrade to aiohttp 2.0.4

* Convert connector close to a callback

* Fix static fingerprinted links
This commit is contained in:
Paulus Schoutsen 2017-03-30 00:50:53 -07:00 committed by GitHub
parent 7b83a836f3
commit 714b516176
36 changed files with 250 additions and 442 deletions

View file

@ -50,9 +50,11 @@ def setup(hass, config):
hass.http.register_view(APIDomainServicesView) hass.http.register_view(APIDomainServicesView)
hass.http.register_view(APIEventForwardingView) hass.http.register_view(APIEventForwardingView)
hass.http.register_view(APIComponentsView) hass.http.register_view(APIComponentsView)
hass.http.register_view(APIErrorLogView)
hass.http.register_view(APITemplateView) hass.http.register_view(APITemplateView)
hass.http.register_static_path(
URL_API_ERROR_LOG, hass.config.path(ERROR_LOG_FILENAME), False)
return True return True
@ -402,20 +404,6 @@ class APIComponentsView(HomeAssistantView):
return self.json(request.app['hass'].config.components) return self.json(request.app['hass'].config.components)
class APIErrorLogView(HomeAssistantView):
"""View to handle ErrorLog requests."""
url = URL_API_ERROR_LOG
name = "api:error-log"
@asyncio.coroutine
def get(self, request):
"""Serve error log."""
resp = yield from self.file(
request, request.app['hass'].config.path(ERROR_LOG_FILENAME))
return resp
class APITemplateView(HomeAssistantView): class APITemplateView(HomeAssistantView):
"""View to handle requests.""" """View to handle requests."""

View file

@ -58,7 +58,6 @@ def async_get_image(hass, entity_id, timeout=10):
state.attributes.get(ATTR_ENTITY_PICTURE) state.attributes.get(ATTR_ENTITY_PICTURE)
) )
response = None
try: try:
with async_timeout.timeout(timeout, loop=hass.loop): with async_timeout.timeout(timeout, loop=hass.loop):
response = yield from websession.get(url) response = yield from websession.get(url)
@ -70,13 +69,9 @@ def async_get_image(hass, entity_id, timeout=10):
image = yield from response.read() image = yield from response.read()
return image return image
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
raise HomeAssistantError("Can't connect to {0}".format(url)) raise HomeAssistantError("Can't connect to {0}".format(url))
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass, config): def async_setup(hass, config):

View file

@ -16,7 +16,7 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import ( from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream) async_get_clientsession, async_aiohttp_proxy_web)
REQUIREMENTS = ['amcrest==1.1.4'] REQUIREMENTS = ['amcrest==1.1.4']
@ -125,7 +125,7 @@ class AmcrestCam(Camera):
stream_coro = websession.get( stream_coro = websession.get(
streaming_url, auth=self._token, timeout=TIMEOUT) streaming_url, auth=self._token, timeout=TIMEOUT)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro) yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property @property
def name(self): def name(self):

View file

@ -8,14 +8,14 @@ import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
from aiohttp import web
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import ( from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS) DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
DEPENDENCIES = ['ffmpeg'] DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -69,26 +69,10 @@ class FFmpegCamera(Camera):
yield from stream.open_camera( yield from stream.open_camera(
self._input, extra_cmd=self._extra_arguments) self._input, extra_cmd=self._extra_arguments)
response = web.StreamResponse() yield from async_aiohttp_proxy_stream(
response.content_type = 'multipart/x-mixed-replace;boundary=ffserver' self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from response.prepare(request) yield from stream.close()
try:
while True:
data = yield from stream.read(102400)
if not data:
break
response.write(data)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
response = None
finally:
yield from stream.close()
if response is not None:
yield from response.write_eof()
@property @property
def name(self): def name(self):

View file

@ -107,7 +107,6 @@ class GenericCamera(Camera):
None, fetch) None, fetch)
# async # async
else: else:
response = None
try: try:
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
@ -117,14 +116,9 @@ class GenericCamera(Camera):
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image') _LOGGER.error('Timeout getting camera image')
return self._last_image return self._last_image
except (aiohttp.errors.ClientError, except aiohttp.ClientError as err:
aiohttp.errors.DisconnectedError,
aiohttp.errors.HttpProcessingError) as err:
_LOGGER.error('Error getting new camera image: %s', err) _LOGGER.error('Error getting new camera image: %s', err)
return self._last_image return self._last_image
finally:
if response is not None:
yield from response.release()
self._last_url = url self._last_url = url
return self._last_image return self._last_image

View file

@ -19,7 +19,7 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import ( from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream) async_get_clientsession, async_aiohttp_proxy_web)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -93,7 +93,6 @@ class MjpegCamera(Camera):
return image return image
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
response = None
try: try:
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from websession.get( response = yield from websession.get(
@ -105,14 +104,9 @@ class MjpegCamera(Camera):
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image') _LOGGER.error('Timeout getting camera image')
except (aiohttp.errors.ClientError, except aiohttp.ClientError as err:
aiohttp.errors.ClientDisconnectedError) as err:
_LOGGER.error('Error getting new camera image: %s', err) _LOGGER.error('Error getting new camera image: %s', err)
finally:
if response is not None:
yield from response.release()
def camera_image(self): def camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
if self._username and self._password: if self._username and self._password:
@ -140,7 +134,7 @@ class MjpegCamera(Camera):
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
stream_coro = websession.get(self._mjpeg_url, auth=self._auth) stream_coro = websession.get(self._mjpeg_url, auth=self._auth)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro) yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property @property
def name(self): def name(self):

View file

@ -19,7 +19,7 @@ from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA) Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import ( from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_create_clientsession, async_get_clientsession, async_create_clientsession,
async_aiohttp_proxy_stream) async_aiohttp_proxy_web)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe from homeassistant.util.async import run_coroutine_threadsafe
@ -74,7 +74,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
'version': '1', 'version': '1',
'query': 'SYNO.' 'query': 'SYNO.'
} }
query_req = None
try: try:
with async_timeout.timeout(timeout, loop=hass.loop): with async_timeout.timeout(timeout, loop=hass.loop):
query_req = yield from websession_init.get( query_req = yield from websession_init.get(
@ -88,14 +87,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera_path = query_resp['data'][CAMERA_API]['path'] camera_path = query_resp['data'][CAMERA_API]['path']
streaming_path = query_resp['data'][STREAMING_API]['path'] streaming_path = query_resp['data'][STREAMING_API]['path']
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_api_url) _LOGGER.exception("Error on %s", syno_api_url)
return False return False
finally:
if query_req is not None:
yield from query_req.release()
# Authticate to NAS to get a session id # Authticate to NAS to get a session id
syno_auth_url = SYNO_API_URL.format( syno_auth_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, auth_path) config.get(CONF_URL), WEBAPI_PATH, auth_path)
@ -128,13 +123,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
syno_camera_url, syno_camera_url,
params=camera_payload params=camera_payload
) )
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_camera_url) _LOGGER.exception("Error on %s", syno_camera_url)
return False return False
camera_resp = yield from camera_req.json() camera_resp = yield from camera_req.json()
cameras = camera_resp['data']['cameras'] cameras = camera_resp['data']['cameras']
yield from camera_req.release()
# add cameras # add cameras
devices = [] devices = []
@ -172,7 +166,6 @@ def get_session_id(hass, websession, username, password, login_url, timeout):
'session': 'SurveillanceStation', 'session': 'SurveillanceStation',
'format': 'sid' 'format': 'sid'
} }
auth_req = None
try: try:
with async_timeout.timeout(timeout, loop=hass.loop): with async_timeout.timeout(timeout, loop=hass.loop):
auth_req = yield from websession.get( auth_req = yield from websession.get(
@ -182,14 +175,10 @@ def get_session_id(hass, websession, username, password, login_url, timeout):
auth_resp = yield from auth_req.json() auth_resp = yield from auth_req.json()
return auth_resp['data']['sid'] return auth_resp['data']['sid']
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", login_url) _LOGGER.exception("Error on %s", login_url)
return False return False
finally:
if auth_req is not None:
yield from auth_req.release()
class SynologyCamera(Camera): class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera.""" """An implementation of a Synology NAS based IP camera."""
@ -235,12 +224,11 @@ class SynologyCamera(Camera):
image_url, image_url,
params=image_payload params=image_payload
) )
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", image_url) _LOGGER.error("Error fetching %s", image_url)
return None return None
image = yield from response.read() image = yield from response.read()
yield from response.release()
return image return image
@ -260,7 +248,7 @@ class SynologyCamera(Camera):
stream_coro = self._websession.get( stream_coro = self._websession.get(
streaming_url, params=streaming_payload) streaming_url, params=streaming_payload)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro) yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property @property
def name(self): def name(self):

View file

@ -553,7 +553,6 @@ class Device(Entity):
# bytes like 00 get truncates to 0, API needs full bytes # bytes like 00 get truncates to 0, API needs full bytes
oui = '{:02x}:{:02x}:{:02x}'.format(*[int(b, 16) for b in oui_bytes]) oui = '{:02x}:{:02x}:{:02x}'.format(*[int(b, 16) for b in oui_bytes])
url = 'http://api.macvendors.com/' + oui url = 'http://api.macvendors.com/' + oui
resp = None
try: try:
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
@ -570,13 +569,9 @@ class Device(Entity):
# in the 'known_devices.yaml' file which only happens # in the 'known_devices.yaml' file which only happens
# the first time the device is seen. # the first time the device is seen.
return 'unknown' return 'unknown'
except (asyncio.TimeoutError, aiohttp.errors.ClientError, except (asyncio.TimeoutError, aiohttp.ClientError):
aiohttp.errors.ClientDisconnectedError):
# same as above # same as above
return 'unknown' return 'unknown'
finally:
if resp is not None:
yield from resp.release()
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):

View file

@ -105,8 +105,6 @@ class TadoDeviceScanner(DeviceScanner):
_LOGGER.debug("Requesting Tado") _LOGGER.debug("Requesting Tado")
last_results = [] last_results = []
response = None
tado_json = None
try: try:
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
@ -127,14 +125,10 @@ class TadoDeviceScanner(DeviceScanner):
tado_json = yield from response.json() tado_json = yield from response.json()
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Cannot load Tado data") _LOGGER.error("Cannot load Tado data")
return False return False
finally:
if response is not None:
yield from response.release()
# Without a home_id, we fetched an URL where the mobile devices can be # Without a home_id, we fetched an URL where the mobile devices can be
# found under the mobileDevices key. # found under the mobileDevices key.
if 'mobileDevices' in tado_json: if 'mobileDevices' in tado_json:

View file

@ -101,7 +101,6 @@ class UPCDeviceScanner(DeviceScanner):
@asyncio.coroutine @asyncio.coroutine
def async_login(self): def async_login(self):
"""Login into firmware and get first token.""" """Login into firmware and get first token."""
response = None
try: try:
# get first token # get first token
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
@ -109,7 +108,8 @@ class UPCDeviceScanner(DeviceScanner):
"http://{}/common_page/login.html".format(self.host) "http://{}/common_page/login.html".format(self.host)
) )
yield from response.text() yield from response.text()
self.token = response.cookies['sessionToken'].value self.token = response.cookies['sessionToken'].value
# login # login
@ -119,18 +119,12 @@ class UPCDeviceScanner(DeviceScanner):
}) })
# successfull? # successfull?
if data is not None: return data is not None
return True
return False
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Can not load login page from %s", self.host) _LOGGER.error("Can not load login page from %s", self.host)
return False return False
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine @asyncio.coroutine
def _async_ws_function(self, function, additional_form=None): def _async_ws_function(self, function, additional_form=None):
"""Execute a command on UPC firmware webservice.""" """Execute a command on UPC firmware webservice."""
@ -142,8 +136,7 @@ class UPCDeviceScanner(DeviceScanner):
if additional_form: if additional_form:
form_data.update(additional_form) form_data.update(additional_form)
redirects = True if function != CMD_DEVICES else False redirects = function != CMD_DEVICES
response = None
try: try:
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.post( response = yield from self.websession.post(
@ -163,10 +156,6 @@ class UPCDeviceScanner(DeviceScanner):
self.token = response.cookies['sessionToken'].value self.token = response.cookies['sessionToken'].value
return (yield from response.text()) return (yield from response.text())
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error on %s", function) _LOGGER.error("Error on %s", function)
self.token = None self.token = None
finally:
if response is not None:
yield from response.release()

View file

@ -153,7 +153,7 @@ def setup(hass, config):
sw_path = "service_worker.js" sw_path = "service_worker.js"
hass.http.register_static_path("/service_worker.js", hass.http.register_static_path("/service_worker.js",
os.path.join(STATIC_PATH, sw_path), 0) os.path.join(STATIC_PATH, sw_path), False)
hass.http.register_static_path("/robots.txt", hass.http.register_static_path("/robots.txt",
os.path.join(STATIC_PATH, "robots.txt")) os.path.join(STATIC_PATH, "robots.txt"))
hass.http.register_static_path("/static", STATIC_PATH) hass.http.register_static_path("/static", STATIC_PATH)

View file

@ -9,7 +9,6 @@ import json
import logging import logging
import ssl import ssl
from ipaddress import ip_network from ipaddress import ip_network
from pathlib import Path
import os import os
import voluptuous as vol import voluptuous as vol
@ -31,7 +30,8 @@ from .const import (
KEY_USE_X_FORWARDED_FOR, KEY_TRUSTED_NETWORKS, KEY_USE_X_FORWARDED_FOR, KEY_TRUSTED_NETWORKS,
KEY_BANS_ENABLED, KEY_LOGIN_THRESHOLD, KEY_BANS_ENABLED, KEY_LOGIN_THRESHOLD,
KEY_DEVELOPMENT, KEY_AUTHENTICATED) KEY_DEVELOPMENT, KEY_AUTHENTICATED)
from .static import FILE_SENDER, CACHING_FILE_SENDER, staticresource_middleware from .static import (
staticresource_middleware, CachingFileResponse, CachingStaticResource)
from .util import get_real_ip from .util import get_real_ip
DOMAIN = 'http' DOMAIN = 'http'
@ -187,7 +187,7 @@ class HomeAssistantWSGI(object):
if is_ban_enabled: if is_ban_enabled:
middlewares.insert(0, ban_middleware) middlewares.insert(0, ban_middleware)
self.app = web.Application(middlewares=middlewares, loop=hass.loop) self.app = web.Application(middlewares=middlewares)
self.app['hass'] = hass self.app['hass'] = hass
self.app[KEY_USE_X_FORWARDED_FOR] = use_x_forwarded_for self.app[KEY_USE_X_FORWARDED_FOR] = use_x_forwarded_for
self.app[KEY_TRUSTED_NETWORKS] = trusted_networks self.app[KEY_TRUSTED_NETWORKS] = trusted_networks
@ -255,31 +255,39 @@ class HomeAssistantWSGI(object):
self.app.router.add_route('GET', url, redirect) self.app.router.add_route('GET', url, redirect)
def register_static_path(self, url_root, path, cache_length=31): def register_static_path(self, url_path, path, cache_headers=True):
"""Register a folder to serve as a static path. """Register a folder or file to serve as a static path."""
Specify optional cache length of asset in days.
"""
if os.path.isdir(path): if os.path.isdir(path):
self.app.router.add_static(url_root, path) if cache_headers:
resource = CachingStaticResource
else:
resource = web.StaticResource
self.app.router.register_resource(resource(url_path, path))
return return
filepath = Path(path) if cache_headers:
@asyncio.coroutine
@asyncio.coroutine def serve_file(request):
def serve_file(request): """Serve file from disk."""
"""Serve file from disk.""" return CachingFileResponse(path)
res = yield from CACHING_FILE_SENDER.send(request, filepath) else:
return res @asyncio.coroutine
def serve_file(request):
"""Serve file from disk."""
return web.FileResponse(path)
# aiohttp supports regex matching for variables. Using that as temp # aiohttp supports regex matching for variables. Using that as temp
# to work around cache busting MD5. # to work around cache busting MD5.
# Turns something like /static/dev-panel.html into # Turns something like /static/dev-panel.html into
# /static/{filename:dev-panel(-[a-z0-9]{32}|)\.html} # /static/{filename:dev-panel(-[a-z0-9]{32}|)\.html}
base, ext = url_root.rsplit('.', 1) base, ext = os.path.splitext(url_path)
base, file = base.rsplit('/', 1) if ext:
regex = r"{}(-[a-z0-9]{{32}}|)\.{}".format(file, ext) base, file = base.rsplit('/', 1)
url_pattern = "{}/{{filename:{}}}".format(base, regex) regex = r"{}(-[a-z0-9]{{32}}|){}".format(file, ext)
url_pattern = "{}/{{filename:{}}}".format(base, regex)
else:
url_pattern = url_path
self.app.router.add_route('GET', url_pattern, serve_file) self.app.router.add_route('GET', url_pattern, serve_file)
@ -318,7 +326,7 @@ class HomeAssistantWSGI(object):
# re-register all redirects, views, static paths. # re-register all redirects, views, static paths.
self.app._frozen = True # pylint: disable=protected-access self.app._frozen = True # pylint: disable=protected-access
self._handler = self.app.make_handler() self._handler = self.app.make_handler(loop=self.hass.loop)
try: try:
self.server = yield from self.hass.loop.create_server( self.server = yield from self.hass.loop.create_server(
@ -365,8 +373,7 @@ class HomeAssistantView(object):
def file(self, request, fil): def file(self, request, fil):
"""Return a file.""" """Return a file."""
assert isinstance(fil, str), 'only string paths allowed' assert isinstance(fil, str), 'only string paths allowed'
response = yield from FILE_SENDER.send(request, Path(fil)) return web.FileResponse(fil)
return response
def register(self, router): def register(self, router):
"""Register the view with a router.""" """Register the view with a router."""

View file

@ -3,14 +3,45 @@ import asyncio
import re import re
from aiohttp import hdrs from aiohttp import hdrs
from aiohttp.file_sender import FileSender from aiohttp.web import FileResponse
from aiohttp.web_exceptions import HTTPNotFound
from aiohttp.web_urldispatcher import StaticResource from aiohttp.web_urldispatcher import StaticResource
from yarl import unquote
from .const import KEY_DEVELOPMENT from .const import KEY_DEVELOPMENT
_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE) _FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
class CachingFileSender(FileSender): class CachingStaticResource(StaticResource):
"""Static Resource handler that will add cache headers."""
@asyncio.coroutine
def _handle(self, request):
filename = unquote(request.match_info['filename'])
try:
# PyLint is wrong about resolve not being a member.
# pylint: disable=no-member
filepath = self._directory.joinpath(filename).resolve()
if not self._follow_symlinks:
filepath.relative_to(self._directory)
except (ValueError, FileNotFoundError) as error:
# relatively safe
raise HTTPNotFound() from error
except Exception as error:
# perm error or other kind!
request.app.logger.exception(error)
raise HTTPNotFound() from error
if filepath.is_dir():
return (yield from super()._handle(request))
elif filepath.is_file():
return CachingFileResponse(filepath, chunk_size=self._chunk_size)
else:
raise HTTPNotFound
class CachingFileResponse(FileResponse):
"""FileSender class that caches output if not in dev mode.""" """FileSender class that caches output if not in dev mode."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -20,46 +51,34 @@ class CachingFileSender(FileSender):
orig_sendfile = self._sendfile orig_sendfile = self._sendfile
@asyncio.coroutine @asyncio.coroutine
def sendfile(request, resp, fobj, count): def sendfile(request, fobj, count):
"""Sendfile that includes a cache header.""" """Sendfile that includes a cache header."""
if not request.app[KEY_DEVELOPMENT]: if not request.app[KEY_DEVELOPMENT]:
cache_time = 31 * 86400 # = 1 month cache_time = 31 * 86400 # = 1 month
resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format( self.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
cache_time) cache_time)
yield from orig_sendfile(request, resp, fobj, count) yield from orig_sendfile(request, fobj, count)
# Overwriting like this because __init__ can change implementation. # Overwriting like this because __init__ can change implementation.
self._sendfile = sendfile self._sendfile = sendfile
FILE_SENDER = FileSender()
CACHING_FILE_SENDER = CachingFileSender()
@asyncio.coroutine @asyncio.coroutine
def staticresource_middleware(app, handler): def staticresource_middleware(app, handler):
"""Enhance StaticResourceHandler middleware. """Middleware to strip out fingerprint from fingerprinted assets."""
Adds gzip encoding and fingerprinting matching.
"""
inst = getattr(handler, '__self__', None)
if not isinstance(inst, StaticResource):
return handler
# pylint: disable=protected-access
inst._file_sender = CACHING_FILE_SENDER
@asyncio.coroutine @asyncio.coroutine
def static_middleware_handler(request): def static_middleware_handler(request):
"""Strip out fingerprints from resource names.""" """Strip out fingerprints from resource names."""
if not request.path.startswith('/static/'):
return handler(request)
fingerprinted = _FINGERPRINT.match(request.match_info['filename']) fingerprinted = _FINGERPRINT.match(request.match_info['filename'])
if fingerprinted: if fingerprinted:
request.match_info['filename'] = \ request.match_info['filename'] = \
'{}.{}'.format(*fingerprinted.groups()) '{}.{}'.format(*fingerprinted.groups())
resp = yield from handler(request) return handler(request)
return resp
return static_middleware_handler return static_middleware_handler

View file

@ -112,8 +112,6 @@ class OpenAlprCloudEntity(ImageProcessingAlprEntity):
params['image_bytes'] = str(b64encode(image), 'utf-8') params['image_bytes'] = str(b64encode(image), 'utf-8')
data = None
request = None
try: try:
with async_timeout.timeout(self.timeout, loop=self.hass.loop): with async_timeout.timeout(self.timeout, loop=self.hass.loop):
request = yield from websession.post( request = yield from websession.post(
@ -127,14 +125,10 @@ class OpenAlprCloudEntity(ImageProcessingAlprEntity):
request.status, data.get('error')) request.status, data.get('error'))
return return
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout for openalpr api.") _LOGGER.error("Timeout for openalpr api.")
return return
finally:
if request is not None:
yield from request.release()
# processing api data # processing api data
vehicles = 0 vehicles = 0
result = {} result = {}

View file

@ -864,21 +864,17 @@ def _async_fetch_image(hass, url):
content, content_type = (None, None) content, content_type = (None, None)
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
response = None
try: try:
with async_timeout.timeout(10, loop=hass.loop): with async_timeout.timeout(10, loop=hass.loop):
response = yield from websession.get(url) response = yield from websession.get(url)
if response.status == 200:
content = yield from response.read() if response.status == 200:
content_type = response.headers.get(CONTENT_TYPE_HEADER) content = yield from response.read()
content_type = response.headers.get(CONTENT_TYPE_HEADER)
except asyncio.TimeoutError: except asyncio.TimeoutError:
pass pass
finally:
if response is not None:
yield from response.release()
if not content: if not content:
return (None, None) return (None, None)

View file

@ -117,7 +117,6 @@ class LogitechMediaServer(object):
@asyncio.coroutine @asyncio.coroutine
def async_query(self, *command, player=""): def async_query(self, *command, player=""):
"""Abstract out the JSON-RPC connection.""" """Abstract out the JSON-RPC connection."""
response = None
auth = None if self._username is None else aiohttp.BasicAuth( auth = None if self._username is None else aiohttp.BasicAuth(
self._username, self._password) self._username, self._password)
url = "http://{}:{}/jsonrpc.js".format( url = "http://{}:{}/jsonrpc.js".format(
@ -138,22 +137,17 @@ class LogitechMediaServer(object):
data=data, data=data,
auth=auth) auth=auth)
if response.status == 200: if response.status != 200:
data = yield from response.json()
else:
_LOGGER.error( _LOGGER.error(
"Query failed, response code: %s Full message: %s", "Query failed, response code: %s Full message: %s",
response.status, response) response.status, response)
return False return False
except (asyncio.TimeoutError, data = yield from response.json()
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error: except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed communicating with LMS: %s", type(error)) _LOGGER.error("Failed communicating with LMS: %s", type(error))
return False return False
finally:
if response is not None:
yield from response.release()
try: try:
return data['result'] return data['result']

View file

@ -68,7 +68,6 @@ class Volumio(MediaPlayerDevice):
def send_volumio_msg(self, method, params=None): def send_volumio_msg(self, method, params=None):
"""Send message.""" """Send message."""
url = "http://{}:{}/api/v1/{}/".format(self.host, self.port, method) url = "http://{}:{}/api/v1/{}/".format(self.host, self.port, method)
response = None
_LOGGER.debug("URL: %s params: %s", url, params) _LOGGER.debug("URL: %s params: %s", url, params)
@ -83,14 +82,9 @@ class Volumio(MediaPlayerDevice):
response.status, response) response.status, response)
return False return False
except (asyncio.TimeoutError, except (asyncio.TimeoutError, aiohttp.ClientError) as error:
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
_LOGGER.error("Failed communicating with Volumio: %s", type(error)) _LOGGER.error("Failed communicating with Volumio: %s", type(error))
return False return False
finally:
if response is not None:
yield from response.release()
try: try:
return data return data

View file

@ -359,30 +359,25 @@ class MicrosoftFace(object):
else: else:
payload = None payload = None
response = None
try: try:
with async_timeout.timeout(self.timeout, loop=self.hass.loop): with async_timeout.timeout(self.timeout, loop=self.hass.loop):
response = yield from getattr(self.websession, method)( response = yield from getattr(self.websession, method)(
url, data=payload, headers=headers, params=params) url, data=payload, headers=headers, params=params)
answer = yield from response.json() answer = yield from response.json()
_LOGGER.debug("Read from microsoft face api: %s", answer)
if response.status == 200 or response.status == 202:
return answer
_LOGGER.warning("Error %d microsoft face api %s", _LOGGER.debug("Read from microsoft face api: %s", answer)
response.status, response.url) if response.status < 300:
raise HomeAssistantError(answer['error']['message']) return answer
except (aiohttp.errors.ClientError, _LOGGER.warning("Error %d microsoft face api %s",
aiohttp.errors.ClientDisconnectedError): response.status, response.url)
raise HomeAssistantError(answer['error']['message'])
except aiohttp.ClientError:
_LOGGER.warning("Can't connect to microsoft face api") _LOGGER.warning("Can't connect to microsoft face api")
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.warning("Timeout from microsoft face api %s", response.url) _LOGGER.warning("Timeout from microsoft face api %s", response.url)
finally:
if response is not None:
yield from response.release()
raise HomeAssistantError("Network error on microsoft face api.") raise HomeAssistantError("Network error on microsoft face api.")

View file

@ -81,7 +81,6 @@ def async_setup(hass, config):
template_payload.async_render(variables=service.data), template_payload.async_render(variables=service.data),
'utf-8') 'utf-8')
request = None
try: try:
with async_timeout.timeout(timeout, loop=hass.loop): with async_timeout.timeout(timeout, loop=hass.loop):
request = yield from getattr(websession, method)( request = yield from getattr(websession, method)(
@ -90,22 +89,18 @@ def async_setup(hass, config):
auth=auth auth=auth
) )
if request.status < 400: if request.status < 400:
_LOGGER.info("Success call %s.", request.url) _LOGGER.info("Success call %s.", request.url)
return else:
_LOGGER.warning( _LOGGER.warning(
"Error %d on call %s.", request.status, request.url) "Error %d on call %s.", request.status, request.url)
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.warning("Timeout call %s.", request.url) _LOGGER.warning("Timeout call %s.", request.url)
except aiohttp.errors.ClientError: except aiohttp.ClientError:
_LOGGER.error("Client error %s.", request.url) _LOGGER.error("Client error %s.", request.url)
finally:
if request is not None:
yield from request.release()
# register services # register services
hass.services.async_register(DOMAIN, name, async_service_handler) hass.services.async_register(DOMAIN, name, async_service_handler)

View file

@ -151,6 +151,8 @@ class YrData(object):
@asyncio.coroutine @asyncio.coroutine
def async_update(self, *_): def async_update(self, *_):
"""Get the latest data from yr.no.""" """Get the latest data from yr.no."""
import xmltodict
def try_again(err: str): def try_again(err: str):
"""Retry in 15 minutes.""" """Retry in 15 minutes."""
_LOGGER.warning('Retrying in 15 minutes: %s', err) _LOGGER.warning('Retrying in 15 minutes: %s', err)
@ -161,7 +163,6 @@ class YrData(object):
nxt) nxt)
if self._nextrun is None or dt_util.utcnow() >= self._nextrun: if self._nextrun is None or dt_util.utcnow() >= self._nextrun:
resp = None
try: try:
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
@ -172,17 +173,11 @@ class YrData(object):
return return
text = yield from resp.text() text = yield from resp.text()
except (asyncio.TimeoutError, aiohttp.errors.ClientError, except (asyncio.TimeoutError, aiohttp.ClientError) as err:
aiohttp.errors.ClientDisconnectedError) as err:
try_again(err) try_again(err)
return return
finally:
if resp is not None:
yield from resp.release()
try: try:
import xmltodict
self.data = xmltodict.parse(text)['weatherdata'] self.data = xmltodict.parse(text)['weatherdata']
model = self.data['meta']['model'] model = self.data['meta']['model']
if '@nextrun' not in model: if '@nextrun' not in model:

View file

@ -34,7 +34,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
response = None
try: try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop): with async_timeout.timeout(TIMEOUT, loop=hass.loop):
response = yield from websession.post( response = yield from websession.post(
@ -43,14 +42,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
'username': username, 'username': username,
'password': password}) 'password': password})
data = yield from response.json() data = yield from response.json()
except (asyncio.TimeoutError, except (asyncio.TimeoutError, aiohttp.ClientError) as error:
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
_LOGGER.error("Failed authentication API call: %s", error) _LOGGER.error("Failed authentication API call: %s", error)
return False return False
finally:
if response is not None:
yield from response.release()
try: try:
token = data['data']['token'] token = data['data']['token']
@ -58,21 +52,15 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error("No token. Check username and password") _LOGGER.error("No token. Check username and password")
return False return False
response = None
try: try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop): with async_timeout.timeout(TIMEOUT, loop=hass.loop):
response = yield from websession.get( response = yield from websession.get(
'{}{}'.format(HOOK_ENDPOINT, 'device'), '{}{}'.format(HOOK_ENDPOINT, 'device'),
params={"token": data['data']['token']}) params={"token": data['data']['token']})
data = yield from response.json() data = yield from response.json()
except (asyncio.TimeoutError, except (asyncio.TimeoutError, aiohttp.ClientError) as error:
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
_LOGGER.error("Failed getting devices: %s", error) _LOGGER.error("Failed getting devices: %s", error)
return False return False
finally:
if response is not None:
yield from response.release()
async_add_devices( async_add_devices(
HookSmartHome( HookSmartHome(
@ -110,7 +98,6 @@ class HookSmartHome(SwitchDevice):
@asyncio.coroutine @asyncio.coroutine
def _send(self, url): def _send(self, url):
"""Send the url to the Hook API.""" """Send the url to the Hook API."""
response = None
try: try:
_LOGGER.debug("Sending: %s", url) _LOGGER.debug("Sending: %s", url)
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
@ -119,16 +106,10 @@ class HookSmartHome(SwitchDevice):
url, params={"token": self._token}) url, params={"token": self._token})
data = yield from response.json() data = yield from response.json()
except (asyncio.TimeoutError, except (asyncio.TimeoutError, aiohttp.ClientError) as error:
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
_LOGGER.error("Failed setting state: %s", error) _LOGGER.error("Failed setting state: %s", error)
return False return False
finally:
if response is not None:
yield from response.release()
_LOGGER.debug("Got: %s", data) _LOGGER.debug("Got: %s", data)
return data['return_value'] == '1' return data['return_value'] == '1'

View file

@ -57,20 +57,21 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
body_off.hass = hass body_off.hass = hass
timeout = config.get(CONF_TIMEOUT) timeout = config.get(CONF_TIMEOUT)
req = None
try: try:
with async_timeout.timeout(timeout, loop=hass.loop): with async_timeout.timeout(timeout, loop=hass.loop):
req = yield from websession.get(resource) req = yield from websession.get(resource)
if req.status >= 400:
_LOGGER.error('Got non-ok response from resource: %s', req.status)
return False
except (TypeError, ValueError): except (TypeError, ValueError):
_LOGGER.error("Missing resource or schema in configuration. " _LOGGER.error("Missing resource or schema in configuration. "
"Add http:// or https:// to your URL") "Add http:// or https:// to your URL")
return False return False
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("No route to resource/endpoint: %s", resource) _LOGGER.error("No route to resource/endpoint: %s", resource)
return False return False
finally:
if req is not None:
yield from req.release()
async_add_devices( async_add_devices(
[RestSwitch(hass, name, resource, body_on, body_off, [RestSwitch(hass, name, resource, body_on, body_off,
@ -108,17 +109,13 @@ class RestSwitch(SwitchDevice):
body_on_t = self._body_on.async_render() body_on_t = self._body_on.async_render()
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
request = None
try: try:
with async_timeout.timeout(self._timeout, loop=self.hass.loop): with async_timeout.timeout(self._timeout, loop=self.hass.loop):
request = yield from websession.post( request = yield from websession.post(
self._resource, data=bytes(body_on_t, 'utf-8')) self._resource, data=bytes(body_on_t, 'utf-8'))
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error while turn on %s", self._resource) _LOGGER.error("Error while turn on %s", self._resource)
return return
finally:
if request is not None:
yield from request.release()
if request.status == 200: if request.status == 200:
self._state = True self._state = True
@ -132,17 +129,13 @@ class RestSwitch(SwitchDevice):
body_off_t = self._body_off.async_render() body_off_t = self._body_off.async_render()
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
request = None
try: try:
with async_timeout.timeout(self._timeout, loop=self.hass.loop): with async_timeout.timeout(self._timeout, loop=self.hass.loop):
request = yield from websession.post( request = yield from websession.post(
self._resource, data=bytes(body_off_t, 'utf-8')) self._resource, data=bytes(body_off_t, 'utf-8'))
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error while turn off %s", self._resource) _LOGGER.error("Error while turn off %s", self._resource)
return return
finally:
if request is not None:
yield from request.release()
if request.status == 200: if request.status == 200:
self._state = False self._state = False
@ -155,17 +148,13 @@ class RestSwitch(SwitchDevice):
"""Get the latest data from REST API and update the state.""" """Get the latest data from REST API and update the state."""
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
request = None
try: try:
with async_timeout.timeout(self._timeout, loop=self.hass.loop): with async_timeout.timeout(self._timeout, loop=self.hass.loop):
request = yield from websession.get(self._resource) request = yield from websession.get(self._resource)
text = yield from request.text() text = yield from request.text()
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error while fetch data.") _LOGGER.exception("Error while fetch data.")
return return
finally:
if request is not None:
yield from request.release()
if self._is_on_template is not None: if self._is_on_template is not None:
text = self._is_on_template.async_render_with_possible_json_value( text = self._is_on_template.async_render_with_possible_json_value(

View file

@ -95,7 +95,6 @@ class GoogleProvider(Provider):
'textlen': len(part), 'textlen': len(part),
} }
request = None
try: try:
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
request = yield from websession.get( request = yield from websession.get(
@ -109,14 +108,10 @@ class GoogleProvider(Provider):
return (None, None) return (None, None)
data += yield from request.read() data += yield from request.read()
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout for google speech.") _LOGGER.error("Timeout for google speech.")
return (None, None) return (None, None)
finally:
if request is not None:
yield from request.release()
return ("mp3", data) return ("mp3", data)
@staticmethod @staticmethod

View file

@ -123,7 +123,6 @@ class VoiceRSSProvider(Provider):
form_data['src'] = message form_data['src'] = message
form_data['hl'] = language form_data['hl'] = language
request = None
try: try:
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
request = yield from websession.post( request = yield from websession.post(
@ -141,12 +140,8 @@ class VoiceRSSProvider(Provider):
"Error receive %s from VoiceRSS", str(data, 'utf-8')) "Error receive %s from VoiceRSS", str(data, 'utf-8'))
return (None, None) return (None, None)
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout for VoiceRSS API") _LOGGER.error("Timeout for VoiceRSS API")
return (None, None) return (None, None)
finally:
if request is not None:
yield from request.release()
return (self._extension, data) return (self._extension, data)

View file

@ -98,10 +98,8 @@ class YandexSpeechKitProvider(Provider):
def async_get_tts_audio(self, message, language, options=None): def async_get_tts_audio(self, message, language, options=None):
"""Load TTS from yandex.""" """Load TTS from yandex."""
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
actual_language = language actual_language = language
request = None
try: try:
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
url_param = { url_param = {
@ -123,12 +121,8 @@ class YandexSpeechKitProvider(Provider):
return (None, None) return (None, None)
data = yield from request.read() data = yield from request.read()
except (asyncio.TimeoutError, aiohttp.errors.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout for yandex speech kit api.") _LOGGER.error("Timeout for yandex speech kit api.")
return (None, None) return (None, None)
finally:
if request is not None:
yield from request.release()
return (self._codec, data) return (self._codec, data)

View file

@ -5,7 +5,7 @@ import sys
import aiohttp import aiohttp
from aiohttp.hdrs import USER_AGENT, CONTENT_TYPE from aiohttp.hdrs import USER_AGENT, CONTENT_TYPE
from aiohttp import web from aiohttp import web
from aiohttp.web_exceptions import HTTPGatewayTimeout from aiohttp.web_exceptions import HTTPGatewayTimeout, HTTPBadGateway
import async_timeout import async_timeout
from homeassistant.core import callback from homeassistant.core import callback
@ -71,42 +71,46 @@ def async_create_clientsession(hass, verify_ssl=True, auto_cleanup=True,
@asyncio.coroutine @asyncio.coroutine
def async_aiohttp_proxy_stream(hass, request, stream_coro, buffer_size=102400, def async_aiohttp_proxy_web(hass, request, web_coro, buffer_size=102400,
timeout=10): timeout=10):
"""Stream websession request to aiohttp web response.""" """Stream websession request to aiohttp web response."""
response = None
stream = None
try: try:
with async_timeout.timeout(timeout, loop=hass.loop): with async_timeout.timeout(timeout, loop=hass.loop):
stream = yield from stream_coro req = yield from web_coro
response = web.StreamResponse() except asyncio.TimeoutError as err:
response.content_type = stream.headers.get(CONTENT_TYPE) raise HTTPGatewayTimeout() from err
yield from response.prepare(request) except aiohttp.ClientError as err:
raise HTTPBadGateway() from err
yield from async_aiohttp_proxy_stream(hass, request, req.content,
req.headers.get(CONTENT_TYPE))
@asyncio.coroutine
def async_aiohttp_proxy_stream(hass, request, stream, content_type,
buffer_size=102400, timeout=10):
"""Stream a stream to aiohttp web response."""
response = web.StreamResponse()
response.content_type = content_type
yield from response.prepare(request)
try:
while True: while True:
data = yield from stream.content.read(buffer_size) with async_timeout.timeout(timeout, loop=hass.loop):
data = yield from stream.read(buffer_size)
if not data: if not data:
yield from response.write_eof()
break break
response.write(data) response.write(data)
except asyncio.TimeoutError: except (asyncio.TimeoutError, aiohttp.ClientError):
raise HTTPGatewayTimeout()
except (aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError):
pass pass
except (asyncio.CancelledError, ConnectionResetError): yield from response.write_eof()
response = None
finally:
if stream is not None:
stream.close()
if response is not None:
yield from response.write_eof()
@callback @callback
@ -149,10 +153,10 @@ def _async_get_connector(hass, verify_ssl=True):
connector = hass.data[DATA_CONNECTOR_NOTVERIFY] connector = hass.data[DATA_CONNECTOR_NOTVERIFY]
if is_new: if is_new:
@asyncio.coroutine @callback
def _async_close_connector(event): def _async_close_connector(event):
"""Close connector pool.""" """Close connector pool."""
yield from connector.close() connector.close()
hass.bus.async_listen_once( hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_CLOSE, _async_close_connector) EVENT_HOMEASSISTANT_CLOSE, _async_close_connector)

View file

@ -5,5 +5,5 @@ pip>=7.1.0
jinja2>=2.9.5 jinja2>=2.9.5
voluptuous==0.9.3 voluptuous==0.9.3
typing>=3,<4 typing>=3,<4
aiohttp==1.3.5 aiohttp==2.0.4
async_timeout==1.2.0 async_timeout==1.2.0

View file

@ -6,7 +6,7 @@ pip>=7.1.0
jinja2>=2.9.5 jinja2>=2.9.5
voluptuous==0.9.3 voluptuous==0.9.3
typing>=3,<4 typing>=3,<4
aiohttp==1.3.5 aiohttp==2.0.4
async_timeout==1.2.0 async_timeout==1.2.0
# homeassistant.components.nuimo_controller # homeassistant.components.nuimo_controller

View file

@ -22,7 +22,7 @@ REQUIRES = [
'jinja2>=2.9.5', 'jinja2>=2.9.5',
'voluptuous==0.9.3', 'voluptuous==0.9.3',
'typing>=3,<4', 'typing>=3,<4',
'aiohttp==1.3.5', 'aiohttp==2.0.4',
'async_timeout==1.2.0', 'async_timeout==1.2.0',
] ]

View file

@ -36,10 +36,8 @@ class TestDarkSkySetup(unittest.TestCase):
'monitored_conditions': ['summary', 'icon', 'temperature_max'], 'monitored_conditions': ['summary', 'icon', 'temperature_max'],
'update_interval': timedelta(seconds=120), 'update_interval': timedelta(seconds=120),
} }
self.lat = 37.8267 self.lat = self.hass.config.latitude = 37.8267
self.lon = -122.423 self.lon = self.hass.config.longitude = -122.423
self.hass.config.latitude = self.lat
self.hass.config.longitude = self.lon
self.entities = [] self.entities = []
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
@ -51,11 +49,6 @@ class TestDarkSkySetup(unittest.TestCase):
self.assertTrue( self.assertTrue(
setup_component(self.hass, 'sensor', {'darksky': self.config})) setup_component(self.hass, 'sensor', {'darksky': self.config}))
def test_setup_no_latitude(self):
"""Test that the component is not loaded without required config."""
self.hass.config.latitude = None
self.assertFalse(darksky.setup_platform(self.hass, {}, MagicMock()))
@patch('forecastio.api.get_forecast') @patch('forecastio.api.get_forecast')
def test_setup_bad_api_key(self, mock_get_forecast): def test_setup_bad_api_key(self, mock_get_forecast):
"""Test for handling a bad API key.""" """Test for handling a bad API key."""

View file

@ -42,7 +42,7 @@ class TestRestSwitchSetup:
def test_setup_failed_connect(self, aioclient_mock): def test_setup_failed_connect(self, aioclient_mock):
"""Test setup when connection error occurs.""" """Test setup when connection error occurs."""
aioclient_mock.get('http://localhost', exc=aiohttp.errors.ClientError) aioclient_mock.get('http://localhost', exc=aiohttp.ClientError)
assert not run_coroutine_threadsafe( assert not run_coroutine_threadsafe(
rest.async_setup_platform(self.hass, { rest.async_setup_platform(self.hass, {
'platform': 'rest', 'platform': 'rest',

View file

@ -1,12 +1,9 @@
"""The tests for the Home Assistant API component.""" """The tests for the Home Assistant API component."""
# pylint: disable=protected-access # pylint: disable=protected-access
import asyncio
from contextlib import closing from contextlib import closing
import json import json
import unittest import unittest
from unittest.mock import Mock, patch
from aiohttp import web
import requests import requests
from homeassistant import setup, const from homeassistant import setup, const
@ -247,22 +244,6 @@ class TestAPI(unittest.TestCase):
headers=HA_HEADERS) headers=HA_HEADERS)
self.assertEqual(hass.config.components, set(req.json())) self.assertEqual(hass.config.components, set(req.json()))
def test_api_get_error_log(self):
"""Test the return of the error log."""
test_string = 'Test String°'
@asyncio.coroutine
def mock_send():
"""Mock file send."""
return web.Response(text=test_string)
with patch('homeassistant.components.http.HomeAssistantView.file',
Mock(return_value=mock_send())):
req = requests.get(_url(const.URL_API_ERROR_LOG),
headers=HA_HEADERS)
self.assertEqual(test_string, req.text)
self.assertIsNone(req.headers.get('expires'))
def test_api_get_event_listeners(self): def test_api_get_event_listeners(self):
"""Test if we can get the list of events being listened for.""" """Test if we can get the list of events being listened for."""
req = requests.get(_url(const.URL_API_EVENTS), req = requests.get(_url(const.URL_API_EVENTS),

View file

@ -1,89 +1,69 @@
"""The tests for Home Assistant frontend.""" """The tests for Home Assistant frontend."""
# pylint: disable=protected-access import asyncio
import re import re
import unittest
import requests import pytest
from homeassistant import setup from homeassistant.setup import async_setup_component
from homeassistant.components import http
from homeassistant.const import HTTP_HEADER_HA_AUTH
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 = {HTTP_HEADER_HA_AUTH: API_PASSWORD}
hass = None
def _url(path=""): @pytest.fixture
"""Helper method to generate URLs.""" def mock_http_client(loop, hass, test_client):
return HTTP_BASE_URL + path """Start the Hass HTTP component."""
loop.run_until_complete(async_setup_component(hass, 'frontend', {}))
return loop.run_until_complete(test_client(hass.http.app))
# pylint: disable=invalid-name @asyncio.coroutine
def setUpModule(): def test_frontend_and_static(mock_http_client):
"""Initialize a Home Assistant server.""" """Test if we can get the frontend."""
global hass resp = yield from mock_http_client.get('')
assert resp.status == 200
assert 'cache-control' not in resp.headers
hass = get_test_home_assistant() text = yield from resp.text()
assert setup.setup_component( # Test we can retrieve frontend.js
hass, http.DOMAIN, frontendjs = re.search(
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, r'(?P<app>\/static\/frontend-[A-Za-z0-9]{32}.html)', text)
http.CONF_SERVER_PORT: SERVER_PORT}})
assert setup.setup_component(hass, 'frontend') assert frontendjs is not None
resp = yield from mock_http_client.get(frontendjs.groups(0)[0])
hass.start() assert resp.status == 200
assert 'public' in resp.headers.get('cache-control')
# pylint: disable=invalid-name @asyncio.coroutine
def tearDownModule(): def test_dont_cache_service_worker(mock_http_client):
"""Stop everything that was started.""" """Test that we don't cache the service worker."""
hass.stop() resp = yield from mock_http_client.get('/service_worker.js')
assert resp.status == 200
assert 'cache-control' not in resp.headers
class TestFrontend(unittest.TestCase): @asyncio.coroutine
"""Test the frontend.""" def test_404(mock_http_client):
"""Test for HTTP 404 error."""
resp = yield from mock_http_client.get('/not-existing')
assert resp.status == 404
def tearDown(self):
"""Stop everything that was started."""
hass.block_till_done()
def test_frontend_and_static(self): @asyncio.coroutine
"""Test if we can get the frontend.""" def test_we_cannot_POST_to_root(mock_http_client):
req = requests.get(_url("")) """Test that POST is not allow to root."""
self.assertEqual(200, req.status_code) resp = yield from mock_http_client.post('/')
assert resp.status == 405
# Test we can retrieve frontend.js
frontendjs = re.search(
r'(?P<app>\/static\/frontend-[A-Za-z0-9]{32}.html)',
req.text)
self.assertIsNotNone(frontendjs) @asyncio.coroutine
req = requests.get(_url(frontendjs.groups(0)[0])) def test_states_routes(hass, mock_http_client):
self.assertEqual(200, req.status_code) """All served by index."""
resp = yield from mock_http_client.get('/states')
assert resp.status == 200
def test_404(self): resp = yield from mock_http_client.get('/states/group.non_existing')
"""Test for HTTP 404 error.""" assert resp.status == 404
self.assertEqual(404, requests.get(_url("/not-existing")).status_code)
def test_we_cannot_POST_to_root(self): hass.states.async_set('group.existing', 'on', {'view': True})
"""Test that POST is not allow to root.""" resp = yield from mock_http_client.get('/states/group.existing')
self.assertEqual(405, requests.post(_url("")).status_code) assert resp.status == 200
def test_states_routes(self):
"""All served by index."""
req = requests.get(_url("/states"))
self.assertEqual(200, req.status_code)
req = requests.get(_url("/states/group.non_existing"))
self.assertEqual(404, req.status_code)
hass.states.set('group.existing', 'on', {'view': True})
req = requests.get(_url("/states/group.existing"))
self.assertEqual(200, req.status_code)

View file

@ -107,7 +107,7 @@ class TestRestCommandComponent(object):
with assert_setup_component(4): with assert_setup_component(4):
setup_component(self.hass, rc.DOMAIN, self.config) setup_component(self.hass, rc.DOMAIN, self.config)
aioclient_mock.get(self.url, exc=aiohttp.errors.ClientError()) aioclient_mock.get(self.url, exc=aiohttp.ClientError())
self.hass.services.call(rc.DOMAIN, 'get_test', {}) self.hass.services.call(rc.DOMAIN, 'get_test', {})
self.hass.block_till_done() self.hass.block_till_done()

View file

@ -1,5 +1,7 @@
"""The tests for the SleepIQ component.""" """The tests for the SleepIQ component."""
import unittest import unittest
from unittest.mock import MagicMock, patch
import requests_mock import requests_mock
from homeassistant import setup from homeassistant import setup
@ -49,8 +51,12 @@ class TestSleepIQ(unittest.TestCase):
"""Test the setup.""" """Test the setup."""
mock_responses(mock) mock_responses(mock)
response = sleepiq.setup(self.hass, self.config) # We're mocking the load_platform discoveries or else the platforms
self.assertTrue(response) # will be setup during tear down when blocking till done, but the mocks
# are no longer active.
with patch(
'homeassistant.helpers.discovery.load_platform', MagicMock()):
assert sleepiq.setup(self.hass, self.config)
@requests_mock.Mocker() @requests_mock.Mocker()
def test_setup_login_failed(self, mock): def test_setup_login_failed(self, mock):

View file

@ -5,7 +5,7 @@ import unittest
import aiohttp import aiohttp
from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import setup_component from homeassistant.setup import async_setup_component
import homeassistant.helpers.aiohttp_client as client import homeassistant.helpers.aiohttp_client as client
from homeassistant.util.async import run_callback_threadsafe from homeassistant.util.async import run_callback_threadsafe
@ -119,22 +119,19 @@ class TestHelpersAiohttpClient(unittest.TestCase):
@asyncio.coroutine @asyncio.coroutine
def test_fetching_url(aioclient_mock, hass, test_client): def test_async_aiohttp_proxy_stream(aioclient_mock, hass, test_client):
"""Test that it fetches the given url.""" """Test that it fetches the given url."""
aioclient_mock.get('http://example.com/mjpeg_stream', content=[ aioclient_mock.get('http://example.com/mjpeg_stream', content=[
b'Frame1', b'Frame2', b'Frame3' b'Frame1', b'Frame2', b'Frame3'
]) ])
def setup_platform(): result = yield from async_setup_component(hass, 'camera', {
"""Setup the platform.""" 'camera': {
assert setup_component(hass, 'camera', { 'name': 'config_test',
'camera': { 'platform': 'mjpeg',
'name': 'config_test', 'mjpeg_url': 'http://example.com/mjpeg_stream',
'platform': 'mjpeg', }})
'mjpeg_url': 'http://example.com/mjpeg_stream', assert result, 'Failed to setup camera'
}})
yield from hass.loop.run_in_executor(None, setup_platform)
client = yield from test_client(hass.http.app) client = yield from test_client(hass.http.app)
@ -151,29 +148,12 @@ def test_fetching_url(aioclient_mock, hass, test_client):
content=[b'Frame1', b'Frame2', b'Frame3']) content=[b'Frame1', b'Frame2', b'Frame3'])
resp = yield from client.get('/api/camera_proxy_stream/camera.config_test') resp = yield from client.get('/api/camera_proxy_stream/camera.config_test')
assert resp.status == 504
assert resp.status == 200
body = yield from resp.text()
assert body == ''
aioclient_mock.clear_requests() aioclient_mock.clear_requests()
aioclient_mock.get( aioclient_mock.get(
'http://example.com/mjpeg_stream', exc=asyncio.CancelledError(), 'http://example.com/mjpeg_stream', exc=aiohttp.ClientError(),
content=[b'Frame1', b'Frame2', b'Frame3']) content=[b'Frame1', b'Frame2', b'Frame3'])
resp = yield from client.get('/api/camera_proxy_stream/camera.config_test') resp = yield from client.get('/api/camera_proxy_stream/camera.config_test')
assert resp.status == 502
assert resp.status == 200
body = yield from resp.text()
assert body == ''
aioclient_mock.clear_requests()
aioclient_mock.get(
'http://example.com/mjpeg_stream', exc=aiohttp.errors.ClientError(),
content=[b'Frame1', b'Frame2', b'Frame3'])
resp = yield from client.get('/api/camera_proxy_stream/camera.config_test')
assert resp.status == 200
body = yield from resp.text()
assert body == ''