cwms-python 0.7.1__tar.gz → 0.8.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.
Files changed (39) hide show
  1. {cwms_python-0.7.1 → cwms_python-0.8.0}/PKG-INFO +1 -1
  2. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/__init__.py +1 -0
  3. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/api.py +3 -2
  4. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/cwms_types.py +41 -0
  5. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/measurements/measurements.py +34 -2
  6. cwms_python-0.8.0/cwms/projects/water_supply/accounting.py +145 -0
  7. {cwms_python-0.7.1 → cwms_python-0.8.0}/pyproject.toml +1 -1
  8. {cwms_python-0.7.1 → cwms_python-0.8.0}/LICENSE +0 -0
  9. {cwms_python-0.7.1 → cwms_python-0.8.0}/README.md +0 -0
  10. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/catalog/blobs.py +0 -0
  11. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/catalog/catalog.py +0 -0
  12. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/catalog/clobs.py +0 -0
  13. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/forecast/forecast_instance.py +0 -0
  14. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/forecast/forecast_spec.py +0 -0
  15. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/levels/location_levels.py +0 -0
  16. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/levels/specified_levels.py +0 -0
  17. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/locations/gate_changes.py +0 -0
  18. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/locations/location_groups.py +0 -0
  19. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/locations/physical_locations.py +0 -0
  20. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/outlets/outlets.py +0 -0
  21. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/outlets/virtual_outlets.py +0 -0
  22. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/projects/project_lock_rights.py +0 -0
  23. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/projects/project_locks.py +0 -0
  24. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/projects/projects.py +0 -0
  25. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/ratings/ratings.py +0 -0
  26. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/ratings/ratings_spec.py +0 -0
  27. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/ratings/ratings_template.py +0 -0
  28. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/standard_text/standard_text.py +0 -0
  29. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries.py +0 -0
  30. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries_bin.py +0 -0
  31. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries_group.py +0 -0
  32. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries_identifier.py +0 -0
  33. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries_profile.py +0 -0
  34. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries_profile_instance.py +0 -0
  35. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries_profile_parser.py +0 -0
  36. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/timeseries/timeseries_txt.py +0 -0
  37. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/turbines/turbines.py +0 -0
  38. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/utils/__init__.py +0 -0
  39. {cwms_python-0.7.1 → cwms_python-0.8.0}/cwms/utils/checks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cwms-python
3
- Version: 0.7.1
3
+ Version: 0.8.0
4
4
  Summary: Corps water management systems (CWMS) REST API for Data Retrieval of USACE water data
5
5
  License: LICENSE
6
6
  Keywords: USACE,water data,CWMS
@@ -17,6 +17,7 @@ from cwms.outlets.virtual_outlets import *
17
17
  from cwms.projects.project_lock_rights import *
18
18
  from cwms.projects.project_locks import *
19
19
  from cwms.projects.projects import *
20
+ from cwms.projects.water_supply.accounting import *
20
21
  from cwms.ratings.ratings import *
21
22
  from cwms.ratings.ratings_spec import *
22
23
  from cwms.ratings.ratings_template import *
@@ -131,7 +131,6 @@ def init_session(
131
131
  """
132
132
 
133
133
  global SESSION
134
-
135
134
  if api_root:
136
135
  logging.debug(f"Initializing root URL: api_root={api_root}")
137
136
  SESSION = sessions.BaseUrlSession(base_url=api_root)
@@ -142,8 +141,10 @@ def init_session(
142
141
  )
143
142
  SESSION.mount("https://", adapter)
144
143
  if api_key:
144
+ if api_key.startswith("apikey "):
145
+ api_key = api_key.replace("apikey ", "")
145
146
  logging.debug(f"Setting authorization key: api_key={api_key}")
146
- SESSION.headers.update({"Authorization": api_key})
147
+ SESSION.headers.update({"Authorization": "apikey " + api_key})
147
148
 
148
149
  return SESSION
149
150
 
@@ -79,6 +79,44 @@ class Data:
79
79
  df["date-time"] = to_datetime(df["date-time"], unit="ms", utc=True)
80
80
  return df
81
81
 
82
+ def reorder_measurement_cols(df: DataFrame) -> DataFrame:
83
+ # reorders measurement columns for usability
84
+
85
+ # Define the columns to bring to the front
86
+ front_columns = [
87
+ "id.office-id",
88
+ "id.name",
89
+ "number",
90
+ "instant",
91
+ "streamflow-measurement.gage-height",
92
+ "streamflow-measurement.flow",
93
+ "streamflow-measurement.quality",
94
+ "used",
95
+ "agency",
96
+ "wm-comments",
97
+ ]
98
+
99
+ # Identify columns containing 'unit' to be last
100
+ unit_columns = [col for col in df.columns if "unit" in col]
101
+
102
+ # Identify remaining columns (not in front_columns or unit_columns)
103
+ remaining_columns = [
104
+ col
105
+ for col in df.columns
106
+ if col not in front_columns and col not in unit_columns
107
+ ]
108
+
109
+ # Construct the new column order
110
+ new_column_order = front_columns + remaining_columns + unit_columns
111
+
112
+ # Filter out columns that might not actually exist in the DataFrame.
113
+ existing_columns = [col for col in new_column_order if col in df.columns]
114
+
115
+ # Reorder the DataFrame
116
+ df = df[existing_columns]
117
+
118
+ return df
119
+
82
120
  data = deepcopy(json)
83
121
 
84
122
  if selector:
@@ -95,6 +133,9 @@ class Data:
95
133
  df = json_normalize(df_data) if df_data else DataFrame()
96
134
  else:
97
135
  df = json_normalize(data)
136
+ # if streamflow-measurement reorder columns
137
+ if "streamflow-measurement.flow" in df.columns:
138
+ df = reorder_measurement_cols(df)
98
139
 
99
140
  return df
100
141
 
@@ -116,8 +116,15 @@ def store_measurements(
116
116
  "fail-if-exists": fail_if_exists,
117
117
  }
118
118
 
119
- if not isinstance(data, dict):
120
- raise ValueError("Cannot store a timeseries without a JSON data dictionary")
119
+ if not isinstance(data, list):
120
+ raise ValueError(
121
+ "Cannot store a measurement without a JSON list, object is not a list of dictionaries"
122
+ )
123
+ for item in data:
124
+ if not isinstance(item, dict):
125
+ raise ValueError(
126
+ "Cannot store a measurement without a JSON list: a non-dictionary object was found"
127
+ )
121
128
 
122
129
  return api.post(endpoint, data, params, api_version=1)
123
130
 
@@ -175,3 +182,28 @@ def delete_measurements(
175
182
  }
176
183
 
177
184
  return api.delete(endpoint, params, api_version=1)
185
+
186
+
187
+ def get_measurements_extents(
188
+ office_mask: Optional[str] = None,
189
+ ) -> Data:
190
+ """Get time extents of streamflow measurements
191
+
192
+ Parameters
193
+ ----------
194
+ office_mask: string
195
+ Office Id used to filter the results.
196
+
197
+ Returns
198
+ -------
199
+ cwms data type. data.json will return the JSON output and data.df will return a dataframe. Dates returned are all in UTC.
200
+
201
+ """
202
+ endpoint = "measurements/time-extents"
203
+
204
+ params = {
205
+ "office-mask": office_mask,
206
+ }
207
+
208
+ response = api.get(endpoint, params, api_version=1)
209
+ return Data(response) # , selector=selector)
@@ -0,0 +1,145 @@
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
+ import cwms.api as api
6
+ from cwms.cwms_types import JSON, Data
7
+
8
+
9
+ def get_pump_accounting(
10
+ office_id: str,
11
+ project_id: str,
12
+ water_user: str,
13
+ contract_name: str,
14
+ start: str,
15
+ end: str,
16
+ timezone: str = "UTC",
17
+ unit: str = "cms",
18
+ start_time_inclusive: bool = True,
19
+ end_time_inclusive: bool = True,
20
+ ascending: bool = True,
21
+ row_limit: int = 0,
22
+ ) -> Data:
23
+ """
24
+ Retrieves pump accounting entries associated with a water supply contract.
25
+
26
+ Parameters
27
+ ----------
28
+ office_id : str
29
+ The office ID the pump accounting is associated with. (Path)
30
+ project_id : str
31
+ The project ID the pump accounting is associated with. (Path)
32
+ water_user : str
33
+ The water user the pump accounting is associated with. (Path)
34
+ contract_name : str
35
+ The name of the contract associated with the pump accounting. (Path)
36
+ start : str
37
+ The start time of the time window for pump accounting entries to retrieve.
38
+ Format: ISO 8601 extended, with optional offset and timezone. (Query)
39
+ end : str
40
+ The end time of the time window for pump accounting entries to retrieve.
41
+ Format: ISO 8601 extended, with optional offset and timezone. (Query)
42
+ timezone : str, optional
43
+ The default timezone to use if `start` or `end` lacks offset/timezone info.
44
+ Defaults to "UTC". (Query)
45
+ unit : str, optional
46
+ Unit of flow rate for accounting entries. Defaults to "cms". (Query)
47
+ start_time_inclusive : bool, optional
48
+ Whether the start time is inclusive. Defaults to True. (Query)
49
+ end_time_inclusive : bool, optional
50
+ Whether the end time is inclusive. Defaults to True. (Query)
51
+ ascending : bool, optional
52
+ Whether entries should be returned in ascending order. Defaults to True. (Query)
53
+ row_limit : int, optional
54
+ Maximum number of rows to return. Defaults to 0, meaning no limit. (Query)
55
+
56
+ Returns
57
+ -------
58
+ Data
59
+ The JSON response from CWMS Data API wrapped in a Data object.
60
+
61
+ Raises
62
+ ------
63
+ ValueError
64
+ If any required path parameters are None.
65
+ ClientError
66
+ If a 400-level error occurs.
67
+ NoDataFoundError
68
+ If a 404-level error occurs.
69
+ ServerError
70
+ If a 500-level error occurs.
71
+ """
72
+ if not all([office_id, project_id, water_user, contract_name, start, end]):
73
+ raise ValueError("All required parameters must be provided.")
74
+
75
+ endpoint = f"projects/{office_id}/{project_id}/water-user/{water_user}/contracts/{contract_name}/accounting"
76
+
77
+ params: dict[str, str | int] = {
78
+ "start": start,
79
+ "end": end,
80
+ "timezone": timezone,
81
+ "unit": unit,
82
+ "start-time-inclusive": str(start_time_inclusive).lower(),
83
+ "end-time-inclusive": str(end_time_inclusive).lower(),
84
+ "ascending": str(ascending).lower(),
85
+ "row-limit": row_limit,
86
+ }
87
+
88
+ response = api.get(endpoint, params, api_version=1)
89
+ return Data(response)
90
+
91
+
92
+ def store_pump_accounting(
93
+ office: str,
94
+ project_id: str,
95
+ water_user: str,
96
+ contract_name: str,
97
+ data: JSON,
98
+ ) -> None:
99
+ """
100
+ Creates a new pump accounting entry associated with a water supply contract.
101
+
102
+ Parameters
103
+ ----------
104
+ office : str
105
+ The office ID the accounting is associated with. (Path)
106
+ project_id : str
107
+ The project ID the accounting is associated with. (Path)
108
+ water_user : str
109
+ The water user the accounting is associated with. (Path)
110
+ contract_name : str
111
+ The name of the contract associated with the accounting. (Path)
112
+ data : dict
113
+ A dictionary representing the JSON data to be stored. This should match the
114
+ WaterSupplyAccounting structure as defined by the API.
115
+
116
+ Returns
117
+ -------
118
+ None
119
+
120
+ Raises
121
+ ------
122
+ ValueError
123
+ If any required argument is missing.
124
+ ClientError
125
+ If a 400 range error code response is returned from the server.
126
+ NoDataFoundError
127
+ If a 404 range error code response is returned from the server.
128
+ ServerError
129
+ If a 500 range error code response is returned from the server.
130
+ """
131
+ if not all([office, project_id, water_user, contract_name]):
132
+ raise ValueError(
133
+ "Office, project_id, water_user, and contract_name must be provided."
134
+ )
135
+ if not data:
136
+ raise ValueError("Data must be provided and cannot be empty.")
137
+
138
+ endpoint = f"projects/{office}/{project_id}/water-user/{water_user}/contracts/{contract_name}/accounting"
139
+ params = {
140
+ "office": office,
141
+ "project-id": project_id,
142
+ "water-user": water_user,
143
+ "contract-name": contract_name,
144
+ }
145
+ api.post(endpoint, data, params)
@@ -2,7 +2,7 @@
2
2
  name = "cwms-python"
3
3
  repository = "https://github.com/HydrologicEngineeringCenter/cwms-python"
4
4
 
5
- version = "0.7.1"
5
+ version = "0.8.0"
6
6
 
7
7
 
8
8
  packages = [
File without changes
File without changes