nwp500-python 3.1.3__tar.gz → 3.1.4__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 (127) hide show
  1. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/CHANGELOG.rst +17 -0
  2. {nwp500_python-3.1.3/src/nwp500_python.egg-info → nwp500_python-3.1.4}/PKG-INFO +1 -1
  3. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/auth.py +47 -5
  4. {nwp500_python-3.1.3 → nwp500_python-3.1.4/src/nwp500_python.egg-info}/PKG-INFO +1 -1
  5. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500_python.egg-info/SOURCES.txt +1 -0
  6. nwp500_python-3.1.4/tests/test_auth.py +837 -0
  7. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/.coveragerc +0 -0
  8. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/.github/copilot-instructions.md +0 -0
  9. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/.github/workflows/ci.yml +0 -0
  10. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/.github/workflows/release.yml +0 -0
  11. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/.gitignore +0 -0
  12. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/.pre-commit-config.yaml +0 -0
  13. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/.readthedocs.yml +0 -0
  14. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/AUTHORS.rst +0 -0
  15. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/CONTRIBUTING.rst +0 -0
  16. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/LICENSE.txt +0 -0
  17. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/Makefile +0 -0
  18. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/README.rst +0 -0
  19. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/RELEASE.md +0 -0
  20. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/Makefile +0 -0
  21. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/_static/.gitignore +0 -0
  22. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/authors.rst +0 -0
  23. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/changelog.rst +0 -0
  24. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/conf.py +0 -0
  25. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/configuration.rst +0 -0
  26. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/development/contributing.rst +0 -0
  27. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/development/history.rst +0 -0
  28. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/guides/auto_recovery.rst +0 -0
  29. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/guides/command_queue.rst +0 -0
  30. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/guides/energy_monitoring.rst +0 -0
  31. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/guides/event_system.rst +0 -0
  32. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/guides/reservations.rst +0 -0
  33. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/guides/time_of_use.rst +0 -0
  34. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/index.rst +0 -0
  35. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/installation.rst +0 -0
  36. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/license.rst +0 -0
  37. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/openapi.yaml +0 -0
  38. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/protocol/device_features.rst +0 -0
  39. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/protocol/device_status.rst +0 -0
  40. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/protocol/error_codes.rst +0 -0
  41. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/protocol/firmware_tracking.rst +0 -0
  42. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/protocol/mqtt_protocol.rst +0 -0
  43. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/protocol/rest_api.rst +0 -0
  44. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/api_client.rst +0 -0
  45. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/auth_client.rst +0 -0
  46. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/cli.rst +0 -0
  47. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/constants.rst +0 -0
  48. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/events.rst +0 -0
  49. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/exceptions.rst +0 -0
  50. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/models.rst +0 -0
  51. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/python_api/mqtt_client.rst +0 -0
  52. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/quickstart.rst +0 -0
  53. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/docs/requirements.txt +0 -0
  54. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/.ruff.toml +0 -0
  55. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/README.md +0 -0
  56. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/anti_legionella_example.py +0 -0
  57. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/api_client_example.py +0 -0
  58. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/auth_constructor_example.py +0 -0
  59. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/authenticate.py +0 -0
  60. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/auto_recovery_example.py +0 -0
  61. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/combined_callbacks.py +0 -0
  62. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/command_queue_demo.py +0 -0
  63. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/device_feature_callback.py +0 -0
  64. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/device_status_callback.py +0 -0
  65. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/device_status_callback_debug.py +0 -0
  66. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/energy_usage_example.py +0 -0
  67. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/event_emitter_demo.py +0 -0
  68. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/improved_auth_pattern.py +0 -0
  69. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/mask.py +0 -0
  70. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/mqtt_client_example.py +0 -0
  71. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/periodic_device_info.py +0 -0
  72. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/periodic_requests.py +0 -0
  73. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/power_control_example.py +0 -0
  74. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/reconnection_demo.py +0 -0
  75. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/reservation_schedule_example.py +0 -0
  76. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/set_dhw_temperature_example.py +0 -0
  77. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/set_mode_example.py +0 -0
  78. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/simple_auto_recovery.py +0 -0
  79. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/simple_periodic_info.py +0 -0
  80. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/simple_periodic_status.py +0 -0
  81. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/test_api_client.py +0 -0
  82. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/test_mqtt_connection.py +0 -0
  83. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/test_mqtt_messaging.py +0 -0
  84. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/test_periodic_minimal.py +0 -0
  85. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/tou_openei_example.py +0 -0
  86. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/examples/tou_schedule_example.py +0 -0
  87. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/pyproject.toml +0 -0
  88. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/scripts/format.py +0 -0
  89. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/scripts/lint.py +0 -0
  90. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/scripts/setup-dev.py +0 -0
  91. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/setup.cfg +0 -0
  92. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/setup.py +0 -0
  93. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/__init__.py +0 -0
  94. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/api_client.py +0 -0
  95. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/cli/__init__.py +0 -0
  96. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/cli/__main__.py +0 -0
  97. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/cli/commands.py +0 -0
  98. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/cli/monitoring.py +0 -0
  99. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/cli/output_formatters.py +0 -0
  100. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/cli/token_storage.py +0 -0
  101. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/cli.py +0 -0
  102. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/config.py +0 -0
  103. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/constants.py +0 -0
  104. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/encoding.py +0 -0
  105. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/events.py +0 -0
  106. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/models.py +0 -0
  107. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_client.py +0 -0
  108. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_command_queue.py +0 -0
  109. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_connection.py +0 -0
  110. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_device_control.py +0 -0
  111. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_periodic.py +0 -0
  112. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_reconnection.py +0 -0
  113. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_subscriptions.py +0 -0
  114. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/mqtt_utils.py +0 -0
  115. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/py.typed +0 -0
  116. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500/utils.py +0 -0
  117. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
  118. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500_python.egg-info/entry_points.txt +0 -0
  119. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500_python.egg-info/not-zip-safe +0 -0
  120. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500_python.egg-info/requires.txt +0 -0
  121. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/src/nwp500_python.egg-info/top_level.txt +0 -0
  122. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/tests/conftest.py +0 -0
  123. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/tests/test_api_helpers.py +0 -0
  124. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/tests/test_command_queue.py +0 -0
  125. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/tests/test_events.py +0 -0
  126. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/tests/test_utils.py +0 -0
  127. {nwp500_python-3.1.3 → nwp500_python-3.1.4}/tox.ini +0 -0
@@ -2,6 +2,23 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 3.1.4 (2025-10-26)
6
+ ==========================
7
+
8
+ Fixed
9
+ -----
10
+
11
+ - **MQTT Reconnection**: Fixed MQTT reconnection failures due to expired AWS credentials
12
+
13
+ - Added AWS credential expiration tracking (``_aws_expires_at`` field in ``AuthTokens``)
14
+ - Added ``are_aws_credentials_expired`` property to check AWS credential validity
15
+ - Modified ``ensure_valid_token()`` to prioritize AWS credential expiration check
16
+ - Triggers full re-authentication (not just token refresh) when AWS credentials expire
17
+ - Preserves AWS credential expiration timestamps during token refresh
18
+ - Prevents reconnection failures when connection interrupts after AWS credentials expire but before JWT tokens expire
19
+ - Resolves AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE errors during reconnection attempts
20
+ - Improved test coverage for auth module from 31% to 60% with comprehensive test suite
21
+
5
22
  Version 3.1.3 (2025-10-24)
6
23
  ==========================
7
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 3.1.3
3
+ Version: 3.1.4
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
5
  Home-page: https://github.com/eman/nwp500-python
6
6
  Author: Emmanuel Levijarvi
@@ -73,6 +73,9 @@ class AuthTokens:
73
73
  _expires_at: datetime = field(
74
74
  default=datetime.now(), init=False, repr=False
75
75
  )
76
+ _aws_expires_at: Optional[datetime] = field(
77
+ default=None, init=False, repr=False
78
+ )
76
79
 
77
80
  def __post_init__(self) -> None:
78
81
  """Cache the expiration timestamp after initialization."""
@@ -80,6 +83,11 @@ class AuthTokens:
80
83
  self._expires_at = self.issued_at + timedelta(
81
84
  seconds=self.authentication_expires_in
82
85
  )
86
+ # Calculate AWS credentials expiration if available
87
+ if self.authorization_expires_in:
88
+ self._aws_expires_at = self.issued_at + timedelta(
89
+ seconds=self.authorization_expires_in
90
+ )
83
91
 
84
92
  @classmethod
85
93
  def from_dict(cls, data: dict[str, Any]) -> "AuthTokens":
@@ -106,6 +114,25 @@ class AuthTokens:
106
114
  # Consider expired if within 5 minutes of expiration
107
115
  return datetime.now() >= (self._expires_at - timedelta(minutes=5))
108
116
 
117
+ @property
118
+ def are_aws_credentials_expired(self) -> bool:
119
+ """Check if AWS credentials have expired.
120
+
121
+ AWS credentials have a separate expiration time from JWT tokens.
122
+ If AWS credentials are expired, a full re-authentication is needed
123
+ since the token refresh endpoint doesn't provide new AWS credentials.
124
+
125
+ Returns:
126
+ True if AWS credentials are expired, False if expiration time is
127
+ unknown or credentials are still valid
128
+ """
129
+ if not self._aws_expires_at:
130
+ # If we don't know when AWS credentials expire, consider them valid
131
+ # This handles cases where authorization_expires_in wasn't provided
132
+ return False
133
+ # Consider expired if within 5 minutes of expiration
134
+ return datetime.now() >= (self._aws_expires_at - timedelta(minutes=5))
135
+
109
136
  @property
110
137
  def time_until_expiry(self) -> timedelta:
111
138
  """Get time remaining until token expiration.
@@ -423,6 +450,8 @@ class NavienAuthClient:
423
450
  new_tokens.authorization_expires_in = (
424
451
  old_tokens.authorization_expires_in
425
452
  )
453
+ # Also preserve the AWS expiration timestamp
454
+ new_tokens._aws_expires_at = old_tokens._aws_expires_at
426
455
 
427
456
  # Update stored auth response if we have one
428
457
  if self._auth_response:
@@ -446,23 +475,36 @@ class NavienAuthClient:
446
475
  """
447
476
  Ensure we have a valid access token, refreshing if necessary.
448
477
 
478
+ This method checks both JWT token and AWS credentials expiration.
479
+ If AWS credentials are expired, it triggers a full re-authentication
480
+ since the token refresh endpoint doesn't provide new AWS credentials.
481
+
449
482
  Returns:
450
483
  Valid AuthTokens or None if not authenticated
451
484
 
452
485
  Raises:
453
486
  TokenRefreshError: If token refresh fails
487
+ AuthenticationError: If re-authentication fails
454
488
  """
455
489
  if not self._auth_response:
456
490
  _logger.warning("No authentication response available")
457
491
  return None
458
492
 
459
- if self._auth_response.tokens.is_expired:
493
+ tokens = self._auth_response.tokens
494
+
495
+ # Check if AWS credentials have expired
496
+ if tokens.are_aws_credentials_expired:
497
+ _logger.info("AWS credentials expired, re-authenticating...")
498
+ # Re-authenticate to get fresh AWS credentials
499
+ await self.sign_in(self._user_id, self._password)
500
+ return self._auth_response.tokens if self._auth_response else None
501
+
502
+ # Check if JWT token has expired
503
+ if tokens.is_expired:
460
504
  _logger.info("Token expired, refreshing...")
461
- return await self.refresh_token(
462
- self._auth_response.tokens.refresh_token
463
- )
505
+ return await self.refresh_token(tokens.refresh_token)
464
506
 
465
- return self._auth_response.tokens
507
+ return tokens
466
508
 
467
509
  @property
468
510
  def is_authenticated(self) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 3.1.3
3
+ Version: 3.1.4
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
5
  Home-page: https://github.com/eman/nwp500-python
6
6
  Author: Emmanuel Levijarvi
@@ -120,6 +120,7 @@ src/nwp500_python.egg-info/requires.txt
120
120
  src/nwp500_python.egg-info/top_level.txt
121
121
  tests/conftest.py
122
122
  tests/test_api_helpers.py
123
+ tests/test_auth.py
123
124
  tests/test_command_queue.py
124
125
  tests/test_events.py
125
126
  tests/test_utils.py