cwms-python 0.5.0__tar.gz → 0.6.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.5.0 → cwms_python-0.6.0}/PKG-INFO +15 -4
- {cwms_python-0.5.0 → cwms_python-0.6.0}/README.md +10 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/__init__.py +11 -1
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/api.py +8 -5
- cwms_python-0.6.0/cwms/catalog/blobs.py +85 -0
- cwms_python-0.6.0/cwms/catalog/clobs.py +158 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/cwms_types.py +2 -1
- cwms_python-0.6.0/cwms/datafile_imports/shef_critfile_import.py +130 -0
- cwms_python-0.6.0/cwms/locations/gate_changes.py +185 -0
- cwms_python-0.6.0/cwms/locations/location_groups.py +166 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/locations/physical_locations.py +0 -8
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/ratings/ratings.py +39 -4
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/ratings/ratings_spec.py +50 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/timeseries/timeseries.py +14 -33
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/timeseries/timeseries_bin.py +0 -12
- cwms_python-0.6.0/cwms/timeseries/timeseries_group.py +253 -0
- cwms_python-0.6.0/cwms/timeseries/timeseries_profile.py +166 -0
- cwms_python-0.6.0/cwms/timeseries/timeseries_profile_instance.py +237 -0
- cwms_python-0.6.0/cwms/timeseries/timeseries_profile_parser.py +210 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/timeseries/timeseries_txt.py +0 -14
- cwms_python-0.6.0/cwms/turbines/turbines.py +242 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/pyproject.toml +4 -3
- {cwms_python-0.5.0 → cwms_python-0.6.0}/LICENSE +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/catalog/catalog.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/forecast/forecast_instance.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/forecast/forecast_spec.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/levels/location_levels.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/levels/specified_levels.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/outlets/outlets.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/outlets/virtual_outlets.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/projects/project_lock_rights.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/projects/project_locks.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/projects/projects.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/ratings/ratings_template.py +0 -0
- {cwms_python-0.5.0 → cwms_python-0.6.0}/cwms/standard_text/standard_text.py +0 -0
- /cwms_python-0.5.0/cwms/timeseries/timerseries_identifier.py → /cwms_python-0.6.0/cwms/timeseries/timeseries_identifier.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: cwms-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Corps water managerment systems (CWMS) REST API for Data Retrieval of USACE water data
|
|
5
5
|
License: LICENSE
|
|
6
|
-
Keywords: USACE,water data
|
|
6
|
+
Keywords: USACE,water data,CWMS
|
|
7
7
|
Author: Eric Novotny
|
|
8
8
|
Author-email: eric.v.novotny@usace.army.mil
|
|
9
9
|
Requires-Python: >=3.9,<4.0
|
|
@@ -13,10 +13,11 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Requires-Dist: pandas (>=2.1.3,<3.0.0)
|
|
18
18
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
19
19
|
Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
|
|
20
|
+
Project-URL: Repository, https://github.com/HydrologicEngineeringCenter/cwms-python
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
21
22
|
|
|
22
23
|
# CWMSpy
|
|
@@ -93,3 +94,13 @@ print(json)
|
|
|
93
94
|
'version-date': None}
|
|
94
95
|
```
|
|
95
96
|
|
|
97
|
+
## TimeSeries Profile API Compatibility Warning
|
|
98
|
+
|
|
99
|
+
Currently, the TimeSeries Profile API may not be fully supported
|
|
100
|
+
until a new version of cwms-data-access is released with the updated
|
|
101
|
+
endpoint implementation.
|
|
102
|
+
|
|
103
|
+
## Contributing
|
|
104
|
+
|
|
105
|
+
Please view the contribution documentation here: [CONTRIBUTING.md]
|
|
106
|
+
|
|
@@ -71,3 +71,13 @@ print(json)
|
|
|
71
71
|
['2024-04-23T10:00:00', 86.57999999999997, 3]],
|
|
72
72
|
'version-date': None}
|
|
73
73
|
```
|
|
74
|
+
|
|
75
|
+
## TimeSeries Profile API Compatibility Warning
|
|
76
|
+
|
|
77
|
+
Currently, the TimeSeries Profile API may not be fully supported
|
|
78
|
+
until a new version of cwms-data-access is released with the updated
|
|
79
|
+
endpoint implementation.
|
|
80
|
+
|
|
81
|
+
## Contributing
|
|
82
|
+
|
|
83
|
+
Please view the contribution documentation here: [CONTRIBUTING.md]
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
from importlib.metadata import PackageNotFoundError, version
|
|
2
2
|
|
|
3
3
|
from cwms.api import *
|
|
4
|
+
from cwms.catalog.blobs import *
|
|
4
5
|
from cwms.catalog.catalog import *
|
|
6
|
+
from cwms.catalog.clobs import *
|
|
7
|
+
from cwms.datafile_imports.shef_critfile_import import *
|
|
5
8
|
from cwms.forecast.forecast_instance import *
|
|
6
9
|
from cwms.forecast.forecast_spec import *
|
|
7
10
|
from cwms.levels.location_levels import *
|
|
8
11
|
from cwms.levels.specified_levels import *
|
|
12
|
+
from cwms.locations.gate_changes import *
|
|
13
|
+
from cwms.locations.location_groups import *
|
|
9
14
|
from cwms.locations.physical_locations import *
|
|
10
15
|
from cwms.outlets.outlets import *
|
|
11
16
|
from cwms.outlets.virtual_outlets import *
|
|
@@ -16,10 +21,15 @@ from cwms.ratings.ratings import *
|
|
|
16
21
|
from cwms.ratings.ratings_spec import *
|
|
17
22
|
from cwms.ratings.ratings_template import *
|
|
18
23
|
from cwms.standard_text.standard_text import *
|
|
19
|
-
from cwms.timeseries.timerseries_identifier import *
|
|
20
24
|
from cwms.timeseries.timeseries import *
|
|
21
25
|
from cwms.timeseries.timeseries_bin import *
|
|
26
|
+
from cwms.timeseries.timeseries_group import *
|
|
27
|
+
from cwms.timeseries.timeseries_identifier import *
|
|
28
|
+
from cwms.timeseries.timeseries_profile import *
|
|
29
|
+
from cwms.timeseries.timeseries_profile_instance import *
|
|
30
|
+
from cwms.timeseries.timeseries_profile_parser import *
|
|
22
31
|
from cwms.timeseries.timeseries_txt import *
|
|
32
|
+
from cwms.turbines.turbines import *
|
|
23
33
|
|
|
24
34
|
try:
|
|
25
35
|
__version__ = version("cwms-python")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Session management and REST functions for CWMS Data API.
|
|
2
2
|
|
|
3
3
|
This module provides functions for making REST calls to the CWMS Data API (CDA). These
|
|
4
4
|
functions should be used internally to interact with the API. The user should not have to
|
|
@@ -191,6 +191,7 @@ def get_xml(
|
|
|
191
191
|
|
|
192
192
|
headers = {"Accept": api_version_text(api_version)}
|
|
193
193
|
response = SESSION.get(endpoint, params=params, headers=headers)
|
|
194
|
+
response.close()
|
|
194
195
|
|
|
195
196
|
if response.status_code < 200 or response.status_code >= 300:
|
|
196
197
|
logging.error(f"CDA Error: response={response}")
|
|
@@ -228,6 +229,7 @@ def get(
|
|
|
228
229
|
|
|
229
230
|
headers = {"Accept": api_version_text(api_version)}
|
|
230
231
|
response = SESSION.get(endpoint, params=params, headers=headers)
|
|
232
|
+
response.close()
|
|
231
233
|
if response.status_code < 200 or response.status_code >= 300:
|
|
232
234
|
logging.error(f"CDA Error: response={response}")
|
|
233
235
|
raise ApiError(response)
|
|
@@ -307,10 +309,11 @@ def post(
|
|
|
307
309
|
# post requires different headers than get for
|
|
308
310
|
headers = {"accept": "*/*", "Content-Type": api_version_text(api_version)}
|
|
309
311
|
|
|
310
|
-
if isinstance(data, dict):
|
|
312
|
+
if isinstance(data, dict) or isinstance(data, list):
|
|
311
313
|
data = json.dumps(data)
|
|
312
314
|
|
|
313
315
|
response = SESSION.post(endpoint, params=params, headers=headers, data=data)
|
|
316
|
+
response.close()
|
|
314
317
|
|
|
315
318
|
if response.status_code < 200 or response.status_code >= 300:
|
|
316
319
|
logging.error(f"CDA Error: response={response}")
|
|
@@ -346,10 +349,10 @@ def patch(
|
|
|
346
349
|
if data is None:
|
|
347
350
|
response = SESSION.patch(endpoint, params=params, headers=headers)
|
|
348
351
|
else:
|
|
349
|
-
if isinstance(data, dict):
|
|
352
|
+
if isinstance(data, dict) or isinstance(data, list):
|
|
350
353
|
data = json.dumps(data)
|
|
351
354
|
response = SESSION.patch(endpoint, params=params, headers=headers, data=data)
|
|
352
|
-
|
|
355
|
+
response.close()
|
|
353
356
|
if response.status_code < 200 or response.status_code >= 300:
|
|
354
357
|
logging.error(f"CDA Error: response={response}")
|
|
355
358
|
raise ApiError(response)
|
|
@@ -377,7 +380,7 @@ def delete(
|
|
|
377
380
|
|
|
378
381
|
headers = {"Accept": api_version_text(api_version)}
|
|
379
382
|
response = SESSION.delete(endpoint, params=params, headers=headers)
|
|
380
|
-
|
|
383
|
+
response.close()
|
|
381
384
|
if response.status_code < 200 or response.status_code >= 300:
|
|
382
385
|
logging.error(f"CDA Error: response={response}")
|
|
383
386
|
raise ApiError(response)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import cwms.api as api
|
|
4
|
+
from cwms.cwms_types import JSON, Data
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_blob(blob_id: str, office_id: str) -> Data:
|
|
8
|
+
"""Get a single clob.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
blob_id: string
|
|
13
|
+
Specifies the id of the blob
|
|
14
|
+
office_id: string
|
|
15
|
+
Specifies the office of the blob.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
cwms data type. data.json will return the JSON output and data.df will return a dataframe
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
endpoint = f"blobs/{blob_id}"
|
|
24
|
+
params = {"office": office_id}
|
|
25
|
+
response = api.get(endpoint, params, api_version=1)
|
|
26
|
+
return Data(response)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_blobs(
|
|
30
|
+
office_id: Optional[str] = None,
|
|
31
|
+
page_size: Optional[int] = 100,
|
|
32
|
+
blob_id_like: Optional[str] = None,
|
|
33
|
+
) -> Data:
|
|
34
|
+
"""Get a subset of Blobs
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
office_id: Optional[string]
|
|
39
|
+
Specifies the office of the blob.
|
|
40
|
+
page_sie: Optional[Integer]
|
|
41
|
+
How many entries per page returned. Default 100.
|
|
42
|
+
blob_id_like: Optional[string]
|
|
43
|
+
Posix regular expression matching against the clob id
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
cwms data type. data.json will return the JSON output and data.df will return a dataframe
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
endpoint = "blobs"
|
|
51
|
+
params = {"office": office_id, "page-size": page_size, "like": blob_id_like}
|
|
52
|
+
|
|
53
|
+
response = api.get(endpoint, params, api_version=1)
|
|
54
|
+
return Data(response, selector="blobs")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def store_blobs(data: JSON, fail_if_exists: Optional[bool] = True) -> None:
|
|
58
|
+
"""Create New Blob
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
Data: JSON dictionary
|
|
63
|
+
JSON containing information of Blob to be updated
|
|
64
|
+
{
|
|
65
|
+
"office-id": "string",
|
|
66
|
+
"id": "string",
|
|
67
|
+
"description": "string",
|
|
68
|
+
"media-type-id": "string",
|
|
69
|
+
"value": "string"
|
|
70
|
+
}
|
|
71
|
+
fail_if_exists: Boolean
|
|
72
|
+
Create will fail if provided ID already exists. Default: true
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
None
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if not isinstance(data, dict):
|
|
80
|
+
raise ValueError("Cannot store a Blob without a JSON data dictionary")
|
|
81
|
+
|
|
82
|
+
endpoint = "blobs"
|
|
83
|
+
params = {"fail-if-exists": fail_if_exists}
|
|
84
|
+
|
|
85
|
+
return api.post(endpoint, data, params, api_version=1)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import cwms.api as api
|
|
4
|
+
from cwms.cwms_types import JSON, Data
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_clob(clob_id: str, office_id: str, clob_id_query: Optional[str] = None) -> Data:
|
|
8
|
+
"""Get a single clob.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
clob_id: string
|
|
13
|
+
Specifies the id of the clob
|
|
14
|
+
office_id: string
|
|
15
|
+
Specifies the office of the clob.
|
|
16
|
+
clob_id_query: string
|
|
17
|
+
If this query parameter is provided the id path parameter is ignored and the
|
|
18
|
+
value of the query parameter is used. Note: this query parameter is necessary
|
|
19
|
+
for id's that contain '/' or other special characters. Because of abuse even
|
|
20
|
+
properly escaped '/' in url paths are blocked. When using this query parameter
|
|
21
|
+
a valid path parameter must still be provided for the request to be properly
|
|
22
|
+
routed. If your clob id contains '/' you can't specify the clob-id query
|
|
23
|
+
parameter and also specify the id path parameter because firewall and/or server
|
|
24
|
+
rules will deny the request even though you are specifying this override. "ignored"
|
|
25
|
+
is suggested.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
cwms data type. data.json will return the JSON output and data.df will return a dataframe
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
endpoint = f"clobs/{clob_id}"
|
|
34
|
+
params = {
|
|
35
|
+
"office": office_id,
|
|
36
|
+
"clob-id-query": clob_id_query,
|
|
37
|
+
}
|
|
38
|
+
response = api.get(endpoint, params)
|
|
39
|
+
return Data(response)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_clobs(
|
|
43
|
+
office_id: Optional[str] = None,
|
|
44
|
+
page_size: Optional[int] = 100,
|
|
45
|
+
include_values: Optional[bool] = False,
|
|
46
|
+
clob_id_like: Optional[str] = None,
|
|
47
|
+
) -> Data:
|
|
48
|
+
"""Get a subset of Clobs
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
office_id: Optional[string]
|
|
53
|
+
Specifies the office of the clob.
|
|
54
|
+
page_sie: Optional[Integer]
|
|
55
|
+
How many entries per page returned. Default 100.
|
|
56
|
+
include_values: Optional[Boolean]
|
|
57
|
+
Do you want the value associated with this particular clob (default: false)
|
|
58
|
+
clob_id_like: Optional[string]
|
|
59
|
+
Posix regular expression matching against the clob id
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
cwms data type. data.json will return the JSON output and data.df will return a dataframe
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
endpoint = "clobs"
|
|
67
|
+
params = {
|
|
68
|
+
"office": office_id,
|
|
69
|
+
"page-size": page_size,
|
|
70
|
+
"include-values": include_values,
|
|
71
|
+
"like": clob_id_like,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
response = api.get(endpoint, params)
|
|
75
|
+
return Data(response, selector="clobs")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def delete_clob(clob_id: str, office_id: str) -> None:
|
|
79
|
+
"""Deletes requested clob
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
clob_id: string
|
|
84
|
+
Specifies the id of the clob to be deleted
|
|
85
|
+
office_id: string
|
|
86
|
+
Specifies the office of the clob.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
None
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
endpoint = f"clobs/{clob_id}"
|
|
94
|
+
params = {"office": office_id}
|
|
95
|
+
|
|
96
|
+
return api.delete(endpoint, params=params, api_version=1)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def update_clob(data: JSON, clob_id: str, ignore_nulls: Optional[bool] = True) -> None:
|
|
100
|
+
"""Updates clob
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
Data: JSON dictionary
|
|
105
|
+
JSON containing information of Clob to be updated
|
|
106
|
+
{
|
|
107
|
+
"office-id": "string",
|
|
108
|
+
"id": "string",
|
|
109
|
+
"description": "string",
|
|
110
|
+
"value": "string"
|
|
111
|
+
}
|
|
112
|
+
clob_id: string
|
|
113
|
+
Specifies the id of the clob to be deleted
|
|
114
|
+
ignore_nulls: Boolean
|
|
115
|
+
If true, null and empty fields in the provided clob will be ignored and the existing value of those fields left in place. Default: true
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
None
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
if not isinstance(data, dict):
|
|
123
|
+
raise ValueError("Cannot store a Clob without a JSON data dictionary")
|
|
124
|
+
|
|
125
|
+
endpoint = f"clobs/{clob_id}"
|
|
126
|
+
params = {"ignore-nulls": ignore_nulls}
|
|
127
|
+
|
|
128
|
+
return api.patch(endpoint, data, params, api_version=1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def store_clobs(data: JSON, fail_if_exists: Optional[bool] = True) -> None:
|
|
132
|
+
"""Create New Clob
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
Data: JSON dictionary
|
|
137
|
+
JSON containing information of Clob to be updated
|
|
138
|
+
{
|
|
139
|
+
"office-id": "string",
|
|
140
|
+
"id": "string",
|
|
141
|
+
"description": "string",
|
|
142
|
+
"value": "string"
|
|
143
|
+
}
|
|
144
|
+
fail_if_exists: Boolean
|
|
145
|
+
Create will fail if provided ID already exists. Default: true
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
None
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
if not isinstance(data, dict):
|
|
153
|
+
raise ValueError("Cannot store a Clob without a JSON data dictionary")
|
|
154
|
+
|
|
155
|
+
endpoint = "clobs"
|
|
156
|
+
params = {"fail-if-exists": fail_if_exists}
|
|
157
|
+
|
|
158
|
+
return api.post(endpoint, data, params, api_version=1)
|
|
@@ -2,7 +2,7 @@ from copy import deepcopy
|
|
|
2
2
|
from enum import Enum, auto
|
|
3
3
|
from typing import Any, Optional
|
|
4
4
|
|
|
5
|
-
from pandas import DataFrame, Index, json_normalize, to_datetime
|
|
5
|
+
from pandas import DataFrame, Index, json_normalize, to_datetime, to_numeric
|
|
6
6
|
|
|
7
7
|
# Describes generic JSON serializable data.
|
|
8
8
|
JSON = dict[str, Any]
|
|
@@ -62,6 +62,7 @@ class Data:
|
|
|
62
62
|
def rating_type(data: JSON) -> DataFrame:
|
|
63
63
|
# grab the correct point values for a rating table
|
|
64
64
|
df = DataFrame(data["point"]) if data["point"] else DataFrame()
|
|
65
|
+
df = df.apply(to_numeric)
|
|
65
66
|
return df
|
|
66
67
|
|
|
67
68
|
def timeseries_type(orig_json: JSON, value_json: JSON) -> DataFrame:
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
import cwms
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def import_critfile_to_ts_group(
|
|
10
|
+
file_path: str,
|
|
11
|
+
office_id: str,
|
|
12
|
+
group_id: str = "SHEF Data Acquisition",
|
|
13
|
+
category_id: str = "Data Acquisition",
|
|
14
|
+
group_office_id: str = "CWMS",
|
|
15
|
+
category_office_id: str = "CWMS",
|
|
16
|
+
replace_assigned_ts: bool = False,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Processes a .crit file and saves the information to the SHEF Data Acquisition time series group.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
file_path : str
|
|
24
|
+
Path to the .crit file.
|
|
25
|
+
office_id : str
|
|
26
|
+
The ID of the office associated with the specified timeseries.
|
|
27
|
+
group_id : str, optional
|
|
28
|
+
The specified group associated with the timeseries data. Defaults to "SHEF Data Acquisition".
|
|
29
|
+
category_id : str, optional
|
|
30
|
+
The category ID that contains the timeseries group. Defaults to "Data Acquisition".
|
|
31
|
+
group_office_id : str, optional
|
|
32
|
+
The specified office group associated with the timeseries data. Defaults to "CWMS".
|
|
33
|
+
replace_assigned_ts : bool, optional
|
|
34
|
+
Specifies whether to unassign all existing time series before assigning new time series specified in the content body. Default is False.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
None
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def parse_crit_file(file_path: str) -> List[Dict[str, str]]:
|
|
42
|
+
"""
|
|
43
|
+
Parses a .crit file into a dictionary containing timeseries ID and Alias.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
file_path : str
|
|
48
|
+
Path to the .crit file.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
List[Dict[str, str]]
|
|
53
|
+
A list of dictionaries with "Alias" and "Timeseries ID" as keys.
|
|
54
|
+
"""
|
|
55
|
+
parsed_data = []
|
|
56
|
+
with open(file_path, "r") as file:
|
|
57
|
+
for line in file:
|
|
58
|
+
# Ignore comment lines and empty lines
|
|
59
|
+
if line.startswith("#") or not line.strip():
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# Extract alias, timeseries ID, and TZ
|
|
63
|
+
match = re.match(r"([^=]+)=([^;]+);(.+)", line.strip())
|
|
64
|
+
|
|
65
|
+
if match:
|
|
66
|
+
alias = match.group(1).strip()
|
|
67
|
+
timeseries_id = match.group(2).strip()
|
|
68
|
+
alias2 = match.group(3).strip()
|
|
69
|
+
|
|
70
|
+
parsed_data.append(
|
|
71
|
+
{
|
|
72
|
+
"Alias": alias + ":" + alias2,
|
|
73
|
+
"Timeseries ID": timeseries_id,
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return parsed_data
|
|
78
|
+
|
|
79
|
+
def append_df(
|
|
80
|
+
df: pd.DataFrame, office_id: str, ts_id: str, alias: str
|
|
81
|
+
) -> pd.DataFrame:
|
|
82
|
+
"""
|
|
83
|
+
Appends a row to the DataFrame.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
df : pandas.DataFrame
|
|
88
|
+
The DataFrame to append to.
|
|
89
|
+
office_id : str
|
|
90
|
+
The ID of the office associated with the specified timeseries.
|
|
91
|
+
tsId : str
|
|
92
|
+
The timeseries ID from the file.
|
|
93
|
+
alias : str
|
|
94
|
+
The alias from the file.
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
pandas.DataFrame
|
|
98
|
+
The updated DataFrame.
|
|
99
|
+
"""
|
|
100
|
+
data = {
|
|
101
|
+
"office-id": [office_id],
|
|
102
|
+
"timeseries-id": [ts_id],
|
|
103
|
+
"alias-id": [alias],
|
|
104
|
+
}
|
|
105
|
+
df = pd.concat([df, pd.DataFrame(data)])
|
|
106
|
+
return df
|
|
107
|
+
|
|
108
|
+
# Parse the file and get the parsed data
|
|
109
|
+
parsed_data = parse_crit_file(file_path)
|
|
110
|
+
|
|
111
|
+
df = pd.DataFrame()
|
|
112
|
+
for data in parsed_data:
|
|
113
|
+
# Create DataFrame for the current row
|
|
114
|
+
df = append_df(df, office_id, data["Timeseries ID"], data["Alias"])
|
|
115
|
+
|
|
116
|
+
# Generate JSON dictionary
|
|
117
|
+
json_dict = cwms.timeseries_group_df_to_json(
|
|
118
|
+
data=df,
|
|
119
|
+
group_id=group_id,
|
|
120
|
+
group_office_id=group_office_id,
|
|
121
|
+
category_office_id=category_office_id,
|
|
122
|
+
category_id=category_id,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
cwms.update_timeseries_groups(
|
|
126
|
+
group_id=group_id,
|
|
127
|
+
office_id=office_id,
|
|
128
|
+
replace_assigned_ts=replace_assigned_ts,
|
|
129
|
+
data=json_dict,
|
|
130
|
+
)
|