switch-api 0.6.16__tar.gz → 0.6.18__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 (78) hide show
  1. switch_api-0.6.16/PKG-INFO → switch_api-0.6.18/HISTORY.md +20 -40
  2. {switch_api-0.6.16/switch_api.egg-info → switch_api-0.6.18}/PKG-INFO +45 -7
  3. switch_api-0.6.18/pyproject.toml +6 -0
  4. {switch_api-0.6.16 → switch_api-0.6.18}/setup.py +2 -2
  5. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/__init__.py +4 -2
  6. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/integration/integration.py +3 -1
  7. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/realtime/__init__.py +2 -1
  8. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/realtime/_mqtt.py +27 -8
  9. switch_api-0.6.18/switch_api/realtime/_redis.py +429 -0
  10. switch_api-0.6.18/switch_api/realtime/realtime.py +397 -0
  11. switch_api-0.6.16/HISTORY.md → switch_api-0.6.18/switch_api.egg-info/PKG-INFO +78 -0
  12. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api.egg-info/SOURCES.txt +7 -1
  13. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api.egg-info/requires.txt +1 -0
  14. switch_api-0.6.18/tests/test_dataset.py +7 -0
  15. switch_api-0.6.18/tests/test_error_handlers.py +196 -0
  16. switch_api-0.6.18/tests/test_initialize.py +5 -0
  17. switch_api-0.6.18/tests/test_integration.py +37 -0
  18. switch_api-0.6.18/tests/test_pipeline.py +6 -0
  19. switch_api-0.6.16/pyproject.toml +0 -6
  20. switch_api-0.6.16/switch_api/realtime/realtime.py +0 -235
  21. {switch_api-0.6.16 → switch_api-0.6.18}/AUTHORS.rst +0 -0
  22. {switch_api-0.6.16 → switch_api-0.6.18}/LICENCE +0 -0
  23. {switch_api-0.6.16 → switch_api-0.6.18}/MANIFEST.in +0 -0
  24. {switch_api-0.6.16 → switch_api-0.6.18}/README.md +0 -0
  25. {switch_api-0.6.16 → switch_api-0.6.18}/setup.cfg +0 -0
  26. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_authentication/__init__.py +0 -0
  27. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_authentication/_authentication.py +0 -0
  28. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_authentication/_credentials_store/__init__.py +0 -0
  29. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_authentication/_credentials_store/_credentials_store.py +0 -0
  30. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_authentication/_msal/__init__.py +0 -0
  31. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_authentication/_msal/_custom_application.py +0 -0
  32. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_compute/__init__.py +0 -0
  33. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_compute/_commands.py +0 -0
  34. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_compute/_compute.py +0 -0
  35. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_compute/_iotservice.py +0 -0
  36. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_compute/_models.py +0 -0
  37. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_compute/_utils.py +0 -0
  38. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_guide/__init__.py +0 -0
  39. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_guide/main.py +0 -0
  40. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_guide/processor.py +0 -0
  41. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_utils/__init__.py +0 -0
  42. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_utils/_constants.py +0 -0
  43. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_utils/_marketplace.py +0 -0
  44. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_utils/_mqtt.py +0 -0
  45. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_utils/_platform.py +0 -0
  46. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/_utils/_utils.py +0 -0
  47. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/analytics/__init__.py +0 -0
  48. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/analytics/analytics.py +0 -0
  49. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/cache/__init__.py +0 -0
  50. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/cache/cache.py +0 -0
  51. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/controls/__init__.py +0 -0
  52. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/controls/_constants.py +0 -0
  53. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/controls/_enums.py +0 -0
  54. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/controls/_mqtt.py +0 -0
  55. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/controls/controls.py +0 -0
  56. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/dataset/__init__.py +0 -0
  57. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/dataset/dataset.py +0 -0
  58. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/email/__init__.py +0 -0
  59. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/email/email_sender.py +0 -0
  60. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/error_handlers/__init__.py +0 -0
  61. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/error_handlers/error_handlers.py +0 -0
  62. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/extensions/__init__.py +0 -0
  63. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/extensions/extensions.py +0 -0
  64. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/extensions/field_meta.py +0 -0
  65. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/extensions/helpers.py +0 -0
  66. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/extensions/pipeline.py +0 -0
  67. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/initialize.py +0 -0
  68. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/integration/__init__.py +0 -0
  69. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/integration/_utils.py +0 -0
  70. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/integration/helpers.py +0 -0
  71. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/pipeline/__init__.py +0 -0
  72. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/pipeline/automation.py +0 -0
  73. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/pipeline/definitions.py +0 -0
  74. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/pipeline/pipeline.py +0 -0
  75. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/platform_insights/__init__.py +0 -0
  76. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api/platform_insights/platform_insights.py +0 -0
  77. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api.egg-info/dependency_links.txt +0 -0
  78. {switch_api-0.6.16 → switch_api-0.6.18}/switch_api.egg-info/top_level.txt +0 -0
@@ -1,43 +1,25 @@
1
- Metadata-Version: 2.1
2
- Name: switch_api
3
- Version: 0.6.16
4
- Summary: A complete package for data ingestion into the Switch Automation Platform.
5
- Home-page: UNKNOWN
6
- Author: Switch Automation Pty Ltd.
7
- License: MIT License
8
- Platform: UNKNOWN
9
- Classifier: Development Status :: 2 - Pre-Alpha
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Intended Audience :: Other Audience
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.8
14
- Classifier: Programming Language :: Python :: 3.9
15
- Classifier: Natural Language :: English
16
- Requires-Python: >=3.8.0
17
- Description-Content-Type: text/markdown
18
- License-File: LICENCE
19
- License-File: AUTHORS.rst
20
-
21
- # Switch Automation library for Python
22
- This is a package for data ingestion into the Switch Automation software platform.
23
-
24
- You can find out more about the platform on [Switch Automation](https://www.switchautomation.com)
25
-
26
- ## Getting started
27
-
28
- ### Prerequisites
29
- * Python 3.8 or later is required to use this package.
30
- * You must have a [Switch Automation user account](https://www.switchautomation.com/our-solution/) to use this package.
31
-
32
- ### Install the package
33
- Install the Switch Automation library for Python with [pip](https://pypi.org/project/pip/):
34
-
35
- ```bash
36
- pip install switch_api
37
- ```
38
-
39
1
  # History
40
2
 
3
+ #0.6.18
4
+ ### Changed
5
+ In the `realtime` module:
6
+ - Updated `submit_realtime_updates()` to use the passed `timeout` value when waiting for a retained MQTT message.
7
+ - Added Redis-backed optimistic concurrency support for realtime dataset updates using WATCH/MULTI/EXEC.
8
+ - Added `set_realtime_redis_config()` to configure Redis as the authoritative state store before publishing updates to MQTT.
9
+ - Added `redis` package dependency for Redis-backed realtime state updates.
10
+ - Added a maximum of 3 retained-message subscription attempts for connection or subscribe failures in the MQTT fallback path.
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
+ - 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
+ - Added Redis timeout retry handling with a default maximum of 3 attempts for Redis realtime update operations.
14
+
15
+
16
+ #0.6.17
17
+ ### Changed
18
+ In the `integration` module:
19
+ - Updated `upsert_sites()` to support `YearBuilt` and `Floors` as optional columns when upserting sites.
20
+ - Documented `YearBuilt` and `Floors` in the `upsert_sites()` accepted fields list.
21
+ - `get_sites()` now also returns `YearBuilt` and `Floors` columns.
22
+
41
23
  #0.6.16
42
24
  ### Changed
43
25
  In the `controls` module:
@@ -1189,5 +1171,3 @@ Removed the `name_as_filename` and `treat_as_timeseries` parameter from the foll
1189
1171
  - `switch.integration.replace_data()`
1190
1172
  - `switch.integration.append_data()`
1191
1173
  - `switch.integration.upload_data()`
1192
-
1193
-
@@ -1,11 +1,9 @@
1
- Metadata-Version: 2.1
2
- Name: switch-api
3
- Version: 0.6.16
1
+ Metadata-Version: 2.4
2
+ Name: switch_api
3
+ Version: 0.6.18
4
4
  Summary: A complete package for data ingestion into the Switch Automation Platform.
5
- Home-page: UNKNOWN
6
5
  Author: Switch Automation Pty Ltd.
7
6
  License: MIT License
8
- Platform: UNKNOWN
9
7
  Classifier: Development Status :: 2 - Pre-Alpha
10
8
  Classifier: License :: OSI Approved :: MIT License
11
9
  Classifier: Intended Audience :: Other Audience
@@ -17,6 +15,28 @@ Requires-Python: >=3.8.0
17
15
  Description-Content-Type: text/markdown
18
16
  License-File: LICENCE
19
17
  License-File: AUTHORS.rst
18
+ Requires-Dist: pandas==1.5.3
19
+ Requires-Dist: requests
20
+ Requires-Dist: azure-storage-blob
21
+ Requires-Dist: pandera[io]==0.7.1
22
+ Requires-Dist: azure-servicebus
23
+ Requires-Dist: msal>=1.11.0
24
+ Requires-Dist: paho-mqtt==1.6.1
25
+ Requires-Dist: uvicorn==0.22.0
26
+ Requires-Dist: fastapi==0.98.0
27
+ Requires-Dist: pyodbc==4.0.39
28
+ Requires-Dist: cachetools==5.5.0
29
+ Requires-Dist: numexpr==2.10.2
30
+ Requires-Dist: redis<6,>=4.5.5
31
+ Dynamic: author
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
20
40
 
21
41
  # Switch Automation library for Python
22
42
  This is a package for data ingestion into the Switch Automation software platform.
@@ -38,6 +58,26 @@ pip install switch_api
38
58
 
39
59
  # History
40
60
 
61
+ #0.6.18
62
+ ### Changed
63
+ In the `realtime` module:
64
+ - Updated `submit_realtime_updates()` to use the passed `timeout` value when waiting for a retained MQTT message.
65
+ - Added Redis-backed optimistic concurrency support for realtime dataset updates using WATCH/MULTI/EXEC.
66
+ - Added `set_realtime_redis_config()` to configure Redis as the authoritative state store before publishing updates to MQTT.
67
+ - Added `redis` package dependency for Redis-backed realtime state updates.
68
+ - Added a maximum of 3 retained-message subscription attempts for connection or subscribe failures in the MQTT fallback path.
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
+ - 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
+ - Added Redis timeout retry handling with a default maximum of 3 attempts for Redis realtime update operations.
72
+
73
+
74
+ #0.6.17
75
+ ### Changed
76
+ In the `integration` module:
77
+ - Updated `upsert_sites()` to support `YearBuilt` and `Floors` as optional columns when upserting sites.
78
+ - Documented `YearBuilt` and `Floors` in the `upsert_sites()` accepted fields list.
79
+ - `get_sites()` now also returns `YearBuilt` and `Floors` columns.
80
+
41
81
  #0.6.16
42
82
  ### Changed
43
83
  In the `controls` module:
@@ -1189,5 +1229,3 @@ Removed the `name_as_filename` and `treat_as_timeseries` parameter from the foll
1189
1229
  - `switch.integration.replace_data()`
1190
1230
  - `switch.integration.append_data()`
1191
1231
  - `switch.integration.upload_data()`
1192
-
1193
-
@@ -0,0 +1,6 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=64",
4
+ "wheel"
5
+ ]
6
+ build-backend = "setuptools.build_meta"
@@ -16,11 +16,11 @@ setup(
16
16
  long_description_content_type='text/markdown',
17
17
  license='MIT License',
18
18
  name="switch_api",
19
- version="0.6.16",
19
+ version="0.6.18",
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',
23
- 'numexpr==2.10.2'],
23
+ 'numexpr==2.10.2', 'redis>=4.5.5,<6'],
24
24
  python_requires=">=3.8.0",
25
25
  classifiers=[
26
26
  'Development Status :: 2 - Pre-Alpha',
@@ -10,7 +10,8 @@ Data Ingestion into the Switch Automation
10
10
  Complete package for ingestion data into the Switch Automation Platform.
11
11
  """
12
12
  __all__ = ['dataset', 'analytics', 'pipeline', 'error_handlers', 'integration',
13
- 'initialize', 'cache', 'extensions', 'email', 'controls', 'realtime', '_compute', 'generate_filepath', 'set_mqtt_broker_config']
13
+ 'initialize', 'cache', 'extensions', 'email', 'controls', 'realtime', '_compute', 'generate_filepath',
14
+ 'set_mqtt_broker_config', 'set_realtime_redis_config']
14
15
 
15
16
  from .initialize import initialize
16
17
  from . import dataset
@@ -27,6 +28,7 @@ from . import realtime
27
28
  from . import _compute
28
29
  from ._utils._utils import generate_filepath
29
30
  from ._utils._mqtt import set_mqtt_broker_config
31
+ from .realtime import set_realtime_redis_config
30
32
  from . import _guide
31
33
 
32
34
 
@@ -42,4 +44,4 @@ from . import _guide
42
44
  # log.addHandler(_ch)
43
45
  # https://dev.to/joaomcteixeira/setting-up-python-logging-for-a-library-app-6ml
44
46
 
45
- __version__ = "0.6.16"
47
+ __version__ = "0.6.18"
@@ -840,6 +840,8 @@ def upsert_sites(df: pandas.DataFrame, api_inputs: ApiInputs, tag_columns: list
840
840
  - Latitude
841
841
  - Longitude
842
842
  - Timezone
843
+ - YearBuilt
844
+ - Floors
843
845
  - InstallationId
844
846
  - The UUID of the existing site within the Switch Automation Platform.
845
847
 
@@ -871,7 +873,7 @@ def upsert_sites(df: pandas.DataFrame, api_inputs: ApiInputs, tag_columns: list
871
873
 
872
874
  required_columns = ['InstallationName', 'InstallationCode', 'Address', 'Country', 'Suburb', 'State', 'StateName',
873
875
  'FloorAreaM2', 'ZipPostCode']
874
- optional_columns = ['Latitude', 'Longitude', 'Timezone', 'InstallationId']
876
+ optional_columns = ['Latitude', 'Longitude', 'Timezone', 'InstallationId', 'YearBuilt', 'Floors']
875
877
  proposed_columns = data_frame.columns.tolist()
876
878
 
877
879
  if not set(required_columns).issubset(proposed_columns):
@@ -8,5 +8,6 @@ A module for sending control request of sensors.
8
8
  """
9
9
 
10
10
  from .realtime import submit_realtime_updates
11
+ from ._redis import set_realtime_redis_config
11
12
 
12
- __all__ = ['submit_realtime_updates']
13
+ __all__ = ['submit_realtime_updates', 'set_realtime_redis_config']
@@ -7,7 +7,6 @@
7
7
  A module containing the SwithRealtime class and associated methods referenced by the controls module in the package. This
8
8
  module is not directly referenced by end users.
9
9
  """
10
- import json
11
10
  import logging
12
11
  import sys
13
12
  import threading
@@ -109,8 +108,6 @@ class SwitchRealtime:
109
108
  if topics is None:
110
109
  raise ValueError("topics cannot be None")
111
110
 
112
- self.connect()
113
-
114
111
  topics = list(topics)
115
112
 
116
113
  if not topics:
@@ -122,6 +119,11 @@ class SwitchRealtime:
122
119
  if not isinstance(timeout, int) or timeout <= 0:
123
120
  raise ValueError("timeout must be a positive integer")
124
121
 
122
+ if not self.connect(timeout=timeout):
123
+ raise TimeoutError(
124
+ f"Could not connect to MQTT Broker within {timeout} seconds."
125
+ )
126
+
125
127
  # --- Initialize tracking ---
126
128
  self._expected_topics = set(topics)
127
129
  self._received_retained = set()
@@ -129,19 +131,32 @@ class SwitchRealtime:
129
131
 
130
132
  # --- Subscribe ---
131
133
  for topic in topics:
132
- self.client.subscribe(topic)
134
+ result, _ = self.client.subscribe(topic)
135
+ if result != mqtt.MQTT_ERR_SUCCESS:
136
+ raise RuntimeError(
137
+ f"Failed to subscribe to {topic}. MQTT result code: {result}"
138
+ )
133
139
  logger.info(f"Subscribed to = {topic}")
134
140
 
135
141
  # --- Wait until all retained received OR timeout ---
136
- self._retained_event.wait(timeout=timeout)
142
+ received_all = self._retained_event.wait(timeout=timeout)
137
143
 
138
144
  # --- Return only retained dataset ---
139
- return {
145
+ retained_dataset = {
140
146
  topic: self.topic_state[topic]["payload"]
141
147
  for topic in self._received_retained
142
148
  if topic in self.topic_state
143
149
  }
144
150
 
151
+ if not received_all:
152
+ missing_topics = self._expected_topics - self._received_retained
153
+ logger.info(
154
+ f"No retained messages received within {timeout} seconds "
155
+ f"for topics: {sorted(missing_topics)}"
156
+ )
157
+
158
+ return retained_dataset
159
+
145
160
 
146
161
  def publish(self, topic: str, payload: str, qos: int = 1, retain: bool = True):
147
162
  """
@@ -151,7 +166,11 @@ class SwitchRealtime:
151
166
  result = self.client.publish(topic, payload, qos=qos, retain=retain)
152
167
 
153
168
  if result.rc != mqtt.MQTT_ERR_SUCCESS:
154
- logger.error(f"Failed to publish to {topic} : {json.loads(payload)}")
169
+ message = f"Failed to publish to {topic}. MQTT result code: {result.rc}"
170
+ logger.error(message)
171
+ raise RuntimeError(message)
172
+
173
+ return result
155
174
 
156
175
 
157
176
 
@@ -202,4 +221,4 @@ class SwitchRealtime:
202
221
  self._retained_event.set()
203
222
 
204
223
  else:
205
- logger.info(f'MQTT Message - {topic} : {payload}')
224
+ logger.info(f'MQTT Message - {topic} : {payload}')