python-kasa/kasa/httpclient.py
J. Nick Koston d62b5a55cc
Improve and document close behavior (#654)
* Close connection on smartprotocol timeout

* tweaks
2024-01-19 21:30:01 +01:00

99 lines
3.4 KiB
Python

"""Module for HttpClientSession class."""
import asyncio
from typing import Any, Dict, Optional, Tuple, Union
import aiohttp
from .deviceconfig import DeviceConfig
from .exceptions import ConnectionException, SmartDeviceException, TimeoutException
from .json import loads as json_loads
def get_cookie_jar() -> aiohttp.CookieJar:
"""Return a new cookie jar with the correct options for device communication."""
return aiohttp.CookieJar(unsafe=True, quote_cookie=False)
class HttpClient:
"""HttpClient Class."""
def __init__(self, config: DeviceConfig) -> None:
self._config = config
self._client_session: aiohttp.ClientSession = None
self._jar = aiohttp.CookieJar(unsafe=True, quote_cookie=False)
self._last_url = f"http://{self._config.host}/"
@property
def client(self) -> aiohttp.ClientSession:
"""Return the underlying http client."""
if self._config.http_client and issubclass(
self._config.http_client.__class__, aiohttp.ClientSession
):
return self._config.http_client
if not self._client_session:
self._client_session = aiohttp.ClientSession(cookie_jar=get_cookie_jar())
return self._client_session
async def post(
self,
url: str,
*,
params: Optional[Dict[str, Any]] = None,
data: Optional[bytes] = None,
json: Optional[Dict] = None,
headers: Optional[Dict[str, str]] = None,
cookies_dict: Optional[Dict[str, str]] = None,
) -> Tuple[int, Optional[Union[Dict, bytes]]]:
"""Send an http post request to the device."""
response_data = None
self._last_url = url
self.client.cookie_jar.clear()
try:
resp = await self.client.post(
url,
params=params,
data=data,
json=json,
timeout=self._config.timeout,
cookies=cookies_dict,
headers=headers,
)
async with resp:
if resp.status == 200:
response_data = await resp.read()
if json:
response_data = json_loads(response_data.decode())
except (aiohttp.ServerDisconnectedError, aiohttp.ClientOSError) as ex:
raise ConnectionException(
f"Unable to connect to the device: {self._config.host}: {ex}", ex
) from ex
except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as ex:
raise TimeoutException(
"Unable to query the device, "
+ f"timed out: {self._config.host}: {ex}",
ex,
) from ex
except Exception as ex:
raise SmartDeviceException(
f"Unable to query the device: {self._config.host}: {ex}", ex
) from ex
return resp.status, response_data
def get_cookie(self, cookie_name: str) -> Optional[str]:
"""Return the cookie with cookie_name."""
if cookie := self.client.cookie_jar.filter_cookies(self._last_url).get(
cookie_name
):
return cookie.value
return None
async def close(self) -> None:
"""Close the ClientSession."""
client = self._client_session
self._client_session = None
if client:
await client.close()