pyvesync 3.1.1__tar.gz → 3.1.2__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.
- {pyvesync-3.1.1/src/pyvesync.egg-info → pyvesync-3.1.2}/PKG-INFO +1 -1
- {pyvesync-3.1.1 → pyvesync-3.1.2}/pyproject.toml +1 -1
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/auth.py +9 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/const.py +1 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncpurifier.py +1 -1
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/purifier_models.py +1 -1
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/errors.py +4 -4
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/vesync.py +31 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2/src/pyvesync.egg-info}/PKG-INFO +1 -1
- {pyvesync-3.1.1 → pyvesync-3.1.2}/LICENSE +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/MANIFEST.in +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/README.md +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/requirements.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/setup.cfg +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/setup.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/bulb_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/fan_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/fryer_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/humidifier_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/outlet_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/purifier_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/switch_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/thermostat_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/device_container.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/device_map.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncbulb.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncfan.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesynchumidifier.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesynckitchen.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncoutlet.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncswitch.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncthermostat.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/base_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/bulb_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/bypass_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/fan_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/fryer_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/home_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/humidifier_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/outlet_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/switch_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/thermostat_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/vesync_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/colors.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/device_mixins.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/enum_utils.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/helpers.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/logs.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/vesynchome.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync.egg-info/SOURCES.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync.egg-info/dependency_links.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync.egg-info/requires.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyvesync
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: pyvesync is a library to manage Etekcity Devices, Cosori Air Fryers, and Levoit Air Purifiers run on the VeSync app.
|
|
5
5
|
Author: Mark Perdue
|
|
6
6
|
Author-email: Joe Trabulsy <webdjoe@gmail.com>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyvesync"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.2"
|
|
8
8
|
description = "pyvesync is a library to manage Etekcity Devices, Cosori Air Fryers, and Levoit Air Purifiers run on the VeSync app."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -145,6 +145,15 @@ class VeSyncAuth:
|
|
|
145
145
|
self._country_code = country_code.upper()
|
|
146
146
|
self._current_region = region
|
|
147
147
|
|
|
148
|
+
async def reauthenticate(self) -> bool:
|
|
149
|
+
"""Re-authenticate using stored username and password.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
True if re-authentication successful, False otherwise
|
|
153
|
+
"""
|
|
154
|
+
self.clear_credentials()
|
|
155
|
+
return await self.login()
|
|
156
|
+
|
|
148
157
|
async def load_credentials_from_file(
|
|
149
158
|
self, file_path: str | Path | None = None
|
|
150
159
|
) -> bool:
|
|
@@ -34,6 +34,7 @@ from uuid import uuid4
|
|
|
34
34
|
|
|
35
35
|
from pyvesync.utils.enum_utils import IntEnumMixin
|
|
36
36
|
|
|
37
|
+
MAX_API_REAUTH_RETRIES = 3
|
|
37
38
|
DEFAULT_LANGUAGE = 'en'
|
|
38
39
|
API_BASE_URL = None # Global URL (non-EU regions): "https://smartapi.vesync.com"
|
|
39
40
|
# If device is out of reach, the cloud api sends a timeout response after 7 seconds,
|
|
@@ -146,7 +146,7 @@ class VeSyncAirBypass(BypassV2Mixin, VeSyncPurifier):
|
|
|
146
146
|
self.state.pm25 = result.air_quality_value
|
|
147
147
|
self.state.set_air_quality_level(result.air_quality)
|
|
148
148
|
if result.night_light is not None:
|
|
149
|
-
self.state.nightlight_status = DeviceStatus
|
|
149
|
+
self.state.nightlight_status = DeviceStatus(result.night_light)
|
|
150
150
|
|
|
151
151
|
async def get_details(self) -> None:
|
|
152
152
|
r_dict = await self.call_bypassv2_api('getPurifierStatus')
|
|
@@ -124,7 +124,7 @@ class PurifierCoreDetailsResult(InnerPurifierBaseResult):
|
|
|
124
124
|
configuration: PurifierCoreDetailsConfig | None = None
|
|
125
125
|
extension: dict | None = None
|
|
126
126
|
air_quality_value: int | None = None
|
|
127
|
-
night_light:
|
|
127
|
+
night_light: str | None = None
|
|
128
128
|
fan_rotate: str | None = None
|
|
129
129
|
|
|
130
130
|
|
|
@@ -799,9 +799,11 @@ class VeSyncLoginError(VeSyncError):
|
|
|
799
799
|
class VeSyncTokenError(VeSyncError):
|
|
800
800
|
"""Exception raised for VeSync API authentication errors."""
|
|
801
801
|
|
|
802
|
-
def __init__(self) -> None:
|
|
802
|
+
def __init__(self, msg: str | None = None) -> None:
|
|
803
803
|
"""Initialize the exception with a message."""
|
|
804
|
-
super().__init__(
|
|
804
|
+
super().__init__(
|
|
805
|
+
f'Token expired or invalid - {msg if msg else "Re-authentication required"}'
|
|
806
|
+
)
|
|
805
807
|
|
|
806
808
|
|
|
807
809
|
class VeSyncServerError(VeSyncError):
|
|
@@ -857,8 +859,6 @@ def raise_api_errors(error_info: ResponseInfo) -> None:
|
|
|
857
859
|
raise VeSyncRateLimitError
|
|
858
860
|
case ErrorTypes.AUTHENTICATION:
|
|
859
861
|
raise VeSyncLoginError(error_info.message)
|
|
860
|
-
case ErrorTypes.TOKEN_ERROR:
|
|
861
|
-
raise VeSyncTokenError
|
|
862
862
|
case ErrorTypes.SERVER_ERROR:
|
|
863
863
|
msg = (
|
|
864
864
|
f'{error_info.message} - '
|
|
@@ -16,6 +16,7 @@ from pyvesync.auth import VeSyncAuth
|
|
|
16
16
|
from pyvesync.const import (
|
|
17
17
|
DEFAULT_REGION,
|
|
18
18
|
DEFAULT_TZ,
|
|
19
|
+
MAX_API_REAUTH_RETRIES,
|
|
19
20
|
REGION_API_MAP,
|
|
20
21
|
STATUS_OK,
|
|
21
22
|
)
|
|
@@ -34,6 +35,7 @@ from pyvesync.utils.errors import (
|
|
|
34
35
|
VeSyncAPIStatusCodeError,
|
|
35
36
|
VeSyncError,
|
|
36
37
|
VeSyncServerError,
|
|
38
|
+
VeSyncTokenError,
|
|
37
39
|
raise_api_errors,
|
|
38
40
|
)
|
|
39
41
|
from pyvesync.utils.helpers import Helpers
|
|
@@ -47,6 +49,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
47
49
|
|
|
48
50
|
__slots__ = (
|
|
49
51
|
'__weakref__',
|
|
52
|
+
'_api_attempts',
|
|
50
53
|
'_auth',
|
|
51
54
|
'_close_session',
|
|
52
55
|
'_debug',
|
|
@@ -130,6 +133,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
130
133
|
Object to store device state information
|
|
131
134
|
"""
|
|
132
135
|
self.session = session
|
|
136
|
+
self._api_attempts = 0
|
|
133
137
|
self._close_session = False
|
|
134
138
|
self._debug = debug
|
|
135
139
|
self.redact = redact
|
|
@@ -381,6 +385,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
381
385
|
VeSyncAPIResponseError: If API response is invalid.
|
|
382
386
|
VeSyncServerError: If server returns an error.
|
|
383
387
|
"""
|
|
388
|
+
self.enabled = False
|
|
384
389
|
success = await self._auth.login()
|
|
385
390
|
if success:
|
|
386
391
|
self.enabled = True
|
|
@@ -427,6 +432,24 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
427
432
|
return
|
|
428
433
|
logger.debug('Session not closed, exiting context manager')
|
|
429
434
|
|
|
435
|
+
async def _reauthenticate(self) -> bool:
|
|
436
|
+
"""Re-authenticate using stored username and password.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
True if re-authentication successful, False otherwise
|
|
440
|
+
"""
|
|
441
|
+
self.enabled = False
|
|
442
|
+
self._api_attempts += 1
|
|
443
|
+
if self._api_attempts >= MAX_API_REAUTH_RETRIES:
|
|
444
|
+
logger.error('Max API re-authentication attempts reached')
|
|
445
|
+
raise VeSyncTokenError
|
|
446
|
+
success = await self.auth.reauthenticate()
|
|
447
|
+
if success:
|
|
448
|
+
self.enabled = True
|
|
449
|
+
self._api_attempts = 0
|
|
450
|
+
return True
|
|
451
|
+
return await self.auth.reauthenticate()
|
|
452
|
+
|
|
430
453
|
async def async_call_api(
|
|
431
454
|
self,
|
|
432
455
|
api: str,
|
|
@@ -463,6 +486,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
463
486
|
if self.session is None:
|
|
464
487
|
self.session = ClientSession()
|
|
465
488
|
self._close_session = True
|
|
489
|
+
self._api_attempts += 1
|
|
466
490
|
response = None
|
|
467
491
|
status_code = None
|
|
468
492
|
if isinstance(json_object, DataClassORJSONMixin):
|
|
@@ -494,6 +518,13 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
494
518
|
resp_dict = Helpers.try_json_loads(resp_bytes)
|
|
495
519
|
if isinstance(resp_dict, dict):
|
|
496
520
|
error_info = ErrorCodes.get_error_info(resp_dict.get('code'))
|
|
521
|
+
if error_info.error_type == ErrorTypes.TOKEN_ERROR:
|
|
522
|
+
if await self._reauthenticate():
|
|
523
|
+
self.enabled = True
|
|
524
|
+
return await self.async_call_api(
|
|
525
|
+
api, method, json_object, headers
|
|
526
|
+
)
|
|
527
|
+
raise VeSyncTokenError
|
|
497
528
|
if resp_dict.get('msg') is not None:
|
|
498
529
|
error_info.message = f'{error_info.message} ({resp_dict["msg"]})'
|
|
499
530
|
raise_api_errors(error_info)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyvesync
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: pyvesync is a library to manage Etekcity Devices, Cosori Air Fryers, and Levoit Air Purifiers run on the VeSync app.
|
|
5
5
|
Author: Mark Perdue
|
|
6
6
|
Author-email: Joe Trabulsy <webdjoe@gmail.com>
|
|
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
|
|
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
|