Skip to content

Commit 9d742c8

Browse files
committed
refactor: implement request retry mechanism and clean up API request handling
1 parent 9e4241e commit 9d742c8

File tree

2 files changed

+31
-36
lines changed

2 files changed

+31
-36
lines changed

custom_components/solis_cloud_control/api.py

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import asyncio
22
import json
33
import logging
4-
from datetime import datetime
54

65
import aiohttp
76

@@ -52,11 +51,33 @@ def __init__(
5251
self._session = session
5352
self._request_semaphore = asyncio.Semaphore(API_CONCURRENT_REQUESTS)
5453

55-
async def _request(self, date: datetime, endpoint: str, payload: dict[str, any] = None) -> dict[str, any] | None:
54+
async def _execute_request_with_retry(
55+
self,
56+
endpoint: str,
57+
payload: dict[str, any],
58+
retry_count: int = API_RETRY_COUNT,
59+
retry_delay: float = API_RETRY_DELAY_SECONDS,
60+
) -> any:
61+
attempt = 0
62+
63+
while attempt < retry_count:
64+
try:
65+
return await self._execute_request(endpoint, payload)
66+
except SolisCloudControlApiError as err:
67+
attempt += 1
68+
if attempt < retry_count:
69+
_LOGGER.warning("Retrying due to error: %s (attempt %d/%d)", str(err), attempt, retry_count)
70+
await asyncio.sleep(retry_delay)
71+
else:
72+
raise
73+
74+
async def _execute_request(self, endpoint: str, payload: dict[str, any] = None) -> any:
5675
body = json.dumps(payload)
5776

5877
payload_digest = digest(body)
5978
content_type = "application/json"
79+
80+
date = current_date()
6081
date_formatted = format_date(date)
6182

6283
authorization_str = "\n".join(["POST", payload_digest, content_type, date_formatted, endpoint])
@@ -77,8 +98,8 @@ async def _request(self, date: datetime, endpoint: str, payload: dict[str, any]
7798
_LOGGER.debug("API request '%s': %s", endpoint, json.dumps(payload, indent=2))
7899

79100
try:
80-
async with self._request_semaphore:
81-
async with asyncio.timeout(API_TIMEOUT_SECONDS):
101+
async with asyncio.timeout(API_TIMEOUT_SECONDS):
102+
async with self._request_semaphore:
82103
async with self._session.post(url, headers=headers, json=payload) as response:
83104
if response.status != 200:
84105
error_text = await response.text()
@@ -100,13 +121,9 @@ async def _request(self, date: datetime, endpoint: str, payload: dict[str, any]
100121
raise SolisCloudControlApiError(f"Error accessing {url}: {str(err)}") from err
101122

102123
async def read(self, inverter_sn: str, cid: int) -> str:
103-
date = current_date()
104124
payload = {"inverterSn": inverter_sn, "cid": cid}
105125

106-
async def request() -> str:
107-
return await self._request(date, API_READ_ENDPOINT, payload)
108-
109-
data = await _retry_request(request)
126+
data = await self._execute_request_with_retry(API_READ_ENDPOINT, payload)
110127

111128
if data is None:
112129
raise SolisCloudControlApiError("Read failed: 'data' field is missing in response")
@@ -117,13 +134,9 @@ async def request() -> str:
117134
return data["msg"]
118135

119136
async def read_batch(self, inverter_sn: str, cids: list[int]) -> dict[int, str]:
120-
date = current_date()
121137
payload = {"inverterSn": inverter_sn, "cids": ",".join(map(str, cids))}
122138

123-
async def request() -> dict[str, any]:
124-
return await self._request(date, API_READ_BATCH_ENDPOINT, payload)
125-
126-
data = await _retry_request(request)
139+
data = await self._execute_request_with_retry(API_READ_BATCH_ENDPOINT, payload)
127140

128141
if data is None:
129142
raise SolisCloudControlApiError("ReadBatch failed: 'data' field is missing in response")
@@ -150,15 +163,11 @@ async def request() -> dict[str, any]:
150163
return result
151164

152165
async def control(self, inverter_sn: str, cid: int, value: str, old_value: str | None = None) -> None:
153-
date = current_date()
154166
payload = {"inverterSn": inverter_sn, "cid": cid, "value": value}
155167
if old_value is not None:
156168
payload["yuanzhi"] = old_value
157169

158-
async def request() -> None:
159-
return await self._request(date, API_CONTROL_ENDPOINT, payload)
160-
161-
data_array = _retry_request(request)
170+
data_array = await self._execute_request_with_retry(API_CONTROL_ENDPOINT, payload)
162171

163172
if data_array is None:
164173
return
@@ -172,17 +181,3 @@ async def request() -> None:
172181
raise SolisCloudControlApiError(f"Control failed: {error_msg}", response_code=str(code))
173182

174183
return
175-
176-
177-
async def _retry_request(request: callable) -> any:
178-
attempt = 0
179-
while attempt < API_RETRY_COUNT:
180-
try:
181-
return await request()
182-
except SolisCloudControlApiError as err:
183-
attempt += 1
184-
if attempt < API_RETRY_COUNT:
185-
_LOGGER.warning("Retrying due to error: %s (attempt %d/%d)", str(err), attempt, API_RETRY_COUNT)
186-
await asyncio.sleep(API_RETRY_DELAY_SECONDS)
187-
else:
188-
raise

tests/test_api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ async def mock_read_endpoint_http_error(request):
6060

6161
@pytest.fixture
6262
def mock_retry_request():
63-
async def no_retry_request(request):
64-
return await request()
63+
async def _execute_without_retry(self, endpoint: str, payload):
64+
return await self._execute_request(endpoint, payload)
6565

66-
with patch("custom_components.solis_cloud_control.api._retry_request", new=no_retry_request):
66+
with patch.object(SolisCloudControlApiClient, "_execute_request_with_retry", _execute_without_retry):
6767
yield
6868

6969

0 commit comments

Comments
 (0)