"""Support for RESTful API.""" from __future__ import annotations import logging from typing import Any import aiohttp from multidict import CIMultiDictProxy import xmltodict from homeassistant.core import HomeAssistant from homeassistant.helpers import template from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.json import json_dumps from homeassistant.util.ssl import SSLCipherList from .const import XML_MIME_TYPES DEFAULT_TIMEOUT = 10 _LOGGER = logging.getLogger(__name__) class RestData: """Class for handling the data retrieval.""" def __init__( self, hass: HomeAssistant, method: str, resource: str, encoding: str, auth: aiohttp.DigestAuthMiddleware | aiohttp.BasicAuth | tuple[str, str] | None, headers: dict[str, str] | None, params: dict[str, str] | None, data: str | None, verify_ssl: bool, ssl_cipher_list: str, timeout: int = DEFAULT_TIMEOUT, ) -> None: """Initialize the data object.""" self._hass = hass self._method = method self._resource = resource self._encoding = encoding # Convert auth tuple to aiohttp.BasicAuth if needed if isinstance(auth, tuple) and len(auth) == 2: self._auth: aiohttp.BasicAuth | aiohttp.DigestAuthMiddleware | None = ( aiohttp.BasicAuth(auth[0], auth[1]) ) else: self._auth = auth self._headers = headers self._params = params self._request_data = data self._timeout = aiohttp.ClientTimeout(total=timeout) self._verify_ssl = verify_ssl self._ssl_cipher_list = SSLCipherList(ssl_cipher_list) self._session: aiohttp.ClientSession | None = None self.data: str | None = None self.last_exception: Exception | None = None self.headers: CIMultiDictProxy[str] | None = None def set_payload(self, payload: str) -> None: """Set request data.""" self._request_data = payload @property def url(self) -> str: """Get url.""" return self._resource def set_url(self, url: str) -> None: """Set url.""" self._resource = url def data_without_xml(self) -> str | None: """If the data is an XML string, convert it to a JSON string.""" _LOGGER.debug("Data fetched from resource: %s", self.data) if ( (value := self.data) is not None # If the http request failed, headers will be None and (headers := self.headers) is not None and (content_type := headers.get("content-type")) and content_type.startswith(XML_MIME_TYPES) ): value = json_dumps(xmltodict.parse(value)) _LOGGER.debug("JSON converted from XML: %s", value) return value async def async_update(self, log_errors: bool = True) -> None: """Get the latest data from REST service with provided method.""" if not self._session: self._session = async_create_clientsession( self._hass, verify_ssl=self._verify_ssl, ssl_cipher=self._ssl_cipher_list, ) rendered_headers = template.render_complex(self._headers, parse_result=False) rendered_params = template.render_complex(self._params) _LOGGER.debug("Updating from %s", self._resource) # Create request kwargs request_kwargs: dict[str, Any] = { "headers": rendered_headers, "params": rendered_params, "timeout": self._timeout, } # Handle authentication if isinstance(self._auth, aiohttp.BasicAuth): request_kwargs["auth"] = self._auth elif isinstance(self._auth, aiohttp.DigestAuthMiddleware): request_kwargs["middlewares"] = (self._auth,) # Handle data/content if self._request_data: request_kwargs["data"] = self._request_data try: # Make the request async with self._session.request( self._method, self._resource, **request_kwargs ) as response: # Read the response self.data = await response.text(encoding=self._encoding) self.headers = response.headers except TimeoutError as ex: if log_errors: _LOGGER.error("Timeout while fetching data: %s", self._resource) self.last_exception = ex self.data = None self.headers = None except aiohttp.ClientError as ex: if log_errors: _LOGGER.error( "Error fetching data: %s failed with %s", self._resource, ex ) self.last_exception = ex self.data = None self.headers = None