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.
Files changed (59) hide show
  1. {pyvesync-3.1.1/src/pyvesync.egg-info → pyvesync-3.1.2}/PKG-INFO +1 -1
  2. {pyvesync-3.1.1 → pyvesync-3.1.2}/pyproject.toml +1 -1
  3. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/auth.py +9 -0
  4. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/const.py +1 -0
  5. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncpurifier.py +1 -1
  6. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/purifier_models.py +1 -1
  7. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/errors.py +4 -4
  8. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/vesync.py +31 -0
  9. {pyvesync-3.1.1 → pyvesync-3.1.2/src/pyvesync.egg-info}/PKG-INFO +1 -1
  10. {pyvesync-3.1.1 → pyvesync-3.1.2}/LICENSE +0 -0
  11. {pyvesync-3.1.1 → pyvesync-3.1.2}/MANIFEST.in +0 -0
  12. {pyvesync-3.1.1 → pyvesync-3.1.2}/README.md +0 -0
  13. {pyvesync-3.1.1 → pyvesync-3.1.2}/requirements.txt +0 -0
  14. {pyvesync-3.1.1 → pyvesync-3.1.2}/setup.cfg +0 -0
  15. {pyvesync-3.1.1 → pyvesync-3.1.2}/setup.py +0 -0
  16. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/__init__.py +0 -0
  17. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/__init__.py +0 -0
  18. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/bulb_base.py +0 -0
  19. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/fan_base.py +0 -0
  20. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/fryer_base.py +0 -0
  21. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/humidifier_base.py +0 -0
  22. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/outlet_base.py +0 -0
  23. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/purifier_base.py +0 -0
  24. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/switch_base.py +0 -0
  25. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/thermostat_base.py +0 -0
  26. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
  27. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/device_container.py +0 -0
  28. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/device_map.py +0 -0
  29. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/__init__.py +0 -0
  30. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncbulb.py +0 -0
  31. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncfan.py +0 -0
  32. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesynchumidifier.py +0 -0
  33. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesynckitchen.py +0 -0
  34. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncoutlet.py +0 -0
  35. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncswitch.py +0 -0
  36. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncthermostat.py +0 -0
  37. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/__init__.py +0 -0
  38. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/base_models.py +0 -0
  39. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/bulb_models.py +0 -0
  40. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/bypass_models.py +0 -0
  41. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/fan_models.py +0 -0
  42. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/fryer_models.py +0 -0
  43. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/home_models.py +0 -0
  44. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/humidifier_models.py +0 -0
  45. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/outlet_models.py +0 -0
  46. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/switch_models.py +0 -0
  47. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/thermostat_models.py +0 -0
  48. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/models/vesync_models.py +0 -0
  49. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/__init__.py +0 -0
  50. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/colors.py +0 -0
  51. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/device_mixins.py +0 -0
  52. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/enum_utils.py +0 -0
  53. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/helpers.py +0 -0
  54. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/utils/logs.py +0 -0
  55. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync/vesynchome.py +0 -0
  56. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync.egg-info/SOURCES.txt +0 -0
  57. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync.egg-info/dependency_links.txt +0 -0
  58. {pyvesync-3.1.1 → pyvesync-3.1.2}/src/pyvesync.egg-info/requires.txt +0 -0
  59. {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.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.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.from_bool(result.night_light)
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: bool | None = None
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__('Token expired or invalid - please re-authenticate with login()')
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.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