switch-api 0.6.13__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.
- {switch_api-0.6.13 → switch_api-0.6.15}/HISTORY.md +22 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/PKG-INFO +23 -1
- {switch_api-0.6.13 → switch_api-0.6.15}/setup.py +1 -1
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/__init__.py +1 -1
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_utils/_constants.py +8 -6
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_utils/_utils.py +10 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/integration/integration.py +35 -16
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/pipeline/__init__.py +2 -2
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/pipeline/automation.py +338 -4
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/pipeline/pipeline.py +10 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api.egg-info/PKG-INFO +23 -1
- {switch_api-0.6.13 → switch_api-0.6.15}/AUTHORS.rst +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/LICENCE +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/MANIFEST.in +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/README.md +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/pyproject.toml +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/setup.cfg +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_authentication/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_authentication/_authentication.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_authentication/_credentials_store/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_authentication/_credentials_store/_credentials_store.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_authentication/_msal/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_authentication/_msal/_custom_application.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_compute/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_compute/_commands.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_compute/_compute.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_compute/_iotservice.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_compute/_models.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_compute/_utils.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_guide/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_guide/main.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_guide/processor.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_utils/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_utils/_marketplace.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_utils/_mqtt.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/_utils/_platform.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/analytics/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/analytics/analytics.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/cache/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/cache/cache.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/controls/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/controls/_constants.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/controls/_enums.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/controls/_mqtt.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/controls/controls.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/dataset/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/dataset/dataset.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/email/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/email/email_sender.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/error_handlers/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/error_handlers/error_handlers.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/extensions/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/extensions/extensions.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/extensions/field_meta.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/extensions/helpers.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/extensions/pipeline.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/initialize.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/integration/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/integration/_utils.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/integration/helpers.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/pipeline/definitions.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/platform_insights/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/platform_insights/platform_insights.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/realtime/__init__.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/realtime/_mqtt.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api/realtime/realtime.py +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api.egg-info/SOURCES.txt +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api.egg-info/dependency_links.txt +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api.egg-info/requires.txt +0 -0
- {switch_api-0.6.13 → switch_api-0.6.15}/switch_api.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,27 @@
|
|
|
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
|
+
|
|
17
|
+
#0.6.14
|
|
18
|
+
### Modified
|
|
19
|
+
In the `integration` module:
|
|
20
|
+
- Updated `add_carbon_column()` to enforce priority selection of carbon calculation rules.
|
|
21
|
+
- When overlapping date ranges exist for the same `ObjectPropertyId`, the rule with a non-null `ObjectPropertyTemplateID` is now preferred over the default (NULL) rule.
|
|
22
|
+
- Ensured only one carbon calculation is applied per input row to prevent duplicate records.
|
|
23
|
+
- Refactored logic to preserve original row count and avoid row loss during rule matching.
|
|
24
|
+
|
|
3
25
|
#0.6.13
|
|
4
26
|
### Added
|
|
5
27
|
In the `controls` module
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: switch_api
|
|
3
|
-
Version: 0.6.
|
|
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,28 @@ 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
|
+
|
|
55
|
+
#0.6.14
|
|
56
|
+
### Modified
|
|
57
|
+
In the `integration` module:
|
|
58
|
+
- Updated `add_carbon_column()` to enforce priority selection of carbon calculation rules.
|
|
59
|
+
- When overlapping date ranges exist for the same `ObjectPropertyId`, the rule with a non-null `ObjectPropertyTemplateID` is now preferred over the default (NULL) rule.
|
|
60
|
+
- Ensured only one carbon calculation is applied per input row to prevent duplicate records.
|
|
61
|
+
- Refactored logic to preserve original row count and avoid row loss during rule matching.
|
|
62
|
+
|
|
41
63
|
#0.6.13
|
|
42
64
|
### Added
|
|
43
65
|
In the `controls` 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.
|
|
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',
|
|
@@ -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.")
|
|
@@ -1855,6 +1855,7 @@ def upsert_timeseries(df: pandas.DataFrame, api_inputs: ApiInputs, is_local_time
|
|
|
1855
1855
|
df = df.assign(Carbon=0.0)
|
|
1856
1856
|
|
|
1857
1857
|
df = df.copy()
|
|
1858
|
+
df['__row_id'] = np.arange(len(df))
|
|
1858
1859
|
carbon_calcs_df = carbon_calculations_df.copy()
|
|
1859
1860
|
|
|
1860
1861
|
if 'ObjectPropertyID' in carbon_calcs_df.columns:
|
|
@@ -1864,7 +1865,7 @@ def upsert_timeseries(df: pandas.DataFrame, api_inputs: ApiInputs, is_local_time
|
|
|
1864
1865
|
if not pd.api.types.is_datetime64_any_dtype(df['TimestampLocal']):
|
|
1865
1866
|
df['TimestampLocal'] = pd.to_datetime(df['TimestampLocal'])
|
|
1866
1867
|
|
|
1867
|
-
required_carbon_cols = ['ObjectPropertyId', 'CarbonExpression', 'FromDate', 'ToDate']
|
|
1868
|
+
required_carbon_cols = ['ObjectPropertyId', 'CarbonExpression', 'FromDate', 'ToDate', 'ObjectPropertyTemplateID']
|
|
1868
1869
|
if not all(col in carbon_calcs_df.columns for col in required_carbon_cols):
|
|
1869
1870
|
logger.error(f"Missing required columns in carbon calculations DataFrame. Required: {required_carbon_cols}")
|
|
1870
1871
|
return df.assign(Carbon=0.0)
|
|
@@ -1877,29 +1878,47 @@ def upsert_timeseries(df: pandas.DataFrame, api_inputs: ApiInputs, is_local_time
|
|
|
1877
1878
|
carbon_calcs_df['ToDate'] = pd.to_datetime(carbon_calcs_df['ToDate'])
|
|
1878
1879
|
|
|
1879
1880
|
merged_df = pd.merge(df, carbon_calcs_df, on='ObjectPropertyId', how='left', indicator=True)
|
|
1880
|
-
no_valid_carbon_calcs = merged_df[merged_df._merge== 'left_only']
|
|
1881
1881
|
|
|
1882
1882
|
matches_mask = (merged_df['TimestampLocal'] >= merged_df['FromDate']) & \
|
|
1883
1883
|
(merged_df['TimestampLocal'] <= merged_df['ToDate'])
|
|
1884
|
-
valid_carbon_calcs = merged_df[matches_mask]
|
|
1885
1884
|
|
|
1886
|
-
|
|
1887
|
-
|
|
1885
|
+
valid_carbon_calcs = merged_df[matches_mask].copy()
|
|
1886
|
+
|
|
1887
|
+
if not valid_carbon_calcs.empty:
|
|
1888
|
+
# priority: non-NULL ObjectPropertyTemplateID first
|
|
1889
|
+
valid_carbon_calcs["__tmplt_priority"] = valid_carbon_calcs["ObjectPropertyTemplateID"].notna().astype(int)
|
|
1890
|
+
|
|
1891
|
+
# choose ONE calc row per original df row
|
|
1892
|
+
valid_carbon_calcs = (
|
|
1893
|
+
valid_carbon_calcs
|
|
1894
|
+
.sort_values(["__row_id", "__tmplt_priority"], ascending=[True, False])
|
|
1895
|
+
.drop_duplicates(subset="__row_id", keep="first")
|
|
1896
|
+
)
|
|
1897
|
+
|
|
1898
|
+
unique_expressions = valid_carbon_calcs["CarbonExpression"].dropna().unique()
|
|
1888
1899
|
for expr in unique_expressions:
|
|
1889
|
-
expr_mask = valid_carbon_calcs[
|
|
1890
|
-
numexpr_expression = expr.replace(
|
|
1891
|
-
valid_carbon_calcs.loc[expr_mask,
|
|
1892
|
-
numexpr_expression,
|
|
1893
|
-
local_dict
|
|
1900
|
+
expr_mask = valid_carbon_calcs["CarbonExpression"] == expr
|
|
1901
|
+
numexpr_expression = expr.replace("[Value]", "Value")
|
|
1902
|
+
valid_carbon_calcs.loc[expr_mask, "Carbon"] = numexpr.evaluate(
|
|
1903
|
+
numexpr_expression,
|
|
1904
|
+
local_dict={"Value": valid_carbon_calcs.loc[expr_mask, "Value"].values}
|
|
1894
1905
|
)
|
|
1895
|
-
|
|
1896
|
-
final_df = pd.concat([no_valid_carbon_calcs, valid_carbon_calcs])
|
|
1897
|
-
final_row_count = final_df.shape[0]
|
|
1898
1906
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1907
|
+
df = df.merge(
|
|
1908
|
+
valid_carbon_calcs[["__row_id", "Carbon"]],
|
|
1909
|
+
on="__row_id",
|
|
1910
|
+
how="left",
|
|
1911
|
+
suffixes=("", "_calc"),
|
|
1912
|
+
)
|
|
1913
|
+
df["Carbon"] = df["Carbon_calc"].fillna(df["Carbon"])
|
|
1914
|
+
df = df.drop(columns=["Carbon_calc"])
|
|
1915
|
+
|
|
1916
|
+
final_row_count = df.shape[0]
|
|
1917
|
+
logger.info(
|
|
1918
|
+
f"Original row count = {original_row_count}, final row count = {final_row_count}. Matches = {original_row_count==final_row_count}"
|
|
1919
|
+
)
|
|
1901
1920
|
|
|
1902
|
-
return
|
|
1921
|
+
return df.drop(columns=["__row_id"])
|
|
1903
1922
|
|
|
1904
1923
|
def create_meta_column(df, slice_columns, save_slices, cost_carbon_calculation):
|
|
1905
1924
|
"""Handles creating the 'Meta' column and optionally saves additional columns as slices."""
|
|
@@ -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
|
-
"""
|
|
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.
|
|
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,28 @@ 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
|
+
|
|
55
|
+
#0.6.14
|
|
56
|
+
### Modified
|
|
57
|
+
In the `integration` module:
|
|
58
|
+
- Updated `add_carbon_column()` to enforce priority selection of carbon calculation rules.
|
|
59
|
+
- When overlapping date ranges exist for the same `ObjectPropertyId`, the rule with a non-null `ObjectPropertyTemplateID` is now preferred over the default (NULL) rule.
|
|
60
|
+
- Ensured only one carbon calculation is applied per input row to prevent duplicate records.
|
|
61
|
+
- Refactored logic to preserve original row count and avoid row loss during rule matching.
|
|
62
|
+
|
|
41
63
|
#0.6.13
|
|
42
64
|
### Added
|
|
43
65
|
In the `controls` module
|
|
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.13 → switch_api-0.6.15}/switch_api/_authentication/_credentials_store/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{switch_api-0.6.13 → switch_api-0.6.15}/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
|