cwms-python 0.3.0__tar.gz → 0.4.4__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 (30) hide show
  1. {cwms_python-0.3.0 → cwms_python-0.4.4}/PKG-INFO +2 -2
  2. {cwms_python-0.3.0 → cwms_python-0.4.4}/README.md +1 -1
  3. cwms_python-0.4.4/cwms/__init__.py +22 -0
  4. {cwms_python-0.3.0 → cwms_python-0.4.4}/cwms/api.py +57 -15
  5. cwms_python-0.4.4/cwms/catalog/catalog.py +128 -0
  6. cwms_python-0.4.4/cwms/forecast/forecast_instance.py +208 -0
  7. cwms_python-0.4.4/cwms/forecast/forecast_spec.py +181 -0
  8. cwms_python-0.4.4/cwms/levels/location_levels.py +221 -0
  9. cwms_python-0.4.4/cwms/levels/specified_levels.py +126 -0
  10. cwms_python-0.4.4/cwms/locations/physical_locations.py +157 -0
  11. cwms_python-0.4.4/cwms/ratings/ratings.py +378 -0
  12. cwms_python-0.4.4/cwms/ratings/ratings_spec.py +154 -0
  13. cwms_python-0.4.4/cwms/ratings/ratings_template.py +148 -0
  14. cwms_python-0.4.4/cwms/standard_text/standard_text.py +201 -0
  15. cwms_python-0.4.4/cwms/timeseries/timerseries_identifier.py +135 -0
  16. {cwms_python-0.3.0 → cwms_python-0.4.4}/cwms/timeseries/timeseries.py +81 -16
  17. {cwms_python-0.3.0 → cwms_python-0.4.4}/cwms/timeseries/timeseries_bin.py +0 -7
  18. {cwms_python-0.3.0 → cwms_python-0.4.4}/cwms/timeseries/timeseries_txt.py +0 -167
  19. {cwms_python-0.3.0 → cwms_python-0.4.4}/cwms/types.py +27 -7
  20. {cwms_python-0.3.0 → cwms_python-0.4.4}/pyproject.toml +1 -1
  21. cwms_python-0.3.0/cwms/__init__.py +0 -13
  22. cwms_python-0.3.0/cwms/_constants.py +0 -33
  23. cwms_python-0.3.0/cwms/core.py +0 -26
  24. cwms_python-0.3.0/cwms/exceptions.py +0 -131
  25. cwms_python-0.3.0/cwms/forecast/forecast_instance.py +0 -260
  26. cwms_python-0.3.0/cwms/forecast/forecast_spec.py +0 -227
  27. cwms_python-0.3.0/cwms/levels/location_levels.py +0 -484
  28. cwms_python-0.3.0/cwms/locations/physical_locations.py +0 -47
  29. cwms_python-0.3.0/cwms/utils.py +0 -85
  30. {cwms_python-0.3.0 → cwms_python-0.4.4}/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cwms-python
3
- Version: 0.3.0
3
+ Version: 0.4.4
4
4
  Summary: Corps water managerment systems (CWMS) REST API for Data Retrieval of USACE water data
5
5
  License: LICENSE
6
6
  Keywords: USACE,water data
@@ -49,7 +49,7 @@ from datetime import datetime, timedelta
49
49
 
50
50
  end = datetime.now()
51
51
  begin = end - timedelta(days = 10)
52
- data = cwms.get_timeseries(p_tsId='Some.Fully.Qualified.Ts.Id',begin = begin, end = end)
52
+ data = cwms.get_timeseries(ts_id_='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , begin = begin, end = end)
53
53
 
54
54
  #a cwms data object will be provided this object containes both the JSON as well
55
55
  #as the values converted into a dataframe
@@ -28,7 +28,7 @@ from datetime import datetime, timedelta
28
28
 
29
29
  end = datetime.now()
30
30
  begin = end - timedelta(days = 10)
31
- data = cwms.get_timeseries(p_tsId='Some.Fully.Qualified.Ts.Id',begin = begin, end = end)
31
+ data = cwms.get_timeseries(ts_id_='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , begin = begin, end = end)
32
32
 
33
33
  #a cwms data object will be provided this object containes both the JSON as well
34
34
  #as the values converted into a dataframe
@@ -0,0 +1,22 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ from .api import *
4
+ from .catalog.catalog import *
5
+ from .forecast.forecast_instance import *
6
+ from .forecast.forecast_spec import *
7
+ from .levels.location_levels import *
8
+ from .levels.specified_levels import *
9
+ from .locations.physical_locations import *
10
+ from .ratings.ratings import *
11
+ from .ratings.ratings_spec import *
12
+ from .ratings.ratings_template import *
13
+ from .standard_text.standard_text import *
14
+ from .timeseries.timerseries_identifier import *
15
+ from .timeseries.timeseries import *
16
+ from .timeseries.timeseries_bin import *
17
+ from .timeseries.timeseries_txt import *
18
+
19
+ try:
20
+ __version__ = version("cwms-python")
21
+ except PackageNotFoundError:
22
+ __version__ = "version-unknown"
@@ -29,7 +29,7 @@ the error.
29
29
  import json
30
30
  import logging
31
31
  from json import JSONDecodeError
32
- from typing import Optional, cast
32
+ from typing import Any, Optional, cast
33
33
 
34
34
  from requests import Response
35
35
  from requests_toolbelt import sessions # type: ignore
@@ -150,12 +150,51 @@ def api_version_text(api_version: int) -> str:
150
150
  version = "application/json"
151
151
  elif api_version == 2:
152
152
  version = "application/json;version=2"
153
+ elif api_version == 102:
154
+ version = "application/xml;version=2"
153
155
  else:
154
156
  raise InvalidVersion(f"API version {api_version} is not supported.")
155
157
 
156
158
  return version
157
159
 
158
160
 
161
+ def get_xml(
162
+ endpoint: str,
163
+ params: Optional[RequestParams] = None,
164
+ *,
165
+ api_version: int = API_VERSION,
166
+ ) -> Any:
167
+ """Make a GET request to the CWMS Data API.
168
+
169
+ Args:
170
+ endpoint: The CDA endpoint for the record(s).
171
+ params (optional): Query parameters for the request.
172
+
173
+ Keyword Args:
174
+ api_version (optional): The CDA version to use for the request. If not specified,
175
+ the default API_VERSION will be used.
176
+
177
+ Returns:
178
+ The deserialized JSON response data.
179
+
180
+ Raises:
181
+ ApiError: If an error response is return by the API.
182
+ """
183
+
184
+ headers = {"Accept": api_version_text(api_version)}
185
+ response = SESSION.get(endpoint, params=params, headers=headers)
186
+
187
+ if response.status_code < 200 or response.status_code >= 300:
188
+ logging.error(f"CDA Error: response={response}")
189
+ raise ApiError(response)
190
+
191
+ try:
192
+ return response.content.decode("utf-8")
193
+ except JSONDecodeError as error:
194
+ logging.error(f"Error decoding CDA response as xml: {error}")
195
+ return {}
196
+
197
+
159
198
  def get(
160
199
  endpoint: str,
161
200
  params: Optional[RequestParams] = None,
@@ -182,20 +221,20 @@ def get(
182
221
  headers = {"Accept": api_version_text(api_version)}
183
222
  response = SESSION.get(endpoint, params=params, headers=headers)
184
223
 
185
- if response.status_code != 200:
224
+ if response.status_code < 200 or response.status_code >= 300:
186
225
  logging.error(f"CDA Error: response={response}")
187
226
  raise ApiError(response)
188
227
 
189
228
  try:
190
229
  return cast(JSON, response.json())
191
230
  except JSONDecodeError as error:
192
- logging.error(f"Error decoding CDA response: {error}")
231
+ logging.error(f"Error decoding CDA response as json: {error}")
193
232
  return {}
194
233
 
195
234
 
196
235
  def post(
197
236
  endpoint: str,
198
- data: JSON,
237
+ data: Any,
199
238
  params: Optional[RequestParams] = None,
200
239
  *,
201
240
  api_version: int = API_VERSION,
@@ -221,18 +260,19 @@ def post(
221
260
  # post requires different headers than get for
222
261
  headers = {"accept": "*/*", "Content-Type": api_version_text(api_version)}
223
262
 
224
- response = SESSION.post(
225
- endpoint, params=params, headers=headers, data=json.dumps(data)
226
- )
263
+ if isinstance(data, dict):
264
+ data = json.dumps(data)
265
+
266
+ response = SESSION.post(endpoint, params=params, headers=headers, data=data)
227
267
 
228
- if response.status_code != 200:
268
+ if response.status_code < 200 or response.status_code >= 300:
229
269
  logging.error(f"CDA Error: response={response}")
230
270
  raise ApiError(response)
231
271
 
232
272
 
233
273
  def patch(
234
274
  endpoint: str,
235
- data: JSON,
275
+ data: Optional[Any] = None,
236
276
  params: Optional[RequestParams] = None,
237
277
  *,
238
278
  api_version: int = API_VERSION,
@@ -256,12 +296,14 @@ def patch(
256
296
  """
257
297
 
258
298
  headers = {"accept": "*/*", "Content-Type": api_version_text(api_version)}
299
+ if data is None:
300
+ response = SESSION.patch(endpoint, params=params, headers=headers)
301
+ else:
302
+ if isinstance(data, dict):
303
+ data = json.dumps(data)
304
+ response = SESSION.patch(endpoint, params=params, headers=headers, data=data)
259
305
 
260
- response = SESSION.patch(
261
- endpoint, params=params, headers=headers, data=json.dumps(data)
262
- )
263
-
264
- if response.status_code != 200:
306
+ if response.status_code < 200 or response.status_code >= 300:
265
307
  logging.error(f"CDA Error: response={response}")
266
308
  raise ApiError(response)
267
309
 
@@ -289,6 +331,6 @@ def delete(
289
331
  headers = {"Accept": api_version_text(api_version)}
290
332
  response = SESSION.delete(endpoint, params=params, headers=headers)
291
333
 
292
- if response.status_code != 200:
334
+ if response.status_code < 200 or response.status_code >= 300:
293
335
  logging.error(f"CDA Error: response={response}")
294
336
  raise ApiError(response)
@@ -0,0 +1,128 @@
1
+ from typing import Optional
2
+
3
+ import cwms.api as api
4
+ from cwms.types import Data
5
+
6
+
7
+ def get_locations_catalog(
8
+ office_id: str,
9
+ page: Optional[str] = None,
10
+ page_size: Optional[int] = 5000,
11
+ unit_system: Optional[str] = None,
12
+ like: Optional[str] = None,
13
+ location_category_like: Optional[str] = None,
14
+ location_group_like: Optional[str] = None,
15
+ bounding_office_like: Optional[str] = None,
16
+ location_kind_like: Optional[str] = None,
17
+ ) -> Data:
18
+ """Retrieves filters for a locations catalog
19
+
20
+ Parameters
21
+ ----------
22
+ page: string
23
+ The endpoint used to identify where the request is located.
24
+ page_size: integer
25
+ The entries per page returned. The default value is 5000.
26
+ unit_system: string
27
+ The unit system desired in response. Valid values for this
28
+ field are:
29
+ 1. SI
30
+ 2. EN
31
+ office_id: string
32
+ The owning office of the timeseries group.
33
+ like: string
34
+ The regex for matching against the id
35
+ location_category_like: string
36
+ The regex for matching against the location category id
37
+ location_group_like: string
38
+ The regex for matching against the location group id
39
+ bounding_office_like: string
40
+ The regex for matching against the location bounding office
41
+ location_kind_like: string
42
+ Posix regular expression matching against the location kind. The location-kind is typically unset or one of the following: {"SITE", "EMBANKMENT", "OVERFLOW", "TURBINE", "STREAM", "PROJECT", "STREAMGAGE", "BASIN", "OUTLET", "LOCK", "GATE"}. Multiple kinds can be matched by using Regular Expression OR clauses. For example: "(SITE|STREAM)"
43
+
44
+ Returns
45
+ -------
46
+ cwms data type
47
+ """
48
+
49
+ # CHECKS
50
+ if office_id is None:
51
+ raise ValueError("Retrieve locations catalog requires an office")
52
+
53
+ dataset = "LOCATIONS"
54
+ endpoint = f"catalog/{dataset}"
55
+ params = {
56
+ "page": page,
57
+ "page-size": page_size,
58
+ "units": unit_system,
59
+ "office": office_id,
60
+ "like": like,
61
+ "location-category-like": location_category_like,
62
+ "location-group-like": location_group_like,
63
+ "bounding-office-like": bounding_office_like,
64
+ "location-kind-like": location_kind_like,
65
+ }
66
+
67
+ response = api.get(endpoint=endpoint, params=params, api_version=2)
68
+ return Data(response, selector="entries")
69
+
70
+
71
+ def get_timeseries_catalog(
72
+ office_id: str,
73
+ page: Optional[str] = None,
74
+ page_size: Optional[int] = 5000,
75
+ unit_system: Optional[str] = None,
76
+ like: Optional[str] = None,
77
+ timeseries_category_like: Optional[str] = None,
78
+ timeseries_group_like: Optional[str] = "DMZ Include List",
79
+ bounding_office_like: Optional[str] = None,
80
+ ) -> Data:
81
+ """Retrieves filters for the timeseries catalog
82
+
83
+ Parameters
84
+ ----------
85
+ page: string
86
+ The endpoint used to identify where the request is located.
87
+ page_size: integer
88
+ The entries per page returned. The default value is 500.
89
+ unit_system: string
90
+ The unit system desired in response. Valid values for this
91
+ field are:
92
+ 1. SI
93
+ 2. EN
94
+ office_id: string
95
+ The owning office of the timeseries group.
96
+ like: string
97
+ The regex for matching against the id
98
+ timeseries_category_like: string
99
+ The regex for matching against the category id
100
+ timeseries_group_like: string
101
+ The regex for matching against the timeseries group id. This will default to pull only public datasets
102
+ bounding_office_like: string
103
+ The regex for matching against the location bounding office
104
+
105
+ Returns
106
+ -------
107
+ cwms data type
108
+ """
109
+
110
+ # CHECKS
111
+ if office_id is None:
112
+ raise ValueError("Retrieve timeseries catalog requires an office")
113
+
114
+ dataset = "TIMESERIES"
115
+ endpoint = f"catalog/{dataset}"
116
+ params = {
117
+ "page": page,
118
+ "page-size": page_size,
119
+ "unit-system": unit_system,
120
+ "office": office_id,
121
+ "like": like,
122
+ "timeseries-category-like": timeseries_category_like,
123
+ "timeseries-group-like": timeseries_group_like,
124
+ "bounding-office-like": bounding_office_like,
125
+ }
126
+
127
+ response = api.get(endpoint=endpoint, params=params, api_version=2)
128
+ return Data(response, selector="entries")
@@ -0,0 +1,208 @@
1
+ # Copyright (c) 2024
2
+ # United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC)
3
+ # All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL.
4
+ # Source may not be released without written approval from HEC
5
+ from datetime import datetime
6
+ from typing import Optional
7
+
8
+ import cwms.api as api
9
+ from cwms.types import JSON, Data
10
+
11
+
12
+ def get_forecast_instances(
13
+ spec_id: Optional[str] = None,
14
+ office: Optional[str] = None,
15
+ designator: Optional[str] = None,
16
+ ) -> Data:
17
+ """
18
+ Parameters
19
+ ----------
20
+ spec_id : str, optional
21
+ The forecast spec id.
22
+ office : str, optional
23
+ The spec office id.
24
+ designator : str, optional
25
+ The spec designator.
26
+
27
+ Returns
28
+ -------
29
+ response : dict
30
+ the JSON response from CWMS Data API.
31
+
32
+ Raises
33
+ ------
34
+ ValueError
35
+ If any of spec_id, office, or designator is None.
36
+ ClientError
37
+ If a 400 range error code response is returned from the server.
38
+ NoDataFoundError
39
+ If a 404 range error code response is returned from the server.
40
+ ServerError
41
+ If a 500 range error code response is returned from the server.
42
+ """
43
+ if spec_id is None:
44
+ raise ValueError("Retrieving forecast instances requires an id")
45
+ if office is None:
46
+ raise ValueError("Retrieving forecast instances requires an office")
47
+ if designator is None:
48
+ raise ValueError("Retrieving forecast instances requires a designator")
49
+ endpoint = "forecast-instance"
50
+
51
+ params = {
52
+ "office": office,
53
+ "name": spec_id,
54
+ "designator": designator,
55
+ }
56
+
57
+ response = api.get(endpoint, params)
58
+ return Data(response)
59
+
60
+
61
+ def get_forecast_instance(
62
+ spec_id: str,
63
+ office: str,
64
+ designator: str,
65
+ forecast_date: datetime,
66
+ issue_date: datetime,
67
+ ) -> Data:
68
+ """
69
+ Parameters
70
+ ----------
71
+ spec_id : str
72
+ The ID of the forecast spec.
73
+ office : str
74
+ The ID of the office.
75
+ designator : str
76
+ The designator of the forecast spec
77
+
78
+ Returns
79
+ -------
80
+ response : dict
81
+ the JSON response from CWMS Data API.
82
+
83
+ Raises
84
+ ------
85
+ ValueError
86
+ If any of spec_id, office, or designator is None.
87
+ ClientError
88
+ If a 400 range error code response is returned from the server.
89
+ NoDataFoundError
90
+ If a 404 range error code response is returned from the server.
91
+ ServerError
92
+ If a 500 range error code response is returned from the server.
93
+ """
94
+
95
+ if spec_id is None:
96
+ raise ValueError("Retrieve forecast instance requires an id")
97
+ if office is None:
98
+ raise ValueError("Retrieve a forecast instance requires an office")
99
+ if designator is None:
100
+ raise ValueError("Retrieve a forecast instance requires a designator")
101
+ if forecast_date is None:
102
+ raise ValueError("Retrieve a forecast instance requires a forecast date")
103
+ if issue_date is None:
104
+ raise ValueError("Retrieve a forecast instance requires a issue date")
105
+
106
+ endpoint = f"forecast-instance/{spec_id}"
107
+
108
+ params = {
109
+ "office": office,
110
+ "designator": designator,
111
+ "forecast-date": forecast_date.isoformat(),
112
+ "issue-date": issue_date.isoformat(),
113
+ }
114
+
115
+ response = api.get(endpoint, params)
116
+ return Data(response)
117
+
118
+
119
+ def store_forecast_instance(data: JSON) -> None:
120
+ """
121
+ This method is used to store a forecast instance through CWMS Data API.
122
+
123
+ Parameters
124
+ ----------
125
+ data : dict
126
+ A dictionary representing the JSON data to be stored.
127
+ If the `data` value is None, a `ValueError` will be raised.
128
+
129
+ Returns
130
+ -------
131
+ None
132
+
133
+ Raises
134
+ ------
135
+ ValueError
136
+ If dict is None.
137
+ ClientError
138
+ If a 400 range error code response is returned from the server.
139
+ NoDataFoundError
140
+ If a 404 range error code response is returned from the server.
141
+ ServerError
142
+ If a 500 range error code response is returned from the server.
143
+ """
144
+ if data is None:
145
+ raise ValueError("Storing a forecast instance requires a JSON data dictionary")
146
+ endpoint = "forecast-instance"
147
+
148
+ return api.post(endpoint, data, params=None)
149
+
150
+
151
+ def delete_forecast_instance(
152
+ spec_id: str,
153
+ office: str,
154
+ designator: str,
155
+ forecast_date: datetime,
156
+ issue_date: datetime,
157
+ ) -> None:
158
+ """
159
+ Parameters
160
+ ----------
161
+ spec_id : str
162
+ The ID of the forecast spec.
163
+ office : str
164
+ The ID of the office.
165
+ designator : str
166
+ The designator of the forecast spec
167
+ forecast_date : datetime
168
+ The forecast date of the forecast instance
169
+ issue_date : datetime
170
+ The forecast issue date of the forecast instance
171
+
172
+ Returns
173
+ -------
174
+ response : dict
175
+ the JSON response from CWMS Data API.
176
+
177
+ Raises
178
+ ------
179
+ ValueError
180
+ If any of spec_id, office, designator,
181
+ forecast_date, or issue_date is None.
182
+ ClientError
183
+ If a 400 range error code response is returned from the server.
184
+ NoDataFoundError
185
+ If a 404 range error code response is returned from the server.
186
+ ServerError
187
+ If a 500 range error code response is returned from the server.
188
+ """
189
+ if spec_id is None:
190
+ raise ValueError("Deleting a forecast instance requires an id")
191
+ if office is None:
192
+ raise ValueError("Deleting a forecast instance requires an office")
193
+ if designator is None:
194
+ raise ValueError("Deleting a forecast instance requires a designator")
195
+ if forecast_date is None:
196
+ raise ValueError("Deleting a forecast instance requires a forecast date")
197
+ if issue_date is None:
198
+ raise ValueError("Deleting a forecast instance requires a issue date")
199
+
200
+ endpoint = f"forecast-instance/{spec_id}"
201
+
202
+ params = {
203
+ "office": office,
204
+ "designator": designator,
205
+ "forecast-date": forecast_date.isoformat(),
206
+ "issue-date": issue_date.isoformat(),
207
+ }
208
+ return api.delete(endpoint, params)
@@ -0,0 +1,181 @@
1
+ # Copyright (c) 2024
2
+ # United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC)
3
+ # All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL.
4
+ # Source may not be released without written approval from HEC
5
+ from typing import Optional
6
+
7
+ import cwms.api as api
8
+ from cwms.types import JSON, Data, DeleteMethod
9
+
10
+
11
+ def get_forecast_specs(
12
+ id_mask: Optional[str] = None,
13
+ office: Optional[str] = None,
14
+ designator_mask: Optional[str] = None,
15
+ source_entity: Optional[str] = None,
16
+ ) -> Data:
17
+ """
18
+ Parameters
19
+ ----------
20
+ id_mask : str, optional
21
+ The regex filter for the forecast spec id.
22
+ office : str, optional
23
+ The regex filter for the forecast spec id.
24
+ designator_mask : str, optional
25
+ The regex filter for the forecast spec id.
26
+ source_entity : str, optional
27
+ The regex filter for the forecast spec id.
28
+
29
+ Returns
30
+ -------
31
+ response : dict
32
+ the JSON response from CWMS Data API.
33
+
34
+ Raises
35
+ ------
36
+ ClientError
37
+ If a 400 range error code response is returned from the server.
38
+ NoDataFoundError
39
+ If a 404 range error code response is returned from the server.
40
+ ServerError
41
+ If a 500 range error code response is returned from the server.
42
+ """
43
+ endpoint = "forecast-spec"
44
+
45
+ params = {
46
+ "office": office,
47
+ "id_mask": id_mask,
48
+ "designator-mask": designator_mask,
49
+ "source-entity": source_entity,
50
+ }
51
+
52
+ response = api.get(endpoint, params)
53
+ return Data(response)
54
+
55
+
56
+ def get_forecast_spec(spec_id: str, office: str, designator: str) -> Data:
57
+ """
58
+ Parameters
59
+ ----------
60
+ spec_id : str
61
+ The ID of the forecast spec.
62
+ office : str
63
+ The ID of the office.
64
+ designator : str
65
+ The designator of the forecast spec
66
+
67
+ Returns
68
+ -------
69
+ response : dict
70
+ the JSON response from CWMS Data API.
71
+
72
+ Raises
73
+ ------
74
+ ValueError
75
+ If any of spec_id, office, or designator is None.
76
+ ClientError
77
+ If a 400 range error code response is returned from the server.
78
+ NoDataFoundError
79
+ If a 404 range error code response is returned from the server.
80
+ ServerError
81
+ If a 500 range error code response is returned from the server.
82
+ """
83
+
84
+ if spec_id is None:
85
+ raise ValueError("Retrieve forecast spec requires an id")
86
+ if office is None:
87
+ raise ValueError("Retrieve a forecast spec requires an office")
88
+ if designator is None:
89
+ raise ValueError("Retrieve a forecast spec requires a designator")
90
+
91
+ endpoint = f"forecast-spec/{spec_id}"
92
+
93
+ params = {
94
+ "office": office,
95
+ "designator": designator,
96
+ }
97
+
98
+ response = api.get(endpoint, params)
99
+ return Data(response)
100
+
101
+
102
+ def store_forecast_spec(data: JSON) -> None:
103
+ """
104
+ This method is used to store a forecast spec through CWMS Data API.
105
+
106
+ Parameters
107
+ ----------
108
+ data : dict
109
+ A dictionary representing the JSON data to be stored.
110
+ If the `data` value is None, a `ValueError` will be raised.
111
+
112
+ Returns
113
+ -------
114
+ None
115
+
116
+ Raises
117
+ ------
118
+ ValueError
119
+ If dict is None.
120
+ ClientError
121
+ If a 400 range error code response is returned from the server.
122
+ NoDataFoundError
123
+ If a 404 range error code response is returned from the server.
124
+ ServerError
125
+ If a 500 range error code response is returned from the server.
126
+ """
127
+ if data is None:
128
+ raise ValueError("Storing a forecast spec requires a JSON data dictionary")
129
+ endpoint = "forecast-spec"
130
+
131
+ return api.post(endpoint, data)
132
+
133
+
134
+ def delete_forecast_spec(
135
+ spec_id: str,
136
+ office: str,
137
+ designator: str,
138
+ delete_method: DeleteMethod,
139
+ ) -> None:
140
+ """
141
+ Parameters
142
+ ----------
143
+ spec_id : str
144
+ The ID of the forecast spec.
145
+ office : str
146
+ The ID of the office.
147
+ designator : str
148
+ The designator of the forecast spec
149
+ delete_method: DeleteMethod
150
+ The method to use to delete forecast spec data
151
+
152
+ Returns
153
+ -------
154
+ response : dict
155
+ the JSON response from CWMS Data API.
156
+
157
+ Raises
158
+ ------
159
+ ValueError
160
+ If any of spec_id, office, or designator is None.
161
+ ClientError
162
+ If a 400 range error code response is returned from the server.
163
+ NoDataFoundError
164
+ If a 404 range error code response is returned from the server.
165
+ ServerError
166
+ If a 500 range error code response is returned from the server.
167
+ """
168
+ if spec_id is None:
169
+ raise ValueError("Deleting a forecast spec requires an id")
170
+ if office is None:
171
+ raise ValueError("Deleting a forecast spec requires an office")
172
+ if designator is None:
173
+ raise ValueError("Deleting a forecast spec requires a designator")
174
+
175
+ endpoint = f"forecast-spec/{spec_id}"
176
+ params = {
177
+ "office": office,
178
+ "designator": designator,
179
+ "method": delete_method.name,
180
+ }
181
+ return api.delete(endpoint, params)