pyhive-integration 1.0.8.dev0__tar.gz → 1.0.8.dev2__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.
- {pyhive_integration-1.0.8.dev0/pyhive_integration.egg-info → pyhive_integration-1.0.8.dev2}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2/pyhive_integration.egg-info}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/setup.py +1 -1
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/api/hive_async_api.py +27 -20
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/hive_exceptions.py +8 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/session.py +29 -6
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/LICENSE +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/MANIFEST.in +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/README.md +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/pyhive_integration.egg-info/SOURCES.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/pyhive_integration.egg-info/dependency_links.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/pyhive_integration.egg-info/requires.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/pyhive_integration.egg-info/top_level.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/pyproject.toml +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/requirements.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/requirements_test.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/setup.cfg +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/action.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/alarm.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/api/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/api/hive_api.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/api/hive_auth.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/api/hive_auth_async.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/camera.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/device_attributes.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/heating.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/const.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/debugger.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/hive_helper.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/hivedataclasses.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/map.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/hive.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/hotwater.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/hub.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/light.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/plug.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/sensor.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/tests/test_hub.py +0 -0
|
@@ -17,7 +17,7 @@ def requirements_from_file(filename="requirements.txt"):
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
setup(
|
|
20
|
-
version="1.0.
|
|
20
|
+
version="1.0.8.dev2",
|
|
21
21
|
packages=["apyhiveapi", "apyhiveapi.api", "apyhiveapi.helper"],
|
|
22
22
|
package_dir={"apyhiveapi": "src"},
|
|
23
23
|
package_data={"data": ["*.json"]},
|
|
@@ -10,8 +10,8 @@ import urllib3
|
|
|
10
10
|
from aiohttp import ClientResponse, ClientSession, web_exceptions
|
|
11
11
|
from pyquery import PyQuery
|
|
12
12
|
|
|
13
|
-
from ..helper.const import HTTP_UNAUTHORIZED
|
|
14
|
-
from ..helper.hive_exceptions import FileInUse, HiveApiError, NoApiToken
|
|
13
|
+
from ..helper.const import HTTP_FORBIDDEN, HTTP_UNAUTHORIZED
|
|
14
|
+
from ..helper.hive_exceptions import FileInUse, HiveApiError, HiveAuthError, NoApiToken
|
|
15
15
|
|
|
16
16
|
_LOGGER = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -56,45 +56,52 @@ class HiveApiAsync:
|
|
|
56
56
|
_LOGGER.debug("API %s request to %s", method.upper(), url)
|
|
57
57
|
data = kwargs.get("data", None)
|
|
58
58
|
|
|
59
|
+
headers = {
|
|
60
|
+
"content-type": "application/json",
|
|
61
|
+
"Accept": "*/*",
|
|
62
|
+
"User-Agent": "Hive/12.04.0 iOS/18.3.1 Apple",
|
|
63
|
+
}
|
|
59
64
|
try:
|
|
60
65
|
if camera:
|
|
61
|
-
headers =
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"x-jwt-token": self.session.tokens.tokenData["token"],
|
|
66
|
-
"User-Agent": "Hive/12.04.0 iOS/18.3.1 Apple",
|
|
67
|
-
}
|
|
66
|
+
headers["Authorization"] = (
|
|
67
|
+
f"Bearer {self.session.tokens.tokenData['token']}"
|
|
68
|
+
)
|
|
69
|
+
headers["x-jwt-token"] = self.session.tokens.tokenData["token"]
|
|
68
70
|
else:
|
|
69
|
-
headers =
|
|
70
|
-
"content-type": "application/json",
|
|
71
|
-
"Accept": "*/*",
|
|
72
|
-
"Authorization": self.session.tokens.tokenData["token"],
|
|
73
|
-
"User-Agent": "Hive/12.04.0 iOS/18.3.1 Apple",
|
|
74
|
-
}
|
|
71
|
+
headers["Authorization"] = self.session.tokens.tokenData["token"]
|
|
75
72
|
except KeyError:
|
|
76
73
|
if "sso" in url:
|
|
77
74
|
pass
|
|
78
75
|
else:
|
|
79
76
|
raise NoApiToken
|
|
80
77
|
|
|
78
|
+
auth_token = headers.get("Authorization", "")
|
|
79
|
+
_LOGGER.debug(
|
|
80
|
+
"Using token (len=%d, tail=…%s)",
|
|
81
|
+
len(auth_token),
|
|
82
|
+
auth_token[-4:] if len(auth_token) >= 4 else auth_token,
|
|
83
|
+
)
|
|
84
|
+
|
|
81
85
|
async with self.websession.request(
|
|
82
86
|
method, url, headers=headers, data=data
|
|
83
87
|
) as resp:
|
|
84
|
-
await resp.text()
|
|
88
|
+
resp_body = await resp.text()
|
|
85
89
|
if str(resp.status).startswith("20"):
|
|
86
90
|
_LOGGER.debug("API response %s from %s", resp.status, url)
|
|
87
91
|
return resp
|
|
88
92
|
|
|
89
|
-
if resp.status
|
|
93
|
+
if resp.status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN):
|
|
90
94
|
_LOGGER.error(
|
|
91
|
-
f"Hive token
|
|
92
|
-
f"HTTP status
|
|
95
|
+
f"Hive token rejected calling {url} - "
|
|
96
|
+
f"HTTP {resp.status} — response: {resp_body[:200]}"
|
|
97
|
+
)
|
|
98
|
+
raise HiveAuthError(
|
|
99
|
+
f"Token expired or forbidden calling {url} — HTTP {resp.status}"
|
|
93
100
|
)
|
|
94
101
|
elif url is not None and resp.status is not None:
|
|
95
102
|
_LOGGER.error(
|
|
96
103
|
f"Something has gone wrong calling {url} - "
|
|
97
|
-
f"HTTP status is - {resp.status}"
|
|
104
|
+
f"HTTP status is - {resp.status} — response: {resp_body[:200]}"
|
|
98
105
|
)
|
|
99
106
|
|
|
100
107
|
raise HiveApiError
|
{pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/hive_exceptions.py
RENAMED
|
@@ -27,6 +27,14 @@ class HiveApiError(Exception):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
class HiveAuthError(HiveApiError):
|
|
31
|
+
"""Auth error (401/403) — token may be expired or invalid.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
HiveApiError (object): Parent API error class
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
30
38
|
class HiveRefreshTokenExpired(Exception):
|
|
31
39
|
"""Refresh token expired.
|
|
32
40
|
|
|
@@ -17,6 +17,7 @@ from .device_attributes import HiveAttributes
|
|
|
17
17
|
from .helper.const import ACTIONS, DEVICES, HIVE_TYPES, PRODUCTS
|
|
18
18
|
from .helper.hive_exceptions import (
|
|
19
19
|
HiveApiError,
|
|
20
|
+
HiveAuthError,
|
|
20
21
|
HiveFailedToRefreshTokens,
|
|
21
22
|
HiveInvalid2FACode,
|
|
22
23
|
HiveInvalidDeviceAuthentication,
|
|
@@ -73,7 +74,7 @@ class HiveSession:
|
|
|
73
74
|
self.tokens = Map(
|
|
74
75
|
{
|
|
75
76
|
"tokenData": {},
|
|
76
|
-
"tokenCreated": datetime.
|
|
77
|
+
"tokenCreated": datetime.min,
|
|
77
78
|
"tokenExpiry": timedelta(seconds=3600),
|
|
78
79
|
}
|
|
79
80
|
)
|
|
@@ -407,12 +408,16 @@ class HiveSession:
|
|
|
407
408
|
if datetime.now() < ep:
|
|
408
409
|
return updated
|
|
409
410
|
_LOGGER.debug("Polling Hive API for device updates.")
|
|
410
|
-
await self.getDevices(device["hiveID"])
|
|
411
|
-
if len(self.deviceList["camera"]) > 0:
|
|
411
|
+
updated = await self.getDevices(device["hiveID"])
|
|
412
|
+
if updated and len(self.deviceList["camera"]) > 0:
|
|
412
413
|
for camera in self.data.camera:
|
|
413
414
|
await self.getCamera(self.devices[camera])
|
|
414
|
-
updated
|
|
415
|
-
|
|
415
|
+
if updated:
|
|
416
|
+
_LOGGER.debug("Device update completed successfully.")
|
|
417
|
+
else:
|
|
418
|
+
_LOGGER.debug(
|
|
419
|
+
"Device update failed, will retry after scan interval."
|
|
420
|
+
)
|
|
416
421
|
|
|
417
422
|
return updated
|
|
418
423
|
|
|
@@ -505,7 +510,18 @@ class HiveSession:
|
|
|
505
510
|
elif self.tokens is not None:
|
|
506
511
|
await self.hiveRefreshTokens()
|
|
507
512
|
_LOGGER.debug("Fetching all devices from Hive API.")
|
|
508
|
-
|
|
513
|
+
try:
|
|
514
|
+
api_resp_d = await self.api.getAll()
|
|
515
|
+
except HiveAuthError:
|
|
516
|
+
_LOGGER.warning(
|
|
517
|
+
"Auth error (401/403) after token refresh, "
|
|
518
|
+
"falling back to full device re-login."
|
|
519
|
+
)
|
|
520
|
+
await self._retryDeviceLogin()
|
|
521
|
+
try:
|
|
522
|
+
api_resp_d = await self.api.getAll()
|
|
523
|
+
except HiveAuthError as retry_err:
|
|
524
|
+
raise HiveReauthRequired from retry_err
|
|
509
525
|
if operator.contains(str(api_resp_d["original"]), "20") is False:
|
|
510
526
|
raise HTTPException
|
|
511
527
|
elif api_resp_d["parsed"] is None:
|
|
@@ -551,6 +567,10 @@ class HiveSession:
|
|
|
551
567
|
await self.getAlarm()
|
|
552
568
|
self.config.lastUpdate = datetime.now()
|
|
553
569
|
get_nodes_successful = True
|
|
570
|
+
except HiveReauthRequired:
|
|
571
|
+
_LOGGER.error("Reauthentication required, propagating to caller.")
|
|
572
|
+
self.config.lastUpdate = datetime.now()
|
|
573
|
+
raise
|
|
554
574
|
except (
|
|
555
575
|
OSError,
|
|
556
576
|
RuntimeError,
|
|
@@ -559,6 +579,9 @@ class HiveSession:
|
|
|
559
579
|
HTTPException,
|
|
560
580
|
) as err:
|
|
561
581
|
_LOGGER.error("Failed to fetch devices: %s", err)
|
|
582
|
+
self.config.lastUpdate = (
|
|
583
|
+
datetime.now() - self.config.scanInterval + timedelta(seconds=30)
|
|
584
|
+
)
|
|
562
585
|
get_nodes_successful = False
|
|
563
586
|
|
|
564
587
|
return get_nodes_successful
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev2}/src/helper/hivedataclasses.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|