switch-api 0.6.14__tar.gz → 0.6.15__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 (70) hide show
  1. {switch_api-0.6.14 → switch_api-0.6.15}/HISTORY.md +14 -0
  2. {switch_api-0.6.14 → switch_api-0.6.15}/PKG-INFO +15 -1
  3. {switch_api-0.6.14 → switch_api-0.6.15}/setup.py +1 -1
  4. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/__init__.py +1 -1
  5. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_utils/_constants.py +8 -6
  6. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_utils/_utils.py +10 -0
  7. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/pipeline/__init__.py +2 -2
  8. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/pipeline/automation.py +338 -4
  9. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/pipeline/pipeline.py +10 -0
  10. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api.egg-info/PKG-INFO +15 -1
  11. {switch_api-0.6.14 → switch_api-0.6.15}/AUTHORS.rst +0 -0
  12. {switch_api-0.6.14 → switch_api-0.6.15}/LICENCE +0 -0
  13. {switch_api-0.6.14 → switch_api-0.6.15}/MANIFEST.in +0 -0
  14. {switch_api-0.6.14 → switch_api-0.6.15}/README.md +0 -0
  15. {switch_api-0.6.14 → switch_api-0.6.15}/pyproject.toml +0 -0
  16. {switch_api-0.6.14 → switch_api-0.6.15}/setup.cfg +0 -0
  17. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_authentication/__init__.py +0 -0
  18. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_authentication/_authentication.py +0 -0
  19. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_authentication/_credentials_store/__init__.py +0 -0
  20. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_authentication/_credentials_store/_credentials_store.py +0 -0
  21. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_authentication/_msal/__init__.py +0 -0
  22. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_authentication/_msal/_custom_application.py +0 -0
  23. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_compute/__init__.py +0 -0
  24. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_compute/_commands.py +0 -0
  25. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_compute/_compute.py +0 -0
  26. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_compute/_iotservice.py +0 -0
  27. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_compute/_models.py +0 -0
  28. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_compute/_utils.py +0 -0
  29. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_guide/__init__.py +0 -0
  30. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_guide/main.py +0 -0
  31. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_guide/processor.py +0 -0
  32. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_utils/__init__.py +0 -0
  33. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_utils/_marketplace.py +0 -0
  34. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_utils/_mqtt.py +0 -0
  35. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/_utils/_platform.py +0 -0
  36. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/analytics/__init__.py +0 -0
  37. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/analytics/analytics.py +0 -0
  38. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/cache/__init__.py +0 -0
  39. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/cache/cache.py +0 -0
  40. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/controls/__init__.py +0 -0
  41. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/controls/_constants.py +0 -0
  42. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/controls/_enums.py +0 -0
  43. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/controls/_mqtt.py +0 -0
  44. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/controls/controls.py +0 -0
  45. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/dataset/__init__.py +0 -0
  46. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/dataset/dataset.py +0 -0
  47. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/email/__init__.py +0 -0
  48. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/email/email_sender.py +0 -0
  49. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/error_handlers/__init__.py +0 -0
  50. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/error_handlers/error_handlers.py +0 -0
  51. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/extensions/__init__.py +0 -0
  52. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/extensions/extensions.py +0 -0
  53. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/extensions/field_meta.py +0 -0
  54. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/extensions/helpers.py +0 -0
  55. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/extensions/pipeline.py +0 -0
  56. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/initialize.py +0 -0
  57. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/integration/__init__.py +0 -0
  58. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/integration/_utils.py +0 -0
  59. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/integration/helpers.py +0 -0
  60. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/integration/integration.py +0 -0
  61. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/pipeline/definitions.py +0 -0
  62. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/platform_insights/__init__.py +0 -0
  63. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/platform_insights/platform_insights.py +0 -0
  64. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/realtime/__init__.py +0 -0
  65. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/realtime/_mqtt.py +0 -0
  66. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api/realtime/realtime.py +0 -0
  67. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api.egg-info/SOURCES.txt +0 -0
  68. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api.egg-info/dependency_links.txt +0 -0
  69. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api.egg-info/requires.txt +0 -0
  70. {switch_api-0.6.14 → switch_api-0.6.15}/switch_api.egg-info/top_level.txt +0 -0
@@ -1,5 +1,19 @@
1
1
  # History
2
2
 
3
+ #0.6.15
4
+ ### Added
5
+ In the `automation` module:
6
+ - Added `send_cds_command()` to allow programmatic execution of CDS action commands (`Start`, `Stop`, `Restart`) for a specified data feed.
7
+ - Added `deploy_as_cds_data_feed()` to support deployment of custom drivers as CDS-triggered data feeds with settings.
8
+ - Added `deploy_as_webhooks_data_feed()` to enable deployment of custom drivers as webhook-triggered data feeds with support for client signing secrets, task priority, and framework configuration.
9
+
10
+ In the `utils` module:
11
+ - Added `ensure_uuid()` helper function to validate and normalize UUID inputs, supporting both `uuid.UUID` objects and UUID strings.
12
+
13
+ ### Modified
14
+ In the `constants` module:
15
+ - Extended `TASK_PRIORITY` to include new priority levels `control` and `lendlease-beot`.
16
+
3
17
  #0.6.14
4
18
  ### Modified
5
19
  In the `integration` module:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: switch_api
3
- Version: 0.6.14
3
+ Version: 0.6.15
4
4
  Summary: A complete package for data ingestion into the Switch Automation Platform.
5
5
  Home-page: UNKNOWN
6
6
  Author: Switch Automation Pty Ltd.
@@ -38,6 +38,20 @@ pip install switch_api
38
38
 
39
39
  # History
40
40
 
41
+ #0.6.15
42
+ ### Added
43
+ In the `automation` module:
44
+ - Added `send_cds_command()` to allow programmatic execution of CDS action commands (`Start`, `Stop`, `Restart`) for a specified data feed.
45
+ - Added `deploy_as_cds_data_feed()` to support deployment of custom drivers as CDS-triggered data feeds with settings.
46
+ - Added `deploy_as_webhooks_data_feed()` to enable deployment of custom drivers as webhook-triggered data feeds with support for client signing secrets, task priority, and framework configuration.
47
+
48
+ In the `utils` module:
49
+ - Added `ensure_uuid()` helper function to validate and normalize UUID inputs, supporting both `uuid.UUID` objects and UUID strings.
50
+
51
+ ### Modified
52
+ In the `constants` module:
53
+ - Extended `TASK_PRIORITY` to include new priority levels `control` and `lendlease-beot`.
54
+
41
55
  #0.6.14
42
56
  ### Modified
43
57
  In the `integration` module:
@@ -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.14",
19
+ version="0.6.15",
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',
@@ -42,4 +42,4 @@ from . import _guide
42
42
  # log.addHandler(_ch)
43
43
  # https://dev.to/joaomcteixeira/setting-up-python-logging-for-a-library-app-6ml
44
44
 
45
- __version__ = "0.6.14"
45
+ __version__ = "0.6.15"
@@ -31,10 +31,10 @@ DATETIME_COL_FMT = Literal['DateTime', 'Date', 'Time']
31
31
  # 'Split year and month in 2 columns',
32
32
  # 'Start and End Date in 2 columns']
33
33
 
34
- EXPECTED_DELIVERY = Literal['5min', '15min', 'Hourly', 'Daily', 'Weekly', 'Monthly', 'Quarterly']
34
+ EXPECTED_DELIVERY = Literal[ 'AdHoc', '1min', '5min', '15min', 'Hourly', 'Daily', 'Weekly', 'Monthly', 'Quarterly']
35
35
 
36
36
  # deploy_type 'File' for (FTP. Email, Upload)
37
- DEPLOY_TYPE = Literal['Email', 'Ftp', 'Upload', 'Timer']
37
+ DEPLOY_TYPE = Literal['Email', 'Ftp', 'Upload', 'Timer', 'CDS']
38
38
 
39
39
  ACCOUNT = Literal['SwitchStorage', 'SwitchContainer', 'Argus', 'DataIngestion', 'GatewayMqttStorage']
40
40
 
@@ -45,11 +45,11 @@ QUERY_LANGUAGE = Literal['sql', 'kql']
45
45
  RESPONSE_TYPE = Literal['dataframe', 'json', 'string']
46
46
 
47
47
  ERROR_TYPE = Literal['DateTime', 'MissingDevices', 'NonNumeric', 'MissingRequiredField(s)', 'MissingSite(s)',
48
- 'DuplicateRecords', 'MissingSensors', 'UnableToReadFile', 'InvalidFileType', 'InvalidInputSettings', 'ReadingSummariesTimeout']
48
+ 'DuplicateRecords', 'MissingSensors', 'UnableToReadFile', 'InvalidFileType', 'InvalidInputSettings', 'ReadingSummariesTimeout', 'InvalidDataType', 'NullValues']
49
49
 
50
50
  PROCESS_STATUS = Literal['ActionRequired', 'Failed']
51
51
 
52
- MAPPING_ENTITIES = Literal['Installations', 'Devices/Sensors', 'Readings', 'Work Orders', 'IQ']
52
+ MAPPING_ENTITIES = Literal['Installations', 'Devices/Sensors', 'Readings', 'Work Orders', 'IQ', 'CustomData', 'Benchmarks']
53
53
 
54
54
  WORK_ORDER_CATEGORY = Literal['Preventative Maintenance', 'Tenant Request']
55
55
 
@@ -81,9 +81,9 @@ DATA_SET_QUERY_PARAMETER_TYPES = Literal['String', 'Number', 'DateTime', 'Keywor
81
81
 
82
82
  ADX_TABLE_DEF_TYPES = Literal['object', 'dynamic', 'int32', 'int64', 'float32', 'float64', 'datetime', 'string']
83
83
 
84
- TASK_PRIORITY = Literal['default', 'standard', 'advanced']
84
+ TASK_PRIORITY = Literal['default', 'standard', 'advanced', 'control', 'lendlease-beot']
85
85
 
86
- TASK_FRAMEWORK = Literal['PythonScriptFramework', 'TaskInsightsEngine']
86
+ TASK_FRAMEWORK = Literal['PythonScriptFramework', 'TaskInsightsEngine', 'CDSEngine']
87
87
 
88
88
  AMORTISATION_METHOD = Literal['Exclusive', 'Inclusive']
89
89
 
@@ -95,6 +95,8 @@ GUIDES_SCOPE = Literal['Portfolio-wide']
95
95
 
96
96
  IMPORT_STATUS = Literal['New', 'ToImport', 'OnHold', 'Exclude', 'Edited', 'ToRemove']
97
97
 
98
+ CDS_ACTION_COMMAND = Literal["Start", "Stop", "Restart"]
99
+
98
100
  WS_DEFAULT_PORT = 443
99
101
  WS_MQTT_CONNECTION_TIMEOUT = 180
100
102
  WS_MQTT_DEFAULT_MAX_TIMEOUT = 180
@@ -521,3 +521,13 @@ def requests_retry_session2(retries: int = 5, status_forcelist: List[int] = [408
521
521
  session.mount('https://', adapter=adapter)
522
522
  session.retries = retries
523
523
  return session
524
+
525
+ def ensure_uuid(value, field_name):
526
+ if isinstance(value, uuid.UUID):
527
+ return value
528
+ if isinstance(value, str):
529
+ try:
530
+ return uuid.UUID(value)
531
+ except ValueError:
532
+ pass
533
+ raise ValueError(f"{field_name} must be a valid UUID or UUID string.")
@@ -75,13 +75,13 @@ functions for retrieving details of existing tasks on the Switch Automation Plat
75
75
  """
76
76
 
77
77
  from .pipeline import (IntegrationTask, QueueTask, LogicModuleTask, AnalyticsTask, DiscoverableIntegrationTask,
78
- EventWorkOrderTask, BlobTask, Guide, IQTask, logger)
78
+ EventWorkOrderTask, BlobTask, Guide, IQTask, CDSTask, logger)
79
79
  from .automation import Automation
80
80
  from .definitions import (IntegrationDeviceConfigPropertyDefinition, IntegrationDeviceDefinition,
81
81
  AnalyticsSettings, EventWorkOrderFieldDefinition, IntegrationSettings)
82
82
 
83
83
 
84
84
  __all__ = ['IntegrationTask', 'QueueTask', 'LogicModuleTask', 'AnalyticsTask', 'DiscoverableIntegrationTask',
85
- 'EventWorkOrderTask', 'BlobTask', 'Guide', 'IQTask', 'Automation', 'logger', 'IntegrationDeviceConfigPropertyDefinition',
85
+ 'EventWorkOrderTask', 'BlobTask', 'Guide', 'IQTask', 'CDSTask', 'Automation', 'logger', 'IntegrationDeviceConfigPropertyDefinition',
86
86
  'IntegrationDeviceDefinition', 'EventWorkOrderFieldDefinition',
87
87
  'AnalyticsSettings', 'IntegrationSettings']
@@ -23,12 +23,12 @@ import sys
23
23
  import uuid
24
24
  from io import StringIO
25
25
  from azure.servicebus import ServiceBusClient, ServiceBusReceiveMode
26
- from .._utils._utils import _is_valid_regex, ApiInputs, _column_name_cap, DataFeedFileProcessOutput
26
+ from .._utils._utils import _is_valid_regex, ApiInputs, _column_name_cap, DataFeedFileProcessOutput, ensure_uuid
27
27
  from .._utils._marketplace import add_marketplace_item
28
- from .._utils._constants import (argus_prefix, EXPECTED_DELIVERY, MAPPING_ENTITIES,
28
+ from .._utils._constants import (CDS_ACTION_COMMAND, argus_prefix, EXPECTED_DELIVERY, MAPPING_ENTITIES,
29
29
  QUEUE_NAME, ERROR_TYPE, SCHEDULE_TIMEZONE, DEPLOY_TYPE, TASK_PRIORITY, TASK_FRAMEWORK, GUIDES_EXTERNAL_TYPES, GUIDES_SCOPE)
30
30
  from .._utils._platform import _get_ingestion_service_bus_connection_string, Blob
31
- from .pipeline import (Task, QueueTask, IntegrationTask, LogicModuleTask, AnalyticsTask, EventWorkOrderTask,
31
+ from .pipeline import (CDSTask, Task, QueueTask, IntegrationTask, LogicModuleTask, AnalyticsTask, EventWorkOrderTask,
32
32
  DiscoverableIntegrationTask, BlobTask, Guide, IQTask)
33
33
  from ..extensions import ExtensionTask, replace_extensions_imports, has_extensions_support
34
34
  from .definitions import (IntegrationDeviceDefinition, EventWorkOrderFieldDefinition, AnalyticsSettings,
@@ -1040,6 +1040,227 @@ class Automation:
1040
1040
  df.columns = _column_name_cap(df.columns)
1041
1041
 
1042
1042
  return df
1043
+
1044
+ @staticmethod
1045
+ def deploy_as_cds_data_feed(task: Task,
1046
+ api_inputs: ApiInputs,
1047
+ data_feed_id: uuid.UUID,
1048
+ expected_delivery: EXPECTED_DELIVERY,
1049
+ settings: dict = {},
1050
+ data_feed_name: str = None):
1051
+ """
1052
+
1053
+ Parameters
1054
+ ----------
1055
+ task : Task
1056
+ The custom driver class created from the Abstract Base Class 'Task'
1057
+ api_inputs : ApiInputs
1058
+ Object returned by the initialize() function.
1059
+ data_feed_id : uuid.UUID
1060
+ The DataFeedId to update if existing, else will create a new record with the given value.
1061
+ expected_delivery : EXPECTED_DELIVERY
1062
+ The expected delivery frequency of the data.
1063
+ data_feed_name : str, Optional
1064
+ The name of the data feed (to be displayed in Task Insights UI). If not provided, will default to the
1065
+ task name.
1066
+ settings : dict
1067
+ List of settings used to deploy the driver. May containing information needed to access 3rd party api
1068
+ - e.g. URI, Username, Pwd, AccessToken, etc
1069
+
1070
+ Returns
1071
+ -------
1072
+ df : pandas.DataFrame
1073
+ Dataframe containing the details of the deployed https endpoint data feed.
1074
+
1075
+ """
1076
+ if not isinstance(task, CDSTask):
1077
+ logger.error(f"deploy_as_cds_data_feed only accepts tasks that implement CDSTask, got {type(task).__name__}")
1078
+ return pandas.DataFrame()
1079
+
1080
+ if api_inputs.api_base_url == '' or api_inputs.bearer_token == '':
1081
+ logger.error("You must call initialize() before using API.")
1082
+ return pandas.DataFrame()
1083
+
1084
+ if data_feed_id is None and data_feed_name is None:
1085
+ data_feed_id = uuid.UUID('00000000-0000-0000-0000-000000000000')
1086
+ data_feed_name = task.__class__.__qualname__
1087
+ elif data_feed_id is not None and data_feed_name is None:
1088
+ data_feed_id = data_feed_id
1089
+ data_feed_name = data_feed_name
1090
+
1091
+ if not set([expected_delivery]).issubset(set(EXPECTED_DELIVERY.__args__)):
1092
+ logger.error('expected_delivery parameter must be set to one of the allowed values defined by the '
1093
+ 'EXPECTED_DELIVERY literal: %s', EXPECTED_DELIVERY.__args__)
1094
+ return pandas.DataFrame()
1095
+
1096
+ if settings and not isinstance(settings, dict):
1097
+ logger.error("setting parameter should be of type dict.")
1098
+ return pandas.DataFrame()
1099
+
1100
+ headers = api_inputs.api_headers.default
1101
+
1102
+ logger.info('Deploy %s (%s) as a data feed for ApiProjectID: %s', type(task).__name__, str(task.id), api_inputs.api_project_id)
1103
+
1104
+ task_framework: TASK_FRAMEWORK = 'CDSEngine'
1105
+ task_priority: TASK_PRIORITY = 'standard'
1106
+
1107
+ logger.info('Task Framework is "%s"', str(task_framework))
1108
+ logger.info('Task Priotiy is "%s"', str(task_priority))
1109
+
1110
+ payload = {
1111
+ "driverId": str(task.id),
1112
+ "dataFeedId": str(data_feed_id),
1113
+ "name": data_feed_name,
1114
+ "feedType": "CDS",
1115
+ "sourceType": "CDS",
1116
+ "expectedDelivery": expected_delivery,
1117
+ "taskFramework": task_framework,
1118
+ "taskPriority": task_priority,
1119
+ "settings": settings
1120
+ }
1121
+
1122
+ url = f"{api_inputs.api_projects_endpoint}/{api_inputs.api_project_id}/data-ingestion/tasks/deployment"
1123
+
1124
+ logger.info("Sending request: POST %s", url)
1125
+
1126
+ response = requests.post(url, json=payload, headers=headers)
1127
+ response_status = '{} {}'.format(response.status_code, response.reason)
1128
+ if response.status_code != 200:
1129
+ logger.error("API Call was not successful. Response Status: %s. Reason: %s.", response.status_code,
1130
+ response.reason)
1131
+ return response_status, pandas.DataFrame()
1132
+ elif len(response.text) == 0:
1133
+ logger.error('No data returned for this API call. %s', response.request.url)
1134
+ return response_status, pandas.DataFrame()
1135
+
1136
+ df = pandas.read_json(StringIO(response.text), typ='Series').to_frame().T
1137
+ df.columns = _column_name_cap(df.columns)
1138
+
1139
+ return df
1140
+
1141
+ @staticmethod
1142
+ def deploy_as_webhooks_data_feed(task: Task,
1143
+ api_inputs: ApiInputs,
1144
+ data_feed_id: uuid.UUID,
1145
+ client_signing_secret: str,
1146
+ expected_delivery: EXPECTED_DELIVERY,
1147
+ settings: dict = {},
1148
+ data_feed_name: str = None,
1149
+ task_priority: TASK_PRIORITY = 'standard',
1150
+ task_framework: TASK_FRAMEWORK = 'TaskInsightsEngine'):
1151
+
1152
+ """Deploy the custom driver as a Webhook triggered Data Feed.
1153
+
1154
+ Parameters
1155
+ ----------
1156
+ task : Task
1157
+ The custom driver class created from the Abstract Base Class 'Task'
1158
+ api_inputs : ApiInputs
1159
+ Object returned by the initialize() function.
1160
+ data_feed_id : uuid.UUID
1161
+ The DataFeedId to update if existing, else will create a new record with the given value.
1162
+ client_signing_secret : uuid.UUID
1163
+ Client Signing Secret
1164
+ expected_delivery : EXPECTED_DELIVERY
1165
+ The expected delivery frequency of the data.
1166
+ queue_name : QUEUE_NAME, optional
1167
+ The queue name (Default value = 'task').
1168
+ data_feed_name : str, Optional
1169
+ The name of the data feed (to be displayed in Task Insights UI). If not provided, will default to the
1170
+ task name.
1171
+ task_priority: TASK_PRIORITY
1172
+ Determines the priority of the datafeed tasks when processing. This equates to how much resources would be alloted
1173
+ to run the task - 'default`, 'standard', or 'advanced'. Defaults to 'default'.
1174
+ task_framework: TASK_FRAMEWORK
1175
+ Determines the framework of the datafeed tasks when processing.
1176
+ 'PythonScriptFramework' for the old task runner engine.
1177
+ 'TaskInsightsEngine' for the new task running in container apps.
1178
+ settings : dict
1179
+ List of settings used to deploy the driver. May containing information needed to access 3rd party api
1180
+ - e.g. URI, Username, Pwd, AccessToken, etc
1181
+
1182
+ Returns
1183
+ -------
1184
+ df : pandas.DataFrame
1185
+ Dataframe containing the details of the deployed webhook endpoint data feed.
1186
+
1187
+ """
1188
+
1189
+ if api_inputs.api_base_url == '' or api_inputs.bearer_token == '':
1190
+ logger.error("You must call initialize() before using API.")
1191
+ return pandas.DataFrame()
1192
+
1193
+ if data_feed_id is None and data_feed_name is None:
1194
+ data_feed_id = uuid.UUID('00000000-0000-0000-0000-000000000000')
1195
+ data_feed_name = task.__class__.__qualname__
1196
+ elif data_feed_id is not None and data_feed_name is None:
1197
+ data_feed_id = data_feed_id
1198
+ data_feed_name = data_feed_name
1199
+
1200
+ if client_signing_secret == '' or client_signing_secret == None:
1201
+ logger.error("Client Signing Secret cannot be null.")
1202
+ return pandas.DataFrame()
1203
+
1204
+ headers = api_inputs.api_headers.default
1205
+
1206
+ if not set([expected_delivery]).issubset(set(EXPECTED_DELIVERY.__args__)):
1207
+ logger.error('expected_delivery parameter must be set to one of the allowed values defined by the '
1208
+ 'EXPECTED_DELIVERY literal: %s', EXPECTED_DELIVERY.__args__)
1209
+ return pandas.DataFrame()
1210
+
1211
+ if not set([task_framework]).issubset(set(TASK_FRAMEWORK.__args__)):
1212
+ logger.error('task_framework parameter must be set to one of the allowed values defined by the '
1213
+ 'TASK_FRAMEWORK literal: %s', TASK_FRAMEWORK.__args__)
1214
+ return pandas.DataFrame()
1215
+
1216
+ if not set([task_priority]).issubset(set(TASK_PRIORITY.__args__)):
1217
+ logger.error('task_priority parameter must be set to one of the allowed values defined by the '
1218
+ 'TASK_PRIORITY literal: %s', TASK_PRIORITY.__args__)
1219
+ return pandas.DataFrame()
1220
+
1221
+ if settings and not isinstance(settings, dict):
1222
+ logger.error("setting parameter should be of type dict.")
1223
+ return pandas.DataFrame()
1224
+
1225
+ logger.info('Deploy %s (%s) as a data feed for ApiProjectID: %s', type(task).__name__, str(task.id),
1226
+ api_inputs.api_project_id)
1227
+ logger.info('Task Framework is "%s"', str(task_framework))
1228
+ logger.info('Task Priotiy is "%s"', str(task_priority))
1229
+
1230
+ payload = {
1231
+ "dataFeedId": str(data_feed_id),
1232
+ "driverId": str(task.id),
1233
+ "name": data_feed_name,
1234
+ "feedType": "Webhooks",
1235
+ "expectedDelivery": expected_delivery,
1236
+ "sourceType": "Webhooks",
1237
+ "taskFramework": task_framework,
1238
+ "taskPriority": task_priority,
1239
+ "webhook": {
1240
+ "clientSigningSecret": client_signing_secret
1241
+ },
1242
+ "settings": settings
1243
+ }
1244
+
1245
+ url = f"{api_inputs.api_projects_endpoint}/{api_inputs.api_project_id}/data-ingestion/tasks/deployment"
1246
+
1247
+ logger.info("Sending request: POST %s", url)
1248
+
1249
+ response = requests.post(url, json=payload, headers=headers)
1250
+ response_status = '{} {}'.format(response.status_code, response.reason)
1251
+
1252
+ if response.status_code != 200:
1253
+ logger.error("API Call was not successful. Response Status: %s. Reason: %s.", response.status_code,
1254
+ response.reason)
1255
+ return response_status, pandas.DataFrame()
1256
+ elif len(response.text) == 0:
1257
+ logger.error('No data returned for this API call. %s', response.request.url)
1258
+ return response_status, pandas.DataFrame()
1259
+
1260
+ df = pandas.read_json(StringIO(response.text), typ='Series').to_frame().T
1261
+ df.columns = _column_name_cap(df.columns)
1262
+
1263
+ return df
1043
1264
 
1044
1265
  @staticmethod
1045
1266
  def deploy_as_blob_data_feed(task: BlobTask,
@@ -1377,6 +1598,10 @@ class Automation:
1377
1598
  # Check if there's any timer deploy_type being cancelled, delete it as well by calling delete method
1378
1599
  if any("Timer" in deploy_type for deploy_type in deployment_type):
1379
1600
  return Automation.delete_deployed_data_feed(api_inputs=api_inputs, data_feed_id=data_feed_id)
1601
+
1602
+ # Check if there's any CDS deploy_type being cancelled, send `STOP` action command by calling send_cds_command()
1603
+ if any("CDS" in deploy_type for deploy_type in deployment_type):
1604
+ return Automation.send_cds_command(api_inputs=api_inputs, data_feed_id=data_feed_id, action_command='Stop')
1380
1605
 
1381
1606
  df = pandas.read_json(response.text, typ='Series').to_frame().T
1382
1607
  df.columns = _column_name_cap(df.columns)
@@ -1385,7 +1610,8 @@ class Automation:
1385
1610
 
1386
1611
  @staticmethod
1387
1612
  def delete_deployed_data_feed(api_inputs: ApiInputs, data_feed_id: uuid.UUID):
1388
- """Delete deployment for a given `data_feed_id`.
1613
+ """
1614
+ Delete timer schedule for a given `data_feed_id`.
1389
1615
 
1390
1616
  Parameters
1391
1617
  ----------
@@ -1813,3 +2039,111 @@ class Automation:
1813
2039
  task_code += 'task = ' + task_type.__name__ + '()'
1814
2040
 
1815
2041
  return task_code
2042
+
2043
+ @staticmethod
2044
+ def send_cds_command(
2045
+ api_inputs: ApiInputs,
2046
+ data_feed_id: uuid.UUID,
2047
+ action_command: CDS_ACTION_COMMAND):
2048
+ """Send a CDS action command for a given data feed.
2049
+
2050
+ This method sends a POST request to the CDS action-command endpoint for the
2051
+ specified data feed. It validates the required API inputs and command values
2052
+ before building the request payload and submitting it to the API.
2053
+
2054
+ Parameters
2055
+ ----------
2056
+ api_inputs : ApiInputs
2057
+ Object returned by the initialize.initialize() function. This must contain
2058
+ a valid ``api_base_url``, ``bearer_token``, ``api_project_id``,
2059
+ ``api_projects_endpoint``, and default request headers.
2060
+ data_feed_id : uuid.UUID
2061
+ The unique identifier of the data feed that the CDS command will be sent to.
2062
+ action_command : CDS_ACTION_COMMAND
2063
+ The action command to execute for the target data feed. This must be set
2064
+ to one of the allowed literal values defined by ``CDS_ACTION_COMMAND``.
2065
+
2066
+ Returns
2067
+ -------
2068
+ pandas.DataFrame
2069
+ A dataframe containing the API response when the request is successful.
2070
+
2071
+ tuple[str, pandas.DataFrame]
2072
+ Returns a tuple of ``(response_status, empty_dataframe)`` when the API call
2073
+ is not successful.
2074
+
2075
+ pandas.DataFrame
2076
+ Returns an empty dataframe when API inputs are not initialized or when no
2077
+ data is returned from the API.
2078
+
2079
+ Raises
2080
+ ------
2081
+ ValueError
2082
+ Raised when:
2083
+ - ``api_inputs`` is None
2084
+ - ``data_feed_id`` is not a valid ``uuid.UUID``
2085
+ - ``action_command`` is not one of the allowed ``CDS_ACTION_COMMAND`` values
2086
+
2087
+ Notes
2088
+ -----
2089
+ This method performs the following steps:
2090
+
2091
+ 1. Validates the required inputs.
2092
+ 2. Builds the request payload using the API project ID, data feed ID,
2093
+ and action command.
2094
+ 3. Sends a POST request to the CDS action-command API endpoint.
2095
+ 4. Converts the JSON response into a single-row pandas dataframe.
2096
+ 5. Normalizes the dataframe column names using ``_column_name_cap``.
2097
+
2098
+ Examples
2099
+ --------
2100
+ >>> df = sw.pipeline.Automation._send_cds_command(
2101
+ ... api_inputs=api_inputs,
2102
+ ... data_feed_id=uuid.UUID("12345678-1234-1234-1234-123456789abc"),
2103
+ ... action_command="Start"
2104
+ ... )
2105
+
2106
+ """
2107
+
2108
+ # ensure data_feed_id is UUID
2109
+ data_feed_id = ensure_uuid(data_feed_id, "data_feed_id")
2110
+
2111
+ # Validate api_inputs
2112
+ if api_inputs is None:
2113
+ raise ValueError("api_inputs cannot be None.")
2114
+
2115
+ if not api_inputs.api_base_url or not api_inputs.bearer_token:
2116
+ logger.error("You must call initialize() before using API.")
2117
+ return pandas.DataFrame()
2118
+
2119
+ # Validate enums/literals
2120
+ if action_command not in CDS_ACTION_COMMAND.__args__:
2121
+ raise ValueError('action_command parameter must be set to one of the allowed values defined by the '
2122
+ 'CDS_ACTION_COMMAND literal: %s', CDS_ACTION_COMMAND.__args__)
2123
+
2124
+ payload = {
2125
+ "apiProjectId": api_inputs.api_project_id,
2126
+ "dataFeedId": str(data_feed_id),
2127
+ "actionCommand": action_command
2128
+ }
2129
+
2130
+ url = f"{api_inputs.api_projects_endpoint}/{api_inputs.api_project_id}/data-ingestion/data-feed/{data_feed_id}/cds/action-command"
2131
+
2132
+ logger.info("Sending request: POST %s", url)
2133
+
2134
+ headers = api_inputs.api_headers.default
2135
+ response = requests.post(url, json=payload, headers=headers)
2136
+ response_status = '{} {}'.format(response.status_code, response.reason)
2137
+
2138
+ if response.status_code != 200:
2139
+ logger.error("API Call was not successful. Response Status: %s. Reason: %s.", response.status_code,
2140
+ response.reason)
2141
+ return response_status, pandas.DataFrame()
2142
+ elif len(response.text) == 0:
2143
+ logger.error('No data returned for this API call. %s', response.request.url)
2144
+ return response_status, pandas.DataFrame()
2145
+
2146
+ df = pandas.read_json(StringIO(response.text), typ='Series').to_frame().T
2147
+ df.columns = _column_name_cap(df.columns)
2148
+
2149
+ return df
@@ -621,6 +621,16 @@ class LogicModuleTask(Task):
621
621
 
622
622
  # Needs to be implemented into the deploy_on_timer
623
623
 
624
+ class CDSTask(Task):
625
+ """
626
+ Base class for long-running CDS driver tasks.
627
+
628
+ Implement process_loop() for continuous execution / polling / worker-style behavior.
629
+ """
630
+
631
+ @abstractmethod
632
+ def process_loop(self, api_inputs, settings):
633
+ pass
624
634
 
625
635
  class RunAt(enum.Enum):
626
636
  """ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: switch-api
3
- Version: 0.6.14
3
+ Version: 0.6.15
4
4
  Summary: A complete package for data ingestion into the Switch Automation Platform.
5
5
  Home-page: UNKNOWN
6
6
  Author: Switch Automation Pty Ltd.
@@ -38,6 +38,20 @@ pip install switch_api
38
38
 
39
39
  # History
40
40
 
41
+ #0.6.15
42
+ ### Added
43
+ In the `automation` module:
44
+ - Added `send_cds_command()` to allow programmatic execution of CDS action commands (`Start`, `Stop`, `Restart`) for a specified data feed.
45
+ - Added `deploy_as_cds_data_feed()` to support deployment of custom drivers as CDS-triggered data feeds with settings.
46
+ - Added `deploy_as_webhooks_data_feed()` to enable deployment of custom drivers as webhook-triggered data feeds with support for client signing secrets, task priority, and framework configuration.
47
+
48
+ In the `utils` module:
49
+ - Added `ensure_uuid()` helper function to validate and normalize UUID inputs, supporting both `uuid.UUID` objects and UUID strings.
50
+
51
+ ### Modified
52
+ In the `constants` module:
53
+ - Extended `TASK_PRIORITY` to include new priority levels `control` and `lendlease-beot`.
54
+
41
55
  #0.6.14
42
56
  ### Modified
43
57
  In the `integration` module:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes