Add support for payload_template in rest component (#107464)
* Add support for payload_template in rest component * Update homeassistant/components/rest/schema.py * Update homeassistant/components/rest/data.py --------- Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
8ba1340c2e
commit
ceaf8f2402
5 changed files with 86 additions and 6 deletions
|
@ -45,6 +45,7 @@ from homeassistant.util.async_ import create_eager_task
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_ENCODING,
|
CONF_ENCODING,
|
||||||
|
CONF_PAYLOAD_TEMPLATE,
|
||||||
CONF_SSL_CIPHER_LIST,
|
CONF_SSL_CIPHER_LIST,
|
||||||
COORDINATOR,
|
COORDINATOR,
|
||||||
DEFAULT_SSL_CIPHER_LIST,
|
DEFAULT_SSL_CIPHER_LIST,
|
||||||
|
@ -108,8 +109,11 @@ async def _async_process_config(hass: HomeAssistant, config: ConfigType) -> bool
|
||||||
for rest_idx, conf in enumerate(rest_config):
|
for rest_idx, conf in enumerate(rest_config):
|
||||||
scan_interval: timedelta = conf.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
scan_interval: timedelta = conf.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||||
resource_template: template.Template | None = conf.get(CONF_RESOURCE_TEMPLATE)
|
resource_template: template.Template | None = conf.get(CONF_RESOURCE_TEMPLATE)
|
||||||
|
payload_template: template.Template | None = conf.get(CONF_PAYLOAD_TEMPLATE)
|
||||||
rest = create_rest_data_from_config(hass, conf)
|
rest = create_rest_data_from_config(hass, conf)
|
||||||
coordinator = _rest_coordinator(hass, rest, resource_template, scan_interval)
|
coordinator = _rest_coordinator(
|
||||||
|
hass, rest, resource_template, payload_template, scan_interval
|
||||||
|
)
|
||||||
refresh_coroutines.append(coordinator.async_refresh())
|
refresh_coroutines.append(coordinator.async_refresh())
|
||||||
hass.data[DOMAIN][REST_DATA].append({REST: rest, COORDINATOR: coordinator})
|
hass.data[DOMAIN][REST_DATA].append({REST: rest, COORDINATOR: coordinator})
|
||||||
|
|
||||||
|
@ -156,16 +160,20 @@ def _rest_coordinator(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
rest: RestData,
|
rest: RestData,
|
||||||
resource_template: template.Template | None,
|
resource_template: template.Template | None,
|
||||||
|
payload_template: template.Template | None,
|
||||||
update_interval: timedelta,
|
update_interval: timedelta,
|
||||||
) -> DataUpdateCoordinator[None]:
|
) -> DataUpdateCoordinator[None]:
|
||||||
"""Wrap a DataUpdateCoordinator around the rest object."""
|
"""Wrap a DataUpdateCoordinator around the rest object."""
|
||||||
if resource_template:
|
if resource_template or payload_template:
|
||||||
|
|
||||||
async def _async_refresh_with_resource_template() -> None:
|
async def _async_refresh_with_templates() -> None:
|
||||||
rest.set_url(resource_template.async_render(parse_result=False))
|
if resource_template:
|
||||||
|
rest.set_url(resource_template.async_render(parse_result=False))
|
||||||
|
if payload_template:
|
||||||
|
rest.set_payload(payload_template.async_render(parse_result=False))
|
||||||
await rest.async_update()
|
await rest.async_update()
|
||||||
|
|
||||||
update_method = _async_refresh_with_resource_template
|
update_method = _async_refresh_with_templates
|
||||||
else:
|
else:
|
||||||
update_method = rest.async_update
|
update_method = rest.async_update
|
||||||
|
|
||||||
|
@ -184,6 +192,7 @@ def create_rest_data_from_config(hass: HomeAssistant, config: ConfigType) -> Res
|
||||||
resource_template: template.Template | None = config.get(CONF_RESOURCE_TEMPLATE)
|
resource_template: template.Template | None = config.get(CONF_RESOURCE_TEMPLATE)
|
||||||
method: str = config[CONF_METHOD]
|
method: str = config[CONF_METHOD]
|
||||||
payload: str | None = config.get(CONF_PAYLOAD)
|
payload: str | None = config.get(CONF_PAYLOAD)
|
||||||
|
payload_template: template.Template | None = config.get(CONF_PAYLOAD_TEMPLATE)
|
||||||
verify_ssl: bool = config[CONF_VERIFY_SSL]
|
verify_ssl: bool = config[CONF_VERIFY_SSL]
|
||||||
ssl_cipher_list: str = config.get(CONF_SSL_CIPHER_LIST, DEFAULT_SSL_CIPHER_LIST)
|
ssl_cipher_list: str = config.get(CONF_SSL_CIPHER_LIST, DEFAULT_SSL_CIPHER_LIST)
|
||||||
username: str | None = config.get(CONF_USERNAME)
|
username: str | None = config.get(CONF_USERNAME)
|
||||||
|
@ -196,6 +205,10 @@ def create_rest_data_from_config(hass: HomeAssistant, config: ConfigType) -> Res
|
||||||
resource_template.hass = hass
|
resource_template.hass = hass
|
||||||
resource = resource_template.async_render(parse_result=False)
|
resource = resource_template.async_render(parse_result=False)
|
||||||
|
|
||||||
|
if payload_template is not None:
|
||||||
|
payload_template.hass = hass
|
||||||
|
payload = payload_template.async_render(parse_result=False)
|
||||||
|
|
||||||
if not resource:
|
if not resource:
|
||||||
raise HomeAssistantError("Resource not set for RestData")
|
raise HomeAssistantError("Resource not set for RestData")
|
||||||
|
|
||||||
|
|
|
@ -33,3 +33,5 @@ XML_MIME_TYPES = (
|
||||||
"application/xml",
|
"application/xml",
|
||||||
"text/xml",
|
"text/xml",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONF_PAYLOAD_TEMPLATE = "payload_template"
|
||||||
|
|
|
@ -56,6 +56,10 @@ class RestData:
|
||||||
self.last_exception: Exception | None = None
|
self.last_exception: Exception | None = None
|
||||||
self.headers: httpx.Headers | None = None
|
self.headers: httpx.Headers | None = None
|
||||||
|
|
||||||
|
def set_payload(self, payload: str) -> None:
|
||||||
|
"""Set request data."""
|
||||||
|
self._request_data = payload
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self) -> str:
|
def url(self) -> str:
|
||||||
"""Get url."""
|
"""Get url."""
|
||||||
|
|
|
@ -38,6 +38,7 @@ from .const import (
|
||||||
CONF_ENCODING,
|
CONF_ENCODING,
|
||||||
CONF_JSON_ATTRS,
|
CONF_JSON_ATTRS,
|
||||||
CONF_JSON_ATTRS_PATH,
|
CONF_JSON_ATTRS_PATH,
|
||||||
|
CONF_PAYLOAD_TEMPLATE,
|
||||||
CONF_SSL_CIPHER_LIST,
|
CONF_SSL_CIPHER_LIST,
|
||||||
DEFAULT_ENCODING,
|
DEFAULT_ENCODING,
|
||||||
DEFAULT_FORCE_UPDATE,
|
DEFAULT_FORCE_UPDATE,
|
||||||
|
@ -60,7 +61,8 @@ RESOURCE_SCHEMA = {
|
||||||
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(METHODS),
|
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(METHODS),
|
||||||
vol.Optional(CONF_USERNAME): cv.string,
|
vol.Optional(CONF_USERNAME): cv.string,
|
||||||
vol.Optional(CONF_PASSWORD): cv.string,
|
vol.Optional(CONF_PASSWORD): cv.string,
|
||||||
vol.Optional(CONF_PAYLOAD): cv.string,
|
vol.Exclusive(CONF_PAYLOAD, CONF_PAYLOAD): cv.string,
|
||||||
|
vol.Exclusive(CONF_PAYLOAD_TEMPLATE, CONF_PAYLOAD): cv.template,
|
||||||
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_SSL_CIPHER_LIST,
|
CONF_SSL_CIPHER_LIST,
|
||||||
|
|
|
@ -475,3 +475,62 @@ async def test_config_schema_via_packages(hass: HomeAssistant) -> None:
|
||||||
assert len(config["rest"]) == 2
|
assert len(config["rest"]) == 2
|
||||||
assert config["rest"][0]["resource"] == "http://url1"
|
assert config["rest"][0]["resource"] == "http://url1"
|
||||||
assert config["rest"][1]["resource"] == "http://url2"
|
assert config["rest"][1]["resource"] == "http://url2"
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_setup_minimum_payload_template(hass: HomeAssistant) -> None:
|
||||||
|
"""Test setup with minimum configuration (payload_template)."""
|
||||||
|
|
||||||
|
respx.post("http://localhost", json={"data": "value"}).respond(
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
json={
|
||||||
|
"sensor1": "1",
|
||||||
|
"sensor2": "2",
|
||||||
|
"binary_sensor1": "on",
|
||||||
|
"binary_sensor2": "off",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: [
|
||||||
|
{
|
||||||
|
"resource": "http://localhost",
|
||||||
|
"payload_template": '{% set payload = {"data": "value"} %}{{ payload | to_json }}',
|
||||||
|
"method": "POST",
|
||||||
|
"verify_ssl": "false",
|
||||||
|
"timeout": 30,
|
||||||
|
"sensor": [
|
||||||
|
{
|
||||||
|
"unit_of_measurement": UnitOfInformation.MEGABYTES,
|
||||||
|
"name": "sensor1",
|
||||||
|
"value_template": "{{ value_json.sensor1 }}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unit_of_measurement": UnitOfInformation.MEGABYTES,
|
||||||
|
"name": "sensor2",
|
||||||
|
"value_template": "{{ value_json.sensor2 }}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"binary_sensor": [
|
||||||
|
{
|
||||||
|
"name": "binary_sensor1",
|
||||||
|
"value_template": "{{ value_json.binary_sensor1 }}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "binary_sensor2",
|
||||||
|
"value_template": "{{ value_json.binary_sensor2 }}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 4
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.sensor1").state == "1"
|
||||||
|
assert hass.states.get("sensor.sensor2").state == "2"
|
||||||
|
assert hass.states.get("binary_sensor.binary_sensor1").state == "on"
|
||||||
|
assert hass.states.get("binary_sensor.binary_sensor2").state == "off"
|
||||||
|
|
Loading…
Add table
Reference in a new issue