aioamazondevices 1.8.0__py3-none-any.whl → 1.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  """aioamazondevices library."""
2
2
 
3
- __version__ = "1.8.0"
3
+ __version__ = "1.9.0"
4
4
 
5
5
 
6
6
  from .api import AmazonDevice, AmazonEchoApi
aioamazondevices/api.py CHANGED
@@ -44,6 +44,10 @@ from .const import (
44
44
  URI_QUERIES,
45
45
  )
46
46
  from .exceptions import CannotAuthenticate, CannotRegisterDevice, WrongMethod
47
+ from .httpx import HttpxClientResponseWrapper, HttpxClientSession
48
+
49
+ # Values: "aiohttp", or "httpx"
50
+ LIBRARY = "httpx"
47
51
 
48
52
 
49
53
  @dataclass
@@ -236,11 +240,18 @@ class AmazonEchoApi:
236
240
  def _client_session(self) -> None:
237
241
  """Create HTTP client session."""
238
242
  if not hasattr(self, "session") or self.session.closed:
239
- _LOGGER.debug("Creating HTTP session (aiohttp)")
240
- self.session = ClientSession(
241
- headers=DEFAULT_HEADERS,
242
- cookies=self._cookies,
243
- )
243
+ _LOGGER.debug("Creating HTTP session (%s)", LIBRARY)
244
+ if LIBRARY == "httpx":
245
+ self.session = HttpxClientSession(
246
+ headers=DEFAULT_HEADERS,
247
+ cookies=self._cookies,
248
+ follow_redirects=True,
249
+ )
250
+ else:
251
+ self.session = ClientSession(
252
+ headers=DEFAULT_HEADERS,
253
+ cookies=self._cookies,
254
+ )
244
255
 
245
256
  async def _session_request(
246
257
  self,
@@ -248,7 +259,7 @@ class AmazonEchoApi:
248
259
  url: str,
249
260
  input_data: dict[str, Any] | None = None,
250
261
  json_data: bool = False,
251
- ) -> tuple[BeautifulSoup, ClientResponse]:
262
+ ) -> tuple[BeautifulSoup, ClientResponse | HttpxClientResponseWrapper]:
252
263
  """Return request response context data."""
253
264
  _LOGGER.debug(
254
265
  "%s request: %s with payload %s [json=%s]",
@@ -519,7 +530,7 @@ class AmazonEchoApi:
519
530
  async def close(self) -> None:
520
531
  """Close http client session."""
521
532
  if hasattr(self, "session"):
522
- _LOGGER.debug("Closing HTTP session (aiohttp)")
533
+ _LOGGER.debug("Closing HTTP session (%s)", LIBRARY)
523
534
  await self.session.close()
524
535
 
525
536
  async def get_devices_data(
@@ -0,0 +1,159 @@
1
+ """Wrapper around httpx.Response to mimic aiohttp.ClientResponse."""
2
+
3
+ import types
4
+ from http.cookies import SimpleCookie
5
+ from typing import Any, Self, cast
6
+
7
+ from httpx import AsyncClient, Cookies, Response
8
+ from httpx._types import RequestData
9
+
10
+
11
+ def convert_httpx_cookies_to_simplecookie(httpx_cookies: Cookies) -> SimpleCookie:
12
+ """Convert an httpx.Cookies object to a single SimpleCookie."""
13
+ simple_cookie = SimpleCookie()
14
+
15
+ for name, value in httpx_cookies.items():
16
+ simple_cookie[name] = value
17
+
18
+ return simple_cookie
19
+
20
+
21
+ class HttpxClientResponseWrapper:
22
+ """aiohttp-like Wrapper for httpx.Response."""
23
+
24
+ def __init__(self, response: Response) -> None:
25
+ """Init wrapper."""
26
+ self._response = response
27
+
28
+ # Basic aiohttp-like attributes
29
+ self.status = response.status_code
30
+ self.headers = response.headers
31
+ self.url = response.url
32
+ self.reason = response.reason_phrase
33
+ self.cookies = convert_httpx_cookies_to_simplecookie(response.cookies)
34
+ self.ok = response.is_success
35
+
36
+ # Aiohttp compatibility
37
+ self.content_type = response.headers.get("Content-Type", "")
38
+ self.real_url = str(response.url)
39
+ self.request_info = types.SimpleNamespace(
40
+ real_url=self.url,
41
+ method=response.request.method,
42
+ headers=response.request.headers,
43
+ )
44
+
45
+ # History (aiohttp returns redirects as history)
46
+ self.history = (
47
+ [HttpxClientResponseWrapper(r) for r in response.history]
48
+ if response.history
49
+ else []
50
+ )
51
+
52
+ async def text(self, encoding: str | None = None) -> str:
53
+ """Text."""
54
+ # httpx auto-decodes
55
+ if encoding:
56
+ return cast("str", self._response.content.decode(encoding))
57
+ return cast("str", self._response.text)
58
+
59
+ async def json(self) -> Any: # noqa: ANN401
60
+ """Json."""
61
+ return self._response.json()
62
+
63
+ async def read(self) -> bytes:
64
+ """Read."""
65
+ return cast("bytes", self._response.content)
66
+
67
+ async def release(self) -> None:
68
+ """Release."""
69
+ # No-op: aiohttp requires this, httpx does not
70
+
71
+ def raise_for_status(self) -> Response:
72
+ """Raise for status."""
73
+ return self._response.raise_for_status()
74
+
75
+ def __repr__(self) -> str:
76
+ """Repr."""
77
+ return f"<HttpxClientResponseWrapper [{self.status} {self.reason}]>"
78
+
79
+ async def __aenter__(self) -> Self:
80
+ """Aenter."""
81
+ return self
82
+
83
+ async def __aexit__(
84
+ self,
85
+ exc_type: type[BaseException] | None,
86
+ exc_val: BaseException | None,
87
+ exc_tb: types.TracebackType | None,
88
+ ) -> None:
89
+ """Aexit."""
90
+ await self.release()
91
+
92
+
93
+ class HttpxClientSession:
94
+ """aiohttp-like Wrapper for httpx.AsyncClient."""
95
+
96
+ def __init__(self, **kwargs: Any) -> None: # noqa: ANN401
97
+ """Init session."""
98
+ # Allow passing any httpx.AsyncClient init kwargs (e.g., headers, cookies)
99
+ self._client = AsyncClient(**kwargs)
100
+
101
+ @property
102
+ def closed(self) -> bool:
103
+ """Indicates whether the underlying client session is closed."""
104
+ return cast("bool", self._client.is_closed)
105
+
106
+ async def request(
107
+ self,
108
+ method: str,
109
+ url: str,
110
+ *,
111
+ params: dict[str, str | int | float] | None = None,
112
+ data: Any = None, # noqa: ANN401
113
+ json: Any = None, # noqa: ANN401
114
+ **kwargs: Any, # noqa: ANN401
115
+ ) -> HttpxClientResponseWrapper:
116
+ """Make a generic request method for any HTTP verb."""
117
+ response = await self._client.request(
118
+ method=method.upper(),
119
+ url=url,
120
+ params=params,
121
+ data=data,
122
+ json=json,
123
+ **kwargs,
124
+ )
125
+ return HttpxClientResponseWrapper(response)
126
+
127
+ async def get(self, url: str, **kwargs: Any) -> HttpxClientResponseWrapper: # noqa: ANN401
128
+ """Get."""
129
+ response = await self._client.get(url, **kwargs)
130
+ return HttpxClientResponseWrapper(response)
131
+
132
+ async def post(
133
+ self,
134
+ url: str,
135
+ data: RequestData | None = None,
136
+ json: Any = None, # noqa: ANN401
137
+ **kwargs: Any, # noqa: ANN401
138
+ ) -> HttpxClientResponseWrapper:
139
+ """Post."""
140
+ response = await self._client.post(url, data=data, json=json, **kwargs)
141
+ return HttpxClientResponseWrapper(response)
142
+
143
+ async def close(self) -> None:
144
+ """Close."""
145
+ await self._client.aclose()
146
+
147
+ async def __aenter__(self) -> Self:
148
+ """AEnter."""
149
+ await self._client.__aenter__()
150
+ return self
151
+
152
+ async def __aexit__(
153
+ self,
154
+ exc_type: type[BaseException] | None,
155
+ exc_val: BaseException | None,
156
+ exc_tb: types.TracebackType | None,
157
+ ) -> None:
158
+ """AExit."""
159
+ await self._client.__aexit__(exc_type, exc_val, exc_tb)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: aioamazondevices
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary: Python library to control Amazon devices
5
5
  License: Apache-2.0
6
6
  Author: Simone Chemelli
@@ -19,6 +19,7 @@ Requires-Dist: aiohttp
19
19
  Requires-Dist: babel
20
20
  Requires-Dist: beautifulsoup4
21
21
  Requires-Dist: colorlog
22
+ Requires-Dist: httpx
22
23
  Requires-Dist: orjson
23
24
  Requires-Dist: yarl
24
25
  Project-URL: Bug Tracker, https://github.com/chemelli74/aioamazondevices/issues
@@ -0,0 +1,10 @@
1
+ aioamazondevices/__init__.py,sha256=j0fW4NsfctzqB0gzPXx8Og0Owh-PWVIiN4wAl4J3z9M,276
2
+ aioamazondevices/api.py,sha256=Wqq80_IJwVT9eJlo1TdSv14p8sWDe19jg96zDITE4r8,28363
3
+ aioamazondevices/const.py,sha256=6BBEg_q2BkYVYJcz3hMrLNyEwOJBWziPSStMzftWQLg,2106
4
+ aioamazondevices/exceptions.py,sha256=qK_Hak9pc-lC2FPW-0i4rYIwNpEOHMmA9Rii8F2lkQo,1260
5
+ aioamazondevices/httpx.py,sha256=DyuD2HD3GGGbBq65qcjPCCxeuSkALKzDAdrUSeZcRMM,4935
6
+ aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ aioamazondevices-1.9.0.dist-info/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
8
+ aioamazondevices-1.9.0.dist-info/METADATA,sha256=81EZrAn7bG1pdd7YLwXrd8knBu52DMhsVqVoV14uN8A,5031
9
+ aioamazondevices-1.9.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
10
+ aioamazondevices-1.9.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- aioamazondevices/__init__.py,sha256=YjVGmADfaQ0huPg-MEYlwK_KbqYqoZCjordvIITyAk0,276
2
- aioamazondevices/api.py,sha256=SfwPrEl3s4aJjb-fpiKVJ75TfKJzdBwC5m0W9NshZEI,27940
3
- aioamazondevices/const.py,sha256=6BBEg_q2BkYVYJcz3hMrLNyEwOJBWziPSStMzftWQLg,2106
4
- aioamazondevices/exceptions.py,sha256=qK_Hak9pc-lC2FPW-0i4rYIwNpEOHMmA9Rii8F2lkQo,1260
5
- aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- aioamazondevices-1.8.0.dist-info/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
7
- aioamazondevices-1.8.0.dist-info/METADATA,sha256=lKjLf3eyeTqzli2R5BYEM2RssjQYLbRP6nz3EtTywwE,5010
8
- aioamazondevices-1.8.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
9
- aioamazondevices-1.8.0.dist-info/RECORD,,