cwms-python 0.1.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.
- cwms_python-0.4.4/LICENSE +21 -0
- cwms_python-0.4.4/PKG-INFO +95 -0
- cwms_python-0.4.4/README.md +73 -0
- cwms_python-0.4.4/cwms/__init__.py +22 -0
- cwms_python-0.4.4/cwms/api.py +336 -0
- cwms_python-0.4.4/cwms/catalog/catalog.py +128 -0
- cwms_python-0.4.4/cwms/forecast/forecast_instance.py +208 -0
- cwms_python-0.4.4/cwms/forecast/forecast_spec.py +181 -0
- cwms_python-0.4.4/cwms/levels/location_levels.py +221 -0
- cwms_python-0.4.4/cwms/levels/specified_levels.py +126 -0
- cwms_python-0.4.4/cwms/locations/physical_locations.py +157 -0
- cwms_python-0.4.4/cwms/ratings/ratings.py +378 -0
- cwms_python-0.4.4/cwms/ratings/ratings_spec.py +154 -0
- cwms_python-0.4.4/cwms/ratings/ratings_template.py +148 -0
- cwms_python-0.4.4/cwms/standard_text/standard_text.py +201 -0
- cwms_python-0.4.4/cwms/timeseries/timerseries_identifier.py +135 -0
- cwms_python-0.4.4/cwms/timeseries/timeseries.py +273 -0
- cwms_python-0.4.4/cwms/timeseries/timeseries_bin.py +212 -0
- cwms_python-0.4.4/cwms/timeseries/timeseries_txt.py +206 -0
- cwms_python-0.4.4/cwms/types.py +87 -0
- cwms_python-0.4.4/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.4.4
|
|
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(ts_id_='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , 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(ts_id_='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , 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,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"
|
|
@@ -0,0 +1,336 @@
|
|
|
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 Any, 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
|
+
elif api_version == 102:
|
|
154
|
+
version = "application/xml;version=2"
|
|
155
|
+
else:
|
|
156
|
+
raise InvalidVersion(f"API version {api_version} is not supported.")
|
|
157
|
+
|
|
158
|
+
return version
|
|
159
|
+
|
|
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
|
+
|
|
198
|
+
def get(
|
|
199
|
+
endpoint: str,
|
|
200
|
+
params: Optional[RequestParams] = None,
|
|
201
|
+
*,
|
|
202
|
+
api_version: int = API_VERSION,
|
|
203
|
+
) -> JSON:
|
|
204
|
+
"""Make a GET request to the CWMS Data API.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
endpoint: The CDA endpoint for the record(s).
|
|
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
|
+
headers = {"Accept": api_version_text(api_version)}
|
|
222
|
+
response = SESSION.get(endpoint, params=params, headers=headers)
|
|
223
|
+
|
|
224
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
225
|
+
logging.error(f"CDA Error: response={response}")
|
|
226
|
+
raise ApiError(response)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
return cast(JSON, response.json())
|
|
230
|
+
except JSONDecodeError as error:
|
|
231
|
+
logging.error(f"Error decoding CDA response as json: {error}")
|
|
232
|
+
return {}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def post(
|
|
236
|
+
endpoint: str,
|
|
237
|
+
data: Any,
|
|
238
|
+
params: Optional[RequestParams] = None,
|
|
239
|
+
*,
|
|
240
|
+
api_version: int = API_VERSION,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Make a POST request to the CWMS Data API.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
endpoint: The CDA endpoint for the record type.
|
|
246
|
+
data: A dict containing the new record data. Must be JSON-serializable.
|
|
247
|
+
params (optional): Query parameters for the request.
|
|
248
|
+
|
|
249
|
+
Keyword Args:
|
|
250
|
+
api_version (optional): The CDA version to use for the request. If not specified,
|
|
251
|
+
the default API_VERSION will be used.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
The deserialized JSON response data.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
ApiError: If an error response is return by the API.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
# post requires different headers than get for
|
|
261
|
+
headers = {"accept": "*/*", "Content-Type": api_version_text(api_version)}
|
|
262
|
+
|
|
263
|
+
if isinstance(data, dict):
|
|
264
|
+
data = json.dumps(data)
|
|
265
|
+
|
|
266
|
+
response = SESSION.post(endpoint, params=params, headers=headers, data=data)
|
|
267
|
+
|
|
268
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
269
|
+
logging.error(f"CDA Error: response={response}")
|
|
270
|
+
raise ApiError(response)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def patch(
|
|
274
|
+
endpoint: str,
|
|
275
|
+
data: Optional[Any] = None,
|
|
276
|
+
params: Optional[RequestParams] = None,
|
|
277
|
+
*,
|
|
278
|
+
api_version: int = API_VERSION,
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Make a PATCH request to the CWMS Data API.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
endpoint: The CDA endpoint for the record.
|
|
284
|
+
data: A dict containing the updated record data. Must be JSON-serializable.
|
|
285
|
+
params (optional): Query parameters for the request.
|
|
286
|
+
|
|
287
|
+
Keyword Args:
|
|
288
|
+
api_version (optional): The CDA version to use for the request. If not specified,
|
|
289
|
+
the default API_VERSION will be used.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The deserialized JSON response data.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ApiError: If an error response is return by the API.
|
|
296
|
+
"""
|
|
297
|
+
|
|
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)
|
|
305
|
+
|
|
306
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
307
|
+
logging.error(f"CDA Error: response={response}")
|
|
308
|
+
raise ApiError(response)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def delete(
|
|
312
|
+
endpoint: str,
|
|
313
|
+
params: Optional[RequestParams] = None,
|
|
314
|
+
*,
|
|
315
|
+
api_version: int = API_VERSION,
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Make a DELETE request to the CWMS Data API.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
endpoint: The CDA endpoint for the record.
|
|
321
|
+
params (optional): Query parameters for the request.
|
|
322
|
+
|
|
323
|
+
Keyword Args:
|
|
324
|
+
api_version (optional): The CDA version to use for the request. If not specified,
|
|
325
|
+
the default API_VERSION will be used.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
ApiError: If an error response is return by the API.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
headers = {"Accept": api_version_text(api_version)}
|
|
332
|
+
response = SESSION.delete(endpoint, params=params, headers=headers)
|
|
333
|
+
|
|
334
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
335
|
+
logging.error(f"CDA Error: response={response}")
|
|
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")
|