diff --git a/README.md b/README.md index 7e3dd757..b333c2ad 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,9 @@ print(json) ['2024-04-23T10:00:00', 86.57999999999997, 3]], 'version-date': None} ``` + +## TimeSeries Profile API Compatibility Warning + +Currently, the TimeSeries Profile API may not be fully supported +until a new version of cwms-data-access is released with the updated +endpoint implementation. diff --git a/cwms/__init__.py b/cwms/__init__.py index f1e9a48e..f8db7d6f 100644 --- a/cwms/__init__.py +++ b/cwms/__init__.py @@ -14,6 +14,9 @@ from cwms.timeseries.timerseries_identifier import * from cwms.timeseries.timeseries import * from cwms.timeseries.timeseries_bin import * +from cwms.timeseries.timeseries_profile import * +from cwms.timeseries.timeseries_profile_instance import * +from cwms.timeseries.timeseries_profile_parser import * from cwms.timeseries.timeseries_txt import * try: diff --git a/cwms/timeseries/timeseries_profile.py b/cwms/timeseries/timeseries_profile.py new file mode 100644 index 00000000..87e0ee73 --- /dev/null +++ b/cwms/timeseries/timeseries_profile.py @@ -0,0 +1,166 @@ +# Copyright (c) 2024 +# United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC) +# All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL. +# Source may not be released without written approval from HEC + +from typing import Optional + +import cwms.api as api +from cwms.cwms_types import Data + + +def get_timeseries_profile(office_id: str, location_id: str, parameter_id: str) -> Data: + """ + Retrieves a timeseries profile. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_id: string + The owning office of the timeseries profile + location_id: string + The location associated with the timeseries profile parser + parameter_id: string + Name of the key parameter associated with the timeseries profile + + Returns + ------- + cwms data type + """ + + endpoint = f"timeseries/profile/{location_id}/{parameter_id}" + params = { + "office": office_id, + } + + response = api.get(endpoint, params) + return Data(response) + + +def get_timeseries_profiles( + office_mask: Optional[str], + location_mask: Optional[str], + parameter_id_mask: Optional[str], + page: Optional[str] = None, + page_size: Optional[int] = 1000, +) -> Data: + """ + Retrieves all timeseries profiles that fit the provided masks. Does not include time series values. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_mask: string + A mask to limit the results based on office. Uses regex to compare with office IDs in the database. + Default value is '*' + location_mask: string + A mask to limit the results based on location. Uses regex to compare with location IDs in the database. + Default value is '*' + parameter_id_mask: string + A mask to limit the results based on the parameter associated with the timeseries profile. Uses regex to + compare the parameter IDs in the database. Default value is '*' + + Returns + ------- + cwms data type + """ + + endpoint = "timeseries/profile" + params = { + "office-mask": office_mask, + "location-mask": location_mask, + "parameter-id-mask": parameter_id_mask, + "page": page, + "page-size": page_size, + } + + response = api.get(endpoint, params) + return Data(response) + + +def delete_timeseries_profile( + office_id: str, parameter_id: str, location_id: str +) -> None: + """ + Deletes a specified timeseries profile + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_id: string + The owning office of the timeseries profile + parameter_id: string + Name of the key parameter associated with the timeseries profile + location_id: string + The location associated with the timeseries profile + + Returns + ------- + None + """ + + endpoint = f"timeseries/profile/{location_id}/{parameter_id}" + params = { + "office": office_id, + } + + return api.delete(endpoint, params) + + +def store_timeseries_profile(data: str, fail_if_exists: Optional[bool] = True) -> None: + """ + Stores a new timeseries profile + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + data: string + json for storing a timeseries profile + { + "description": "string", + "parameter-list": [ + "string", + ... + ], + "location-id": { + "office-id": "string", + "name": "string" + }, + "reference-ts-id": { + "office-id": "string", + "name": "string" + }, + "key-parameter": "string" + } + + fail_if_exists: boolean, optional + Throw a ClientError if the profile already exists + Default is `True` + + Returns + ------- + None + """ + + endpoint = "timeseries/profile" + params = { + "fail-if-exists": fail_if_exists, + } + + return api.post(endpoint, data, params) diff --git a/cwms/timeseries/timeseries_profile_instance.py b/cwms/timeseries/timeseries_profile_instance.py new file mode 100644 index 00000000..d696d2ff --- /dev/null +++ b/cwms/timeseries/timeseries_profile_instance.py @@ -0,0 +1,237 @@ +# Copyright (c) 2024 +# United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC) +# All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL. +# Source may not be released without written approval from HEC + +from datetime import datetime +from typing import Optional + +import cwms.api as api +from cwms.cwms_types import Data + + +def get_timeseries_profile_instance( + office_id: str, + location_id: str, + parameter_id: str, + version: str, + unit: str, + version_date: Optional[datetime], + start: Optional[datetime], + end: Optional[datetime], + page: Optional[str] = None, + page_size: Optional[int] = 500, + start_inclusive: Optional[bool] = True, + end_inclusive: Optional[bool] = True, + previous: Optional[bool] = False, + next: Optional[bool] = False, + max_version: Optional[bool] = False, +) -> Data: + """ + Returns a timeseries profile instance with associated timeseries values. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_id: string + The owning office of the timeseries profile instance + location_id: string + The location associated with the timeseries profile instance + parameter_id: string + The name of the key parameter associated with the timeseries profile instance + version: str + The version of the timeseries profile instance + unit: str + The requested units to use for the key parameter values of the timeseries profile instance + version_date: datetime, optional + The version date associated with the timeseries profile instance + start_inclusive: boolean, optional + Whether the returned timeseries profile instance should include data from the specified + start timestamp. Default is `True`. + end_inclusive: boolean, optional + Whether the returned timeseries profile instance should include data from the specified + end timestamp. Default is `True`. + previous: boolean, optional + The previous timeseries profile instance. Default is `False`. + next: boolean, optional + The next timeseries profile instance. Default is `False`. + max_version: boolean, optional + Whether the provided version is the maximum version of the timeseries profile instance. + Default is `False`. + start: datetime, optional + The start timestamp of the timeseries profile instance. Default is the year 1800. + end: datetime, optional + The end timestamp of the timeseries profile instance. Default is the year 3000. + page: string, optional + The page cursor of the timeseries profile instance. + page_size: string, optional + The number of timeseries profile instance data records retrieved as part of the instance. Default is `1000`. + + Returns + ------- + cwms data type + """ + + endpoint = f"timeseries/profile-instance/{location_id}/{parameter_id}/{version}" + params = { + "office": office_id, + "version-date": version_date.isoformat() if version_date else None, + "unit": unit, + "start-time-inclusive": start_inclusive, + "end-time-inclusive": end_inclusive, + "previous": previous, + "next": next, + "max-version": max_version, + "start": start.isoformat() if start else None, + "end": end.isoformat() if end else None, + "page": page, + "page-size": page_size, + } + + response = api.get(endpoint, params) + return Data(response) + + +def get_timeseries_profile_instances( + office_mask: Optional[str], + location_mask: Optional[str], + parameter_id_mask: Optional[str], + version_mask: Optional[str], +) -> Data: + """ + Retrieves a list of timeseries profile instances that match the specified masks. Does not return timeseries values. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_mask: string + A mask to limit the results based on office ID. Uses regex to compare with office IDs in the database. + Default value is `*` + location_mask: string + A mask to limit the results based on location ID. Uses regex to compare with location IDs in the database. + Default value is `*` + parameter_id_mask: string + A mask to limit the results based on the parameter associated with the timeseries profile instance. + Uses regex to compare the parameter IDs in the database. Default value is `*` + version_mask: string + A mask to limit the results based on the version associated with the timeseries profile instance. + Default value is `*` + + Returns + ------- + cwms data type + """ + + endpoint = "timeseries/profile-instance" + params = { + "office-mask": office_mask, + "location-mask": location_mask, + "parameter-id-mask": parameter_id_mask, + "version-mask": version_mask, + } + + response = api.get(endpoint, params) + return Data(response) + + +def delete_timeseries_profile_instance( + office_id: str, + location_id: str, + parameter_id: str, + version: str, + version_date: datetime, + first_date: datetime, + override_protection: Optional[bool] = True, +) -> None: + """ + Deletes a timeseries profile instance. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_id: string + The owning office of the timeseries profile instance + location_id: string + The name identifier for the timeseries profile instance to delete + parameter_id: string + The name of the key parameter associated with the timeseries profile instance + version: string + The version of the timeseries profile instance + version_date: datetime + The timestamp of the timeseries profile instance version + first_date: datetime + The first date of the timeseries profile instance + override_protection: boolean, optional + Whether to enable override protection for the timeseries profile instance. Default is `True`. + + Returns + ------- + None + """ + + endpoint = f"timeseries/profile-instance/{location_id}/{parameter_id}/{version}" + params = { + "office": office_id, + "version-date": version_date.isoformat() if version_date else None, + "date": first_date.isoformat() if first_date else None, + "override-protection": override_protection, + } + + return api.delete(endpoint, params) + + +def store_timeseries_profile_instance( + profile_data: str, + version: str, + version_date: datetime, + store_rule: Optional[str] = None, + override_protection: Optional[bool] = False, +) -> None: + """ + Stores a new timeseries profile instance. Requires timeseries profile and parser to already be stored. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + profile_data: string + The profile data of the timeseries profile instance + store_rule: boolean, optional + The method of storing the timeseries profile instance. Default is `REPLACE_ALL`. + version: string + The version of the timeseries profile instance. + version_date: datetime + The version date of the timeseries profile instance. + override_protection: boolean, optional + Whether to enable override protection for the timeseries profile instance. Default is `False`. + + Returns + ------- + None + """ + + endpoint = "timeseries/profile-instance" + params = { + "profile-data": profile_data, + "method": store_rule, + "version": version, + "version-date": version_date.isoformat() if version_date else None, + "override-protection": override_protection, + } + + return api.post(endpoint, None, params) diff --git a/cwms/timeseries/timeseries_profile_parser.py b/cwms/timeseries/timeseries_profile_parser.py new file mode 100644 index 00000000..1d844378 --- /dev/null +++ b/cwms/timeseries/timeseries_profile_parser.py @@ -0,0 +1,210 @@ +# Copyright (c) 2024 +# United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC) +# All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL. +# Source may not be released without written approval from HEC + +from typing import Optional + +import cwms.api as api +from cwms.cwms_types import Data + + +def get_timeseries_profile_parser( + office_id: str, location_id: str, parameter_id: str +) -> Data: + """ + Returns a timeseries profile parser used to interpret timeseries data input. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_id: string + The owning office of the timeseries profile parser + location_id: string + The location name associated with the timeseries profile parser + parameter_id: string + The name of the key parameter associated with the timeseries profile parser + + Returns + ------- + cwms data type + """ + + endpoint = f"timeseries/profile-parser/{location_id}/{parameter_id}" + params = { + "office": office_id, + } + + response = api.get(endpoint, params) + return Data(response) + + +def get_timeseries_profile_parsers( + office_mask: Optional[str], + location_mask: Optional[str], + parameter_id_mask: Optional[str], +) -> Data: + """ + Returns a list of timeseries profile parsers. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_mask: string, optional + A mask to limit the results based on office. Uses regex to compare with office IDs in the database. + Default value is '*' + location_mask: string, optional + A mask to limit the results based on location. Uses regex to compare with location IDs in the database. + Default value is '*' + parameter_id_mask: string, optional + A mask to limit the results based on the parameter associated with the timeseries profile. Uses regex to + compare the parameter IDs in the database. Default value is '*' + + Returns + ------- + cwms data type + """ + + endpoint = "timeseries/profile-parser" + params = { + "office-mask": office_mask, + "location-mask": location_mask, + "parameter-id-mask": parameter_id_mask, + } + + response = api.get(endpoint, params) + return Data(response) + + +def delete_timeseries_profile_parser( + office_id: str, location_id: str, parameter_id: str +) -> None: + """ + Deletes a specified timeseries profile parser + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + office_id: string + The owning office of the timeseries profile parser + location_id: string + The location associated with the timeseries profile parser + parameter_id: string + The name of the key parameter associated with the timeseries profile parser + + Returns + ------- + None + """ + + endpoint = f"timeseries/profile-parser/{location_id}/{parameter_id}" + params = {"office": office_id} + + return api.delete(endpoint, params) + + +def store_timeseries_profile_parser( + data: str, fail_if_exists: Optional[bool] = True +) -> None: + """ + Stores a new timeseries profile parser. + + Compatibility Warning: + Currently, the TimeSeries Profile API may not be fully supported + until a new version of cwms-data-access is released with the updated + endpoint implementation. + + Parameters + ---------- + data: string + JSON for storing a timeseries profile parser + + Indexed: + { + "type": "indexed-timeseries-profile-parser", + "location-id": { + "office-id": "string", + "name": "string" + }, + "key-parameter": "string", + "record-delimiter": "char", + "time-format": "MM/DD/YYYY,HH24:MI:SS", + "time-zone": "string", + "parameter-info-list": [ + { + "type": "indexed-parameter-info", + "parameter": "string", + "unit": "string", + "index": int + }, + { + "type": "indexed-parameter-info", + "parameter": "string", + "unit": "string", + "index": int + } + ], + "time-in-two-fields": bool, + "field-delimiter": "char", + "time-field": int + } + + Columnar: + { + "type": "columnar-timeseries-profile-parser", + "location-id": { + "office-id": "string", + "name": "string" + }, + "key-parameter": "string", + "record-delimiter": "char", + "time-format": "MM/DD/YYYY,HH24:MI:SS", + "time-zone": "string", + "parameter-info-list": [ + { + "type": "columnar-parameter-info", + "parameter": "string", + "unit": "string", + "start-column": int, + "end-column": int + }, + { + "type": "columnar-parameter-info", + "parameter": "string", + "unit": "string", + "start-column": int, + "end-column": int + } + ], + "time-in-two-fields": bool, + "time-start-column": int, + "time-end-column": int + } + + fail_if_exists: boolean, optional + Throw a ClientError if the parser already exists + Default is `True` + + Returns + ------- + None + """ + + endpoint = "timeseries/profile-parser" + params = { + "fail-if-exists": fail_if_exists, + } + + return api.post(endpoint, data, params) diff --git a/tests/resources/timeseries_profile.json b/tests/resources/timeseries_profile.json new file mode 100644 index 00000000..49913f5c --- /dev/null +++ b/tests/resources/timeseries_profile.json @@ -0,0 +1,16 @@ +{ + "description": "Description", + "parameter-list": [ + "Temp-Water", + "Depth" + ], + "location-id": { + "office-id": "SWT", + "name": "SWAN" + }, + "reference-ts-id": { + "office-id": "SWT", + "name": "SWAN.Elev.Inst.1Hour.0.DSS-Obs" + }, + "key-parameter": "Depth" +} \ No newline at end of file diff --git a/tests/resources/timeseries_profile_data.txt b/tests/resources/timeseries_profile_data.txt new file mode 100644 index 00000000..e41d591e --- /dev/null +++ b/tests/resources/timeseries_profile_data.txt @@ -0,0 +1,28 @@ +'sep=, Date,Time,Site,Unit ID,User ID,F,mmHg,DO %,DO mg/L,SPC-uS/cm,pH,NTU, +BGA-PCRFU-18H110486,BGA-PCug/L-18H110486,Chl RFU-18H110486,Chl ug/L-18H110486,DEP ft-18H105572,Batt V-18H111574,Lat-18H111574,Lon-18H111574, +09/09/2019,12:48:57,DET,,Holly Bellringer,67.108,719.6,98.3,9.03,43.9,8.54,0.19,0.21,-0.65,0.17,-0.01,3.152,5.35,44.71898,-122.24620, +09/09/2019,12:49:19,DET,,Holly Bellringer,67.563,719.6,97.2,8.88,43.4,8.43,0.12,0.21,-0.65,0.19,0.05,4.028,5.34,44.71898,-122.24620, +09/09/2019,12:52:54,DET,,Holly Bellringer,67.820,719.5,96.8,8.82,43.3,8.04,0.23,0.14,-0.71,0.20,0.10,6.264,5.32,44.71899,-122.24610, +09/09/2019,12:53:17,DET,,Holly Bellringer,67.767,719.6,96.6,8.81,43.3,8.02,0.12,0.15,-0.70,0.24,0.24,8.525,5.31,44.71900,-122.24610, +09/09/2019,12:54:42,DET,,Holly Bellringer,67.718,719.6,96.5,8.80,43.3,7.97,0.11,0.13,-0.72,0.25,0.29,10.789,5.31,44.71898,-122.24620, +09/09/2019,12:55:50,DET,,Holly Bellringer,67.700,719.6,96.4,8.79,43.3,7.95,0.08,0.17,-0.68,0.23,0.19,12.045,5.27,44.71898,-122.24620, +09/09/2019,12:56:27,DET,,Holly Bellringer,67.682,719.5,96.3,8.79,43.2,7.92,0.08,0.16,-0.70,0.24,0.24,14.188,5.28,44.71897,-122.24620, +09/09/2019,12:56:54,DET,,Holly Bellringer,67.669,719.5,96.2,8.78,43.2,7.92,0.17,0.10,-0.75,0.27,0.39,16.218,5.26,44.71897,-122.24620, +09/09/2019,12:57:48,DET,,Holly Bellringer,67.651,719.5,96.1,8.77,43.2,7.86,0.13,0.10,-0.75,0.22,0.16,18.303,5.26,44.71901,-122.24620, +09/09/2019,12:58:57,DET,,Holly Bellringer,67.613,719.6,95.8,8.75,43.2,7.86,0.05,0.15,-0.70,0.24,0.26,23.081,5.24,44.71901,-122.24620, +09/09/2019,12:59:31,DET,,Holly Bellringer,67.572,719.6,95.3,8.71,43.2,7.83,0.12,0.12,-0.74,0.30,0.48,28.262,5.26,44.71900,-122.24620, +09/09/2019,13:00:37,DET,,Holly Bellringer,67.557,719.6,95.1,8.69,43.2,7.82,0.07,0.11,-0.74,0.27,0.39,33.043,5.28,44.71904,-122.24610, +09/09/2019,13:01:08,DET,,Holly Bellringer,67.495,719.6,94.6,8.65,43.2,7.78,0.17,0.15,-0.71,0.26,0.35,38.527,5.27,44.71902,-122.24620, +09/09/2019,13:02:17,DET,,Holly Bellringer,67.003,719.7,91.4,8.40,43.4,7.74,0.14,0.26,-0.59,0.57,1.55,43.937,5.28,44.71902,-122.24620, +09/09/2019,13:02:51,DET,,Holly Bellringer,64.150,719.6,77.9,7.40,44.2,7.63,0.12,0.15,-0.70,0.33,0.62,48.191,5.27,44.71902,-122.24620, +09/09/2019,13:03:50,DET,,Holly Bellringer,62.782,719.6,71.1,6.86,44.2,7.49,0.13,0.26,-0.59,0.40,0.88,53.288,5.28,44.71902,-122.24620, +09/09/2019,13:04:50,DET,,Holly Bellringer,61.698,719.7,65.8,6.43,43.5,7.37,0.19,0.22,-0.63,0.37,0.76,58.457,5.28,44.71902,-122.24620, +09/09/2019,13:07:48,DET,,Holly Bellringer,54.818,719.8,61.8,6.56,39.4,7.12,0.67,0.18,-0.67,0.12,-0.23,88.792,5.29,44.71902,-122.24620, +09/09/2019,13:08:57,DET,,Holly Bellringer,52.003,719.8,68.4,7.53,37.9,7.07,0.08,0.28,-0.58,0.13,-0.19,103.598,5.28,44.71904,-122.24620, +09/09/2019,13:09:46,DET,,Holly Bellringer,49.363,719.8,73.9,8.42,37.4,7.05,0.17,0.23,-0.63,0.13,-0.19,118.692,5.30,44.71901,-122.24620, +09/09/2019,13:11:20,DET,,Holly Bellringer,47.156,719.8,76.6,8.98,37.2,7.03,0.28,0.28,-0.58,0.10,-0.29,133.659,5.32,44.71902,-122.24620, +09/09/2019,13:12:45,DET,,Holly Bellringer,45.468,719.8,75.9,9.11,37.0,7.00,0.43,0.30,-0.55,0.11,-0.26,148.541,5.31,44.71902,-122.24620, +09/09/2019,13:13:33,DET,,Holly Bellringer,44.743,719.8,75.4,9.13,37.0,6.99,0.52,0.35,-0.50,0.10,-0.30,163.665,5.31,44.71900,-122.24620, +09/09/2019,13:14:49,DET,,Holly Bellringer,44.135,719.8,75.5,9.22,36.9,6.98,0.67,0.30,-0.55,0.13,-0.20,178.560,5.31,44.71901,-122.24620, +09/09/2019,13:16:01,DET,,Holly Bellringer,43.701,719.8,75.7,9.30,36.9,6.97,0.88,0.31,-0.54,0.08,-0.38,193.010,5.32,44.71903,-122.24620, +09/09/2019,13:17:20,DET,,Holly Bellringer,43.390,719.8,75.6,9.33,36.9,6.96,1.00,0.32,-0.53,0.10,-0.31,208.607,5.31,44.71902,-122.24620,' diff --git a/tests/resources/timeseries_profile_indexed.json b/tests/resources/timeseries_profile_indexed.json new file mode 100644 index 00000000..864f7312 --- /dev/null +++ b/tests/resources/timeseries_profile_indexed.json @@ -0,0 +1,28 @@ +{ + "type": "indexed-timeseries-profile-parser", + "location-id": { + "office-id": "SWT", + "name": "location" + }, + "key-parameter": "Depth", + "record-delimiter": "\n", + "time-format": "MM/DD/YYYY,HH24:MI:SS", + "time-zone": "UTC", + "parameter-info-list": [ + { + "type": "indexed-parameter-info", + "parameter": "Depth", + "unit": "m", + "index": 3 + }, + { + "type": "indexed-parameter-info", + "parameter": "Temp-Water", + "unit": "F", + "index": 5 + } + ], + "time-in-two-fields": false, + "field-delimiter": ",", + "time-field": 1 +} \ No newline at end of file diff --git a/tests/resources/timeseries_profile_instance.json b/tests/resources/timeseries_profile_instance.json new file mode 100644 index 00000000..4583e043 --- /dev/null +++ b/tests/resources/timeseries_profile_instance.json @@ -0,0 +1,56 @@ +{ + "time-series-profile": { + "location-id": { + "office-id": "SWT", + "name": "SWAN" + }, + "description": "Description", + "parameter-list": [ + "Temp-Water", + "Depth" + ], + "key-parameter": "Depth", + "reference-ts-id": { + "office-id": "SWT", + "name": "SWAN.Elev.Inst.1Hour.0.DSS-Obs" + } + }, + "time-series-list": [ + { + "parameter": "Temp-Water", + "unit": "F", + "time-zone": "PST", + "values": [ + { + "date-time": 1612869582120, + "value": 1.0, + "quality": 0 + }, + { + "date-time": 1612869582220, + "value": 3.0, + "quality": 0 + } + ] + }, + { + "parameter": "Depth", + "unit": "ft", + "time-zone": "PST", + "values": [ + { + "date-time": 1612869582120, + "value": 1.0, + "quality": 0 + }, + { + "date-time": 1612869582220, + "value": 3.0, + "quality": 0 + } + ] + } + ], + "first-date": 1594296000000, + "last-date": 1752062400000 +} \ No newline at end of file diff --git a/tests/resources/timeseries_profile_instances.json b/tests/resources/timeseries_profile_instances.json new file mode 100644 index 00000000..08e5a819 --- /dev/null +++ b/tests/resources/timeseries_profile_instances.json @@ -0,0 +1,38 @@ +[ + { + "time-series-profile": { + "location-id": { + "office-id": "SWT", + "name": "SWAN" + }, + "description": "Description", + "parameter-list": [], + "key-parameter": "Depth", + "reference-ts-id": { + "office-id": "SWT", + "name": "SWAN.Elev.Inst.1Hour.0.DSS-Obs" + } + }, + "time-series-list": [], + "first-date": 1594296000000, + "last-date": 1752062400000 + }, + { + "time-series-profile": { + "location-id": { + "office-id": "SWT", + "name": "SWAN2" + }, + "description": "Description", + "parameter-list": [], + "key-parameter": "Depth", + "reference-ts-id": { + "office-id": "SWT", + "name": "SWAN2.Elev.Inst.1Hour.0.DSS-Obs" + } + }, + "time-series-list": [], + "first-date": 1594296000000, + "last-date": 1752062400000 + } +] \ No newline at end of file diff --git a/tests/resources/timeseries_profiles.json b/tests/resources/timeseries_profiles.json new file mode 100644 index 00000000..68a42231 --- /dev/null +++ b/tests/resources/timeseries_profiles.json @@ -0,0 +1,34 @@ +[ + { + "description": "Description", + "parameter-list": [ + "Temp-Water", + "Depth" + ], + "location-id": { + "office-id": "SWT", + "name": "SWAN" + }, + "reference-ts-id": { + "office-id": "SWT", + "name": "SWAN.Elev.Inst.1Hour.0.DSS-Obs" + }, + "key-parameter": "Depth" + }, + { + "description": "Description 2", + "parameter-list": [ + "Temp", + "Length" + ], + "location-id": { + "office-id": "SWT", + "name": "SWAN2" + }, + "reference-ts-id": { + "office-id": "SWT", + "name": "SWAN2.Elev.Inst.1Hour.0.DSS-Obs" + }, + "key-parameter": "Length" + } +] \ No newline at end of file diff --git a/tests/resources/timeseries_profiles_indexed.json b/tests/resources/timeseries_profiles_indexed.json new file mode 100644 index 00000000..e0b6c0fa --- /dev/null +++ b/tests/resources/timeseries_profiles_indexed.json @@ -0,0 +1,58 @@ +[ + { + "type": "indexed-timeseries-profile-parser", + "location-id": { + "office-id": "SWT", + "name": "location" + }, + "key-parameter": "Depth", + "record-delimiter": "\n", + "time-format": "MM/DD/YYYY,HH24:MI:SS", + "time-zone": "UTC", + "parameter-info-list": [ + { + "type": "indexed-parameter-info", + "parameter": "Depth", + "unit": "m", + "index": 3 + }, + { + "type": "indexed-parameter-info", + "parameter": "Temp-Water", + "unit": "F", + "index": 5 + } + ], + "time-in-two-fields": false, + "field-delimiter": ",", + "time-field": 1 + }, + { + "type": "indexed-timeseries-profile-parser", + "location-id": { + "office-id": "SWT", + "name": "location" + }, + "key-parameter": "Length", + "record-delimiter": "\n", + "time-format": "MM/DD/YYYY,HH24:MI:SS", + "time-zone": "UTC", + "parameter-info-list": [ + { + "type": "indexed-parameter-info", + "parameter": "Length", + "unit": "m", + "index": 4 + }, + { + "type": "indexed-parameter-info", + "parameter": "Temp", + "unit": "C", + "index": 6 + } + ], + "time-in-two-fields": false, + "field-delimiter": ",", + "time-field": 1 + } +] \ No newline at end of file diff --git a/tests/timeseries/timeseries_profile_instance_test.py b/tests/timeseries/timeseries_profile_instance_test.py new file mode 100644 index 00000000..baf68c48 --- /dev/null +++ b/tests/timeseries/timeseries_profile_instance_test.py @@ -0,0 +1,113 @@ +# Copyright (c) 2024 +# United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC) +# All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL. +# Source may not be released without written approval from HEC + +import urllib.parse +from datetime import datetime +from pathlib import Path + +import pytest +import pytz + +import cwms.api +import cwms.timeseries.timeseries_profile_instance as timeseries +from tests._test_utils import read_resource_file + +_MOCK_ROOT = "https://mockwebserver.cwms.gov" +_TSP_INST_JSON = read_resource_file("timeseries_profile_instance.json") +_TSP_INST_ARRAY_JSON = read_resource_file("timeseries_profile_instances.json") +current_path = Path(__file__).resolve().parent.parent.parent +resource_path = current_path / "tests" / "resources" / "timeseries_profile_data.txt" +with open(resource_path, "r") as file: + _TSP_PROFILE_DATA = file.read().strip("\n") + +tz = pytz.timezone("UTC") + + +@pytest.fixture(autouse=True) +def init_session(): + cwms.api.init_session(api_root=_MOCK_ROOT) + + +def test_get_timeseries_profile_instance(requests_mock): + requests_mock.get( + f"{_MOCK_ROOT}" + "/timeseries/profile-instance/SWAN/Temp-Water/Raw?office=SWT&" + "unit=C", + json=_TSP_INST_JSON, + ) + + location_id = "SWAN" + parameter_id = "Temp-Water" + version = "Raw" + unit = "C" + office_id = "SWT" + version_date = tz.localize(datetime(2014, 8, 16, 4, 55, 0)) + start = tz.localize(datetime(2015, 3, 3, 6, 45, 0)) + end = tz.localize(datetime(2015, 3, 3, 7, 15, 0)) + + data = timeseries.get_timeseries_profile_instance( + office_id, + location_id, + parameter_id, + version, + unit, + version_date, + start, + end, + ) + + assert data.json == _TSP_INST_JSON + + +def test_store_timeseries_profile_instance(requests_mock): + data = urllib.parse.quote_plus(_TSP_PROFILE_DATA) + requests_mock.post( + f"{_MOCK_ROOT}/timeseries/profile-instance" + f"?profile-data={data}&" + "version=Raw&override-protection=False" + "&version-date=2020-01-01T13%3A30%3A00%2B00%3A00" + ) + + version = "Raw" + version_date = tz.localize(datetime(2020, 1, 1, 13, 30, 0)) + + timeseries.store_timeseries_profile_instance( + _TSP_PROFILE_DATA, version, version_date, None, False + ) + + assert requests_mock.called + assert requests_mock.call_count == 1 + + +def test_delete_timeseries_profile_instance(requests_mock): + requests_mock.delete( + f"{_MOCK_ROOT}" "/timeseries/profile-instance/SWAN/Length/Raw?office=SWT", + json=_TSP_INST_JSON, + ) + + location_id = "SWAN" + office_id = "SWT" + parameter_id = "Length" + version = "Raw" + version_date = tz.localize(datetime(2010, 6, 4, 12, 0, 0)) + date = tz.localize(datetime(2010, 6, 4, 14, 0, 0)) + + timeseries.delete_timeseries_profile_instance( + office_id, location_id, parameter_id, version, version_date, date + ) + + assert requests_mock.called + assert requests_mock.call_count == 1 + + +def test_get_all_timeseries_profile_instance(requests_mock): + requests_mock.get( + f"{_MOCK_ROOT}/timeseries/profile-instance", + json=_TSP_INST_ARRAY_JSON, + ) + + data = timeseries.get_timeseries_profile_instances("*", "*", "*", "*") + + assert data.json == _TSP_INST_ARRAY_JSON diff --git a/tests/timeseries/timeseries_profile_parser_test.py b/tests/timeseries/timeseries_profile_parser_test.py new file mode 100644 index 00000000..6047066c --- /dev/null +++ b/tests/timeseries/timeseries_profile_parser_test.py @@ -0,0 +1,73 @@ +# Copyright (c) 2024 +# United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC) +# All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL. +# Source may not be released without written approval from HEC + +import pytest + +import cwms.api +import cwms.timeseries.timeseries_profile_parser as timeseries +from tests._test_utils import read_resource_file + +_MOCK_ROOT = "https://mockwebserver.cwms.gov" +_TSP_PARSER_JSON = read_resource_file("timeseries_profile_indexed.json") +_TSP_PARSER_ARRAY_JSON = read_resource_file("timeseries_profiles_indexed.json") + + +@pytest.fixture(autouse=True) +def init_session(): + cwms.api.init_session(api_root=_MOCK_ROOT) + + +def test_get_timeseries_profile_parser(requests_mock): + requests_mock.get( + f"{_MOCK_ROOT}" "/timeseries/profile-parser/SWAN/Temp?office=SWT", + json=_TSP_PARSER_JSON, + ) + + location_id = "SWAN" + office_id = "SWT" + parameter_id = "Temp" + + data = timeseries.get_timeseries_profile_parser( + office_id, location_id, parameter_id + ) + + assert data.json == _TSP_PARSER_JSON + + +def test_store_timeseries_profile_parser(requests_mock): + requests_mock.post(f"{_MOCK_ROOT}/timeseries/profile-parser?fail-if-exists=False") + + data = _TSP_PARSER_JSON + timeseries.store_timeseries_profile_parser(data, False) + + assert requests_mock.called + assert requests_mock.call_count == 1 + + +def test_delete_timeseries_profile_parser(requests_mock): + requests_mock.delete( + f"{_MOCK_ROOT}" "/timeseries/profile-parser/SWAN/Length?office=SWT", + json=_TSP_PARSER_JSON, + ) + + parameter_id = "Length" + location_id = "SWAN" + office_id = "SWT" + + timeseries.delete_timeseries_profile_parser(office_id, location_id, parameter_id) + + assert requests_mock.called + assert requests_mock.call_count == 1 + + +def test_get_all_timeseries_profile_parser(requests_mock): + requests_mock.get( + f"{_MOCK_ROOT}" "/timeseries/profile-parser", + json=_TSP_PARSER_ARRAY_JSON, + ) + + data = timeseries.get_timeseries_profile_parsers("*", "*", "*") + + assert data.json == _TSP_PARSER_ARRAY_JSON diff --git a/tests/timeseries/timeseries_profile_test.py b/tests/timeseries/timeseries_profile_test.py new file mode 100644 index 00000000..cabba885 --- /dev/null +++ b/tests/timeseries/timeseries_profile_test.py @@ -0,0 +1,70 @@ +# Copyright (c) 2024 +# United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC) +# All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL. +# Source may not be released without written approval from HEC + +import pytest + +import cwms.api +import cwms.timeseries.timeseries_profile as timeseries +from tests._test_utils import read_resource_file + +_MOCK_ROOT = "https://mockwebserver.cwms.gov" +_TSP_JSON = read_resource_file("timeseries_profile.json") +_TSP_ARRAY_JSON = read_resource_file("timeseries_profiles.json") + + +@pytest.fixture(autouse=True) +def init_session(): + cwms.api.init_session(api_root=_MOCK_ROOT) + + +def test_get_timeseries_profile(requests_mock): + requests_mock.get( + f"{_MOCK_ROOT}" "/timeseries/profile/SWAN/Depth?office=SWT", + json=_TSP_JSON, + ) + + parameter_id = "Depth" + office_id = "SWT" + location_id = "SWAN" + + data = timeseries.get_timeseries_profile(office_id, location_id, parameter_id) + assert data.json == _TSP_JSON + + +def test_store_timeseries_profile(requests_mock): + requests_mock.post(f"{_MOCK_ROOT}/timeseries/profile?fail-if-exists=False") + + data = _TSP_JSON + timeseries.store_timeseries_profile(data, False) + + assert requests_mock.called + assert requests_mock.call_count == 1 + + +def test_get_all_timeseries_profile(requests_mock): + requests_mock.get( + f"{_MOCK_ROOT}/timeseries/profile", + json=_TSP_ARRAY_JSON, + ) + + data = timeseries.get_timeseries_profiles("*", "*", "*") + + assert data.json == _TSP_ARRAY_JSON + + +def test_delete_timeseries_profile(requests_mock): + requests_mock.delete( + f"{_MOCK_ROOT}" "/timeseries/profile/SWAN/Depth?office=SWT", + json=_TSP_JSON, + ) + + location_id = "SWAN" + parameter_id = "Depth" + office_id = "SWT" + + timeseries.delete_timeseries_profile(office_id, parameter_id, location_id) + + assert requests_mock.called + assert requests_mock.call_count == 1