aiohttp 2 (#6835)
* 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:
parent
7b83a836f3
commit
714b516176
36 changed files with 250 additions and 442 deletions
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
2
setup.py
2
setup.py
|
@ -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',
|
||||
]
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue