switch-api 0.6.18__tar.gz → 0.6.20__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.20}/HISTORY.md +1 -0
  2. {switch_api-0.6.18 → switch_api-0.6.20}/PKG-INFO +2 -1
  3. {switch_api-0.6.18 → switch_api-0.6.20}/setup.py +1 -1
  4. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/__init__.py +1 -1
  5. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/_mqtt.py +82 -10
  6. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/realtime.py +1 -1
  7. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/PKG-INFO +2 -1
  8. {switch_api-0.6.18 → switch_api-0.6.20}/AUTHORS.rst +0 -0
  9. {switch_api-0.6.18 → switch_api-0.6.20}/LICENCE +0 -0
  10. {switch_api-0.6.18 → switch_api-0.6.20}/MANIFEST.in +0 -0
  11. {switch_api-0.6.18 → switch_api-0.6.20}/README.md +0 -0
  12. {switch_api-0.6.18 → switch_api-0.6.20}/pyproject.toml +0 -0
  13. {switch_api-0.6.18 → switch_api-0.6.20}/setup.cfg +0 -0
  14. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/__init__.py +0 -0
  15. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_authentication.py +0 -0
  16. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_credentials_store/__init__.py +0 -0
  17. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_credentials_store/_credentials_store.py +0 -0
  18. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_msal/__init__.py +0 -0
  19. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_msal/_custom_application.py +0 -0
  20. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/__init__.py +0 -0
  21. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_commands.py +0 -0
  22. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_compute.py +0 -0
  23. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_iotservice.py +0 -0
  24. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_models.py +0 -0
  25. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_utils.py +0 -0
  26. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_guide/__init__.py +0 -0
  27. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_guide/main.py +0 -0
  28. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_guide/processor.py +0 -0
  29. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/__init__.py +0 -0
  30. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_constants.py +0 -0
  31. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_marketplace.py +0 -0
  32. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_mqtt.py +0 -0
  33. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_platform.py +0 -0
  34. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_utils.py +0 -0
  35. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/analytics/__init__.py +0 -0
  36. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/analytics/analytics.py +0 -0
  37. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/cache/__init__.py +0 -0
  38. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/cache/cache.py +0 -0
  39. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/__init__.py +0 -0
  40. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/_constants.py +0 -0
  41. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/_enums.py +0 -0
  42. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/_mqtt.py +0 -0
  43. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/controls.py +0 -0
  44. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/dataset/__init__.py +0 -0
  45. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/dataset/dataset.py +0 -0
  46. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/email/__init__.py +0 -0
  47. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/email/email_sender.py +0 -0
  48. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/error_handlers/__init__.py +0 -0
  49. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/error_handlers/error_handlers.py +0 -0
  50. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/__init__.py +0 -0
  51. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/extensions.py +0 -0
  52. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/field_meta.py +0 -0
  53. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/helpers.py +0 -0
  54. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/pipeline.py +0 -0
  55. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/initialize.py +0 -0
  56. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/__init__.py +0 -0
  57. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/_utils.py +0 -0
  58. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/helpers.py +0 -0
  59. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/integration.py +0 -0
  60. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/__init__.py +0 -0
  61. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/automation.py +0 -0
  62. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/definitions.py +0 -0
  63. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/pipeline.py +0 -0
  64. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/platform_insights/__init__.py +0 -0
  65. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/platform_insights/platform_insights.py +0 -0
  66. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/__init__.py +0 -0
  67. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/_redis.py +0 -0
  68. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/SOURCES.txt +0 -0
  69. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/dependency_links.txt +0 -0
  70. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/requires.txt +0 -0
  71. {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/top_level.txt +0 -0
  72. {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_dataset.py +0 -0
  73. {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_error_handlers.py +0 -0
  74. {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_initialize.py +0 -0
  75. {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_integration.py +0 -0
  76. {switch_api-0.6.18 → switch_api-0.6.20}/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.20
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.20",
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.20"
@@ -8,10 +8,13 @@ A module containing the SwithRealtime class and associated methods referenced by
8
8
  module is not directly referenced by end users.
9
9
  """
10
10
  import logging
11
+ import ssl
11
12
  import sys
12
13
  import threading
14
+ import time
13
15
  from typing import Iterable
14
16
  from urllib.parse import urlparse
17
+ import certifi
15
18
  import paho.mqtt.client as mqtt
16
19
  from paho.mqtt.client import MQTTMessage
17
20
  from .._utils._constants import (WS_MQTT_CONNECTION_TIMEOUT)
@@ -53,7 +56,16 @@ class SwitchRealtime:
53
56
 
54
57
  self.client_id = client_id
55
58
  self.client = mqtt.Client(client_id=client_id, transport='websockets', clean_session=True)
56
- self.client.tls_set()
59
+ # self.client.tls_set()
60
+
61
+ # -- adhoc for cds engine server --
62
+ self.client.tls_set(
63
+ ca_certs=certifi.where(),
64
+ cert_reqs=ssl.CERT_REQUIRED,
65
+ tls_version=ssl.PROTOCOL_TLS_CLIENT,
66
+ )
67
+ self.client.tls_insecure_set(False)
68
+ # -- adhoc for cds engine server --
57
69
 
58
70
  if username and password:
59
71
  self.client.username_pw_set(username, password)
@@ -67,6 +79,7 @@ class SwitchRealtime:
67
79
 
68
80
  self._connected = False
69
81
  self._connect_event = threading.Event()
82
+ self._loop_started = False
70
83
 
71
84
  # Store all topic states here
72
85
  self.topic_state = {}
@@ -93,8 +106,13 @@ class SwitchRealtime:
93
106
  logger.info(f'Attempting connection to MQTT broker with client_id={self.client_id} on host={self.host} port={self.port}')
94
107
  logger.info(f'hostname={url.hostname}')
95
108
 
96
- self.client.connect(host=url.hostname, port=self.port)
97
- self.client.loop_start() # Run network loop in background thread
109
+ if self._loop_started:
110
+ logger.info("Reconnecting existing MQTT client network loop.")
111
+ self.client.reconnect()
112
+ else:
113
+ self.client.connect(host=url.hostname, port=self.port)
114
+ self.client.loop_start() # Run network loop in background thread
115
+ self._loop_started = True
98
116
 
99
117
  self._connected = self._connect_event.wait(timeout=timeout)
100
118
 
@@ -158,19 +176,73 @@ class SwitchRealtime:
158
176
  return retained_dataset
159
177
 
160
178
 
161
- def publish(self, topic: str, payload: str, qos: int = 1, retain: bool = True):
179
+ def publish(
180
+ self,
181
+ topic: str,
182
+ payload: str,
183
+ qos: int = 1,
184
+ retain: bool = True,
185
+ timeout: int = WS_MQTT_CONNECTION_TIMEOUT,
186
+ max_retries: int = 3,
187
+ ):
162
188
  """
163
189
  Publish message to broker.
164
190
  Set retain=True to store as last known state.
165
191
  """
166
- result = self.client.publish(topic, payload, qos=qos, retain=retain)
192
+ if not isinstance(max_retries, int) or max_retries < 1:
193
+ raise ValueError("max_retries must be a positive integer.")
194
+
195
+ last_error = None
196
+
197
+ for attempt in range(1, max_retries + 1):
198
+ if not self._connected:
199
+ logger.warning(
200
+ f"MQTT client is not connected before publish to {topic}. "
201
+ f"Reconnect attempt {attempt}/{max_retries}."
202
+ )
203
+ reconnect_error = None
204
+ try:
205
+ connected = self.connect(timeout=timeout)
206
+ except Exception as e:
207
+ connected = False
208
+ reconnect_error = f"Could not reconnect to MQTT Broker: {e}"
209
+ logger.error(reconnect_error)
210
+
211
+ if not connected:
212
+ last_error = reconnect_error or "Could not reconnect to MQTT Broker."
213
+ logger.error(last_error)
214
+ time.sleep(1)
215
+ continue
216
+
217
+ result = self.client.publish(topic, payload, qos=qos, retain=retain)
218
+
219
+ if result.rc == mqtt.MQTT_ERR_SUCCESS:
220
+ logger.info(
221
+ f"MQTT publish accepted for topic={topic}, "
222
+ f"mid={result.mid}, attempt={attempt}/{max_retries}"
223
+ )
224
+ return result
225
+
226
+ last_error = (
227
+ f"Failed to publish to {topic}. MQTT result code: {result.rc}"
228
+ )
229
+ logger.error(last_error)
230
+
231
+ if result.rc != mqtt.MQTT_ERR_NO_CONN or attempt >= max_retries:
232
+ break
233
+
234
+ self._connected = False
235
+ logger.warning(
236
+ f"MQTT publish failed because client is not connected. "
237
+ f"Retrying publish to {topic} after reconnect "
238
+ f"(attempt {attempt + 1}/{max_retries})."
239
+ )
240
+ time.sleep(1)
167
241
 
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)
242
+ message = last_error or f"Failed to publish to {topic}."
243
+ logger.error(message)
244
+ raise RuntimeError(message)
172
245
 
173
- return result
174
246
 
175
247
 
176
248
 
@@ -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.20
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