@camstack/addon-import-alexa 0.2.181 → 0.2.183
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.
- package/CHANGELOG.md +12 -0
- package/dist/addon.js +1105 -343
- package/dist/addon.mjs +1105 -343
- package/package.json +6 -2
- package/python/alexa_gateway.py +13 -1
- package/python/test_alexa_gateway_echo.py +35 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-import-alexa",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.183",
|
|
4
4
|
"description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"addons": [
|
|
36
36
|
{
|
|
37
37
|
"id": "import-alexa",
|
|
38
|
+
"integrationRole": "both",
|
|
38
39
|
"category": "providers",
|
|
39
40
|
"name": "Alexa",
|
|
40
41
|
"version": "0.1.0",
|
|
@@ -52,6 +53,9 @@
|
|
|
52
53
|
{
|
|
53
54
|
"name": "broker"
|
|
54
55
|
},
|
|
56
|
+
{
|
|
57
|
+
"name": "integration-instances"
|
|
58
|
+
},
|
|
55
59
|
{
|
|
56
60
|
"name": "device-adoption"
|
|
57
61
|
},
|
|
@@ -68,7 +72,7 @@
|
|
|
68
72
|
}
|
|
69
73
|
],
|
|
70
74
|
"requires": {
|
|
71
|
-
"server": ">=1.2.
|
|
75
|
+
"server": ">=1.2.761"
|
|
72
76
|
}
|
|
73
77
|
},
|
|
74
78
|
"files": [
|
package/python/alexa_gateway.py
CHANGED
|
@@ -322,8 +322,20 @@ class AlexaPyGateway:
|
|
|
322
322
|
if is_rate_limited(err):
|
|
323
323
|
raise RateLimitedError("amazon rate limit (429) on the device volumes") from err
|
|
324
324
|
raise
|
|
325
|
+
# `_static_request` hands back the raw response (None on status >= 400): parse it here.
|
|
326
|
+
if body is None:
|
|
327
|
+
raise RuntimeError("amazon returned no response for the device volume list (HTTP >= 400)")
|
|
328
|
+
status = getattr(body, "status", None)
|
|
329
|
+
try:
|
|
330
|
+
body = await body.json(content_type=None)
|
|
331
|
+
except Exception as err: # noqa: BLE001 — typed below
|
|
332
|
+
raise RuntimeError(
|
|
333
|
+
f"amazon returned an unexpected body for the device volume list (HTTP {status}, unparseable)"
|
|
334
|
+
) from err
|
|
325
335
|
if not isinstance(body, dict):
|
|
326
|
-
raise RuntimeError(
|
|
336
|
+
raise RuntimeError(
|
|
337
|
+
f"amazon returned an unexpected body for the device volume list (HTTP {status}, not an object)"
|
|
338
|
+
)
|
|
327
339
|
rows = body.get("volumes")
|
|
328
340
|
devices = []
|
|
329
341
|
for row in rows if isinstance(rows, list) else []:
|
|
@@ -18,6 +18,20 @@ class AlexapyTooManyRequestsError(Exception):
|
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
class FakeResponse:
|
|
22
|
+
"""What alexapy's `_static_request` really returns: an aiohttp-like response, parsed by the caller."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, status, payload=None, bad_json=False):
|
|
25
|
+
self.status = status
|
|
26
|
+
self._payload = payload
|
|
27
|
+
self._bad_json = bad_json
|
|
28
|
+
|
|
29
|
+
async def json(self, content_type=None):
|
|
30
|
+
if self._bad_json:
|
|
31
|
+
raise ValueError("not json")
|
|
32
|
+
return self._payload
|
|
33
|
+
|
|
34
|
+
|
|
21
35
|
def run(coro):
|
|
22
36
|
return asyncio.run(coro)
|
|
23
37
|
|
|
@@ -143,13 +157,16 @@ class EchoMediaTests(unittest.TestCase):
|
|
|
143
157
|
|
|
144
158
|
def test_device_volumes_is_one_call_keeping_only_typed_readings(self) -> None:
|
|
145
159
|
gateway, calls = install_fake(
|
|
146
|
-
volumes=
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
volumes=FakeResponse(
|
|
161
|
+
200,
|
|
162
|
+
{
|
|
163
|
+
"volumes": [
|
|
164
|
+
{"dsn": "s1", "deviceType": "T1", "speakerVolume": 46, "speakerMuted": False},
|
|
165
|
+
{"dsn": "s2", "speakerVolume": None, "speakerMuted": True},
|
|
166
|
+
{"deviceType": "T1", "speakerVolume": 10},
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
)
|
|
153
170
|
)
|
|
154
171
|
result = run(gateway.device_volumes())
|
|
155
172
|
self.assertEqual(
|
|
@@ -160,6 +177,17 @@ class EchoMediaTests(unittest.TestCase):
|
|
|
160
177
|
calls["static"], [("get", "/api/devices/deviceType/dsn/audio/v1/allDeviceVolumes")]
|
|
161
178
|
)
|
|
162
179
|
|
|
180
|
+
def test_device_volumes_http_failure_is_told_apart_from_a_bad_body(self) -> None:
|
|
181
|
+
gateway, _ = install_fake(volumes=None)
|
|
182
|
+
with self.assertRaisesRegex(RuntimeError, "no response"):
|
|
183
|
+
run(gateway.device_volumes())
|
|
184
|
+
gateway, _ = install_fake(volumes=FakeResponse(200, ["not", "a", "dict"]))
|
|
185
|
+
with self.assertRaisesRegex(RuntimeError, "unexpected body"):
|
|
186
|
+
run(gateway.device_volumes())
|
|
187
|
+
gateway, _ = install_fake(volumes=FakeResponse(200, bad_json=True))
|
|
188
|
+
with self.assertRaisesRegex(RuntimeError, "unexpected body"):
|
|
189
|
+
run(gateway.device_volumes())
|
|
190
|
+
|
|
163
191
|
def test_device_volumes_429_is_typed(self) -> None:
|
|
164
192
|
gateway, _ = install_fake(raises=AlexapyTooManyRequestsError("Too Many Requests"))
|
|
165
193
|
with self.assertRaises(RateLimitedError):
|