switch-api 0.6.18__tar.gz → 0.6.19__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 (76) hide show
  1. {switch_api-0.6.18 → switch_api-0.6.19}/HISTORY.md +1 -0
  2. {switch_api-0.6.18 → switch_api-0.6.19}/PKG-INFO +2 -1
  3. {switch_api-0.6.18 → switch_api-0.6.19}/setup.py +1 -1
  4. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/__init__.py +1 -1
  5. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/realtime/_mqtt.py +70 -9
  6. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/realtime/realtime.py +1 -1
  7. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api.egg-info/PKG-INFO +2 -1
  8. {switch_api-0.6.18 → switch_api-0.6.19}/AUTHORS.rst +0 -0
  9. {switch_api-0.6.18 → switch_api-0.6.19}/LICENCE +0 -0
  10. {switch_api-0.6.18 → switch_api-0.6.19}/MANIFEST.in +0 -0
  11. {switch_api-0.6.18 → switch_api-0.6.19}/README.md +0 -0
  12. {switch_api-0.6.18 → switch_api-0.6.19}/pyproject.toml +0 -0
  13. {switch_api-0.6.18 → switch_api-0.6.19}/setup.cfg +0 -0
  14. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_authentication/__init__.py +0 -0
  15. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_authentication/_authentication.py +0 -0
  16. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_authentication/_credentials_store/__init__.py +0 -0
  17. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_authentication/_credentials_store/_credentials_store.py +0 -0
  18. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_authentication/_msal/__init__.py +0 -0
  19. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_authentication/_msal/_custom_application.py +0 -0
  20. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_compute/__init__.py +0 -0
  21. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_compute/_commands.py +0 -0
  22. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_compute/_compute.py +0 -0
  23. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_compute/_iotservice.py +0 -0
  24. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_compute/_models.py +0 -0
  25. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_compute/_utils.py +0 -0
  26. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_guide/__init__.py +0 -0
  27. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_guide/main.py +0 -0
  28. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_guide/processor.py +0 -0
  29. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_utils/__init__.py +0 -0
  30. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_utils/_constants.py +0 -0
  31. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_utils/_marketplace.py +0 -0
  32. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_utils/_mqtt.py +0 -0
  33. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_utils/_platform.py +0 -0
  34. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/_utils/_utils.py +0 -0
  35. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/analytics/__init__.py +0 -0
  36. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/analytics/analytics.py +0 -0
  37. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/cache/__init__.py +0 -0
  38. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/cache/cache.py +0 -0
  39. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/controls/__init__.py +0 -0
  40. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/controls/_constants.py +0 -0
  41. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/controls/_enums.py +0 -0
  42. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/controls/_mqtt.py +0 -0
  43. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/controls/controls.py +0 -0
  44. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/dataset/__init__.py +0 -0
  45. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/dataset/dataset.py +0 -0
  46. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/email/__init__.py +0 -0
  47. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/email/email_sender.py +0 -0
  48. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/error_handlers/__init__.py +0 -0
  49. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/error_handlers/error_handlers.py +0 -0
  50. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/extensions/__init__.py +0 -0
  51. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/extensions/extensions.py +0 -0
  52. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/extensions/field_meta.py +0 -0
  53. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/extensions/helpers.py +0 -0
  54. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/extensions/pipeline.py +0 -0
  55. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/initialize.py +0 -0
  56. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/integration/__init__.py +0 -0
  57. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/integration/_utils.py +0 -0
  58. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/integration/helpers.py +0 -0
  59. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/integration/integration.py +0 -0
  60. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/pipeline/__init__.py +0 -0
  61. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/pipeline/automation.py +0 -0
  62. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/pipeline/definitions.py +0 -0
  63. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/pipeline/pipeline.py +0 -0
  64. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/platform_insights/__init__.py +0 -0
  65. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/platform_insights/platform_insights.py +0 -0
  66. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/realtime/__init__.py +0 -0
  67. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api/realtime/_redis.py +0 -0
  68. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api.egg-info/SOURCES.txt +0 -0
  69. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api.egg-info/dependency_links.txt +0 -0
  70. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api.egg-info/requires.txt +0 -0
  71. {switch_api-0.6.18 → switch_api-0.6.19}/switch_api.egg-info/top_level.txt +0 -0
  72. {switch_api-0.6.18 → switch_api-0.6.19}/tests/test_dataset.py +0 -0
  73. {switch_api-0.6.18 → switch_api-0.6.19}/tests/test_error_handlers.py +0 -0
  74. {switch_api-0.6.18 → switch_api-0.6.19}/tests/test_initialize.py +0 -0
  75. {switch_api-0.6.18 → switch_api-0.6.19}/tests/test_integration.py +0 -0
  76. {switch_api-0.6.18 → switch_api-0.6.19}/tests/test_pipeline.py +0 -0
@@ -11,6 +11,7 @@ In the `realtime` module:
11
11
  - Preserved new-topic behavior: when no retained message exists and Redis is not configured, the provided dataframe is used and published as the initial retained dataset.
12
12
  - Updated `subscribe_with_retained_sync()` to apply the passed timeout to the MQTT connection, check subscribe failures, and log when no retained topics are received within the wait period.
13
13
  - Added Redis timeout retry handling with a default maximum of 3 attempts for Redis realtime update operations.
14
+ - Added MQTT publish reconnect/retry handling for transient `MQTT_ERR_NO_CONN` publish failures.
14
15
 
15
16
 
16
17
  #0.6.17
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: switch_api
3
- Version: 0.6.18
3
+ Version: 0.6.19
4
4
  Summary: A complete package for data ingestion into the Switch Automation Platform.
5
5
  Author: Switch Automation Pty Ltd.
6
6
  License: MIT License
@@ -69,6 +69,7 @@ In the `realtime` module:
69
69
  - Preserved new-topic behavior: when no retained message exists and Redis is not configured, the provided dataframe is used and published as the initial retained dataset.
70
70
  - Updated `subscribe_with_retained_sync()` to apply the passed timeout to the MQTT connection, check subscribe failures, and log when no retained topics are received within the wait period.
71
71
  - Added Redis timeout retry handling with a default maximum of 3 attempts for Redis realtime update operations.
72
+ - Added MQTT publish reconnect/retry handling for transient `MQTT_ERR_NO_CONN` publish failures.
72
73
 
73
74
 
74
75
  #0.6.17
@@ -16,7 +16,7 @@ setup(
16
16
  long_description_content_type='text/markdown',
17
17
  license='MIT License',
18
18
  name="switch_api",
19
- version="0.6.18",
19
+ version="0.6.19",
20
20
  packages=find_packages(include=["switch_api", "switch_api.*"]),
21
21
  install_requires=['pandas==1.5.3', 'requests', 'azure-storage-blob', 'pandera[io]==0.7.1', 'azure-servicebus',
22
22
  'msal>=1.11.0', 'paho-mqtt==1.6.1', 'uvicorn==0.22.0', 'fastapi==0.98.0', 'pyodbc==4.0.39', 'cachetools==5.5.0',
@@ -44,4 +44,4 @@ from . import _guide
44
44
  # log.addHandler(_ch)
45
45
  # https://dev.to/joaomcteixeira/setting-up-python-logging-for-a-library-app-6ml
46
46
 
47
- __version__ = "0.6.18"
47
+ __version__ = "0.6.19"
@@ -10,6 +10,7 @@ module is not directly referenced by end users.
10
10
  import logging
11
11
  import sys
12
12
  import threading
13
+ import time
13
14
  from typing import Iterable
14
15
  from urllib.parse import urlparse
15
16
  import paho.mqtt.client as mqtt
@@ -67,6 +68,7 @@ class SwitchRealtime:
67
68
 
68
69
  self._connected = False
69
70
  self._connect_event = threading.Event()
71
+ self._loop_started = False
70
72
 
71
73
  # Store all topic states here
72
74
  self.topic_state = {}
@@ -93,8 +95,13 @@ class SwitchRealtime:
93
95
  logger.info(f'Attempting connection to MQTT broker with client_id={self.client_id} on host={self.host} port={self.port}')
94
96
  logger.info(f'hostname={url.hostname}')
95
97
 
96
- self.client.connect(host=url.hostname, port=self.port)
97
- self.client.loop_start() # Run network loop in background thread
98
+ if self._loop_started:
99
+ logger.info("Reconnecting existing MQTT client network loop.")
100
+ self.client.reconnect()
101
+ else:
102
+ self.client.connect(host=url.hostname, port=self.port)
103
+ self.client.loop_start() # Run network loop in background thread
104
+ self._loop_started = True
98
105
 
99
106
  self._connected = self._connect_event.wait(timeout=timeout)
100
107
 
@@ -158,19 +165,73 @@ class SwitchRealtime:
158
165
  return retained_dataset
159
166
 
160
167
 
161
- def publish(self, topic: str, payload: str, qos: int = 1, retain: bool = True):
168
+ def publish(
169
+ self,
170
+ topic: str,
171
+ payload: str,
172
+ qos: int = 1,
173
+ retain: bool = True,
174
+ timeout: int = WS_MQTT_CONNECTION_TIMEOUT,
175
+ max_retries: int = 3,
176
+ ):
162
177
  """
163
178
  Publish message to broker.
164
179
  Set retain=True to store as last known state.
165
180
  """
166
- result = self.client.publish(topic, payload, qos=qos, retain=retain)
181
+ if not isinstance(max_retries, int) or max_retries < 1:
182
+ raise ValueError("max_retries must be a positive integer.")
183
+
184
+ last_error = None
185
+
186
+ for attempt in range(1, max_retries + 1):
187
+ if not self._connected:
188
+ logger.warning(
189
+ f"MQTT client is not connected before publish to {topic}. "
190
+ f"Reconnect attempt {attempt}/{max_retries}."
191
+ )
192
+ reconnect_error = None
193
+ try:
194
+ connected = self.connect(timeout=timeout)
195
+ except Exception as e:
196
+ connected = False
197
+ reconnect_error = f"Could not reconnect to MQTT Broker: {e}"
198
+ logger.error(reconnect_error)
199
+
200
+ if not connected:
201
+ last_error = reconnect_error or "Could not reconnect to MQTT Broker."
202
+ logger.error(last_error)
203
+ time.sleep(1)
204
+ continue
205
+
206
+ result = self.client.publish(topic, payload, qos=qos, retain=retain)
207
+
208
+ if result.rc == mqtt.MQTT_ERR_SUCCESS:
209
+ logger.info(
210
+ f"MQTT publish accepted for topic={topic}, "
211
+ f"mid={result.mid}, attempt={attempt}/{max_retries}"
212
+ )
213
+ return result
214
+
215
+ last_error = (
216
+ f"Failed to publish to {topic}. MQTT result code: {result.rc}"
217
+ )
218
+ logger.error(last_error)
219
+
220
+ if result.rc != mqtt.MQTT_ERR_NO_CONN or attempt >= max_retries:
221
+ break
222
+
223
+ self._connected = False
224
+ logger.warning(
225
+ f"MQTT publish failed because client is not connected. "
226
+ f"Retrying publish to {topic} after reconnect "
227
+ f"(attempt {attempt + 1}/{max_retries})."
228
+ )
229
+ time.sleep(1)
167
230
 
168
- if result.rc != mqtt.MQTT_ERR_SUCCESS:
169
- message = f"Failed to publish to {topic}. MQTT result code: {result.rc}"
170
- logger.error(message)
171
- raise RuntimeError(message)
231
+ message = last_error or f"Failed to publish to {topic}."
232
+ logger.error(message)
233
+ raise RuntimeError(message)
172
234
 
173
- return result
174
235
 
175
236
 
176
237
 
@@ -388,7 +388,7 @@ def submit_realtime_updates(
388
388
  # ----------------------------
389
389
  try:
390
390
  _log_json_value(f"Publishing MQTT payload for topic={topic}", payload_json)
391
- _switch_realtime.publish(topic=topic, payload=payload_json)
391
+ _switch_realtime.publish(topic=topic, payload=payload_json, timeout=timeout)
392
392
  logger.info(f"Published merged DataFrame to {topic}")
393
393
  except Exception as e:
394
394
  logger.error(f"Failed to publish DataFrame: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: switch_api
3
- Version: 0.6.18
3
+ Version: 0.6.19
4
4
  Summary: A complete package for data ingestion into the Switch Automation Platform.
5
5
  Author: Switch Automation Pty Ltd.
6
6
  License: MIT License
@@ -69,6 +69,7 @@ In the `realtime` module:
69
69
  - Preserved new-topic behavior: when no retained message exists and Redis is not configured, the provided dataframe is used and published as the initial retained dataset.
70
70
  - Updated `subscribe_with_retained_sync()` to apply the passed timeout to the MQTT connection, check subscribe failures, and log when no retained topics are received within the wait period.
71
71
  - Added Redis timeout retry handling with a default maximum of 3 attempts for Redis realtime update operations.
72
+ - Added MQTT publish reconnect/retry handling for transient `MQTT_ERR_NO_CONN` publish failures.
72
73
 
73
74
 
74
75
  #0.6.17
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes