python-hilo 2024.3.1__tar.gz → 2024.6.1__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.1
2
2
  Name: python-hilo
3
- Version: 2024.3.1
3
+ Version: 2024.6.1
4
4
  Summary: A Python3, async interface to the Hilo API
5
5
  Home-page: https://github.com/dvd-dev/python-hilo
6
6
  License: MIT
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
21
  Classifier: Programming Language :: Python :: Implementation :: PyPy
22
22
  Classifier: Topic :: Home Automation
23
+ Requires-Dist: aiofiles (>=23.2.1)
23
24
  Requires-Dist: aiohttp (>=3.8.0)
24
25
  Requires-Dist: aiosignal (>=1.2.0)
25
26
  Requires-Dist: async-timeout (>=4.0.0)
@@ -28,7 +29,7 @@ Requires-Dist: backoff (>=1.11.1)
28
29
  Requires-Dist: python-dateutil (>=2.8.2)
29
30
  Requires-Dist: ruyaml (>=0.91.0)
30
31
  Requires-Dist: voluptuous (>=0.13.1)
31
- Requires-Dist: websockets (>=8.1,<12.0)
32
+ Requires-Dist: websockets (>=8.1,<13.0)
32
33
  Project-URL: Repository, https://github.com/dvd-dev/python-hilo
33
34
  Description-Content-Type: text/markdown
34
35
 
@@ -45,6 +45,7 @@ from pyhilo.const import (
45
45
  from pyhilo.device import DeviceAttribute, HiloDevice, get_device_attributes
46
46
  from pyhilo.exceptions import InvalidCredentialsError, RequestError
47
47
  from pyhilo.util.state import (
48
+ StateDict,
48
49
  WebsocketDict,
49
50
  WebsocketTransportsDict,
50
51
  get_state,
@@ -75,7 +76,7 @@ class API:
75
76
  self._backoff_refresh_lock_ws = asyncio.Lock()
76
77
  self._request_retries = request_retries
77
78
  self._state_yaml: str = DEFAULT_STATE_FILE
78
- self.state = get_state(self._state_yaml)
79
+ self.state: StateDict = {}
79
80
  self.async_request = self._wrap_request_method(self._request_retries)
80
81
  self.device_attributes = get_device_attributes()
81
82
  self.session: ClientSession = session
@@ -152,14 +153,14 @@ class API:
152
153
  else attribute,
153
154
  )
154
155
 
155
- def _get_fid_state(self) -> bool:
156
+ async def _get_fid_state(self) -> bool:
156
157
  """Looks up the cached state to define the firebase attributes
157
158
  on the API instances.
158
159
 
159
160
  :return: Whether or not we have cached firebase state
160
161
  :rtype: bool
161
162
  """
162
- self.state = get_state(self._state_yaml)
163
+ self.state = await get_state(self._state_yaml)
163
164
  fb_state = self.state.get("firebase", {})
164
165
  if fb_fid := fb_state.get("fid"):
165
166
  self._fb_fid = fb_fid
@@ -171,14 +172,14 @@ class API:
171
172
  return True
172
173
  return False
173
174
 
174
- def _get_android_state(self) -> bool:
175
+ async def _get_android_state(self) -> bool:
175
176
  """Looks up the cached state to define the android device token
176
177
  on the API instances.
177
178
 
178
179
  :return: Whether or not we have cached android state
179
180
  :rtype: bool
180
181
  """
181
- self.state = get_state(self._state_yaml)
182
+ self.state = await get_state(self._state_yaml)
182
183
  android_state = self.state.get("android", {})
183
184
  if token := android_state.get("token"):
184
185
  self._device_token = token
@@ -187,18 +188,18 @@ class API:
187
188
 
188
189
  async def _get_device_token(self) -> None:
189
190
  """Retrieves the android token if it's not cached."""
190
- if not self._get_android_state():
191
+ if not await self._get_android_state():
191
192
  await self.android_register()
192
193
 
193
194
  async def _get_fid(self) -> None:
194
195
  """Retrieves the firebase state if it's not cached."""
195
- if not self._get_fid_state():
196
+ if not await self._get_fid_state():
196
197
  self._fb_id = "".join(
197
198
  random.SystemRandom().choice(string.ascii_letters + string.digits)
198
199
  for _ in range(FB_ID_LEN)
199
200
  )
200
201
  await self.fb_install(self._fb_id)
201
- self._get_fid_state()
202
+ await self._get_fid_state()
202
203
 
203
204
  async def _async_request(
204
205
  self, method: str, endpoint: str, host: str = API_HOSTNAME, **kwargs: Any
@@ -366,7 +367,7 @@ class API:
366
367
  resp = await self.async_request("post", url)
367
368
  ws_url = resp.get("url")
368
369
  ws_token = resp.get("accessToken")
369
- set_state(
370
+ await set_state(
370
371
  self._state_yaml,
371
372
  "websocket",
372
373
  {
@@ -397,7 +398,7 @@ class API:
397
398
  "available_transports": transport_dict,
398
399
  "full_ws_url": self.full_ws_url,
399
400
  }
400
- set_state(self._state_yaml, "websocket", websocket_dict)
401
+ await set_state(self._state_yaml, "websocket", websocket_dict)
401
402
 
402
403
  async def fb_install(self, fb_id: str) -> None:
403
404
  LOG.debug("Posting firebase install")
@@ -422,7 +423,7 @@ class API:
422
423
  raise RequestError(err) from err
423
424
  LOG.debug(f"FB Install data: {resp}")
424
425
  auth_token = resp.get("authToken", {})
425
- set_state(
426
+ await set_state(
426
427
  self._state_yaml,
427
428
  "firebase",
428
429
  {
@@ -463,7 +464,7 @@ class API:
463
464
  LOG.error(f"Android registration error: {msg}")
464
465
  raise RequestError
465
466
  token = msg.split("=")[-1]
466
- set_state(
467
+ await set_state(
467
468
  self._state_yaml,
468
469
  "android",
469
470
  {
@@ -614,6 +615,7 @@ class API:
614
615
  ]
615
616
  """
616
617
  url = self._get_url("Seasons", location_id, challenge=True)
618
+ LOG.debug(f"Seasons URL is {url}")
617
619
  return cast(dict[str, Any], await self.async_request("get", url))
618
620
 
619
621
  async def get_gateway(self, location_id: int) -> dict[str, Any]:
@@ -645,3 +647,22 @@ class API:
645
647
  for attr in saved_attrs:
646
648
  gw[attr] = {"value": req[0].get(attr)}
647
649
  return gw
650
+
651
+ async def get_weather(self, location_id: int) -> dict[str, Any]:
652
+ """This will return the current weather like in the app
653
+ https://api.hiloenergie.com/Automation/v1/api/Locations/XXXX/Weather
654
+ [
655
+ {
656
+ "temperature": -9.0,
657
+ "time":"0001-01-01T00:00:00Z",
658
+ "condition":"Foggy",
659
+ "icon":0,
660
+ "humidity":92.0
661
+ }
662
+ ]
663
+ """
664
+ url = self._get_url("Weather", location_id)
665
+ LOG.debug(f"Weather URL is {url}")
666
+ response = await self.async_request("get", url)
667
+ LOG.debug(f"Weather API response: {response}")
668
+ return cast(dict[str, Any], await self.async_request("get", url))
@@ -8,7 +8,7 @@ import homeassistant.core
8
8
  LOG: Final = logging.getLogger(__package__)
9
9
  DEFAULT_STATE_FILE: Final = "hilo_state.yaml"
10
10
  REQUEST_RETRY: Final = 9
11
- PYHILO_VERSION: Final = "2023.12.02"
11
+ PYHILO_VERSION: Final = "2024.06.01"
12
12
  # TODO: Find a way to keep previous line in sync with pyproject.toml automatically
13
13
 
14
14
  CONTENT_TYPE_FORM: Final = "application/x-www-form-urlencoded"
@@ -239,6 +239,7 @@ HILO_PROVIDERS: Final = {
239
239
  }
240
240
 
241
241
  JASCO_MODELS: Final = [
242
+ "43080",
242
243
  "43082",
243
244
  "43076",
244
245
  "43078",
@@ -247,12 +248,14 @@ JASCO_MODELS: Final = [
247
248
  "9063",
248
249
  "45678",
249
250
  "42405",
251
+ "43094",
250
252
  "43095",
251
253
  "45853",
252
254
  ]
253
255
 
254
256
  JASCO_OUTLETS: Final = [
255
257
  "42405",
258
+ "43094",
256
259
  "43095",
257
260
  "43100",
258
261
  "45853",
@@ -260,5 +263,7 @@ JASCO_OUTLETS: Final = [
260
263
 
261
264
  UNMONITORED_DEVICES: Final = [
262
265
  "43076",
266
+ "43080",
267
+ "43094",
263
268
  "43100",
264
269
  ]
@@ -2,6 +2,7 @@ from datetime import datetime
2
2
  from os.path import isfile
3
3
  from typing import Any, Optional, Type, TypedDict, TypeVar, Union
4
4
 
5
+ import aiofiles
5
6
  import ruyaml as yaml
6
7
 
7
8
  from pyhilo.const import LOG
@@ -71,7 +72,7 @@ def __get_defaults__(cls: Type[T]) -> dict[str, Any]:
71
72
  return new_dict # type: ignore
72
73
 
73
74
 
74
- def get_state(state_yaml: str) -> StateDict:
75
+ async def get_state(state_yaml: str) -> StateDict:
75
76
  """Read in state yaml.
76
77
  :param state_yaml: filename where to read the state
77
78
  :type state_yaml: ``str``
@@ -79,13 +80,14 @@ def get_state(state_yaml: str) -> StateDict:
79
80
  """
80
81
  if not isfile(state_yaml):
81
82
  return __get_defaults__(StateDict) # type: ignore
82
- with open(state_yaml) as yaml_file:
83
+ async with aiofiles.open(state_yaml, mode="r") as yaml_file:
83
84
  LOG.debug("Loading state from yaml")
84
- state_yaml_payload: StateDict = yaml.load(yaml_file, Loader=yaml.Loader)
85
+ content = await yaml_file.read()
86
+ state_yaml_payload: StateDict = yaml.safe_load(content)
85
87
  return state_yaml_payload
86
88
 
87
89
 
88
- def set_state(
90
+ async def set_state(
89
91
  state_yaml: str,
90
92
  key: str,
91
93
  state: Union[
@@ -101,9 +103,10 @@ def set_state(
101
103
  :type state: ``StateDict``
102
104
  :rtype: ``StateDict``
103
105
  """
104
- current_state = get_state(state_yaml) or {}
106
+ current_state = await get_state(state_yaml) or {}
105
107
  merged_state: dict[str, Any] = {key: {**current_state.get(key, {}), **state}} # type: ignore
106
108
  new_state: dict[str, Any] = {**current_state, **merged_state}
107
- with open(state_yaml, "w") as yaml_file:
109
+ async with aiofiles.open(state_yaml, mode="w") as yaml_file:
108
110
  LOG.debug("Saving state to yaml file")
109
- yaml.dump(new_state, yaml_file, Dumper=yaml.RoundTripDumper)
111
+ content = yaml.dump(new_state)
112
+ await yaml_file.write(content)
@@ -27,7 +27,7 @@ disallow_untyped_defs = true
27
27
  follow_imports = "silent"
28
28
  ignore_missing_imports = true
29
29
  no_implicit_optional = true
30
- python_version = "3.9"
30
+ python_version = "3.11"
31
31
  show_error_codes = true
32
32
  strict_equality = true
33
33
  warn_incomplete_stub = true
@@ -40,7 +40,7 @@ exclude = ".venv/.*"
40
40
 
41
41
  [tool.poetry]
42
42
  name = "python-hilo"
43
- version = "2024.3.1"
43
+ version = "2024.6.1"
44
44
  description = "A Python3, async interface to the Hilo API"
45
45
  readme = "README.md"
46
46
  authors = ["David Vallee Delisle <me@dvd.dev>"]
@@ -65,6 +65,7 @@ classifiers = [
65
65
 
66
66
  [tool.poetry.dependencies]
67
67
  aiohttp = ">=3.8.0"
68
+ aiofiles = ">=23.2.1"
68
69
  aiosignal = ">=1.2.0"
69
70
  async-timeout = ">=4.0.0"
70
71
  attrs = ">=21.2.0"
@@ -73,7 +74,7 @@ python-dateutil = ">=2.8.2"
73
74
  ruyaml = ">=0.91.0"
74
75
  python = "^3.9.0"
75
76
  voluptuous = ">=0.13.1"
76
- websockets = ">=8.1,<12.0"
77
+ websockets = ">=8.1,<13.0"
77
78
 
78
79
  [tool.poetry.dev-dependencies]
79
80
  Sphinx = "^7.1.2"
@@ -82,7 +83,7 @@ asynctest = "^0.13.0"
82
83
  pre-commit = "^3.2.2"
83
84
  pytest = "^8.0.0"
84
85
  pytest-aiohttp = "^1.0.4"
85
- pytest-cov = "^4.0.0"
86
+ pytest-cov = "^5.0.0"
86
87
  sphinx-rtd-theme = "^2.0.0"
87
88
  types-pytz = "^2024.1.0"
88
89
 
File without changes
File without changes