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