mirror of
https://github.com/home-assistant/core.git
synced 2025-07-04 11:57:05 +00:00
Convert rest sensors to async using httpx (#41973)
This commit is contained in:
parent
39adf14079
commit
ad6ce5fa83
@ -721,7 +721,6 @@ omit =
|
|||||||
homeassistant/components/repetier/__init__.py
|
homeassistant/components/repetier/__init__.py
|
||||||
homeassistant/components/repetier/sensor.py
|
homeassistant/components/repetier/sensor.py
|
||||||
homeassistant/components/remote_rpi_gpio/*
|
homeassistant/components/remote_rpi_gpio/*
|
||||||
homeassistant/components/rest/binary_sensor.py
|
|
||||||
homeassistant/components/rest/notify.py
|
homeassistant/components/rest/notify.py
|
||||||
homeassistant/components/rest/switch.py
|
homeassistant/components/rest/switch.py
|
||||||
homeassistant/components/ring/camera.py
|
homeassistant/components/ring/camera.py
|
||||||
|
@ -43,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up the PVOutput sensor."""
|
"""Set up the PVOutput sensor."""
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
api_key = config.get(CONF_API_KEY)
|
api_key = config.get(CONF_API_KEY)
|
||||||
@ -54,13 +54,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
headers = {"X-Pvoutput-Apikey": api_key, "X-Pvoutput-SystemId": system_id}
|
headers = {"X-Pvoutput-Apikey": api_key, "X-Pvoutput-SystemId": system_id}
|
||||||
|
|
||||||
rest = RestData(method, _ENDPOINT, auth, headers, payload, verify_ssl)
|
rest = RestData(method, _ENDPOINT, auth, headers, payload, verify_ssl)
|
||||||
rest.update()
|
await rest.async_update()
|
||||||
|
|
||||||
if rest.data is None:
|
if rest.data is None:
|
||||||
_LOGGER.error("Unable to fetch data from PVOutput")
|
_LOGGER.error("Unable to fetch data from PVOutput")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
add_entities([PvoutputSensor(rest, name)], True)
|
async_add_entities([PvoutputSensor(rest, name)], True)
|
||||||
|
|
||||||
|
|
||||||
class PvoutputSensor(Entity):
|
class PvoutputSensor(Entity):
|
||||||
@ -112,11 +112,15 @@ class PvoutputSensor(Entity):
|
|||||||
ATTR_VOLTAGE: self.pvcoutput.voltage,
|
ATTR_VOLTAGE: self.pvcoutput.voltage,
|
||||||
}
|
}
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data from the PVOutput API and updates the state."""
|
"""Get the latest data from the PVOutput API and updates the state."""
|
||||||
try:
|
try:
|
||||||
self.rest.update()
|
await self.rest.async_update()
|
||||||
self.pvcoutput = self.status._make(self.rest.data.split(","))
|
self.pvcoutput = self.status._make(self.rest.data.split(","))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.pvcoutput = None
|
self.pvcoutput = None
|
||||||
_LOGGER.error("Unable to fetch data from PVOutput. %s", self.rest.data)
|
_LOGGER.error("Unable to fetch data from PVOutput. %s", self.rest.data)
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""Shutdown the session."""
|
||||||
|
await self.rest.async_remove()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Support for RESTful binary sensors."""
|
"""Support for RESTful binary sensors."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
import httpx
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
@ -29,7 +29,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.reload import setup_reload_service
|
from homeassistant.helpers.reload import async_setup_reload_service
|
||||||
|
|
||||||
from . import DOMAIN, PLATFORMS
|
from . import DOMAIN, PLATFORMS
|
||||||
from .sensor import RestData
|
from .sensor import RestData
|
||||||
@ -68,10 +68,10 @@ PLATFORM_SCHEMA = vol.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up the REST binary sensor."""
|
"""Set up the REST binary sensor."""
|
||||||
|
|
||||||
setup_reload_service(hass, DOMAIN, PLATFORMS)
|
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||||
|
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
resource = config.get(CONF_RESOURCE)
|
resource = config.get(CONF_RESOURCE)
|
||||||
@ -96,18 +96,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
|
|
||||||
if username and password:
|
if username and password:
|
||||||
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
|
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
|
||||||
auth = HTTPDigestAuth(username, password)
|
auth = httpx.DigestAuth(username, password)
|
||||||
else:
|
else:
|
||||||
auth = HTTPBasicAuth(username, password)
|
auth = (username, password)
|
||||||
else:
|
else:
|
||||||
auth = None
|
auth = None
|
||||||
|
|
||||||
rest = RestData(method, resource, auth, headers, payload, verify_ssl, timeout)
|
rest = RestData(method, resource, auth, headers, payload, verify_ssl, timeout)
|
||||||
rest.update()
|
await rest.async_update()
|
||||||
if rest.data is None:
|
if rest.data is None:
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
|
||||||
add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
RestBinarySensor(
|
RestBinarySensor(
|
||||||
hass,
|
hass,
|
||||||
@ -118,7 +118,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
force_update,
|
force_update,
|
||||||
resource_template,
|
resource_template,
|
||||||
)
|
)
|
||||||
]
|
],
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -186,9 +187,13 @@ class RestBinarySensor(BinarySensorEntity):
|
|||||||
"""Force update."""
|
"""Force update."""
|
||||||
return self._force_update
|
return self._force_update
|
||||||
|
|
||||||
def update(self):
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""Shutdown the session."""
|
||||||
|
await self.rest.async_remove()
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
"""Get the latest data from REST API and updates the state."""
|
"""Get the latest data from REST API and updates the state."""
|
||||||
if self._resource_template is not None:
|
if self._resource_template is not None:
|
||||||
self.rest.set_url(self._resource_template.render())
|
self.rest.set_url(self._resource_template.render())
|
||||||
|
|
||||||
self.rest.update()
|
await self.rest.async_update()
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
"domain": "rest",
|
"domain": "rest",
|
||||||
"name": "RESTful",
|
"name": "RESTful",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/rest",
|
"documentation": "https://www.home-assistant.io/integrations/rest",
|
||||||
"requirements": ["jsonpath==0.82", "xmltodict==0.12.0"],
|
"requirements": ["jsonpath==0.82", "xmltodict==0.12.0", "httpx==0.16.1"],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,8 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
|
|
||||||
|
import httpx
|
||||||
from jsonpath import jsonpath
|
from jsonpath import jsonpath
|
||||||
import requests
|
|
||||||
from requests import Session
|
|
||||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
|
||||||
@ -33,7 +31,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.reload import setup_reload_service
|
from homeassistant.helpers.reload import async_setup_reload_service
|
||||||
|
|
||||||
from . import DOMAIN, PLATFORMS
|
from . import DOMAIN, PLATFORMS
|
||||||
|
|
||||||
@ -79,9 +77,9 @@ PLATFORM_SCHEMA = vol.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up the RESTful sensor."""
|
"""Set up the RESTful sensor."""
|
||||||
setup_reload_service(hass, DOMAIN, PLATFORMS)
|
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||||
|
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
resource = config.get(CONF_RESOURCE)
|
resource = config.get(CONF_RESOURCE)
|
||||||
@ -109,19 +107,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
|
|
||||||
if username and password:
|
if username and password:
|
||||||
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
|
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
|
||||||
auth = HTTPDigestAuth(username, password)
|
auth = httpx.DigestAuth(username, password)
|
||||||
else:
|
else:
|
||||||
auth = HTTPBasicAuth(username, password)
|
auth = (username, password)
|
||||||
else:
|
else:
|
||||||
auth = None
|
auth = None
|
||||||
rest = RestData(method, resource, auth, headers, payload, verify_ssl, timeout)
|
rest = RestData(method, resource, auth, headers, payload, verify_ssl, timeout)
|
||||||
rest.update()
|
await rest.async_update()
|
||||||
|
|
||||||
if rest.data is None:
|
if rest.data is None:
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
|
||||||
# Must update the sensor now (including fetching the rest resource) to
|
# Must update the sensor now (including fetching the rest resource) to
|
||||||
# ensure it's updating its state.
|
# ensure it's updating its state.
|
||||||
add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
RestSensor(
|
RestSensor(
|
||||||
hass,
|
hass,
|
||||||
@ -200,12 +199,13 @@ class RestSensor(Entity):
|
|||||||
"""Force update."""
|
"""Force update."""
|
||||||
return self._force_update
|
return self._force_update
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data from REST API and update the state."""
|
"""Get the latest data from REST API and update the state."""
|
||||||
if self._resource_template is not None:
|
if self._resource_template is not None:
|
||||||
self.rest.set_url(self._resource_template.render())
|
self.rest.set_url(self._resource_template.render())
|
||||||
|
|
||||||
self.rest.update()
|
await self.rest.async_update()
|
||||||
|
|
||||||
value = self.rest.data
|
value = self.rest.data
|
||||||
_LOGGER.debug("Data fetched from resource: %s", value)
|
_LOGGER.debug("Data fetched from resource: %s", value)
|
||||||
if self.rest.headers is not None:
|
if self.rest.headers is not None:
|
||||||
@ -250,13 +250,21 @@ class RestSensor(Entity):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.warning("REST result could not be parsed as JSON")
|
_LOGGER.warning("REST result could not be parsed as JSON")
|
||||||
_LOGGER.debug("Erroneous JSON: %s", value)
|
_LOGGER.debug("Erroneous JSON: %s", value)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Empty reply found when expecting JSON data")
|
_LOGGER.warning("Empty reply found when expecting JSON data")
|
||||||
|
|
||||||
if value is not None and self._value_template is not None:
|
if value is not None and self._value_template is not None:
|
||||||
value = self._value_template.render_with_possible_json_value(value, None)
|
value = self._value_template.async_render_with_possible_json_value(
|
||||||
|
value, None
|
||||||
|
)
|
||||||
|
|
||||||
self._state = value
|
self._state = value
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""Shutdown the session."""
|
||||||
|
await self.rest.async_remove()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
@ -267,7 +275,14 @@ class RestData:
|
|||||||
"""Class for handling the data retrieval."""
|
"""Class for handling the data retrieval."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, method, resource, auth, headers, data, verify_ssl, timeout=DEFAULT_TIMEOUT
|
self,
|
||||||
|
method,
|
||||||
|
resource,
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
data,
|
||||||
|
verify_ssl,
|
||||||
|
timeout=DEFAULT_TIMEOUT,
|
||||||
):
|
):
|
||||||
"""Initialize the data object."""
|
"""Initialize the data object."""
|
||||||
self._method = method
|
self._method = method
|
||||||
@ -275,36 +290,39 @@ class RestData:
|
|||||||
self._auth = auth
|
self._auth = auth
|
||||||
self._headers = headers
|
self._headers = headers
|
||||||
self._request_data = data
|
self._request_data = data
|
||||||
self._verify_ssl = verify_ssl
|
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._http_session = Session()
|
self._verify_ssl = verify_ssl
|
||||||
|
self._async_client = None
|
||||||
self.data = None
|
self.data = None
|
||||||
self.headers = None
|
self.headers = None
|
||||||
|
|
||||||
def __del__(self):
|
async def async_remove(self):
|
||||||
"""Destroy the http session on destroy."""
|
"""Destroy the http session on destroy."""
|
||||||
self._http_session.close()
|
if self._async_client:
|
||||||
|
await self._async_client.aclose()
|
||||||
|
|
||||||
def set_url(self, url):
|
def set_url(self, url):
|
||||||
"""Set url."""
|
"""Set url."""
|
||||||
self._resource = url
|
self._resource = url
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data from REST service with provided method."""
|
"""Get the latest data from REST service with provided method."""
|
||||||
|
if not self._async_client:
|
||||||
|
self._async_client = httpx.AsyncClient(verify=self._verify_ssl)
|
||||||
|
|
||||||
_LOGGER.debug("Updating from %s", self._resource)
|
_LOGGER.debug("Updating from %s", self._resource)
|
||||||
try:
|
try:
|
||||||
response = self._http_session.request(
|
response = await self._async_client.request(
|
||||||
self._method,
|
self._method,
|
||||||
self._resource,
|
self._resource,
|
||||||
headers=self._headers,
|
headers=self._headers,
|
||||||
auth=self._auth,
|
auth=self._auth,
|
||||||
data=self._request_data,
|
data=self._request_data,
|
||||||
timeout=self._timeout,
|
timeout=self._timeout,
|
||||||
verify=self._verify_ssl,
|
|
||||||
)
|
)
|
||||||
self.data = response.text
|
self.data = response.text
|
||||||
self.headers = response.headers
|
self.headers = response.headers
|
||||||
except requests.exceptions.RequestException as ex:
|
except httpx.RequestError as ex:
|
||||||
_LOGGER.error("Error fetching data: %s failed with %s", self._resource, ex)
|
_LOGGER.error("Error fetching data: %s failed with %s", self._resource, ex)
|
||||||
self.data = None
|
self.data = None
|
||||||
self.headers = None
|
self.headers = None
|
||||||
|
@ -53,7 +53,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up the Web scrape sensor."""
|
"""Set up the Web scrape sensor."""
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
resource = config.get(CONF_RESOURCE)
|
resource = config.get(CONF_RESOURCE)
|
||||||
@ -79,12 +79,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
else:
|
else:
|
||||||
auth = None
|
auth = None
|
||||||
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
|
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
|
||||||
rest.update()
|
await rest.async_update()
|
||||||
|
|
||||||
if rest.data is None:
|
if rest.data is None:
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
|
||||||
add_entities(
|
async_add_entities(
|
||||||
[ScrapeSensor(rest, name, select, attr, index, value_template, unit)], True
|
[ScrapeSensor(rest, name, select, attr, index, value_template, unit)], True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,9 +118,9 @@ class ScrapeSensor(Entity):
|
|||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data from the source and updates the state."""
|
"""Get the latest data from the source and updates the state."""
|
||||||
self.rest.update()
|
await self.rest.async_update()
|
||||||
if self.rest.data is None:
|
if self.rest.data is None:
|
||||||
_LOGGER.error("Unable to retrieve data for %s", self.name)
|
_LOGGER.error("Unable to retrieve data for %s", self.name)
|
||||||
return
|
return
|
||||||
@ -143,8 +143,12 @@ class ScrapeSensor(Entity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self._value_template is not None:
|
if self._value_template is not None:
|
||||||
self._state = self._value_template.render_with_possible_json_value(
|
self._state = self._value_template.async_render_with_possible_json_value(
|
||||||
value, None
|
value, None
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._state = value
|
self._state = value
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""Shutdown the session."""
|
||||||
|
await self.rest.async_remove()
|
||||||
|
@ -777,6 +777,9 @@ horimote==0.4.1
|
|||||||
# homeassistant.components.remember_the_milk
|
# homeassistant.components.remember_the_milk
|
||||||
httplib2==0.10.3
|
httplib2==0.10.3
|
||||||
|
|
||||||
|
# homeassistant.components.rest
|
||||||
|
httpx==0.16.1
|
||||||
|
|
||||||
# homeassistant.components.huawei_lte
|
# homeassistant.components.huawei_lte
|
||||||
huawei-lte-api==1.4.12
|
huawei-lte-api==1.4.12
|
||||||
|
|
||||||
|
@ -24,5 +24,6 @@ pytest-xdist==2.1.0
|
|||||||
pytest==6.0.2
|
pytest==6.0.2
|
||||||
requests_mock==1.8.0
|
requests_mock==1.8.0
|
||||||
responses==0.12.0
|
responses==0.12.0
|
||||||
|
respx==0.14.0
|
||||||
stdlib-list==0.7.0
|
stdlib-list==0.7.0
|
||||||
tqdm==4.49.0
|
tqdm==4.49.0
|
||||||
|
@ -400,6 +400,9 @@ homematicip==0.11.0
|
|||||||
# homeassistant.components.remember_the_milk
|
# homeassistant.components.remember_the_milk
|
||||||
httplib2==0.10.3
|
httplib2==0.10.3
|
||||||
|
|
||||||
|
# homeassistant.components.rest
|
||||||
|
httpx==0.16.1
|
||||||
|
|
||||||
# homeassistant.components.huawei_lte
|
# homeassistant.components.huawei_lte
|
||||||
huawei-lte-api==1.4.12
|
huawei-lte-api==1.4.12
|
||||||
|
|
||||||
|
@ -1,101 +1,118 @@
|
|||||||
"""The tests for the REST binary sensor platform."""
|
"""The tests for the REST binary sensor platform."""
|
||||||
import unittest
|
|
||||||
|
|
||||||
import pytest
|
import asyncio
|
||||||
from pytest import raises
|
from os import path
|
||||||
import requests
|
|
||||||
from requests.exceptions import Timeout
|
|
||||||
import requests_mock
|
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import respx
|
||||||
|
|
||||||
|
from homeassistant import config as hass_config
|
||||||
import homeassistant.components.binary_sensor as binary_sensor
|
import homeassistant.components.binary_sensor as binary_sensor
|
||||||
import homeassistant.components.rest.binary_sensor as rest
|
from homeassistant.const import (
|
||||||
from homeassistant.const import CONTENT_TYPE_JSON, STATE_OFF, STATE_ON
|
ATTR_ENTITY_ID,
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
CONTENT_TYPE_JSON,
|
||||||
from homeassistant.helpers import template
|
SERVICE_RELOAD,
|
||||||
from homeassistant.setup import setup_component
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.async_mock import Mock, patch
|
from tests.async_mock import Mock, patch
|
||||||
from tests.common import assert_setup_component, get_test_home_assistant
|
|
||||||
|
|
||||||
|
|
||||||
class TestRestBinarySensorSetup(unittest.TestCase):
|
async def test_setup_missing_basic_config(hass):
|
||||||
"""Tests for setting up the REST binary sensor platform."""
|
|
||||||
|
|
||||||
DEVICES = []
|
|
||||||
|
|
||||||
def add_devices(self, devices, update_before_add=False):
|
|
||||||
"""Mock add devices."""
|
|
||||||
for device in devices:
|
|
||||||
self.DEVICES.append(device)
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up things to be run when tests are started."""
|
|
||||||
self.hass = get_test_home_assistant()
|
|
||||||
# Reset for this test.
|
|
||||||
self.DEVICES = []
|
|
||||||
self.addCleanup(self.hass.stop)
|
|
||||||
|
|
||||||
def test_setup_missing_config(self):
|
|
||||||
"""Test setup with configuration missing required entries."""
|
"""Test setup with configuration missing required entries."""
|
||||||
with assert_setup_component(0):
|
assert await async_setup_component(
|
||||||
assert setup_component(
|
hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "rest"}}
|
||||||
self.hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "rest"}}
|
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 0
|
||||||
|
|
||||||
def test_setup_missing_schema(self):
|
|
||||||
"""Test setup with resource missing schema."""
|
async def test_setup_missing_config(hass):
|
||||||
with pytest.raises(PlatformNotReady):
|
"""Test setup with configuration missing required entries."""
|
||||||
rest.setup_platform(
|
assert await async_setup_component(
|
||||||
self.hass,
|
hass,
|
||||||
{"platform": "rest", "resource": "localhost", "method": "GET"},
|
binary_sensor.DOMAIN,
|
||||||
None,
|
{
|
||||||
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"resource": "localhost",
|
||||||
|
"method": "GET",
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 0
|
||||||
|
|
||||||
@patch("requests.Session.send", side_effect=requests.exceptions.ConnectionError())
|
|
||||||
def test_setup_failed_connect(self, mock_req):
|
@respx.mock
|
||||||
|
async def test_setup_failed_connect(hass):
|
||||||
"""Test setup when connection error occurs."""
|
"""Test setup when connection error occurs."""
|
||||||
with raises(PlatformNotReady):
|
respx.get(
|
||||||
rest.setup_platform(
|
"http://localhost", content=httpx.RequestError(message="any", request=Mock())
|
||||||
self.hass,
|
|
||||||
{"platform": "rest", "resource": "http://localhost", "method": "GET"},
|
|
||||||
self.add_devices,
|
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
assert len(self.DEVICES) == 0
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
binary_sensor.DOMAIN,
|
||||||
|
{
|
||||||
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"resource": "http://localhost",
|
||||||
|
"method": "GET",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 0
|
||||||
|
|
||||||
@patch("requests.Session.send", side_effect=Timeout())
|
|
||||||
def test_setup_timeout(self, mock_req):
|
@respx.mock
|
||||||
|
async def test_setup_timeout(hass):
|
||||||
"""Test setup when connection timeout occurs."""
|
"""Test setup when connection timeout occurs."""
|
||||||
with raises(PlatformNotReady):
|
respx.get("http://localhost", content=asyncio.TimeoutError())
|
||||||
rest.setup_platform(
|
assert await async_setup_component(
|
||||||
self.hass,
|
hass,
|
||||||
{"platform": "rest", "resource": "http://localhost", "method": "GET"},
|
binary_sensor.DOMAIN,
|
||||||
self.add_devices,
|
{
|
||||||
None,
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"resource": "localhost",
|
||||||
|
"method": "GET",
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
assert len(self.DEVICES) == 0
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 0
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
|
||||||
def test_setup_minimum(self, mock_req):
|
@respx.mock
|
||||||
|
async def test_setup_minimum(hass):
|
||||||
"""Test setup with minimum configuration."""
|
"""Test setup with minimum configuration."""
|
||||||
mock_req.get("http://localhost", status_code=200)
|
respx.get("http://localhost", status_code=200)
|
||||||
with assert_setup_component(1, "binary_sensor"):
|
assert await async_setup_component(
|
||||||
assert setup_component(
|
hass,
|
||||||
self.hass,
|
binary_sensor.DOMAIN,
|
||||||
"binary_sensor",
|
{
|
||||||
{"binary_sensor": {"platform": "rest", "resource": "http://localhost"}},
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"resource": "http://localhost",
|
||||||
|
"method": "GET",
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.hass.block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert 1 == mock_req.call_count
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
|
||||||
def test_setup_minimum_resource_template(self, mock_req):
|
@respx.mock
|
||||||
|
async def test_setup_minimum_resource_template(hass):
|
||||||
"""Test setup with minimum configuration (resource_template)."""
|
"""Test setup with minimum configuration (resource_template)."""
|
||||||
mock_req.get("http://localhost", status_code=200)
|
respx.get("http://localhost", status_code=200)
|
||||||
with assert_setup_component(1, "binary_sensor"):
|
assert await async_setup_component(
|
||||||
assert setup_component(
|
hass,
|
||||||
self.hass,
|
binary_sensor.DOMAIN,
|
||||||
"binary_sensor",
|
|
||||||
{
|
{
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
"platform": "rest",
|
"platform": "rest",
|
||||||
@ -103,17 +120,17 @@ class TestRestBinarySensorSetup(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.hass.block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_req.call_count == 1
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
|
||||||
def test_setup_duplicate_resource(self, mock_req):
|
@respx.mock
|
||||||
|
async def test_setup_duplicate_resource_template(hass):
|
||||||
"""Test setup with duplicate resources."""
|
"""Test setup with duplicate resources."""
|
||||||
mock_req.get("http://localhost", status_code=200)
|
respx.get("http://localhost", status_code=200)
|
||||||
with assert_setup_component(0, "binary_sensor"):
|
assert await async_setup_component(
|
||||||
assert setup_component(
|
hass,
|
||||||
self.hass,
|
binary_sensor.DOMAIN,
|
||||||
"binary_sensor",
|
|
||||||
{
|
{
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
"platform": "rest",
|
"platform": "rest",
|
||||||
@ -122,15 +139,16 @@ class TestRestBinarySensorSetup(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.hass.block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 0
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
|
||||||
def test_setup_get(self, mock_req):
|
@respx.mock
|
||||||
|
async def test_setup_get(hass):
|
||||||
"""Test setup with valid configuration."""
|
"""Test setup with valid configuration."""
|
||||||
mock_req.get("http://localhost", status_code=200)
|
respx.get("http://localhost", status_code=200, content="{}")
|
||||||
with assert_setup_component(1, "binary_sensor"):
|
assert await async_setup_component(
|
||||||
assert setup_component(
|
hass,
|
||||||
self.hass,
|
|
||||||
"binary_sensor",
|
"binary_sensor",
|
||||||
{
|
{
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
@ -140,6 +158,7 @@ class TestRestBinarySensorSetup(unittest.TestCase):
|
|||||||
"value_template": "{{ value_json.key }}",
|
"value_template": "{{ value_json.key }}",
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"verify_ssl": "true",
|
"verify_ssl": "true",
|
||||||
|
"timeout": 30,
|
||||||
"authentication": "basic",
|
"authentication": "basic",
|
||||||
"username": "my username",
|
"username": "my username",
|
||||||
"password": "my password",
|
"password": "my password",
|
||||||
@ -147,16 +166,45 @@ class TestRestBinarySensorSetup(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.hass.block_till_done()
|
|
||||||
assert 1 == mock_req.call_count
|
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
await hass.async_block_till_done()
|
||||||
def test_setup_post(self, mock_req):
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_setup_get_digest_auth(hass):
|
||||||
"""Test setup with valid configuration."""
|
"""Test setup with valid configuration."""
|
||||||
mock_req.post("http://localhost", status_code=200)
|
respx.get("http://localhost", status_code=200, content="{}")
|
||||||
with assert_setup_component(1, "binary_sensor"):
|
assert await async_setup_component(
|
||||||
assert setup_component(
|
hass,
|
||||||
self.hass,
|
"binary_sensor",
|
||||||
|
{
|
||||||
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"resource": "http://localhost",
|
||||||
|
"method": "GET",
|
||||||
|
"value_template": "{{ value_json.key }}",
|
||||||
|
"name": "foo",
|
||||||
|
"verify_ssl": "true",
|
||||||
|
"timeout": 30,
|
||||||
|
"authentication": "digest",
|
||||||
|
"username": "my username",
|
||||||
|
"password": "my password",
|
||||||
|
"headers": {"Accept": CONTENT_TYPE_JSON},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_setup_post(hass):
|
||||||
|
"""Test setup with valid configuration."""
|
||||||
|
respx.post("http://localhost", status_code=200, content="{}")
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
"binary_sensor",
|
"binary_sensor",
|
||||||
{
|
{
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
@ -167,6 +215,7 @@ class TestRestBinarySensorSetup(unittest.TestCase):
|
|||||||
"payload": '{ "device": "toaster"}',
|
"payload": '{ "device": "toaster"}',
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"verify_ssl": "true",
|
"verify_ssl": "true",
|
||||||
|
"timeout": 30,
|
||||||
"authentication": "basic",
|
"authentication": "basic",
|
||||||
"username": "my username",
|
"username": "my username",
|
||||||
"password": "my password",
|
"password": "my password",
|
||||||
@ -174,94 +223,159 @@ class TestRestBinarySensorSetup(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.hass.block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert 1 == mock_req.call_count
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
|
|
||||||
class TestRestBinarySensor(unittest.TestCase):
|
@respx.mock
|
||||||
"""Tests for REST binary sensor platform."""
|
async def test_setup_get_off(hass):
|
||||||
|
"""Test setup with valid off configuration."""
|
||||||
def setUp(self):
|
respx.get(
|
||||||
"""Set up things to be run when tests are started."""
|
"http://localhost",
|
||||||
self.hass = get_test_home_assistant()
|
status_code=200,
|
||||||
self.rest = Mock("RestData")
|
headers={"content-type": "text/json"},
|
||||||
self.rest.update = Mock(
|
content='{"dog": false}',
|
||||||
"RestData.update", side_effect=self.update_side_effect('{ "key": false }')
|
|
||||||
)
|
)
|
||||||
self.name = "foo"
|
assert await async_setup_component(
|
||||||
self.device_class = "light"
|
hass,
|
||||||
self.value_template = template.Template("{{ value_json.key }}", self.hass)
|
"binary_sensor",
|
||||||
self.force_update = False
|
{
|
||||||
self.resource_template = None
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
self.binary_sensor = rest.RestBinarySensor(
|
"resource": "http://localhost",
|
||||||
self.hass,
|
"method": "GET",
|
||||||
self.rest,
|
"value_template": "{{ value_json.dog }}",
|
||||||
self.name,
|
"name": "foo",
|
||||||
self.device_class,
|
"verify_ssl": "true",
|
||||||
self.value_template,
|
"timeout": 30,
|
||||||
self.force_update,
|
}
|
||||||
self.resource_template,
|
},
|
||||||
)
|
)
|
||||||
self.addCleanup(self.hass.stop)
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
def update_side_effect(self, data):
|
state = hass.states.get("binary_sensor.foo")
|
||||||
"""Side effect function for mocking RestData.update()."""
|
assert state.state == STATE_OFF
|
||||||
self.rest.data = data
|
|
||||||
|
|
||||||
def test_name(self):
|
|
||||||
"""Test the name."""
|
|
||||||
assert self.name == self.binary_sensor.name
|
|
||||||
|
|
||||||
def test_device_class(self):
|
@respx.mock
|
||||||
"""Test the device class."""
|
async def test_setup_get_on(hass):
|
||||||
assert self.device_class == self.binary_sensor.device_class
|
"""Test setup with valid on configuration."""
|
||||||
|
respx.get(
|
||||||
def test_initial_state(self):
|
"http://localhost",
|
||||||
"""Test the initial state."""
|
status_code=200,
|
||||||
self.binary_sensor.update()
|
headers={"content-type": "text/json"},
|
||||||
assert STATE_OFF == self.binary_sensor.state
|
content='{"dog": true}',
|
||||||
|
|
||||||
def test_update_when_value_is_none(self):
|
|
||||||
"""Test state gets updated to unknown when sensor returns no data."""
|
|
||||||
self.rest.update = Mock(
|
|
||||||
"RestData.update", side_effect=self.update_side_effect(None)
|
|
||||||
)
|
)
|
||||||
self.binary_sensor.update()
|
assert await async_setup_component(
|
||||||
assert not self.binary_sensor.available
|
hass,
|
||||||
|
"binary_sensor",
|
||||||
|
{
|
||||||
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"resource": "http://localhost",
|
||||||
|
"method": "GET",
|
||||||
|
"value_template": "{{ value_json.dog }}",
|
||||||
|
"name": "foo",
|
||||||
|
"verify_ssl": "true",
|
||||||
|
"timeout": 30,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
def test_update_when_value_changed(self):
|
state = hass.states.get("binary_sensor.foo")
|
||||||
"""Test state gets updated when sensor returns a new status."""
|
assert state.state == STATE_ON
|
||||||
self.rest.update = Mock(
|
|
||||||
"rest.RestData.update",
|
|
||||||
side_effect=self.update_side_effect('{ "key": true }'),
|
|
||||||
)
|
|
||||||
self.binary_sensor.update()
|
|
||||||
assert STATE_ON == self.binary_sensor.state
|
|
||||||
assert self.binary_sensor.available
|
|
||||||
|
|
||||||
def test_update_when_failed_request(self):
|
|
||||||
"""Test state gets updated when sensor returns a new status."""
|
|
||||||
self.rest.update = Mock(
|
|
||||||
"rest.RestData.update", side_effect=self.update_side_effect(None)
|
|
||||||
)
|
|
||||||
self.binary_sensor.update()
|
|
||||||
assert not self.binary_sensor.available
|
|
||||||
|
|
||||||
def test_update_with_no_template(self):
|
@respx.mock
|
||||||
"""Test update when there is no value template."""
|
async def test_setup_with_exception(hass):
|
||||||
self.rest.update = Mock(
|
"""Test setup with exception."""
|
||||||
"rest.RestData.update", side_effect=self.update_side_effect("true")
|
respx.get("http://localhost", status_code=200, content="{}")
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"binary_sensor",
|
||||||
|
{
|
||||||
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"resource": "http://localhost",
|
||||||
|
"method": "GET",
|
||||||
|
"value_template": "{{ value_json.dog }}",
|
||||||
|
"name": "foo",
|
||||||
|
"verify_ssl": "true",
|
||||||
|
"timeout": 30,
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.binary_sensor = rest.RestBinarySensor(
|
await hass.async_block_till_done()
|
||||||
self.hass,
|
assert len(hass.states.async_all()) == 1
|
||||||
self.rest,
|
|
||||||
self.name,
|
state = hass.states.get("binary_sensor.foo")
|
||||||
self.device_class,
|
assert state.state == STATE_OFF
|
||||||
None,
|
|
||||||
self.force_update,
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
self.resource_template,
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
respx.clear()
|
||||||
|
respx.get(
|
||||||
|
"http://localhost", content=httpx.RequestError(message="any", request=Mock())
|
||||||
)
|
)
|
||||||
self.binary_sensor.update()
|
await hass.services.async_call(
|
||||||
assert STATE_ON == self.binary_sensor.state
|
"homeassistant",
|
||||||
assert self.binary_sensor.available
|
"update_entity",
|
||||||
|
{ATTR_ENTITY_ID: ["binary_sensor.foo"]},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.foo")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_reload(hass):
|
||||||
|
"""Verify we can reload reset sensors."""
|
||||||
|
|
||||||
|
respx.get("http://localhost", status_code=200)
|
||||||
|
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"binary_sensor",
|
||||||
|
{
|
||||||
|
"binary_sensor": {
|
||||||
|
"platform": "rest",
|
||||||
|
"method": "GET",
|
||||||
|
"name": "mockrest",
|
||||||
|
"resource": "http://localhost",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
|
assert hass.states.get("binary_sensor.mockrest")
|
||||||
|
|
||||||
|
yaml_path = path.join(
|
||||||
|
_get_fixtures_base_path(),
|
||||||
|
"fixtures",
|
||||||
|
"rest/configuration.yaml",
|
||||||
|
)
|
||||||
|
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
|
||||||
|
await hass.services.async_call(
|
||||||
|
"rest",
|
||||||
|
SERVICE_RELOAD,
|
||||||
|
{},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get("binary_sensor.mockreset") is None
|
||||||
|
assert hass.states.get("binary_sensor.rollout")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_fixtures_base_path():
|
||||||
|
return path.dirname(path.dirname(path.dirname(__file__)))
|
||||||
|
File diff suppressed because it is too large
Load Diff
6
tests/fixtures/rest/configuration.yaml
vendored
6
tests/fixtures/rest/configuration.yaml
vendored
@ -4,6 +4,12 @@ sensor:
|
|||||||
method: GET
|
method: GET
|
||||||
name: rollout
|
name: rollout
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: rest
|
||||||
|
resource: "http://localhost"
|
||||||
|
method: GET
|
||||||
|
name: rollout
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
- name: rest_reloaded
|
- name: rest_reloaded
|
||||||
platform: rest
|
platform: rest
|
||||||
|
Loading…
x
Reference in New Issue
Block a user