Skip to content

Commit 004b804

Browse files
authored
Merge pull request #14 from Kanaduchi/13-refactoring
#13 Refactor usage of roles to add support for delegated devices
2 parents 8584582 + cb5cf9a commit 004b804

File tree

4 files changed

+110
-31
lines changed

4 files changed

+110
-31
lines changed

src/pymelcloud/client.py

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""MEL API access."""
22
from datetime import datetime, timedelta
33
from typing import Any, Dict, List, Optional
4+
from aiohttp import ClientResponseError, ContentTypeError
45

56
from aiohttp import ClientSession
67

@@ -172,14 +173,26 @@ async def fetch_device_units(self, device) -> Optional[Dict[Any, Any]]:
172173
173174
User provided info such as indoor/outdoor unit model names and
174175
serial numbers.
176+
If the request returns 403, then ignore it
175177
"""
176-
async with self._session.post(
177-
f"{BASE_URL}/Device/ListDeviceUnits",
178-
headers=_headers(self._token),
179-
json={"deviceId": device.device_id},
180-
raise_for_status=True,
181-
) as resp:
182-
return await resp.json()
178+
try:
179+
async with self._session.post(
180+
f"{BASE_URL}/Device/ListDeviceUnits",
181+
headers=_headers(self._token),
182+
json={"deviceId": device.device_id}
183+
) as resp:
184+
resp.raise_for_status()
185+
try:
186+
data = await resp.json()
187+
except ContentTypeError:
188+
return None
189+
if isinstance(data, dict):
190+
return data
191+
return None
192+
except ClientResponseError as e:
193+
if e.status == 403:
194+
return None
195+
raise
183196

184197
async def fetch_device_state(self, device) -> Optional[Dict[Any, Any]]:
185198
"""Fetch state information of a device.
@@ -197,23 +210,35 @@ async def fetch_device_state(self, device) -> Optional[Dict[Any, Any]]:
197210
return await resp.json()
198211

199212
async def fetch_energy_report(self, device) -> Optional[Dict[Any, Any]]:
200-
"""Fetch energy report containing today and 1-2 days from the past."""
213+
"""Fetch energy report containing today and 1-2 days from the past.
214+
If the request returns 403, then ignore it"""
201215
device_id = device.device_id
202216
from_str = (datetime.today() - timedelta(days=2)).strftime("%Y-%m-%d")
203217
to_str = (datetime.today() + timedelta(days=2)).strftime("%Y-%m-%d")
204218

205-
async with self._session.post(
206-
f"{BASE_URL}/EnergyCost/Report",
207-
headers=_headers(self._token),
208-
json={
209-
"DeviceId": device_id,
210-
"UseCurrency": False,
211-
"FromDate": f"{from_str}T00:00:00",
212-
"ToDate": f"{to_str}T00:00:00"
213-
},
214-
raise_for_status=True,
215-
) as resp:
216-
return await resp.json()
219+
try:
220+
async with self._session.post(
221+
f"{BASE_URL}/EnergyCost/Report",
222+
headers=_headers(self._token),
223+
json={
224+
"DeviceId": device_id,
225+
"UseCurrency": False,
226+
"FromDate": f"{from_str}T00:00:00",
227+
"ToDate": f"{to_str}T00:00:00"
228+
}
229+
) as resp:
230+
resp.raise_for_status()
231+
try:
232+
data = await resp.json()
233+
except ContentTypeError:
234+
return None
235+
if isinstance(data, dict):
236+
return data
237+
return None
238+
except ClientResponseError as e:
239+
if e.status == 403:
240+
return None
241+
raise
217242

218243
async def set_device_state(self, device):
219244
"""Update device state.

src/pymelcloud/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
DEVICE_TYPE_UNKNOWN = "unknown"
77

88
ACCESS_LEVEL = {
9+
"USER": 2,
910
"GUEST": 3,
1011
"OWNER": 4,
1112
}

tests/test_ata_properties.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
import pytest
66
from unittest.mock import AsyncMock, Mock, patch
7-
from aiohttp.web import HTTPForbidden
7+
88
from src.pymelcloud import DEVICE_TYPE_ATA
99

10-
import src.pymelcloud
1110
from src.pymelcloud.const import ACCESS_LEVEL
1211
from src.pymelcloud.ata_device import (
1312
OPERATION_MODE_HEAT,
@@ -92,12 +91,3 @@ async def test_ata():
9291
assert device.wifi_signal == -51
9392
assert device.has_error is False
9493
assert device.error_code == 8000
95-
96-
97-
@pytest.mark.asyncio
98-
async def test_ata_guest():
99-
device = _build_device("ata_guest_listdevices.json", "ata_guest_get.json")
100-
device._client.fetch_device_units = AsyncMock(side_effect=HTTPForbidden)
101-
assert device.device_type == DEVICE_TYPE_ATA
102-
assert device.access_level == ACCESS_LEVEL["GUEST"]
103-
await device.update()

tests/test_client.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Client tests."""
2+
import pytest
3+
from unittest.mock import AsyncMock, Mock, patch
4+
from aiohttp import ClientResponseError, ClientSession
5+
from pymelcloud.client import Client
6+
7+
8+
@pytest.mark.asyncio
9+
async def test_fetch_energy_report_ignores_403():
10+
session = Mock(spec=ClientSession)
11+
cm = AsyncMock()
12+
session.post.return_value = cm
13+
14+
resp = Mock()
15+
cm.__aenter__.return_value = resp
16+
17+
request_info = Mock()
18+
request_info.real_url = "https://example.test/EnergyCost/Report"
19+
20+
resp.raise_for_status.side_effect = ClientResponseError(
21+
request_info=request_info,
22+
history=(),
23+
status=403,
24+
message="Forbidden",
25+
)
26+
27+
client = Client(token="dummy", session=session)
28+
29+
class DummyDevice:
30+
device_id = 123
31+
32+
device = DummyDevice()
33+
result = await client.fetch_energy_report(device)
34+
assert result is None
35+
36+
37+
@pytest.mark.asyncio
38+
async def test_fetch_device_units_ignores_403():
39+
session = Mock(spec=ClientSession)
40+
cm = AsyncMock()
41+
session.post.return_value = cm
42+
43+
resp = Mock()
44+
cm.__aenter__.return_value = resp
45+
46+
request_info = Mock()
47+
request_info.real_url = "https://example.test/Device/ListDeviceUnits"
48+
49+
resp.raise_for_status.side_effect = ClientResponseError(
50+
request_info=request_info,
51+
history=(),
52+
status=403,
53+
message="Forbidden",
54+
)
55+
56+
client = Client(token="dummy", session=session)
57+
58+
class DummyDevice:
59+
device_id = 123
60+
61+
device = DummyDevice()
62+
result = await client.fetch_device_units(device)
63+
assert result is None

0 commit comments

Comments
 (0)