* 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(APIEventForwardingView)
hass.http.register_view(APIComponentsView)
hass.http.register_view(APIErrorLogView)
hass.http.register_view(APITemplateView)
hass.http.register_static_path(
URL_API_ERROR_LOG, hass.config.path(ERROR_LOG_FILENAME), False)
return True
@ -402,20 +404,6 @@ class APIComponentsView(HomeAssistantView):
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):
"""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)
)
response = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
response = yield from websession.get(url)
@ -70,13 +69,9 @@ def async_get_image(hass, entity_id, timeout=10):
image = yield from response.read()
return image
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
raise HomeAssistantError("Can't connect to {0}".format(url))
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine
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)
from homeassistant.helpers import config_validation as cv
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']
@ -125,7 +125,7 @@ class AmcrestCam(Camera):
stream_coro = websession.get(
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
def name(self):

View file

@ -8,14 +8,14 @@ import asyncio
import logging
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.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
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']
_LOGGER = logging.getLogger(__name__)
@ -69,26 +69,10 @@ class FFmpegCamera(Camera):
yield from stream.open_camera(
self._input, extra_cmd=self._extra_arguments)
response = web.StreamResponse()
response.content_type = 'multipart/x-mixed-replace;boundary=ffserver'
yield from response.prepare(request)
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()
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
@property
def name(self):

View file

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

View file

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

View file

@ -19,7 +19,7 @@ from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_create_clientsession,
async_aiohttp_proxy_stream)
async_aiohttp_proxy_web)
import homeassistant.helpers.config_validation as cv
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',
'query': 'SYNO.'
}
query_req = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
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']
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)
return False
finally:
if query_req is not None:
yield from query_req.release()
# Authticate to NAS to get a session id
syno_auth_url = SYNO_API_URL.format(
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,
params=camera_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_camera_url)
return False
camera_resp = yield from camera_req.json()
cameras = camera_resp['data']['cameras']
yield from camera_req.release()
# add cameras
devices = []
@ -172,7 +166,6 @@ def get_session_id(hass, websession, username, password, login_url, timeout):
'session': 'SurveillanceStation',
'format': 'sid'
}
auth_req = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
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()
return auth_resp['data']['sid']
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", login_url)
return False
finally:
if auth_req is not None:
yield from auth_req.release()
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
@ -235,12 +224,11 @@ class SynologyCamera(Camera):
image_url,
params=image_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", image_url)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error fetching %s", image_url)
return None
image = yield from response.read()
yield from response.release()
return image
@ -260,7 +248,7 @@ class SynologyCamera(Camera):
stream_coro = self._websession.get(
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
def name(self):

View file

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

View file

@ -105,8 +105,6 @@ class TadoDeviceScanner(DeviceScanner):
_LOGGER.debug("Requesting Tado")
last_results = []
response = None
tado_json = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
@ -127,14 +125,10 @@ class TadoDeviceScanner(DeviceScanner):
tado_json = yield from response.json()
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Cannot load Tado data")
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
# found under the mobileDevices key.
if 'mobileDevices' in tado_json:

View file

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

View file

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

View file

@ -3,14 +3,45 @@ import asyncio
import re
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 yarl import unquote
from .const import KEY_DEVELOPMENT
_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."""
def __init__(self, *args, **kwargs):
@ -20,46 +51,34 @@ class CachingFileSender(FileSender):
orig_sendfile = self._sendfile
@asyncio.coroutine
def sendfile(request, resp, fobj, count):
def sendfile(request, fobj, count):
"""Sendfile that includes a cache header."""
if not request.app[KEY_DEVELOPMENT]:
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)
yield from orig_sendfile(request, resp, fobj, count)
yield from orig_sendfile(request, fobj, count)
# Overwriting like this because __init__ can change implementation.
self._sendfile = sendfile
FILE_SENDER = FileSender()
CACHING_FILE_SENDER = CachingFileSender()
@asyncio.coroutine
def staticresource_middleware(app, handler):
"""Enhance StaticResourceHandler middleware.
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
"""Middleware to strip out fingerprint from fingerprinted assets."""
@asyncio.coroutine
def static_middleware_handler(request):
"""Strip out fingerprints from resource names."""
if not request.path.startswith('/static/'):
return handler(request)
fingerprinted = _FINGERPRINT.match(request.match_info['filename'])
if fingerprinted:
request.match_info['filename'] = \
'{}.{}'.format(*fingerprinted.groups())
resp = yield from handler(request)
return resp
return handler(request)
return static_middleware_handler

View file

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

View file

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

View file

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

View file

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

View file

@ -359,30 +359,25 @@ class MicrosoftFace(object):
else:
payload = None
response = None
try:
with async_timeout.timeout(self.timeout, loop=self.hass.loop):
response = yield from getattr(self.websession, method)(
url, data=payload, headers=headers, params=params)
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",
response.status, response.url)
raise HomeAssistantError(answer['error']['message'])
_LOGGER.debug("Read from microsoft face api: %s", answer)
if response.status < 300:
return answer
except (aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError):
_LOGGER.warning("Error %d microsoft face api %s",
response.status, response.url)
raise HomeAssistantError(answer['error']['message'])
except aiohttp.ClientError:
_LOGGER.warning("Can't connect to microsoft face api")
except asyncio.TimeoutError:
_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.")

View file

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

View file

@ -151,6 +151,8 @@ class YrData(object):
@asyncio.coroutine
def async_update(self, *_):
"""Get the latest data from yr.no."""
import xmltodict
def try_again(err: str):
"""Retry in 15 minutes."""
_LOGGER.warning('Retrying in 15 minutes: %s', err)
@ -161,7 +163,6 @@ class YrData(object):
nxt)
if self._nextrun is None or dt_util.utcnow() >= self._nextrun:
resp = None
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
@ -172,17 +173,11 @@ class YrData(object):
return
text = yield from resp.text()
except (asyncio.TimeoutError, aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as err:
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
try_again(err)
return
finally:
if resp is not None:
yield from resp.release()
try:
import xmltodict
self.data = xmltodict.parse(text)['weatherdata']
model = self.data['meta']['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)
websession = async_get_clientsession(hass)
response = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
response = yield from websession.post(
@ -43,14 +42,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
'username': username,
'password': password})
data = yield from response.json()
except (asyncio.TimeoutError,
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed authentication API call: %s", error)
return False
finally:
if response is not None:
yield from response.release()
try:
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")
return False
response = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
response = yield from websession.get(
'{}{}'.format(HOOK_ENDPOINT, 'device'),
params={"token": data['data']['token']})
data = yield from response.json()
except (asyncio.TimeoutError,
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed getting devices: %s", error)
return False
finally:
if response is not None:
yield from response.release()
async_add_devices(
HookSmartHome(
@ -110,7 +98,6 @@ class HookSmartHome(SwitchDevice):
@asyncio.coroutine
def _send(self, url):
"""Send the url to the Hook API."""
response = None
try:
_LOGGER.debug("Sending: %s", url)
websession = async_get_clientsession(self.hass)
@ -119,16 +106,10 @@ class HookSmartHome(SwitchDevice):
url, params={"token": self._token})
data = yield from response.json()
except (asyncio.TimeoutError,
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed setting state: %s", error)
return False
finally:
if response is not None:
yield from response.release()
_LOGGER.debug("Got: %s", data)
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
timeout = config.get(CONF_TIMEOUT)
req = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
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):
_LOGGER.error("Missing resource or schema in configuration. "
"Add http:// or https:// to your URL")
return False
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("No route to resource/endpoint: %s", resource)
return False
finally:
if req is not None:
yield from req.release()
async_add_devices(
[RestSwitch(hass, name, resource, body_on, body_off,
@ -108,17 +109,13 @@ class RestSwitch(SwitchDevice):
body_on_t = self._body_on.async_render()
websession = async_get_clientsession(self.hass)
request = None
try:
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
request = yield from websession.post(
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)
return
finally:
if request is not None:
yield from request.release()
if request.status == 200:
self._state = True
@ -132,17 +129,13 @@ class RestSwitch(SwitchDevice):
body_off_t = self._body_off.async_render()
websession = async_get_clientsession(self.hass)
request = None
try:
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
request = yield from websession.post(
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)
return
finally:
if request is not None:
yield from request.release()
if request.status == 200:
self._state = False
@ -155,17 +148,13 @@ class RestSwitch(SwitchDevice):
"""Get the latest data from REST API and update the state."""
websession = async_get_clientsession(self.hass)
request = None
try:
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
request = yield from websession.get(self._resource)
text = yield from request.text()
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error while fetch data.")
return
finally:
if request is not None:
yield from request.release()
if self._is_on_template is not None:
text = self._is_on_template.async_render_with_possible_json_value(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -36,10 +36,8 @@ class TestDarkSkySetup(unittest.TestCase):
'monitored_conditions': ['summary', 'icon', 'temperature_max'],
'update_interval': timedelta(seconds=120),
}
self.lat = 37.8267
self.lon = -122.423
self.hass.config.latitude = self.lat
self.hass.config.longitude = self.lon
self.lat = self.hass.config.latitude = 37.8267
self.lon = self.hass.config.longitude = -122.423
self.entities = []
def tearDown(self): # pylint: disable=invalid-name
@ -51,11 +49,6 @@ class TestDarkSkySetup(unittest.TestCase):
self.assertTrue(
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')
def test_setup_bad_api_key(self, mock_get_forecast):
"""Test for handling a bad API key."""

View file

@ -42,7 +42,7 @@ class TestRestSwitchSetup:
def test_setup_failed_connect(self, aioclient_mock):
"""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(
rest.async_setup_platform(self.hass, {
'platform': 'rest',

View file

@ -1,12 +1,9 @@
"""The tests for the Home Assistant API component."""
# pylint: disable=protected-access
import asyncio
from contextlib import closing
import json
import unittest
from unittest.mock import Mock, patch
from aiohttp import web
import requests
from homeassistant import setup, const
@ -247,22 +244,6 @@ class TestAPI(unittest.TestCase):
headers=HA_HEADERS)
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):
"""Test if we can get the list of events being listened for."""
req = requests.get(_url(const.URL_API_EVENTS),

View file

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

View file

@ -107,7 +107,7 @@ class TestRestCommandComponent(object):
with assert_setup_component(4):
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.block_till_done()

View file

@ -1,5 +1,7 @@
"""The tests for the SleepIQ component."""
import unittest
from unittest.mock import MagicMock, patch
import requests_mock
from homeassistant import setup
@ -49,8 +51,12 @@ class TestSleepIQ(unittest.TestCase):
"""Test the setup."""
mock_responses(mock)
response = sleepiq.setup(self.hass, self.config)
self.assertTrue(response)
# We're mocking the load_platform discoveries or else the platforms
# 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()
def test_setup_login_failed(self, mock):

View file

@ -5,7 +5,7 @@ import unittest
import aiohttp
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
from homeassistant.util.async import run_callback_threadsafe
@ -119,22 +119,19 @@ class TestHelpersAiohttpClient(unittest.TestCase):
@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."""
aioclient_mock.get('http://example.com/mjpeg_stream', content=[
b'Frame1', b'Frame2', b'Frame3'
])
def setup_platform():
"""Setup the platform."""
assert setup_component(hass, 'camera', {
'camera': {
'name': 'config_test',
'platform': 'mjpeg',
'mjpeg_url': 'http://example.com/mjpeg_stream',
}})
yield from hass.loop.run_in_executor(None, setup_platform)
result = yield from async_setup_component(hass, 'camera', {
'camera': {
'name': 'config_test',
'platform': 'mjpeg',
'mjpeg_url': 'http://example.com/mjpeg_stream',
}})
assert result, 'Failed to setup camera'
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'])
resp = yield from client.get('/api/camera_proxy_stream/camera.config_test')
assert resp.status == 200
body = yield from resp.text()
assert body == ''
assert resp.status == 504
aioclient_mock.clear_requests()
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'])
resp = yield from client.get('/api/camera_proxy_stream/camera.config_test')
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 == ''
assert resp.status == 502