hyponcloud 0.9.2__tar.gz → 0.9.3__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.
Files changed (30) hide show
  1. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/.github/workflows/ci.yml +1 -1
  2. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/.gitignore +3 -0
  3. {hyponcloud-0.9.2/hyponcloud.egg-info → hyponcloud-0.9.3}/PKG-INFO +1 -1
  4. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud/_version.py +3 -3
  5. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud/client.py +58 -27
  6. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud/exceptions.py +1 -1
  7. {hyponcloud-0.9.2 → hyponcloud-0.9.3/hyponcloud.egg-info}/PKG-INFO +1 -1
  8. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/tests/test_client.py +415 -292
  9. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/.github/dependabot.yml +0 -0
  10. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/.github/workflows/codeql.yml +0 -0
  11. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/.github/workflows/publish.yml +0 -0
  12. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/.github/workflows/release.yml +0 -0
  13. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/.pre-commit-config.yaml +0 -0
  14. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/AGENTS.md +0 -0
  15. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/API.md +0 -0
  16. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/LICENSE +0 -0
  17. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/MANIFEST.in +0 -0
  18. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/README.md +0 -0
  19. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/example.py +0 -0
  20. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud/__init__.py +0 -0
  21. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud/models.py +0 -0
  22. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud/py.typed +0 -0
  23. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud.egg-info/SOURCES.txt +0 -0
  24. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud.egg-info/dependency_links.txt +0 -0
  25. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud.egg-info/requires.txt +0 -0
  26. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/hyponcloud.egg-info/top_level.txt +0 -0
  27. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/pyproject.toml +0 -0
  28. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/setup.cfg +0 -0
  29. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/setup_instructions.md +0 -0
  30. {hyponcloud-0.9.2 → hyponcloud-0.9.3}/tests/__init__.py +0 -0
@@ -70,7 +70,7 @@ jobs:
70
70
 
71
71
  - name: Upload coverage to Codecov
72
72
  if: matrix.python-version == '3.11'
73
- uses: codecov/codecov-action@v5
73
+ uses: codecov/codecov-action@v6
74
74
  with:
75
75
  file: ./coverage.xml
76
76
  fail_ci_if_error: false
@@ -125,6 +125,9 @@ dmypy.json
125
125
  # IDEs
126
126
  .vscode/
127
127
  .idea/
128
+
129
+ # Claude Code local config
130
+ .claude/settings.local.json
128
131
  *.swp
129
132
  *.swo
130
133
  *~
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyponcloud
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: Python library for Hypontech Cloud API
5
5
  Author-email: jcisio <hainam@jcisio.com>
6
6
  License: MIT
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.9.2'
22
- __version_tuple__ = version_tuple = (0, 9, 2)
21
+ __version__ = version = '0.9.3'
22
+ __version_tuple__ = version_tuple = (0, 9, 3)
23
23
 
24
- __commit_id__ = commit_id = 'g26813dc98'
24
+ __commit_id__ = commit_id = 'gcc84788d0'
@@ -4,7 +4,7 @@ import asyncio
4
4
  import json
5
5
  import logging
6
6
  from time import time
7
- from typing import Any
7
+ from typing import Any, cast
8
8
 
9
9
  import aiohttp
10
10
 
@@ -45,10 +45,10 @@ class HyponCloud:
45
45
 
46
46
  self._session = session
47
47
  self._own_session = session is None
48
- self.__username = username
49
- self.__password = password
50
- self.__token = ""
51
- self.__token_expires_at = 0
48
+ self._username = username
49
+ self._password = password
50
+ self._token = ""
51
+ self._token_expires_at = 0
52
52
 
53
53
  async def __aenter__(self) -> "HyponCloud":
54
54
  """Async context manager entry."""
@@ -73,7 +73,7 @@ class HyponCloud:
73
73
  AuthenticationError: If authentication fails.
74
74
  ConnectionError: If connection to API fails.
75
75
  """
76
- if self.__token and self.__token_expires_at > time():
76
+ if self._token and self._token_expires_at > time():
77
77
  return
78
78
 
79
79
  if not self._session:
@@ -81,14 +81,12 @@ class HyponCloud:
81
81
  self._own_session = True
82
82
 
83
83
  url = f"{self.base_url}/login"
84
- data = {"username": self.__username, "password": self.__password}
84
+ data = {"username": self._username, "password": self._password}
85
85
 
86
86
  try:
87
87
  async with self._session.post(
88
88
  url, json=data, timeout=self.timeout
89
89
  ) as response:
90
- result = await self._parse_response(response, "POST", url)
91
-
92
90
  if response.status == 401:
93
91
  raise AuthenticationError("Invalid credentials")
94
92
  if response.status == 429:
@@ -100,8 +98,9 @@ class HyponCloud:
100
98
  f"Connection failed with status {response.status}"
101
99
  )
102
100
 
103
- self.__token = result["data"]["token"]
104
- self.__token_expires_at = int(time()) + self.token_validity
101
+ result = await self._parse_response(response, "POST", url)
102
+ self._token = result["data"]["token"]
103
+ self._token_expires_at = int(time()) + self.token_validity
105
104
  except aiohttp.ClientError as e:
106
105
  raise RequestError(f"Failed to connect to Hypon Cloud: {e}") from e
107
106
  except KeyError as e:
@@ -119,8 +118,8 @@ class HyponCloud:
119
118
  print(f"Headers: {dict(response.headers)}")
120
119
  raw_text = await response.text()
121
120
  print(f"Response: {raw_text}")
122
- return dict(json.loads(raw_text))
123
- return dict(await response.json())
121
+ return cast(dict, json.loads(raw_text))
122
+ return cast(dict, await response.json())
124
123
 
125
124
  async def _request(
126
125
  self, url: str, endpoint_name: str, retries: int | None = None
@@ -143,13 +142,11 @@ class HyponCloud:
143
142
  await self.connect()
144
143
  assert self._session is not None # connect() ensures session exists
145
144
 
146
- headers = {"authorization": f"Bearer {self.__token}"}
145
+ headers = {"authorization": f"Bearer {self._token}"}
147
146
  try:
148
147
  async with self._session.get(
149
148
  url, headers=headers, timeout=self.timeout
150
149
  ) as response:
151
- result = await self._parse_response(response, "GET", url)
152
-
153
150
  if response.status == 429:
154
151
  if retries > 0:
155
152
  await asyncio.sleep(10)
@@ -164,8 +161,11 @@ class HyponCloud:
164
161
  f"Failed to get {endpoint_name}: HTTP {response.status}"
165
162
  )
166
163
 
167
- return result
164
+ return await self._parse_response(response, "GET", url)
168
165
  except aiohttp.ClientError as e:
166
+ if retries > 0:
167
+ await asyncio.sleep(10)
168
+ return await self._request(url, endpoint_name, retries - 1)
169
169
  raise RequestError(f"Failed to get {endpoint_name}: {e}") from e
170
170
 
171
171
  async def get_overview(self, retries: int | None = None) -> OverviewData:
@@ -182,35 +182,55 @@ class HyponCloud:
182
182
  AuthenticationError: If authentication fails.
183
183
  ConnectionError: If connection to API fails.
184
184
  """
185
+ retries = retries if retries is not None else self.retries
185
186
  url = f"{self.base_url}/plant/overview"
186
187
  try:
187
188
  result = await self._request(url, "plant overview", retries)
188
189
  return OverviewData.from_dict(result["data"])
189
190
  except KeyError as e:
190
191
  _LOGGER.error("Error parsing plant overview data: %s", e)
192
+ if retries > 0:
193
+ return await self.get_overview(retries - 1)
191
194
  return OverviewData()
192
195
 
193
196
  async def get_list(self, retries: int | None = None) -> list[PlantData]:
194
197
  """Get plant list.
195
198
 
199
+ This method automatically fetches all pages of plants.
200
+
196
201
  Args:
197
202
  retries: Number of retry attempts if request fails. If None,
198
203
  uses the client's default retry setting.
199
204
 
200
205
  Returns:
201
- List of PlantData objects.
206
+ List of all PlantData objects across all pages.
202
207
 
203
208
  Raises:
204
209
  AuthenticationError: If authentication fails.
205
210
  ConnectionError: If connection to API fails.
206
211
  """
207
- url = f"{self.base_url}/plant/list2?page=1&page_size=10&refresh=true"
208
- try:
209
- result = await self._request(url, "plant list", retries)
210
- return [PlantData.from_dict(item) for item in result["data"]]
211
- except KeyError as e:
212
- _LOGGER.error("Error getting plant list: %s", e)
213
- raise RequestError(f"Failed to get plant list: {e}") from e
212
+ retries = retries if retries is not None else self.retries
213
+ all_plants: list[PlantData] = []
214
+ page = 1
215
+ total_pages = 1
216
+
217
+ while page <= total_pages:
218
+ url = (
219
+ f"{self.base_url}/plant/list2" f"?page={page}&page_size=10&refresh=true"
220
+ )
221
+ try:
222
+ result = await self._request(url, "plant list", retries)
223
+ if "totalPage" in result:
224
+ total_pages = result["totalPage"]
225
+ all_plants.extend(PlantData.from_dict(item) for item in result["data"])
226
+ page += 1
227
+ except KeyError as e:
228
+ _LOGGER.error("Error parsing plant list data: %s", e)
229
+ if retries > 0:
230
+ return await self.get_list(retries - 1)
231
+ return []
232
+
233
+ return all_plants
214
234
 
215
235
  async def get_inverters(
216
236
  self, plant_id: str, retries: int | None = None
@@ -231,6 +251,7 @@ class HyponCloud:
231
251
  AuthenticationError: If authentication fails.
232
252
  ConnectionError: If connection to API fails.
233
253
  """
254
+ retries = retries if retries is not None else self.retries
234
255
  all_inverters: list[InverterData] = []
235
256
  page = 1
236
257
  total_pages = 1
@@ -247,6 +268,8 @@ class HyponCloud:
247
268
  page += 1
248
269
  except KeyError as e:
249
270
  _LOGGER.error("Error parsing inverter list data: %s", e)
271
+ if retries > 0:
272
+ return await self.get_inverters(plant_id, retries - 1)
250
273
  return []
251
274
 
252
275
  return all_inverters
@@ -268,12 +291,15 @@ class HyponCloud:
268
291
  AuthenticationError: If authentication fails.
269
292
  ConnectionError: If connection to API fails.
270
293
  """
294
+ retries = retries if retries is not None else self.retries
271
295
  url = f"{self.base_url}/plant/{plant_id}/monitor?refresh=true"
272
296
  try:
273
297
  result = await self._request(url, "plant monitor", retries)
274
298
  return PlantMonitorData.from_dict(result["data"])
275
299
  except KeyError as e:
276
300
  _LOGGER.error("Error parsing plant monitor data: %s", e)
301
+ if retries > 0:
302
+ return await self.get_monitor(plant_id, retries - 1)
277
303
  return PlantMonitorData()
278
304
 
279
305
  async def get_admin_info(self, retries: int | None = None) -> AdminInfo:
@@ -290,15 +316,20 @@ class HyponCloud:
290
316
  AuthenticationError: If authentication fails.
291
317
  ConnectionError: If connection to API fails.
292
318
  """
319
+ retries = retries if retries is not None else self.retries
293
320
  url = f"{self.base_url}/administrator/admininfo"
294
321
  try:
295
322
  result = await self._request(url, "admin info", retries)
296
323
  data = result["data"]
297
324
  # Flatten nested "info" object into the main data dict
298
325
  if "info" in data and isinstance(data["info"], dict):
299
- info_data = data.pop("info")
300
- data.update(info_data)
326
+ data = {
327
+ **{k: v for k, v in data.items() if k != "info"},
328
+ **data["info"],
329
+ }
301
330
  return AdminInfo.from_dict(data)
302
331
  except KeyError as e:
303
332
  _LOGGER.error("Error parsing admin info data: %s", e)
333
+ if retries > 0:
334
+ return await self.get_admin_info(retries - 1)
304
335
  return AdminInfo()
@@ -13,5 +13,5 @@ class RequestError(HyponCloudError):
13
13
  """Exception raised when API request fails."""
14
14
 
15
15
 
16
- class RateLimitError(HyponCloudError):
16
+ class RateLimitError(RequestError):
17
17
  """Exception raised when API rate limit is exceeded."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyponcloud
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: Python library for Hypontech Cloud API
5
5
  Author-email: jcisio <hainam@jcisio.com>
6
6
  License: MIT