From 9917bb76fbaed50d57910a167f11ffe1b465cee6 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Mon, 15 Feb 2021 23:37:53 +0800 Subject: [PATCH] Use httpx in generic camera (#46576) * Use httpx in generic camera * Remove commented out code --- homeassistant/components/generic/camera.py | 59 +++++++--------------- tests/components/generic/test_camera.py | 50 ++++++++++-------- 2 files changed, 46 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 2e798b8cc4b..28db66b4f3e 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -2,10 +2,7 @@ import asyncio import logging -import aiohttp -import async_timeout -import requests -from requests.auth import HTTPDigestAuth +import httpx import voluptuous as vol from homeassistant.components.camera import ( @@ -25,7 +22,7 @@ from homeassistant.const import ( ) from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.reload import async_setup_reload_service from . import DOMAIN, PLATFORMS @@ -39,6 +36,7 @@ CONF_STREAM_SOURCE = "stream_source" CONF_FRAMERATE = "framerate" DEFAULT_NAME = "Generic Camera" +GET_IMAGE_TIMEOUT = 10 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -93,9 +91,9 @@ class GenericCamera(Camera): if username and password: if self._authentication == HTTP_DIGEST_AUTHENTICATION: - self._auth = HTTPDigestAuth(username, password) + self._auth = httpx.DigestAuth(username, password) else: - self._auth = aiohttp.BasicAuth(username, password=password) + self._auth = httpx.BasicAuth(username, password=password) else: self._auth = None @@ -129,40 +127,19 @@ class GenericCamera(Camera): if url == self._last_url and self._limit_refetch: return self._last_image - # aiohttp don't support DigestAuth yet - if self._authentication == HTTP_DIGEST_AUTHENTICATION: - - def fetch(): - """Read image from a URL.""" - try: - response = requests.get( - url, timeout=10, auth=self._auth, verify=self.verify_ssl - ) - return response.content - except requests.exceptions.RequestException as error: - _LOGGER.error( - "Error getting new camera image from %s: %s", self._name, error - ) - return self._last_image - - self._last_image = await self.hass.async_add_executor_job(fetch) - # async - else: - try: - websession = async_get_clientsession( - self.hass, verify_ssl=self.verify_ssl - ) - with async_timeout.timeout(10): - response = await websession.get(url, auth=self._auth) - self._last_image = await response.read() - except asyncio.TimeoutError: - _LOGGER.error("Timeout getting camera image from %s", self._name) - return self._last_image - except aiohttp.ClientError as err: - _LOGGER.error( - "Error getting new camera image from %s: %s", self._name, err - ) - return self._last_image + try: + async_client = get_async_client(self.hass, verify_ssl=self.verify_ssl) + response = await async_client.get( + url, auth=self._auth, timeout=GET_IMAGE_TIMEOUT + ) + response.raise_for_status() + self._last_image = response.content + except httpx.TimeoutException: + _LOGGER.error("Timeout getting camera image from %s", self._name) + return self._last_image + except (httpx.RequestError, httpx.HTTPStatusError) as err: + _LOGGER.error("Error getting new camera image from %s: %s", self._name, err) + return self._last_image self._last_url = url return self._last_image diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 65f5306c4d8..7f5b3bb3b53 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -3,6 +3,8 @@ import asyncio from os import path from unittest.mock import patch +import respx + from homeassistant import config as hass_config from homeassistant.components.generic import DOMAIN from homeassistant.components.websocket_api.const import TYPE_RESULT @@ -14,9 +16,10 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component -async def test_fetching_url(aioclient_mock, hass, hass_client): +@respx.mock +async def test_fetching_url(hass, hass_client): """Test that it fetches the given url.""" - aioclient_mock.get("http://example.com", text="hello world") + respx.get("http://example.com").respond(text="hello world") await async_setup_component( hass, @@ -38,12 +41,12 @@ async def test_fetching_url(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 body = await resp.text() assert body == "hello world" resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 async def test_fetching_without_verify_ssl(aioclient_mock, hass, hass_client): @@ -100,12 +103,13 @@ async def test_fetching_url_with_verify_ssl(aioclient_mock, hass, hass_client): assert resp.status == 200 -async def test_limit_refetch(aioclient_mock, hass, hass_client): +@respx.mock +async def test_limit_refetch(hass, hass_client): """Test that it fetches the given url.""" - aioclient_mock.get("http://example.com/5a", text="hello world") - aioclient_mock.get("http://example.com/10a", text="hello world") - aioclient_mock.get("http://example.com/15a", text="hello planet") - aioclient_mock.get("http://example.com/20a", status=HTTP_NOT_FOUND) + respx.get("http://example.com/5a").respond(text="hello world") + respx.get("http://example.com/10a").respond(text="hello world") + respx.get("http://example.com/15a").respond(text="hello planet") + respx.get("http://example.com/20a").respond(status_code=HTTP_NOT_FOUND) await async_setup_component( hass, @@ -129,19 +133,19 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client): with patch("async_timeout.timeout", side_effect=asyncio.TimeoutError()): resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 0 + assert respx.calls.call_count == 0 assert resp.status == HTTP_INTERNAL_SERVER_ERROR hass.states.async_set("sensor.temp", "10") resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 assert resp.status == 200 body = await resp.text() assert body == "hello world" resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 assert resp.status == 200 body = await resp.text() assert body == "hello world" @@ -150,7 +154,7 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client): # Url change = fetch new image resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 assert resp.status == 200 body = await resp.text() assert body == "hello planet" @@ -158,7 +162,7 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client): # Cause a template render error hass.states.async_remove("sensor.temp") resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 assert resp.status == 200 body = await resp.text() assert body == "hello planet" @@ -285,11 +289,12 @@ async def test_no_stream_source(aioclient_mock, hass, hass_client, hass_ws_clien } -async def test_camera_content_type(aioclient_mock, hass, hass_client): +@respx.mock +async def test_camera_content_type(hass, hass_client): """Test generic camera with custom content_type.""" svg_image = "" urlsvg = "https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg" - aioclient_mock.get(urlsvg, text=svg_image) + respx.get(urlsvg).respond(text=svg_image) cam_config_svg = { "name": "config_test_svg", @@ -309,23 +314,24 @@ async def test_camera_content_type(aioclient_mock, hass, hass_client): client = await hass_client() resp_1 = await client.get("/api/camera_proxy/camera.config_test_svg") - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 assert resp_1.status == 200 assert resp_1.content_type == "image/svg+xml" body = await resp_1.text() assert body == svg_image resp_2 = await client.get("/api/camera_proxy/camera.config_test_jpg") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 assert resp_2.status == 200 assert resp_2.content_type == "image/jpeg" body = await resp_2.text() assert body == svg_image -async def test_reloading(aioclient_mock, hass, hass_client): +@respx.mock +async def test_reloading(hass, hass_client): """Test we can cleanly reload.""" - aioclient_mock.get("http://example.com", text="hello world") + respx.get("http://example.com").respond(text="hello world") await async_setup_component( hass, @@ -347,7 +353,7 @@ async def test_reloading(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 body = await resp.text() assert body == "hello world" @@ -374,7 +380,7 @@ async def test_reloading(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.reload") assert resp.status == 200 - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 body = await resp.text() assert body == "hello world"