From 95b439fbd5ad96966dfe1176697c6ee4731a6207 Mon Sep 17 00:00:00 2001 From: Lewis Juggins Date: Fri, 25 Nov 2016 05:37:56 +0000 Subject: [PATCH] Upgrade aiohttp to 1.1.5 (#4213) --- homeassistant/components/frontend/__init__.py | 70 +++++++++++++++++---------- homeassistant/components/http.py | 58 +++++++++++----------- requirements_all.txt | 6 +-- requirements_test.txt | 2 +- setup.py | 4 +- tests/components/media_player/test_demo.py | 10 ++-- tests/components/test_google.py | 36 +++++++------- tests/components/test_panel_iframe.py | 10 ++-- 8 files changed, 106 insertions(+), 90 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 195d79e..6fde1ae 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -8,7 +8,7 @@ from aiohttp import web from homeassistant.core import callback -from homeassistant.const import EVENT_HOMEASSISTANT_START, HTTP_NOT_FOUND +from homeassistant.const import HTTP_NOT_FOUND from homeassistant.components import api, group from homeassistant.components.http import HomeAssistantView from .version import FINGERPRINTS @@ -18,7 +18,6 @@ URL_PANEL_COMPONENT = '/frontend/panels/{}.html' URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html' STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static') -PANELS = {} MANIFEST_JSON = { "background_color": "#FFFFFF", "description": "Open-source home automation platform running on Python 3.", @@ -32,6 +31,16 @@ "theme_color": "#03A9F4" } +for size in (192, 384, 512, 1024): + MANIFEST_JSON['icons'].append({ + "src": "/static/icons/favicon-{}x{}.png".format(size, size), + "sizes": "{}x{}".format(size, size), + "type": "image/png" + }) + +DATA_PANELS = 'frontend_panels' +DATA_INDEX_VIEW = 'frontend_index_view' + # To keep track we don't register a component twice (gives a warning) _REGISTERED_COMPONENTS = set() _LOGGER = logging.getLogger(__name__) @@ -68,10 +77,14 @@ def register_panel(hass, component_name, path, md5=None, sidebar_title=None, Warning: this API will probably change. Use at own risk. """ + panels = hass.data.get(DATA_PANELS) + if panels is None: + panels = hass.data[DATA_PANELS] = {} + if url_path is None: url_path = component_name - if url_path in PANELS: + if url_path in panels: _LOGGER.warning('Overwriting component %s', url_path) if not os.path.isfile(path): _LOGGER.error('Panel %s component does not exist: %s', @@ -106,7 +119,15 @@ def register_panel(hass, component_name, path, md5=None, sidebar_title=None, fprinted_url = URL_PANEL_COMPONENT_FP.format(component_name, md5) data['url'] = fprinted_url - PANELS[url_path] = data + panels[url_path] = data + + # Register index view for this route if IndexView already loaded + # Otherwise it will be done during setup. + index_view = hass.data.get(DATA_INDEX_VIEW) + + if index_view: + hass.http.app.router.add_route('get', '/{}'.format(url_path), + index_view.get) def add_manifest_json_key(key, val): @@ -134,29 +155,24 @@ def setup(hass, config): if os.path.isdir(local): hass.http.register_static_path("/local", local) + index_view = hass.data[DATA_INDEX_VIEW] = IndexView(hass) + hass.http.register_view(index_view) + + # Components have registered panels before frontend got setup. + # Now register their urls. + if DATA_PANELS in hass.data: + for url_path in hass.data[DATA_PANELS]: + hass.http.app.router.add_route('get', '/{}'.format(url_path), + index_view.get) + else: + hass.data[DATA_PANELS] = {} + register_built_in_panel(hass, 'map', 'Map', 'mdi:account-location') for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state', 'dev-template'): register_built_in_panel(hass, panel) - def register_frontend_index(event): - """Register the frontend index urls. - - Done when Home Assistant is started so that all panels are known. - """ - hass.http.register_view(IndexView( - hass, ['/{}'.format(name) for name in PANELS])) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, register_frontend_index) - - for size in (192, 384, 512, 1024): - MANIFEST_JSON['icons'].append({ - "src": "/static/icons/favicon-{}x{}.png".format(size, size), - "sizes": "{}x{}".format(size, size), - "type": "image/png" - }) - return True @@ -174,7 +190,7 @@ def get(self, request): 'states': self.hass.states.async_all(), 'events': api.async_events_json(self.hass), 'services': api.async_services_json(self.hass), - 'panels': PANELS, + 'panels': self.hass.data[DATA_PANELS], }) @@ -186,13 +202,12 @@ class IndexView(HomeAssistantView): requires_auth = False extra_urls = ['/states', '/states/{entity_id}'] - def __init__(self, hass, extra_urls): + def __init__(self, hass): """Initialize the frontend view.""" super().__init__(hass) from jinja2 import FileSystemLoader, Environment - self.extra_urls = self.extra_urls + extra_urls self.templates = Environment( loader=FileSystemLoader( os.path.join(os.path.dirname(__file__), 'templates/') @@ -223,7 +238,10 @@ def get(self, request, entity_id=None): else: panel = request.path.split('/')[1] - panel_url = PANELS[panel]['url'] if panel != 'states' else '' + if panel == 'states': + panel_url = '' + else: + panel_url = self.hass.data[DATA_PANELS][panel]['url'] no_auth = 'true' if self.hass.config.api.api_password: @@ -244,7 +262,7 @@ def get(self, request, entity_id=None): resp = template.render( core_url=core_url, ui_url=ui_url, no_auth=no_auth, icons_url=icons_url, icons=FINGERPRINTS['mdi.html'], - panel_url=panel_url, panels=PANELS) + panel_url=panel_url, panels=self.hass.data[DATA_PANELS]) return web.Response(text=resp, content_type='text/html') diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index a008a3f..a6293d0 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -20,7 +20,7 @@ from aiohttp.file_sender import FileSender from aiohttp.web_exceptions import ( HTTPUnauthorized, HTTPMovedPermanently, HTTPNotModified) -from aiohttp.web_urldispatcher import StaticRoute +from aiohttp.web_urldispatcher import StaticResource from homeassistant.core import is_callback import homeassistant.remote as rem @@ -33,7 +33,7 @@ from homeassistant.components import persistent_notification DOMAIN = 'http' -REQUIREMENTS = ('aiohttp_cors==0.4.0',) +REQUIREMENTS = ('aiohttp_cors==0.5.0',) CONF_API_PASSWORD = 'api_password' CONF_SERVER_HOST = 'server_host' @@ -212,13 +212,8 @@ def send(self, request, filepath): file_size = st.st_size resp.content_length = file_size - resp.set_tcp_cork(True) - try: - with filepath.open('rb') as f: - yield from self._sendfile(request, resp, f, file_size) - - finally: - resp.set_tcp_nodelay(True) + with filepath.open('rb') as f: + yield from self._sendfile(request, resp, f, file_size) return resp @@ -226,26 +221,32 @@ def send(self, request, filepath): _GZIP_FILE_SENDER = GzipFileSender() -class HAStaticRoute(StaticRoute): - """StaticRoute with support for fingerprinting.""" +@asyncio.coroutine +def staticresource_enhancer(app, handler): + """Enhance StaticResourceHandler. + + Adds gzip encoding and fingerprinting matching. + """ + inst = getattr(handler, '__self__', None) + if not isinstance(inst, StaticResource): + return handler - def __init__(self, prefix, path): - """Initialize a static route with gzip and cache busting support.""" - super().__init__(None, prefix, path) - self._file_sender = _GZIP_FILE_SENDER + # pylint: disable=protected-access + inst._file_sender = _GZIP_FILE_SENDER - def match(self, path): - """Match path to filename.""" - if not path.startswith(self._prefix): - return None + @asyncio.coroutine + def middleware_handler(request): + """Strip out fingerprints from resource names.""" + fingerprinted = _FINGERPRINT.match(request.match_info['filename']) - # Extra sauce to remove fingerprinted resource names - filename = path[self._prefix_len:] - fingerprinted = _FINGERPRINT.match(filename) if fingerprinted: - filename = '{}.{}'.format(*fingerprinted.groups()) + request.match_info['filename'] = \ + '{}.{}'.format(*fingerprinted.groups()) + + resp = yield from handler(request) + return resp - return {'filename': filename} + return middleware_handler class HomeAssistantWSGI(object): @@ -257,7 +258,8 @@ def __init__(self, hass, development, api_password, ssl_certificate, """Initialize the WSGI Home Assistant server.""" import aiohttp_cors - self.app = web.Application(loop=hass.loop) + self.app = web.Application(middlewares=[staticresource_enhancer], + loop=hass.loop) self.hass = hass self.development = development self.api_password = api_password @@ -318,11 +320,7 @@ def register_static_path(self, url_root, path, cache_length=31): Specify optional cache length of asset in days. """ if os.path.isdir(path): - assert url_root.startswith('/') - if not url_root.endswith('/'): - url_root += '/' - route = HAStaticRoute(url_root, path) - self.app.router.register_route(route) + self.app.router.add_static(url_root, path) return filepath = Path(path) diff --git a/requirements_all.txt b/requirements_all.txt index af47a1d..f9ff30d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -6,8 +6,8 @@ pip>=7.0.0 jinja2>=2.8 voluptuous==0.9.2 typing>=3,<4 -aiohttp==1.0.5 -async_timeout==1.0.0 +aiohttp==1.1.5 +async_timeout==1.1.0 # homeassistant.components.nuimo_controller --only-binary=all git+https://github.com/getSenic/nuimo-linux-python#nuimo==1.0.0 @@ -31,7 +31,7 @@ SoCo==0.12 TwitterAPI==2.4.2 # homeassistant.components.http -aiohttp_cors==0.4.0 +aiohttp_cors==0.5.0 # homeassistant.components.apcupsd apcaccess==0.0.4 diff --git a/requirements_test.txt b/requirements_test.txt index 7846318..838e4c9 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,7 +10,7 @@ pytest>=2.9.2 pytest-aiohttp>=0.1.3 pytest-asyncio>=0.5.0 pytest-cov>=2.3.1 -pytest-timeout>=1.0.0 +pytest-timeout>=1.2.0 pytest-catchlog>=1.2.2 requests_mock>=1.0 mock-open>=1.3.1 diff --git a/setup.py b/setup.py index 145b027..7060550 100755 --- a/setup.py +++ b/setup.py @@ -21,8 +21,8 @@ 'jinja2>=2.8', 'voluptuous==0.9.2', 'typing>=3,<4', - 'aiohttp==1.0.5', - 'async_timeout==1.0.0', + 'aiohttp==1.1.5', + 'async_timeout==1.1.0', ] setup( diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index 8bcda32..3539c73 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -245,13 +245,17 @@ def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - setup_component(self.hass, http.DOMAIN, { + assert setup_component(self.hass, http.DOMAIN, { http.DOMAIN: { http.CONF_SERVER_PORT: SERVER_PORT, http.CONF_API_PASSWORD: API_PASSWORD, }, }) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) + self.hass.start() def tearDown(self): @@ -287,10 +291,6 @@ def close(self): self.hass._websession = MockWebsession() - self.hass.block_till_done() - assert setup_component( - self.hass, mp.DOMAIN, - {'media_player': {'platform': 'demo'}}) assert self.hass.states.is_state(entity_id, 'playing') state = self.hass.states.get(entity_id) req = requests.get(HTTP_BASE_URL + diff --git a/tests/components/test_google.py b/tests/components/test_google.py index 10db913..fbaddb1 100644 --- a/tests/components/test_google.py +++ b/tests/components/test_google.py @@ -34,6 +34,7 @@ def test_setup_component(self, mock_do_auth): self.assertTrue(setup_component(self.hass, 'google', config)) def test_get_calendar_info(self): + """Test getting the calendar info.""" calendar = { 'id': 'qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com', 'etag': '"3584134138943410"', @@ -61,21 +62,22 @@ def test_get_calendar_info(self): }) def test_found_calendar(self): - calendar = { - 'id': 'qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com', - 'etag': '"3584134138943410"', - 'timeZone': 'UTC', - 'accessRole': 'reader', - 'foregroundColor': '#000000', - 'selected': True, - 'kind': 'calendar#calendarListEntry', - 'backgroundColor': '#16a765', - 'description': 'Test Calendar', - 'summary': 'We are, we are, a... Test Calendar', - 'colorId': '8', - 'defaultReminders': [], - 'track': True - } + """Test when a calendar is found.""" + # calendar = { + # 'id': 'qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com', + # 'etag': '"3584134138943410"', + # 'timeZone': 'UTC', + # 'accessRole': 'reader', + # 'foregroundColor': '#000000', + # 'selected': True, + # 'kind': 'calendar#calendarListEntry', + # 'backgroundColor': '#16a765', + # 'description': 'Test Calendar', + # 'summary': 'We are, we are, a... Test Calendar', + # 'colorId': '8', + # 'defaultReminders': [], + # 'track': True + # } # self.assertIsInstance(self.hass.data[google.DATA_INDEX], dict) # self.assertEquals(self.hass.data[google.DATA_INDEX], {}) @@ -84,8 +86,8 @@ def test_found_calendar(self): self.hass.config.path(google.TOKEN_FILE)) self.assertTrue(google.setup_services(self.hass, True, calendar_service)) - self.hass.services.call('google', 'found_calendar', calendar, - blocking=True) + # self.hass.services.call('google', 'found_calendar', calendar, + # blocking=True) # TODO: Fix this # self.assertTrue(self.hass.data[google.DATA_INDEX] diff --git a/tests/components/test_panel_iframe.py b/tests/components/test_panel_iframe.py index ac479de..cf2fdc2 100644 --- a/tests/components/test_panel_iframe.py +++ b/tests/components/test_panel_iframe.py @@ -14,12 +14,10 @@ class TestPanelIframe(unittest.TestCase): def setup_method(self, method): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - frontend.PANELS = {} def teardown_method(self, method): """Stop everything that was started.""" self.hass.stop() - frontend.PANELS = {} def test_wrong_config(self): """Test setup with wrong configuration.""" @@ -55,9 +53,9 @@ def test_correct_config(self): }, }) - # 5 dev tools + map are automatically loaded - assert len(frontend.PANELS) == 8 - assert frontend.PANELS['router'] == { + # 5 dev tools + map are automatically loaded + 2 iframe panels + assert len(self.hass.data[frontend.DATA_PANELS]) == 8 + assert self.hass.data[frontend.DATA_PANELS]['router'] == { 'component_name': 'iframe', 'config': {'url': 'http://192.168.1.1'}, 'icon': 'mdi:network-wireless', @@ -66,7 +64,7 @@ def test_correct_config(self): 'url_path': 'router' } - assert frontend.PANELS['weather'] == { + assert self.hass.data[frontend.DATA_PANELS]['weather'] == { 'component_name': 'iframe', 'config': {'url': 'https://www.wunderground.com/us/ca/san-diego'}, 'icon': 'mdi:weather',