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.
- {switch_api-0.6.18 → switch_api-0.6.20}/HISTORY.md +1 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/PKG-INFO +2 -1
- {switch_api-0.6.18 → switch_api-0.6.20}/setup.py +1 -1
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/__init__.py +1 -1
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/_mqtt.py +82 -10
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/realtime.py +1 -1
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/PKG-INFO +2 -1
- {switch_api-0.6.18 → switch_api-0.6.20}/AUTHORS.rst +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/LICENCE +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/MANIFEST.in +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/README.md +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/pyproject.toml +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/setup.cfg +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_authentication.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_credentials_store/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_credentials_store/_credentials_store.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_msal/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_msal/_custom_application.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_commands.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_compute.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_iotservice.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_models.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_compute/_utils.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_guide/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_guide/main.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_guide/processor.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_constants.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_marketplace.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_mqtt.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_platform.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_utils/_utils.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/analytics/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/analytics/analytics.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/cache/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/cache/cache.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/_constants.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/_enums.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/_mqtt.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/controls/controls.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/dataset/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/dataset/dataset.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/email/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/email/email_sender.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/error_handlers/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/error_handlers/error_handlers.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/extensions.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/field_meta.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/helpers.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/extensions/pipeline.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/initialize.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/_utils.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/helpers.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/integration/integration.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/automation.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/definitions.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/pipeline/pipeline.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/platform_insights/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/platform_insights/platform_insights.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/__init__.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api/realtime/_redis.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/SOURCES.txt +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/dependency_links.txt +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/requires.txt +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/switch_api.egg-info/top_level.txt +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_dataset.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_error_handlers.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_initialize.py +0 -0
- {switch_api-0.6.18 → switch_api-0.6.20}/tests/test_integration.py +0 -0
- {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.
|
|
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.
|
|
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',
|
|
@@ -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
|
-
|
|
97
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
{switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_credentials_store/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{switch_api-0.6.18 → switch_api-0.6.20}/switch_api/_authentication/_msal/_custom_application.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|