Use httpx in generic camera (#46576)

* Use httpx in generic camera

* Remove commented out code
This commit is contained in:
uvjustin 2021-02-15 23:37:53 +08:00 committed by GitHub
parent c5b9ad83c2
commit 9917bb76fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 63 deletions

View File

@ -2,10 +2,7 @@
import asyncio import asyncio
import logging import logging
import aiohttp import httpx
import async_timeout
import requests
from requests.auth import HTTPDigestAuth
import voluptuous as vol import voluptuous as vol
from homeassistant.components.camera import ( from homeassistant.components.camera import (
@ -25,7 +22,7 @@ from homeassistant.const import (
) )
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.helpers import config_validation as cv 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 homeassistant.helpers.reload import async_setup_reload_service
from . import DOMAIN, PLATFORMS from . import DOMAIN, PLATFORMS
@ -39,6 +36,7 @@ CONF_STREAM_SOURCE = "stream_source"
CONF_FRAMERATE = "framerate" CONF_FRAMERATE = "framerate"
DEFAULT_NAME = "Generic Camera" DEFAULT_NAME = "Generic Camera"
GET_IMAGE_TIMEOUT = 10
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
@ -93,9 +91,9 @@ class GenericCamera(Camera):
if username and password: if username and password:
if self._authentication == HTTP_DIGEST_AUTHENTICATION: if self._authentication == HTTP_DIGEST_AUTHENTICATION:
self._auth = HTTPDigestAuth(username, password) self._auth = httpx.DigestAuth(username, password)
else: else:
self._auth = aiohttp.BasicAuth(username, password=password) self._auth = httpx.BasicAuth(username, password=password)
else: else:
self._auth = None self._auth = None
@ -129,40 +127,19 @@ class GenericCamera(Camera):
if url == self._last_url and self._limit_refetch: if url == self._last_url and self._limit_refetch:
return self._last_image return self._last_image
# aiohttp don't support DigestAuth yet try:
if self._authentication == HTTP_DIGEST_AUTHENTICATION: async_client = get_async_client(self.hass, verify_ssl=self.verify_ssl)
response = await async_client.get(
def fetch(): url, auth=self._auth, timeout=GET_IMAGE_TIMEOUT
"""Read image from a URL.""" )
try: response.raise_for_status()
response = requests.get( self._last_image = response.content
url, timeout=10, auth=self._auth, verify=self.verify_ssl except httpx.TimeoutException:
) _LOGGER.error("Timeout getting camera image from %s", self._name)
return response.content return self._last_image
except requests.exceptions.RequestException as error: except (httpx.RequestError, httpx.HTTPStatusError) as err:
_LOGGER.error( _LOGGER.error("Error getting new camera image from %s: %s", self._name, err)
"Error getting new camera image from %s: %s", self._name, error return self._last_image
)
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
self._last_url = url self._last_url = url
return self._last_image return self._last_image

View File

@ -3,6 +3,8 @@ import asyncio
from os import path from os import path
from unittest.mock import patch from unittest.mock import patch
import respx
from homeassistant import config as hass_config from homeassistant import config as hass_config
from homeassistant.components.generic import DOMAIN from homeassistant.components.generic import DOMAIN
from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.components.websocket_api.const import TYPE_RESULT
@ -14,9 +16,10 @@ from homeassistant.const import (
from homeassistant.setup import async_setup_component 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.""" """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( await async_setup_component(
hass, 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") resp = await client.get("/api/camera_proxy/camera.config_test")
assert resp.status == 200 assert resp.status == 200
assert aioclient_mock.call_count == 1 assert respx.calls.call_count == 1
body = await resp.text() body = await resp.text()
assert body == "hello world" assert body == "hello world"
resp = await client.get("/api/camera_proxy/camera.config_test") 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): 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 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.""" """Test that it fetches the given url."""
aioclient_mock.get("http://example.com/5a", text="hello world") respx.get("http://example.com/5a").respond(text="hello world")
aioclient_mock.get("http://example.com/10a", text="hello world") respx.get("http://example.com/10a").respond(text="hello world")
aioclient_mock.get("http://example.com/15a", text="hello planet") respx.get("http://example.com/15a").respond(text="hello planet")
aioclient_mock.get("http://example.com/20a", status=HTTP_NOT_FOUND) respx.get("http://example.com/20a").respond(status_code=HTTP_NOT_FOUND)
await async_setup_component( await async_setup_component(
hass, hass,
@ -129,19 +133,19 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client):
with patch("async_timeout.timeout", side_effect=asyncio.TimeoutError()): with patch("async_timeout.timeout", side_effect=asyncio.TimeoutError()):
resp = await client.get("/api/camera_proxy/camera.config_test") 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 assert resp.status == HTTP_INTERNAL_SERVER_ERROR
hass.states.async_set("sensor.temp", "10") hass.states.async_set("sensor.temp", "10")
resp = await client.get("/api/camera_proxy/camera.config_test") 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 assert resp.status == 200
body = await resp.text() body = await resp.text()
assert body == "hello world" assert body == "hello world"
resp = await client.get("/api/camera_proxy/camera.config_test") 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 assert resp.status == 200
body = await resp.text() body = await resp.text()
assert body == "hello world" assert body == "hello world"
@ -150,7 +154,7 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client):
# Url change = fetch new image # Url change = fetch new image
resp = await client.get("/api/camera_proxy/camera.config_test") 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 assert resp.status == 200
body = await resp.text() body = await resp.text()
assert body == "hello planet" assert body == "hello planet"
@ -158,7 +162,7 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client):
# Cause a template render error # Cause a template render error
hass.states.async_remove("sensor.temp") hass.states.async_remove("sensor.temp")
resp = await client.get("/api/camera_proxy/camera.config_test") 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 assert resp.status == 200
body = await resp.text() body = await resp.text()
assert body == "hello planet" 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.""" """Test generic camera with custom content_type."""
svg_image = "<some image>" svg_image = "<some image>"
urlsvg = "https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg" 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 = { cam_config_svg = {
"name": "config_test_svg", "name": "config_test_svg",
@ -309,23 +314,24 @@ async def test_camera_content_type(aioclient_mock, hass, hass_client):
client = await hass_client() client = await hass_client()
resp_1 = await client.get("/api/camera_proxy/camera.config_test_svg") 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.status == 200
assert resp_1.content_type == "image/svg+xml" assert resp_1.content_type == "image/svg+xml"
body = await resp_1.text() body = await resp_1.text()
assert body == svg_image assert body == svg_image
resp_2 = await client.get("/api/camera_proxy/camera.config_test_jpg") 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.status == 200
assert resp_2.content_type == "image/jpeg" assert resp_2.content_type == "image/jpeg"
body = await resp_2.text() body = await resp_2.text()
assert body == svg_image 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.""" """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( await async_setup_component(
hass, hass,
@ -347,7 +353,7 @@ async def test_reloading(aioclient_mock, hass, hass_client):
resp = await client.get("/api/camera_proxy/camera.config_test") resp = await client.get("/api/camera_proxy/camera.config_test")
assert resp.status == 200 assert resp.status == 200
assert aioclient_mock.call_count == 1 assert respx.calls.call_count == 1
body = await resp.text() body = await resp.text()
assert body == "hello world" 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") resp = await client.get("/api/camera_proxy/camera.reload")
assert resp.status == 200 assert resp.status == 200
assert aioclient_mock.call_count == 2 assert respx.calls.call_count == 2
body = await resp.text() body = await resp.text()
assert body == "hello world" assert body == "hello world"