cwms-python 0.1.0__tar.gz → 0.3.0__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.
- cwms_python-0.3.0/LICENSE +21 -0
- cwms_python-0.3.0/PKG-INFO +95 -0
- cwms_python-0.3.0/README.md +73 -0
- cwms_python-0.3.0/cwms/__init__.py +13 -0
- cwms_python-0.3.0/cwms/_constants.py +33 -0
- cwms_python-0.3.0/cwms/api.py +294 -0
- cwms_python-0.3.0/cwms/core.py +26 -0
- cwms_python-0.3.0/cwms/exceptions.py +131 -0
- cwms_python-0.3.0/cwms/forecast/forecast_instance.py +260 -0
- cwms_python-0.3.0/cwms/forecast/forecast_spec.py +227 -0
- cwms_python-0.3.0/cwms/levels/location_levels.py +484 -0
- cwms_python-0.3.0/cwms/locations/physical_locations.py +47 -0
- cwms_python-0.3.0/cwms/timeseries/timeseries.py +208 -0
- cwms_python-0.3.0/cwms/timeseries/timeseries_bin.py +219 -0
- cwms_python-0.3.0/cwms/timeseries/timeseries_txt.py +373 -0
- cwms_python-0.3.0/cwms/types.py +67 -0
- cwms_python-0.3.0/cwms/utils.py +85 -0
- cwms_python-0.3.0/pyproject.toml +43 -0
- cwms-python-0.1.0/.github/workflows/publish-to-test-pypi.yml +0 -117
- cwms-python-0.1.0/CWMS/.ipynb_checkpoints/__init__-checkpoint.py +0 -11
- cwms-python-0.1.0/CWMS/.ipynb_checkpoints/core-checkpoint.py +0 -0
- cwms-python-0.1.0/CWMS/.ipynb_checkpoints/cwms_loc-checkpoint.py +0 -38
- cwms-python-0.1.0/CWMS/.ipynb_checkpoints/cwms_ts-checkpoint.py +0 -47
- cwms-python-0.1.0/CWMS/.ipynb_checkpoints/utils-checkpoint.py +0 -52
- cwms-python-0.1.0/CWMS/__init__.py +0 -11
- cwms-python-0.1.0/CWMS/__pycache__/__init__.cpython-39.pyc +0 -0
- cwms-python-0.1.0/CWMS/__pycache__/core.cpython-39.pyc +0 -0
- cwms-python-0.1.0/CWMS/__pycache__/cwms_loc.cpython-39.pyc +0 -0
- cwms-python-0.1.0/CWMS/__pycache__/cwms_ts.cpython-39.pyc +0 -0
- cwms-python-0.1.0/CWMS/__pycache__/utils.cpython-39.pyc +0 -0
- cwms-python-0.1.0/CWMS/core.py +0 -21
- cwms-python-0.1.0/CWMS/cwms_loc.py +0 -58
- cwms-python-0.1.0/CWMS/cwms_ts.py +0 -182
- cwms-python-0.1.0/CWMS/utils.py +0 -79
- cwms-python-0.1.0/LICENSE +0 -24
- cwms-python-0.1.0/PKG-INFO +0 -72
- cwms-python-0.1.0/README.md +0 -49
- cwms-python-0.1.0/cwms_python.egg-info/PKG-INFO +0 -72
- cwms-python-0.1.0/cwms_python.egg-info/SOURCES.txt +0 -26
- cwms-python-0.1.0/cwms_python.egg-info/dependency_links.txt +0 -1
- cwms-python-0.1.0/cwms_python.egg-info/requires.txt +0 -10
- cwms-python-0.1.0/cwms_python.egg-info/top_level.txt +0 -1
- cwms-python-0.1.0/pyproject.toml +0 -48
- cwms-python-0.1.0/setup.cfg +0 -4
- cwms-python-0.1.0/setup.py +0 -24
- cwms-python-0.1.0/tests/__init__.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Hydrologic Engineering Center
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cwms-python
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Corps water managerment systems (CWMS) REST API for Data Retrieval of USACE water data
|
|
5
|
+
License: LICENSE
|
|
6
|
+
Keywords: USACE,water data
|
|
7
|
+
Author: Eric Novotny
|
|
8
|
+
Author-email: eric.v.novotny@usace.army.mil
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
10
|
+
Classifier: License :: Other/Proprietary License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Dist: numpy (>=1.26.4,<2.0.0)
|
|
17
|
+
Requires-Dist: pandas (>=2.1.3,<3.0.0)
|
|
18
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
19
|
+
Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# CWMSpy
|
|
23
|
+
|
|
24
|
+
CWMS REST API for Data Retrieval
|
|
25
|
+
|
|
26
|
+
## Requirements.
|
|
27
|
+
|
|
28
|
+
Python 3.9+
|
|
29
|
+
|
|
30
|
+
## Installation & Usage
|
|
31
|
+
|
|
32
|
+
### pip install
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pip install cwms-python
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then import the package:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import cwms
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Getting Started
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import cwms
|
|
48
|
+
from datetime import datetime, timedelta
|
|
49
|
+
|
|
50
|
+
end = datetime.now()
|
|
51
|
+
begin = end - timedelta(days = 10)
|
|
52
|
+
data = cwms.get_timeseries(p_tsId='Some.Fully.Qualified.Ts.Id',begin = begin, end = end)
|
|
53
|
+
|
|
54
|
+
#a cwms data object will be provided this object containes both the JSON as well
|
|
55
|
+
#as the values converted into a dataframe
|
|
56
|
+
|
|
57
|
+
#display the dataframe
|
|
58
|
+
|
|
59
|
+
df = data.df
|
|
60
|
+
print(df)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
date-time value quality-code
|
|
65
|
+
0 2024-04-23 08:15:00 86.57 3
|
|
66
|
+
1 2024-04-23 08:30:00 86.57 3
|
|
67
|
+
2 2024-04-23 08:45:00 86.58 3
|
|
68
|
+
3 2024-04-23 09:00:00 86.58 3
|
|
69
|
+
4 2024-04-23 09:15:00 86.58 3
|
|
70
|
+
5 2024-04-23 09:30:00 86.58 3
|
|
71
|
+
6 2024-04-23 09:45:00 86.59 3
|
|
72
|
+
7 2024-04-23 10:00:00 86.58 3
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
#display JSON
|
|
77
|
+
json = data.JSON
|
|
78
|
+
print(json)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
{'name': 'Some.Fully.Qualified.Ts.Id',
|
|
83
|
+
'office-id': 'MVP',
|
|
84
|
+
'units': 'ft',
|
|
85
|
+
'values': [['2024-04-23T08:15:00', 86.57, 3],
|
|
86
|
+
['2024-04-23T08:30:00', 86.57, 3],
|
|
87
|
+
['2024-04-23T08:45:00', 86.57999999999997, 3],
|
|
88
|
+
['2024-04-23T09:00:00', 86.57999999999997, 3],
|
|
89
|
+
['2024-04-23T09:15:00', 86.57999999999997, 3],
|
|
90
|
+
['2024-04-23T09:30:00', 86.57999999999997, 3],
|
|
91
|
+
['2024-04-23T09:45:00', 86.59, 3],
|
|
92
|
+
['2024-04-23T10:00:00', 86.57999999999997, 3]],
|
|
93
|
+
'version-date': None}
|
|
94
|
+
```
|
|
95
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# CWMSpy
|
|
2
|
+
|
|
3
|
+
CWMS REST API for Data Retrieval
|
|
4
|
+
|
|
5
|
+
## Requirements.
|
|
6
|
+
|
|
7
|
+
Python 3.9+
|
|
8
|
+
|
|
9
|
+
## Installation & Usage
|
|
10
|
+
|
|
11
|
+
### pip install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pip install cwms-python
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then import the package:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import cwms
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Getting Started
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import cwms
|
|
27
|
+
from datetime import datetime, timedelta
|
|
28
|
+
|
|
29
|
+
end = datetime.now()
|
|
30
|
+
begin = end - timedelta(days = 10)
|
|
31
|
+
data = cwms.get_timeseries(p_tsId='Some.Fully.Qualified.Ts.Id',begin = begin, end = end)
|
|
32
|
+
|
|
33
|
+
#a cwms data object will be provided this object containes both the JSON as well
|
|
34
|
+
#as the values converted into a dataframe
|
|
35
|
+
|
|
36
|
+
#display the dataframe
|
|
37
|
+
|
|
38
|
+
df = data.df
|
|
39
|
+
print(df)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
date-time value quality-code
|
|
44
|
+
0 2024-04-23 08:15:00 86.57 3
|
|
45
|
+
1 2024-04-23 08:30:00 86.57 3
|
|
46
|
+
2 2024-04-23 08:45:00 86.58 3
|
|
47
|
+
3 2024-04-23 09:00:00 86.58 3
|
|
48
|
+
4 2024-04-23 09:15:00 86.58 3
|
|
49
|
+
5 2024-04-23 09:30:00 86.58 3
|
|
50
|
+
6 2024-04-23 09:45:00 86.59 3
|
|
51
|
+
7 2024-04-23 10:00:00 86.58 3
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
#display JSON
|
|
56
|
+
json = data.JSON
|
|
57
|
+
print(json)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
{'name': 'Some.Fully.Qualified.Ts.Id',
|
|
62
|
+
'office-id': 'MVP',
|
|
63
|
+
'units': 'ft',
|
|
64
|
+
'values': [['2024-04-23T08:15:00', 86.57, 3],
|
|
65
|
+
['2024-04-23T08:30:00', 86.57, 3],
|
|
66
|
+
['2024-04-23T08:45:00', 86.57999999999997, 3],
|
|
67
|
+
['2024-04-23T09:00:00', 86.57999999999997, 3],
|
|
68
|
+
['2024-04-23T09:15:00', 86.57999999999997, 3],
|
|
69
|
+
['2024-04-23T09:30:00', 86.57999999999997, 3],
|
|
70
|
+
['2024-04-23T09:45:00', 86.59, 3],
|
|
71
|
+
['2024-04-23T10:00:00', 86.57999999999997, 3]],
|
|
72
|
+
'version-date': None}
|
|
73
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from .api import *
|
|
4
|
+
|
|
5
|
+
# from .core import CwmsApiSession
|
|
6
|
+
from .levels.location_levels import *
|
|
7
|
+
from .locations.physical_locations import *
|
|
8
|
+
from .timeseries.timeseries import *
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
__version__ = version("cwms-python")
|
|
12
|
+
except PackageNotFoundError:
|
|
13
|
+
__version__ = "version-unknown"
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
|
|
6
|
+
|
|
7
|
+
HEADER_JSON_V1 = "application/json"
|
|
8
|
+
HEADER_JSON_V2 = "application/json;version=2"
|
|
9
|
+
|
|
10
|
+
OFFICE_PARAM = "office"
|
|
11
|
+
TEMPLATE_ID_MASK_PARAM = "template-id-mask"
|
|
12
|
+
FAIL_IF_EXISTS = "fail-if-exists"
|
|
13
|
+
UNIT = "unit"
|
|
14
|
+
DATUM = "datum"
|
|
15
|
+
BEGIN = "begin"
|
|
16
|
+
END = "end"
|
|
17
|
+
PAGE_SIZE = "page-size"
|
|
18
|
+
PAGE = "page"
|
|
19
|
+
NAME = "name"
|
|
20
|
+
TIMEZONE = "timezone"
|
|
21
|
+
EFFECTIVE_DATE = "effective-date"
|
|
22
|
+
CASCADE_DELETE = "cascade-delete"
|
|
23
|
+
BINARY_TYPE_MASK = "binary-type-mask"
|
|
24
|
+
REPLACE_ALL = "replace-all"
|
|
25
|
+
VERSION_DATE = "version-date"
|
|
26
|
+
DESIGNATOR = "designator"
|
|
27
|
+
FORECAST_DATE = "forecast-date"
|
|
28
|
+
ISSUE_DATE = "issue-date"
|
|
29
|
+
ID_MASK = "id_mask"
|
|
30
|
+
DESIGNATOR_MASK = "designator-mask"
|
|
31
|
+
LOCATION_MASK = "location-mask"
|
|
32
|
+
SOURCE_ENTITY = "source-entity"
|
|
33
|
+
METHOD = "method"
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
""" Session management and REST functions for CWMS Data API.
|
|
2
|
+
|
|
3
|
+
This module provides functions for making REST calls to the CWMS Data API (CDA). These
|
|
4
|
+
functions should be used internally to interact with the API. The user should not have to
|
|
5
|
+
interact with these directly.
|
|
6
|
+
|
|
7
|
+
The `init_session()` function can be used to specify an alternative root URL, and to
|
|
8
|
+
provide an authentication key (if required). If `init_session()` is not called, the
|
|
9
|
+
default root URL (see `API_ROOT` below) will be used, and no authentication keys will be
|
|
10
|
+
included when making API calls.
|
|
11
|
+
|
|
12
|
+
Example: Initializing a session
|
|
13
|
+
|
|
14
|
+
# Specify an alternate URL
|
|
15
|
+
init_session(api_root="https://example.com/cwms-data")
|
|
16
|
+
|
|
17
|
+
# Specify an alternate URL and an auth key
|
|
18
|
+
init_session(api_root="https://example.com/cwms-data", api_key="API_KEY")
|
|
19
|
+
|
|
20
|
+
Functions which make API calls that _may_ return a JSON response will return a `dict`
|
|
21
|
+
containing the deserialized data. If the API response does not include data, an empty
|
|
22
|
+
`dict` will be returned.
|
|
23
|
+
|
|
24
|
+
In the event the API returns an error response, the function will raise an `APIError`
|
|
25
|
+
which includes the response object and provides some hints to the user on how to address
|
|
26
|
+
the error.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import logging
|
|
31
|
+
from json import JSONDecodeError
|
|
32
|
+
from typing import Optional, cast
|
|
33
|
+
|
|
34
|
+
from requests import Response
|
|
35
|
+
from requests_toolbelt import sessions # type: ignore
|
|
36
|
+
from requests_toolbelt.sessions import BaseUrlSession # type: ignore
|
|
37
|
+
|
|
38
|
+
from cwms.types import JSON, RequestParams
|
|
39
|
+
|
|
40
|
+
# Specify the default API root URL and version.
|
|
41
|
+
API_ROOT = "https://cwms-data.usace.army.mil/cwms-data/"
|
|
42
|
+
API_VERSION = 2
|
|
43
|
+
|
|
44
|
+
# Initialize a non-authenticated session with the default root URL.
|
|
45
|
+
SESSION = sessions.BaseUrlSession(base_url=API_ROOT)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InvalidVersion(Exception):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ApiError(Exception):
|
|
53
|
+
"""CWMS Data Api Error.
|
|
54
|
+
|
|
55
|
+
This class is a light wrapper around a `requests.Response` object. Its primary purpose
|
|
56
|
+
is to generate an error message that includes the request URL and provide additional
|
|
57
|
+
information to the user to help them resolve the error.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, response: Response):
|
|
61
|
+
self.response = response
|
|
62
|
+
|
|
63
|
+
def __str__(self) -> str:
|
|
64
|
+
# Include the request URL in the error message.
|
|
65
|
+
message = f"CWMS API Error ({self.response.url})"
|
|
66
|
+
|
|
67
|
+
# If a reason is provided in the response, add it to the message.
|
|
68
|
+
if reason := self.response.reason:
|
|
69
|
+
message += f" {reason}"
|
|
70
|
+
|
|
71
|
+
message += "."
|
|
72
|
+
|
|
73
|
+
# Add additional context to help the user resolve the issue.
|
|
74
|
+
if hint := self.hint():
|
|
75
|
+
message += f" {hint}"
|
|
76
|
+
|
|
77
|
+
if content := self.response.content:
|
|
78
|
+
message += f" {content.decode('utf8')}"
|
|
79
|
+
|
|
80
|
+
return message
|
|
81
|
+
|
|
82
|
+
def hint(self) -> str:
|
|
83
|
+
"""Return a message with additional information on how to resolve the error."""
|
|
84
|
+
|
|
85
|
+
if self.response.status_code == 400:
|
|
86
|
+
return "Check that your parameters are correct."
|
|
87
|
+
elif self.response.status_code == 404:
|
|
88
|
+
return "May be the result of an empty query."
|
|
89
|
+
else:
|
|
90
|
+
return ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def init_session(
|
|
94
|
+
*, api_root: Optional[str] = None, api_key: Optional[str] = None
|
|
95
|
+
) -> BaseUrlSession:
|
|
96
|
+
"""Specify a root URL and authentication key for the CWMS Data API.
|
|
97
|
+
|
|
98
|
+
This function can be used to change the root URL used when interacting with the CDA.
|
|
99
|
+
All API calls made after this function is called will use the specified URL. If an
|
|
100
|
+
authentication key is given it will be included in all future request headers.
|
|
101
|
+
|
|
102
|
+
Keyword Args:
|
|
103
|
+
api_root (optional): The root URL for the CWMS Data API.
|
|
104
|
+
api_key (optional): An authentication key.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Returns the updated session object.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
global SESSION
|
|
111
|
+
|
|
112
|
+
if api_root:
|
|
113
|
+
logging.debug(f"Initializing root URL: api_root={api_root}")
|
|
114
|
+
SESSION = sessions.BaseUrlSession(base_url=api_root)
|
|
115
|
+
|
|
116
|
+
if api_key:
|
|
117
|
+
logging.debug(f"Setting authorization key: api_key={api_key}")
|
|
118
|
+
SESSION.headers.update({"Authorization": api_key})
|
|
119
|
+
|
|
120
|
+
return SESSION
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def return_base_url() -> str:
|
|
124
|
+
"""returns the base URL for the CDA instance that is connected to.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
str: base URL
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
return str(SESSION.base_url)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def api_version_text(api_version: int) -> str:
|
|
134
|
+
"""Initialize CDA request headers.
|
|
135
|
+
|
|
136
|
+
The CDA supports multiple versions. To request a specific version, the version number
|
|
137
|
+
must be included in the request headers.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
api_version: The CDA version to use for the request.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
A dict containing the request headers.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
InvalidVersion: If an unsupported API version is specified.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
if api_version == 1:
|
|
150
|
+
version = "application/json"
|
|
151
|
+
elif api_version == 2:
|
|
152
|
+
version = "application/json;version=2"
|
|
153
|
+
else:
|
|
154
|
+
raise InvalidVersion(f"API version {api_version} is not supported.")
|
|
155
|
+
|
|
156
|
+
return version
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get(
|
|
160
|
+
endpoint: str,
|
|
161
|
+
params: Optional[RequestParams] = None,
|
|
162
|
+
*,
|
|
163
|
+
api_version: int = API_VERSION,
|
|
164
|
+
) -> JSON:
|
|
165
|
+
"""Make a GET request to the CWMS Data API.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
endpoint: The CDA endpoint for the record(s).
|
|
169
|
+
params (optional): Query parameters for the request.
|
|
170
|
+
|
|
171
|
+
Keyword Args:
|
|
172
|
+
api_version (optional): The CDA version to use for the request. If not specified,
|
|
173
|
+
the default API_VERSION will be used.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
The deserialized JSON response data.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
ApiError: If an error response is return by the API.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
headers = {"Accept": api_version_text(api_version)}
|
|
183
|
+
response = SESSION.get(endpoint, params=params, headers=headers)
|
|
184
|
+
|
|
185
|
+
if response.status_code != 200:
|
|
186
|
+
logging.error(f"CDA Error: response={response}")
|
|
187
|
+
raise ApiError(response)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
return cast(JSON, response.json())
|
|
191
|
+
except JSONDecodeError as error:
|
|
192
|
+
logging.error(f"Error decoding CDA response: {error}")
|
|
193
|
+
return {}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def post(
|
|
197
|
+
endpoint: str,
|
|
198
|
+
data: JSON,
|
|
199
|
+
params: Optional[RequestParams] = None,
|
|
200
|
+
*,
|
|
201
|
+
api_version: int = API_VERSION,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Make a POST request to the CWMS Data API.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
endpoint: The CDA endpoint for the record type.
|
|
207
|
+
data: A dict containing the new record data. Must be JSON-serializable.
|
|
208
|
+
params (optional): Query parameters for the request.
|
|
209
|
+
|
|
210
|
+
Keyword Args:
|
|
211
|
+
api_version (optional): The CDA version to use for the request. If not specified,
|
|
212
|
+
the default API_VERSION will be used.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
The deserialized JSON response data.
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
ApiError: If an error response is return by the API.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
# post requires different headers than get for
|
|
222
|
+
headers = {"accept": "*/*", "Content-Type": api_version_text(api_version)}
|
|
223
|
+
|
|
224
|
+
response = SESSION.post(
|
|
225
|
+
endpoint, params=params, headers=headers, data=json.dumps(data)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if response.status_code != 200:
|
|
229
|
+
logging.error(f"CDA Error: response={response}")
|
|
230
|
+
raise ApiError(response)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def patch(
|
|
234
|
+
endpoint: str,
|
|
235
|
+
data: JSON,
|
|
236
|
+
params: Optional[RequestParams] = None,
|
|
237
|
+
*,
|
|
238
|
+
api_version: int = API_VERSION,
|
|
239
|
+
) -> None:
|
|
240
|
+
"""Make a PATCH request to the CWMS Data API.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
endpoint: The CDA endpoint for the record.
|
|
244
|
+
data: A dict containing the updated record data. Must be JSON-serializable.
|
|
245
|
+
params (optional): Query parameters for the request.
|
|
246
|
+
|
|
247
|
+
Keyword Args:
|
|
248
|
+
api_version (optional): The CDA version to use for the request. If not specified,
|
|
249
|
+
the default API_VERSION will be used.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The deserialized JSON response data.
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
ApiError: If an error response is return by the API.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
headers = {"accept": "*/*", "Content-Type": api_version_text(api_version)}
|
|
259
|
+
|
|
260
|
+
response = SESSION.patch(
|
|
261
|
+
endpoint, params=params, headers=headers, data=json.dumps(data)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if response.status_code != 200:
|
|
265
|
+
logging.error(f"CDA Error: response={response}")
|
|
266
|
+
raise ApiError(response)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def delete(
|
|
270
|
+
endpoint: str,
|
|
271
|
+
params: Optional[RequestParams] = None,
|
|
272
|
+
*,
|
|
273
|
+
api_version: int = API_VERSION,
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Make a DELETE request to the CWMS Data API.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
endpoint: The CDA endpoint for the record.
|
|
279
|
+
params (optional): Query parameters for the request.
|
|
280
|
+
|
|
281
|
+
Keyword Args:
|
|
282
|
+
api_version (optional): The CDA version to use for the request. If not specified,
|
|
283
|
+
the default API_VERSION will be used.
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
ApiError: If an error response is return by the API.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
headers = {"Accept": api_version_text(api_version)}
|
|
290
|
+
response = SESSION.delete(endpoint, params=params, headers=headers)
|
|
291
|
+
|
|
292
|
+
if response.status_code != 200:
|
|
293
|
+
logging.error(f"CDA Error: response={response}")
|
|
294
|
+
raise ApiError(response)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from requests_toolbelt import sessions # type: ignore
|
|
4
|
+
from requests_toolbelt.sessions import BaseUrlSession # type: ignore
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CwmsApiSession:
|
|
8
|
+
def __init__(self, api_root: str, api_key: Optional[str] = None):
|
|
9
|
+
if api_root is None:
|
|
10
|
+
raise ValueError("CWMS API root URL cannot be None")
|
|
11
|
+
self.__session = sessions.BaseUrlSession(base_url=api_root)
|
|
12
|
+
if api_key is not None:
|
|
13
|
+
self.__session.headers.update({"Authorization": api_key})
|
|
14
|
+
|
|
15
|
+
def get_session(self) -> BaseUrlSession:
|
|
16
|
+
return self.__session
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _CwmsBase:
|
|
20
|
+
def __init__(self, cwms_api_session: CwmsApiSession):
|
|
21
|
+
if cwms_api_session is None:
|
|
22
|
+
raise ValueError("CWMS API session information cannot be None")
|
|
23
|
+
self._cwms_api_session = cwms_api_session
|
|
24
|
+
|
|
25
|
+
def get_session(self) -> BaseUrlSession:
|
|
26
|
+
return self._cwms_api_session.get_session()
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
|
|
6
|
+
from requests.exceptions import HTTPError
|
|
7
|
+
from requests.models import Response
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CwmsDataApiError(HTTPError):
|
|
11
|
+
"""
|
|
12
|
+
Class representing an error thrown by the CwmsDataApi.
|
|
13
|
+
|
|
14
|
+
This class inherits from the requests.exceptions HTTPError class.
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
incident_identifier : str
|
|
19
|
+
The incident identifier extracted from the response object.
|
|
20
|
+
response : Response
|
|
21
|
+
The response from the server
|
|
22
|
+
request : Request
|
|
23
|
+
The request made to the server
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str, response: Response):
|
|
27
|
+
"""
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
message : str
|
|
31
|
+
The message to be passed to the parent class constructor.
|
|
32
|
+
response : Response
|
|
33
|
+
The response object containing information about the incident identifier.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
if response.text:
|
|
37
|
+
self.incident_identifier = response.json().get("incidentIdentifier")
|
|
38
|
+
super().__init__(message, response=response)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ClientError(CwmsDataApiError):
|
|
42
|
+
"""
|
|
43
|
+
ClientError
|
|
44
|
+
Exception class representing the error when an error code in the 400 range is returned for a request.
|
|
45
|
+
|
|
46
|
+
Attributes
|
|
47
|
+
----------
|
|
48
|
+
incident_identifier : str
|
|
49
|
+
The incident identifier extracted from the response object.
|
|
50
|
+
response : Response
|
|
51
|
+
The response from the server
|
|
52
|
+
request : Request
|
|
53
|
+
The request made to the server
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, response: Response):
|
|
57
|
+
"""
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
response : Response
|
|
61
|
+
The response object that triggered the error.
|
|
62
|
+
"""
|
|
63
|
+
message = (
|
|
64
|
+
f"CWMS Client error occurred for request:\n{response.request.url}\n"
|
|
65
|
+
f"Response was:\n{response.text}"
|
|
66
|
+
)
|
|
67
|
+
super().__init__(message, response=response)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ServerError(CwmsDataApiError):
|
|
71
|
+
"""
|
|
72
|
+
ServerError
|
|
73
|
+
Exception class representing the error when an error code in the 500 range is returned for a request.
|
|
74
|
+
|
|
75
|
+
Attributes
|
|
76
|
+
----------
|
|
77
|
+
incident_identifier : str
|
|
78
|
+
The incident identifier extracted from the response object.
|
|
79
|
+
response : Response
|
|
80
|
+
The response from the server
|
|
81
|
+
request : Request
|
|
82
|
+
The request made to the server
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, response: Response):
|
|
86
|
+
"""
|
|
87
|
+
Constructs a ServerError instance.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
response : Response
|
|
92
|
+
The response object from the CWMS server.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
message = (
|
|
96
|
+
f"CWMS Server error occurred for request:\n{response.request.url}\n"
|
|
97
|
+
f"Response was:\n{response.text}"
|
|
98
|
+
)
|
|
99
|
+
super().__init__(message, response=response)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class NoDataFoundError(CwmsDataApiError):
|
|
103
|
+
"""
|
|
104
|
+
NoDataFoundError
|
|
105
|
+
Exception class representing the error when no data is found for a request and an error code 404 is returned.
|
|
106
|
+
|
|
107
|
+
Attributes
|
|
108
|
+
----------
|
|
109
|
+
incident_identifier : str
|
|
110
|
+
The incident identifier extracted from the response object.
|
|
111
|
+
response : Response
|
|
112
|
+
The response from the server
|
|
113
|
+
request : Request
|
|
114
|
+
The request made to the server
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, response: Response):
|
|
118
|
+
"""
|
|
119
|
+
Constructs a NoDataFoundError instance.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
response : Response
|
|
124
|
+
The response object from the CWMS server.
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
message = (
|
|
128
|
+
f"No data found for request:\n{response.request.url}\n"
|
|
129
|
+
f"Response was:\n{response.text}"
|
|
130
|
+
)
|
|
131
|
+
super().__init__(message, response=response)
|