AlexaPy 1.29.24__tar.gz → 1.29.25__tar.gz

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
  Metadata-Version: 2.4
2
2
  Name: AlexaPy
3
- Version: 1.29.24
3
+ Version: 1.29.25
4
4
  Summary: Python API to control Amazon Echo Devices Programmatically.
5
5
  License: Apache-2.0
6
6
  License-File: AUTHORS.md
@@ -1475,9 +1475,23 @@ class AlexaAPI:
1475
1475
  "get", login, "/api/devices-v2/device", query=None
1476
1476
  )
1477
1477
  devices, *_ = await get_json_value(response, "devices", list)
1478
- AlexaAPI.devices[login.email] = (
1479
- devices if devices else AlexaAPI.devices[login.email]
1478
+
1479
+ if devices is not None:
1480
+ AlexaAPI.devices[login.email] = devices
1481
+ return devices
1482
+
1483
+ if login.email in AlexaAPI.devices:
1484
+ _LOGGER.debug(
1485
+ "%s: Using cached device list because device API returned no data",
1486
+ hide_email(login.email),
1487
+ )
1488
+ return AlexaAPI.devices[login.email]
1489
+
1490
+ _LOGGER.warning(
1491
+ "%s: Device API returned no data and no cached device list is available",
1492
+ hide_email(login.email),
1480
1493
  )
1494
+ AlexaAPI.devices[login.email] = []
1481
1495
  return AlexaAPI.devices[login.email]
1482
1496
 
1483
1497
  @staticmethod
@@ -63,6 +63,17 @@ from .helpers import (
63
63
  _LOGGER = logging.getLogger(__name__)
64
64
 
65
65
 
66
+ # Ensure http.cookies.Morsel recognizes the Partitioned cookie attribute.
67
+ # Older Python releases do not include the "partitioned" attribute.
68
+ # Newer releases (Python 3.14+) already support it, so this patch is only
69
+ # applied when necessary.
70
+ if "partitioned" not in Morsel._reserved:
71
+ _LOGGER.debug("Adding partitioned support to http.cookies.Morsel")
72
+ Morsel._reserved["partitioned"] = "Partitioned"
73
+
74
+ Morsel._flags.add("partitioned")
75
+
76
+
66
77
  def create_alexa_context() -> ssl.SSLContext:
67
78
  """Create an SSL context for Alexa."""
68
79
  context = ssl.create_default_context(
@@ -203,9 +214,12 @@ def _serialize_cookie_jar(cookie_jar: aiohttp.CookieJar) -> dict[str, Any]:
203
214
 
204
215
 
205
216
  def _deserialize_cookie(
206
- cookie_jar: aiohttp.CookieJar, cookie: dict[str, Any]
217
+ cookie_jar: aiohttp.CookieJar,
218
+ cookie: dict[str, Any],
219
+ *,
220
+ fallback_domain: str = "",
207
221
  ) -> tuple[str, str] | None:
208
- """Restore one cookie entry to an aiohttp CookieJar."""
222
+ """Restore a serialized cookie into an aiohttp CookieJar."""
209
223
  name = str(cookie.get("name") or "")
210
224
  value = cookie.get("value")
211
225
  domain = str(cookie.get("domain") or "")
@@ -221,12 +235,15 @@ def _deserialize_cookie(
221
235
  if not name or value is None:
222
236
  return None
223
237
 
238
+ if partitioned:
239
+ secure = True
240
+
224
241
  cookie_value = _strip_wrapping_quotes(str(value))
225
242
  raw_cookie = SimpleCookie()
226
243
  raw_cookie[name] = cookie_value
227
244
  morsel = raw_cookie[name]
228
245
 
229
- clean_domain = domain.lstrip(".")
246
+ clean_domain = domain.lstrip(".") or fallback_domain.lstrip(".")
230
247
  clean_path = path
231
248
  if not clean_path.startswith("/"):
232
249
  clean_path = f"/{clean_path}"
@@ -255,7 +272,6 @@ def _deserialize_cookie(
255
272
  with contextlib.suppress(KeyError):
256
273
  morsel["samesite"] = samesite
257
274
  if partitioned:
258
- # Only Python/aiohttp combinations that know the attr should keep it.
259
275
  with contextlib.suppress(KeyError):
260
276
  morsel["partitioned"] = True
261
277
 
@@ -264,7 +280,10 @@ def _deserialize_cookie(
264
280
 
265
281
 
266
282
  def _deserialize_cookie_jar(
267
- cookie_jar: aiohttp.CookieJar, serialized: dict[str, Any]
283
+ cookie_jar: aiohttp.CookieJar,
284
+ serialized: dict[str, Any],
285
+ *,
286
+ fallback_domain: str = "",
268
287
  ) -> dict[str, str]:
269
288
  """Restore a serialized alexapy cookie jar into an aiohttp CookieJar."""
270
289
  return_cookies: dict[str, str] = {}
@@ -277,7 +296,11 @@ def _deserialize_cookie_jar(
277
296
  for cookie in serialized.get("cookies", []):
278
297
  if not isinstance(cookie, dict):
279
298
  continue
280
- restored = _deserialize_cookie(cookie_jar, cookie)
299
+ restored = _deserialize_cookie(
300
+ cookie_jar,
301
+ cookie,
302
+ fallback_domain=fallback_domain,
303
+ )
281
304
  if restored:
282
305
  return_cookies[restored[0]] = restored[1]
283
306
 
@@ -294,7 +317,10 @@ def _legacy_cookie_value(value: Any) -> Any:
294
317
 
295
318
 
296
319
  def _restore_legacy_aiohttp_cookie_mapping(
297
- cookie_jar: aiohttp.CookieJar, cookies: dict[Any, Any]
320
+ cookie_jar: aiohttp.CookieJar,
321
+ cookies: dict[Any, Any],
322
+ *,
323
+ fallback_domain: str = "",
298
324
  ) -> dict[str, str]:
299
325
  """Restore old aiohttp.CookieJar.save() mappings.
300
326
 
@@ -304,7 +330,7 @@ def _restore_legacy_aiohttp_cookie_mapping(
304
330
  restored_count = 0
305
331
 
306
332
  for bucket_key, bucket in cookies.items():
307
- domain = ""
333
+ domain = fallback_domain
308
334
  path = "/"
309
335
  if isinstance(bucket_key, tuple):
310
336
  domain = str(bucket_key[0] or "") if len(bucket_key) >= 1 else ""
@@ -362,6 +388,7 @@ def _restore_legacy_aiohttp_cookie_mapping(
362
388
  "samesite": samesite,
363
389
  "partitioned": partitioned,
364
390
  },
391
+ fallback_domain=fallback_domain,
365
392
  )
366
393
  if restored:
367
394
  restored_count += 1
@@ -718,7 +745,11 @@ class AlexaLogin:
718
745
  ):
719
746
  _LOGGER.debug("Loading serialized alexapy cookie jar")
720
747
  cookie_jar = self._session.cookie_jar
721
- return_cookies = _deserialize_cookie_jar(cookie_jar, cookies)
748
+ return_cookies = _deserialize_cookie_jar(
749
+ cookie_jar,
750
+ cookies,
751
+ fallback_domain=self.url,
752
+ )
722
753
  numcookies = len(return_cookies)
723
754
  elif isinstance(cookies, RequestsCookieJar):
724
755
  _LOGGER.debug("Loading RequestsCookieJar")
@@ -726,15 +757,15 @@ class AlexaLogin:
726
757
  for key, value in requests_cookies.items():
727
758
  if self._debug:
728
759
  _LOGGER.debug('Key: "%s", Value: "%s"', key, value)
729
- # Skip "partitioned" so older Python http.cookies does not fail.
730
- if key != "partitioned":
731
- return_cookies[str(key)] = value.strip('"')
760
+ return_cookies[str(key)] = value.strip('"')
732
761
  numcookies = len(return_cookies)
733
762
  elif isinstance(cookies, defaultdict):
734
763
  _LOGGER.debug("Loading legacy aiohttp cookie mapping")
735
764
  cookie_jar = self._session.cookie_jar
736
765
  return_cookies = _restore_legacy_aiohttp_cookie_mapping(
737
- cookie_jar, cookies
766
+ cookie_jar,
767
+ cookies,
768
+ fallback_domain=self.url,
738
769
  )
739
770
  numcookies = len(return_cookies)
740
771
  elif isinstance(cookies, dict):
@@ -743,14 +774,15 @@ class AlexaLogin:
743
774
  return_cookies = {
744
775
  str(key): str(value).strip('"')
745
776
  for key, value in cookies.items()
746
- if key != "partitioned"
747
777
  }
748
778
  numcookies = len(return_cookies)
749
779
  else:
750
780
  _LOGGER.debug("Loading legacy aiohttp cookie dict mapping")
751
781
  cookie_jar = self._session.cookie_jar
752
782
  return_cookies = _restore_legacy_aiohttp_cookie_mapping(
753
- cookie_jar, cookies
783
+ cookie_jar,
784
+ cookies,
785
+ fallback_domain=self.url,
754
786
  )
755
787
  numcookies = len(return_cookies)
756
788
  elif isinstance(cookies, http.cookiejar.MozillaCookieJar):
@@ -1219,11 +1251,6 @@ class AlexaLogin:
1219
1251
  self._cookiefile[0].replace(self.email, hide_email(self.email)),
1220
1252
  EXCEPTION_TEMPLATE.format(type(ex).__name__, ex.args),
1221
1253
  )
1222
- if self._debug:
1223
- _LOGGER.debug(
1224
- "Deleted:\n%s",
1225
- self._cookiefile.replace(self.email, hide_email(self.email))
1226
- )
1227
1254
 
1228
1255
  async def get_tokens(self) -> bool:
1229
1256
  """Get access and refresh tokens after registering device using cookies.
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  [tool.poetry]
3
3
  name = "AlexaPy"
4
- version = "1.29.24"
4
+ version = "1.29.25"
5
5
  description = "Python API to control Amazon Echo Devices Programmatically."
6
6
  authors = [
7
7
  "Keaton Taylor <keatonstaylor@gmail.com>",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes