Home Assistant Unofficial Reference 2024.12.1
notify.py
Go to the documentation of this file.
1 """RESTful platform for notify component."""
2 
3 from __future__ import annotations
4 
5 from http import HTTPStatus
6 import logging
7 from typing import Any
8 
9 import httpx
10 import voluptuous as vol
11 
13  ATTR_MESSAGE,
14  ATTR_TARGET,
15  ATTR_TITLE,
16  ATTR_TITLE_DEFAULT,
17  PLATFORM_SCHEMA as NOTIFY_PLATFORM_SCHEMA,
18  BaseNotificationService,
19 )
20 from homeassistant.const import (
21  CONF_AUTHENTICATION,
22  CONF_HEADERS,
23  CONF_METHOD,
24  CONF_NAME,
25  CONF_PARAMS,
26  CONF_PASSWORD,
27  CONF_RESOURCE,
28  CONF_USERNAME,
29  CONF_VERIFY_SSL,
30  HTTP_BASIC_AUTHENTICATION,
31  HTTP_DIGEST_AUTHENTICATION,
32 )
33 from homeassistant.core import HomeAssistant
35 from homeassistant.helpers.httpx_client import get_async_client
36 from homeassistant.helpers.template import Template
37 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
38 
39 CONF_DATA = "data"
40 CONF_DATA_TEMPLATE = "data_template"
41 CONF_MESSAGE_PARAMETER_NAME = "message_param_name"
42 CONF_TARGET_PARAMETER_NAME = "target_param_name"
43 CONF_TITLE_PARAMETER_NAME = "title_param_name"
44 DEFAULT_MESSAGE_PARAM_NAME = "message"
45 DEFAULT_METHOD = "GET"
46 DEFAULT_VERIFY_SSL = True
47 
48 PLATFORM_SCHEMA = NOTIFY_PLATFORM_SCHEMA.extend(
49  {
50  vol.Required(CONF_RESOURCE): cv.url,
51  vol.Optional(
52  CONF_MESSAGE_PARAMETER_NAME, default=DEFAULT_MESSAGE_PARAM_NAME
53  ): cv.string,
54  vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(
55  ["POST", "GET", "POST_JSON"]
56  ),
57  vol.Optional(CONF_HEADERS): vol.Schema({cv.string: cv.string}),
58  vol.Optional(CONF_PARAMS): vol.Schema({cv.string: cv.string}),
59  vol.Optional(CONF_NAME): cv.string,
60  vol.Optional(CONF_TARGET_PARAMETER_NAME): cv.string,
61  vol.Optional(CONF_TITLE_PARAMETER_NAME): cv.string,
62  vol.Optional(CONF_DATA): vol.All(dict, cv.template_complex),
63  vol.Optional(CONF_DATA_TEMPLATE): vol.All(dict, cv.template_complex),
64  vol.Optional(CONF_AUTHENTICATION): vol.In(
65  [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]
66  ),
67  vol.Optional(CONF_PASSWORD): cv.string,
68  vol.Optional(CONF_USERNAME): cv.string,
69  vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
70  }
71 )
72 
73 _LOGGER = logging.getLogger(__name__)
74 
75 
77  hass: HomeAssistant,
78  config: ConfigType,
79  discovery_info: DiscoveryInfoType | None = None,
80 ) -> RestNotificationService:
81  """Get the RESTful notification service."""
82  resource: str = config[CONF_RESOURCE]
83  method: str = config[CONF_METHOD]
84  headers: dict[str, str] | None = config.get(CONF_HEADERS)
85  params: dict[str, str] | None = config.get(CONF_PARAMS)
86  message_param_name: str = config[CONF_MESSAGE_PARAMETER_NAME]
87  title_param_name: str | None = config.get(CONF_TITLE_PARAMETER_NAME)
88  target_param_name: str | None = config.get(CONF_TARGET_PARAMETER_NAME)
89  data: dict[str, Any] | None = config.get(CONF_DATA)
90  data_template: dict[str, Any] | None = config.get(CONF_DATA_TEMPLATE)
91  username: str | None = config.get(CONF_USERNAME)
92  password: str | None = config.get(CONF_PASSWORD)
93  verify_ssl: bool = config[CONF_VERIFY_SSL]
94 
95  auth: httpx.Auth | None = None
96  if username and password:
97  if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
98  auth = httpx.DigestAuth(username, password)
99  else:
100  auth = httpx.BasicAuth(username, password)
101 
103  hass,
104  resource,
105  method,
106  headers,
107  params,
108  message_param_name,
109  title_param_name,
110  target_param_name,
111  data,
112  data_template,
113  auth,
114  verify_ssl,
115  )
116 
117 
118 class RestNotificationService(BaseNotificationService):
119  """Implementation of a notification service for REST."""
120 
121  def __init__(
122  self,
123  hass: HomeAssistant,
124  resource: str,
125  method: str,
126  headers: dict[str, str] | None,
127  params: dict[str, str] | None,
128  message_param_name: str,
129  title_param_name: str | None,
130  target_param_name: str | None,
131  data: dict[str, Any] | None,
132  data_template: dict[str, Any] | None,
133  auth: httpx.Auth | None,
134  verify_ssl: bool,
135  ) -> None:
136  """Initialize the service."""
137  self._resource_resource = resource
138  self._hass_hass = hass
139  self._method_method = method.upper()
140  self._headers_headers = headers
141  self._params_params = params
142  self._message_param_name_message_param_name = message_param_name
143  self._title_param_name_title_param_name = title_param_name
144  self._target_param_name_target_param_name = target_param_name
145  self._data_data = data
146  self._data_template_data_template = data_template
147  self._auth_auth = auth
148  self._verify_ssl_verify_ssl = verify_ssl
149 
150  async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
151  """Send a message to a user."""
152  data = {self._message_param_name_message_param_name: message}
153 
154  if self._title_param_name_title_param_name is not None:
155  data[self._title_param_name_title_param_name] = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
156 
157  if self._target_param_name_target_param_name is not None and ATTR_TARGET in kwargs:
158  # Target is a list as of 0.29 and we don't want to break existing
159  # integrations, so just return the first target in the list.
160  data[self._target_param_name_target_param_name] = kwargs[ATTR_TARGET][0]
161 
162  if self._data_template_data_template or self._data_data:
163  kwargs[ATTR_MESSAGE] = message
164 
165  def _data_template_creator(value: Any) -> Any:
166  """Recursive template creator helper function."""
167  if isinstance(value, list):
168  return [_data_template_creator(item) for item in value]
169  if isinstance(value, dict):
170  return {
171  key: _data_template_creator(item) for key, item in value.items()
172  }
173  if not isinstance(value, Template):
174  return value
175  return value.async_render(kwargs, parse_result=False)
176 
177  if self._data_data:
178  data.update(_data_template_creator(self._data_data))
179  if self._data_template_data_template:
180  data.update(_data_template_creator(self._data_template_data_template))
181 
182  websession = get_async_client(self._hass_hass, self._verify_ssl_verify_ssl)
183  if self._method_method == "POST":
184  response = await websession.post(
185  self._resource_resource,
186  headers=self._headers_headers,
187  params=self._params_params,
188  data=data,
189  timeout=10,
190  auth=self._auth_auth or httpx.USE_CLIENT_DEFAULT,
191  )
192  elif self._method_method == "POST_JSON":
193  response = await websession.post(
194  self._resource_resource,
195  headers=self._headers_headers,
196  params=self._params_params,
197  json=data,
198  timeout=10,
199  auth=self._auth_auth or httpx.USE_CLIENT_DEFAULT,
200  )
201  else: # default GET
202  response = await websession.get(
203  self._resource_resource,
204  headers=self._headers_headers,
205  params={**self._params_params, **data} if self._params_params else data,
206  timeout=10,
207  auth=self._auth_auth,
208  )
209 
210  if (
211  response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR
212  and response.status_code < 600
213  ):
214  _LOGGER.exception(
215  "Server error. Response %d: %s:",
216  response.status_code,
217  response.reason_phrase,
218  )
219  elif (
220  response.status_code >= HTTPStatus.BAD_REQUEST
221  and response.status_code < HTTPStatus.INTERNAL_SERVER_ERROR
222  ):
223  _LOGGER.exception(
224  "Client error. Response %d: %s:",
225  response.status_code,
226  response.reason_phrase,
227  )
228  elif (
229  response.status_code >= HTTPStatus.OK
230  and response.status_code < HTTPStatus.MULTIPLE_CHOICES
231  ):
232  _LOGGER.debug(
233  "Success. Response %d: %s:",
234  response.status_code,
235  response.reason_phrase,
236  )
237  else:
238  _LOGGER.debug(
239  "Response %d: %s:", response.status_code, response.reason_phrase
240  )
None async_send_message(self, str message="", **Any kwargs)
Definition: notify.py:150
None __init__(self, HomeAssistant hass, str resource, str method, dict[str, str]|None headers, dict[str, str]|None params, str message_param_name, str|None title_param_name, str|None target_param_name, dict[str, Any]|None data, dict[str, Any]|None data_template, httpx.Auth|None auth, bool verify_ssl)
Definition: notify.py:135
RestNotificationService async_get_service(HomeAssistant hass, ConfigType config, DiscoveryInfoType|None discovery_info=None)
Definition: notify.py:80
httpx.AsyncClient get_async_client(HomeAssistant hass, bool verify_ssl=True)
Definition: httpx_client.py:41