pyhive-integration 1.0.8.dev0__tar.gz → 1.0.8.dev3__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.dev3}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3/pyhive_integration.egg-info}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/setup.py +1 -1
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/api/hive_async_api.py +27 -20
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/helper/hive_exceptions.py +8 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/session.py +40 -8
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/LICENSE +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/MANIFEST.in +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/README.md +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/pyhive_integration.egg-info/SOURCES.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/pyhive_integration.egg-info/dependency_links.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/pyhive_integration.egg-info/requires.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/pyhive_integration.egg-info/top_level.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/pyproject.toml +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/requirements.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/requirements_test.txt +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/setup.cfg +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/action.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/alarm.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/api/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/api/hive_api.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/api/hive_auth.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/api/hive_auth_async.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/camera.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/device_attributes.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/heating.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/helper/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/helper/const.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/helper/debugger.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/helper/hive_helper.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/helper/hivedataclasses.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/helper/map.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/hive.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/hotwater.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/hub.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/light.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/plug.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/src/sensor.py +0 -0
- {pyhive_integration-1.0.8.dev0 → pyhive_integration-1.0.8.dev3}/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.dev3",
|
|
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.dev3}/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
|
)
|
|
@@ -356,6 +357,11 @@ class HiveSession:
|
|
|
356
357
|
else:
|
|
357
358
|
expiry_time = self.tokens.tokenCreated + (self.tokens.tokenExpiry * 0.95)
|
|
358
359
|
# Refresh at 95% of token lifetime to prevent expiration during API calls
|
|
360
|
+
_LOGGER.debug(
|
|
361
|
+
"Checking token expiry time ( Current: %s | Expiry: %s)",
|
|
362
|
+
datetime.now(),
|
|
363
|
+
expiry_time,
|
|
364
|
+
)
|
|
359
365
|
if datetime.now() >= expiry_time:
|
|
360
366
|
async with self._refreshLock:
|
|
361
367
|
# Re-check after acquiring lock — another caller may have already refreshed
|
|
@@ -364,10 +370,14 @@ class HiveSession:
|
|
|
364
370
|
)
|
|
365
371
|
if datetime.now() < expiry_time:
|
|
366
372
|
return result
|
|
373
|
+
actual_expiry = self.tokens.tokenCreated + self.tokens.tokenExpiry
|
|
367
374
|
_LOGGER.debug(
|
|
368
|
-
"Token
|
|
375
|
+
"Token created: %s | Actual expiry: %s | "
|
|
376
|
+
"Early refresh (×0.95): %s | Now: %s",
|
|
369
377
|
self.tokens.tokenCreated,
|
|
370
|
-
|
|
378
|
+
actual_expiry,
|
|
379
|
+
expiry_time,
|
|
380
|
+
datetime.now(),
|
|
371
381
|
)
|
|
372
382
|
try:
|
|
373
383
|
result = await self.auth.refresh_token(
|
|
@@ -407,12 +417,16 @@ class HiveSession:
|
|
|
407
417
|
if datetime.now() < ep:
|
|
408
418
|
return updated
|
|
409
419
|
_LOGGER.debug("Polling Hive API for device updates.")
|
|
410
|
-
await self.getDevices(device["hiveID"])
|
|
411
|
-
if len(self.deviceList["camera"]) > 0:
|
|
420
|
+
updated = await self.getDevices(device["hiveID"])
|
|
421
|
+
if updated and len(self.deviceList["camera"]) > 0:
|
|
412
422
|
for camera in self.data.camera:
|
|
413
423
|
await self.getCamera(self.devices[camera])
|
|
414
|
-
updated
|
|
415
|
-
|
|
424
|
+
if updated:
|
|
425
|
+
_LOGGER.debug("Device update completed successfully.")
|
|
426
|
+
else:
|
|
427
|
+
_LOGGER.debug(
|
|
428
|
+
"Device update failed, will retry after scan interval."
|
|
429
|
+
)
|
|
416
430
|
|
|
417
431
|
return updated
|
|
418
432
|
|
|
@@ -505,7 +519,18 @@ class HiveSession:
|
|
|
505
519
|
elif self.tokens is not None:
|
|
506
520
|
await self.hiveRefreshTokens()
|
|
507
521
|
_LOGGER.debug("Fetching all devices from Hive API.")
|
|
508
|
-
|
|
522
|
+
try:
|
|
523
|
+
api_resp_d = await self.api.getAll()
|
|
524
|
+
except HiveAuthError:
|
|
525
|
+
_LOGGER.warning(
|
|
526
|
+
"Auth error (401/403) after token refresh, "
|
|
527
|
+
"falling back to full device re-login."
|
|
528
|
+
)
|
|
529
|
+
await self._retryDeviceLogin()
|
|
530
|
+
try:
|
|
531
|
+
api_resp_d = await self.api.getAll()
|
|
532
|
+
except HiveAuthError as retry_err:
|
|
533
|
+
raise HiveReauthRequired from retry_err
|
|
509
534
|
if operator.contains(str(api_resp_d["original"]), "20") is False:
|
|
510
535
|
raise HTTPException
|
|
511
536
|
elif api_resp_d["parsed"] is None:
|
|
@@ -551,6 +576,10 @@ class HiveSession:
|
|
|
551
576
|
await self.getAlarm()
|
|
552
577
|
self.config.lastUpdate = datetime.now()
|
|
553
578
|
get_nodes_successful = True
|
|
579
|
+
except HiveReauthRequired:
|
|
580
|
+
_LOGGER.error("Reauthentication required, propagating to caller.")
|
|
581
|
+
self.config.lastUpdate = datetime.now()
|
|
582
|
+
raise
|
|
554
583
|
except (
|
|
555
584
|
OSError,
|
|
556
585
|
RuntimeError,
|
|
@@ -559,6 +588,9 @@ class HiveSession:
|
|
|
559
588
|
HTTPException,
|
|
560
589
|
) as err:
|
|
561
590
|
_LOGGER.error("Failed to fetch devices: %s", err)
|
|
591
|
+
self.config.lastUpdate = (
|
|
592
|
+
datetime.now() - self.config.scanInterval + timedelta(seconds=30)
|
|
593
|
+
)
|
|
562
594
|
get_nodes_successful = False
|
|
563
595
|
|
|
564
596
|
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.dev3}/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
|