cwms-python 1.0.9__tar.gz → 1.1.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 (45) hide show
  1. {cwms_python-1.0.9 → cwms_python-1.1.0}/PKG-INFO +4 -1
  2. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/__init__.py +3 -0
  3. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/locations/gate_changes.py +3 -3
  4. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/locations/lookups.py +10 -6
  5. cwms_python-1.1.0/cwms/locks/locks.py +198 -0
  6. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/outlets/outlets.py +1 -1
  7. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/projects/projects.py +4 -4
  8. cwms_python-1.1.0/cwms/projects/water_supply/water_contracts.py +234 -0
  9. cwms_python-1.1.0/cwms/projects/water_supply/water_users.py +198 -0
  10. {cwms_python-1.0.9 → cwms_python-1.1.0}/pyproject.toml +10 -1
  11. {cwms_python-1.0.9 → cwms_python-1.1.0}/LICENSE +0 -0
  12. {cwms_python-1.0.9 → cwms_python-1.1.0}/README.md +0 -0
  13. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/api.py +0 -0
  14. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/catalog/blobs.py +0 -0
  15. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/catalog/catalog.py +0 -0
  16. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/catalog/clobs.py +0 -0
  17. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/cwms_types.py +0 -0
  18. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/forecast/forecast_instance.py +0 -0
  19. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/forecast/forecast_spec.py +0 -0
  20. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/levels/location_levels.py +0 -0
  21. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/levels/specified_levels.py +0 -0
  22. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/locations/location_groups.py +0 -0
  23. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/locations/physical_locations.py +0 -0
  24. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/measurements/measurements.py +0 -0
  25. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/outlets/virtual_outlets.py +0 -0
  26. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/projects/project_lock_rights.py +0 -0
  27. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/projects/project_locks.py +0 -0
  28. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/projects/water_supply/accounting.py +0 -0
  29. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/properties/properties.py +0 -0
  30. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/ratings/ratings.py +0 -0
  31. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/ratings/ratings_spec.py +0 -0
  32. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/ratings/ratings_template.py +0 -0
  33. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/standard_text/standard_text.py +0 -0
  34. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries.py +0 -0
  35. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries_bin.py +0 -0
  36. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries_group.py +0 -0
  37. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries_identifier.py +0 -0
  38. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries_profile.py +0 -0
  39. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries_profile_instance.py +0 -0
  40. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries_profile_parser.py +0 -0
  41. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/timeseries/timeseries_txt.py +0 -0
  42. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/turbines/turbines.py +0 -0
  43. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/users/users.py +0 -0
  44. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/utils/__init__.py +0 -0
  45. {cwms_python-1.0.9 → cwms_python-1.1.0}/cwms/utils/checks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cwms-python
3
- Version: 1.0.9
3
+ Version: 1.1.0
4
4
  Summary: Corps water management systems (CWMS) REST API for Data Retrieval of USACE water data
5
5
  License: LICENSE
6
6
  License-File: LICENSE
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
19
20
  Requires-Dist: numpy (>=1.23.5,<3) ; python_version >= "3.9" and python_version < "3.13"
20
21
  Requires-Dist: numpy (>=2.1.0,<3) ; python_version == "3.13"
21
22
  Requires-Dist: numpy (>=2.3.3,<3) ; python_version >= "3.14"
@@ -23,6 +24,8 @@ Requires-Dist: pandas (>=2.3.3,<3.0.0)
23
24
  Requires-Dist: requests (>=2.32.4,<3.0.0) ; python_version == "3.9"
24
25
  Requires-Dist: requests (>=2.33.0,<3.0.0) ; python_version >= "3.10"
25
26
  Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
27
+ Requires-Dist: urllib3 (>=2.6.3,<2.7) ; python_version == "3.9"
28
+ Requires-Dist: urllib3 (>=2.7.0,<3.0.0) ; python_version >= "3.10"
26
29
  Project-URL: Repository, https://github.com/HydrologicEngineeringCenter/cwms-python
27
30
  Description-Content-Type: text/markdown
28
31
 
@@ -12,6 +12,7 @@ from cwms.locations.gate_changes import *
12
12
  from cwms.locations.location_groups import *
13
13
  from cwms.locations.lookups import *
14
14
  from cwms.locations.physical_locations import *
15
+ from cwms.locks.locks import *
15
16
  from cwms.measurements.measurements import *
16
17
  from cwms.outlets.outlets import *
17
18
  from cwms.outlets.virtual_outlets import *
@@ -19,6 +20,8 @@ from cwms.projects.project_lock_rights import *
19
20
  from cwms.projects.project_locks import *
20
21
  from cwms.projects.projects import *
21
22
  from cwms.projects.water_supply.accounting import *
23
+ from cwms.projects.water_supply.water_contracts import *
24
+ from cwms.projects.water_supply.water_users import *
22
25
  from cwms.properties.properties import *
23
26
  from cwms.ratings.ratings import *
24
27
  from cwms.ratings.ratings_spec import *
@@ -62,7 +62,7 @@ def get_all_gate_changes(
62
62
  "unit-system": unit_system,
63
63
  "page-size": page_size,
64
64
  }
65
- response = api.get(endpoint, params=params)
65
+ response = api.get(endpoint, params=params, api_version=1)
66
66
  return Data(response)
67
67
 
68
68
 
@@ -144,7 +144,7 @@ def store_gate_change(
144
144
  params = {
145
145
  "fail-if-exists": fail_if_exists,
146
146
  }
147
- return api.post(endpoint, data=gate_change_data, params=params)
147
+ return api.post(endpoint, data=gate_change_data, params=params, api_version=1)
148
148
 
149
149
 
150
150
  def delete_gate_change(
@@ -182,4 +182,4 @@ def delete_gate_change(
182
182
  "override-protection": override_protection,
183
183
  }
184
184
 
185
- return api.delete(endpoint, params=params)
185
+ return api.delete(endpoint, params=params, api_version=1)
@@ -123,14 +123,16 @@ def update_lookup(data: JSON, category: str, prefix: str) -> None:
123
123
  api.patch(endpoint, data, params, api_version=1)
124
124
 
125
125
 
126
- def delete_lookup(name: str, category: str, prefix: str, office_id: str) -> None:
126
+ def delete_lookup(
127
+ display_value: str, category: str, prefix: str, office_id: str
128
+ ) -> None:
127
129
  """
128
130
  Deletes a specified lookup entry.
129
131
 
130
132
  Parameters
131
133
  ----------
132
- name : str
133
- Specifies the location type to delete.
134
+ display_value : str
135
+ Specifies the display value of the lookup type to be deleted.
134
136
  category : str
135
137
  Specifies the category id of the lookup type to be deleted.
136
138
  prefix : str
@@ -153,9 +155,11 @@ def delete_lookup(name: str, category: str, prefix: str, office_id: str) -> None
153
155
  ServerError
154
156
  If a 500 range error code response is returned from the server.
155
157
  """
156
- if not all([name, category, prefix, office_id]):
157
- raise ValueError("Name, Category, Prefix, and Office ID must be specified")
158
+ if not all([display_value, category, prefix, office_id]):
159
+ raise ValueError(
160
+ "Display Value, Category, Prefix, and Office ID must be specified"
161
+ )
158
162
 
159
- endpoint = f"{ENDPOINT}/{name}"
163
+ endpoint = f"{ENDPOINT}/{display_value}"
160
164
  params = {"category": category, "prefix": prefix, "office": office_id}
161
165
  api.delete(endpoint, params, api_version=1)
@@ -0,0 +1,198 @@
1
+ # Copyright (c) 2026
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 typing import Optional
7
+
8
+ import cwms.api as api
9
+ from cwms.cwms_types import JSON, Data, DeleteMethod
10
+
11
+
12
+ def get_lock(name: str, office_id: str, unit: Optional[str] = "SI") -> Data:
13
+ """
14
+ Get a specified lock with the given office and name.
15
+
16
+ Parameters
17
+ ----------
18
+ office_id : str
19
+ The office ID of the lock to retrieve. (Query)
20
+ name : str
21
+ The name of the lock to retrieve. (Query)
22
+ unit : str
23
+ The unit system to use for the response. Defaults to "SI". (Query)
24
+
25
+ Returns
26
+ -------
27
+ Data
28
+ The JSON response from CWMS Data API wrapped in a Data object.
29
+
30
+ Raises
31
+ ------
32
+ ValueError
33
+ If any required path parameters are None.
34
+ ClientError
35
+ If a 400-level error occurs.
36
+ NoDataFoundError
37
+ If a 404-level error occurs.
38
+ ServerError
39
+ If a 500-level error occurs.
40
+ """
41
+ if not all([office_id, name]):
42
+ raise ValueError("Office and Name must be provided.")
43
+
44
+ endpoint = f"projects/locks/{name}"
45
+
46
+ params = {"office": office_id, "unit": unit}
47
+
48
+ response = api.get(endpoint, params, api_version=1)
49
+ return Data(response)
50
+
51
+
52
+ def get_locks(office_id: str, project_id: str) -> Data:
53
+ """
54
+ Get all locks for the given office and project.
55
+
56
+ Parameters
57
+ ----------
58
+ office_id : str
59
+ The office ID of the locks to retrieve. (Query)
60
+ project_id : str
61
+ The project ID of the locks to retrieve. (Query)
62
+
63
+ Returns
64
+ -------
65
+ Data
66
+ The JSON response from CWMS Data API wrapped in a Data object.
67
+
68
+ Raises
69
+ ------
70
+ ValueError
71
+ If any required path parameters are None.
72
+ ClientError
73
+ If a 400-level error occurs.
74
+ NoDataFoundError
75
+ If a 404-level error occurs.
76
+ ServerError
77
+ If a 500-level error occurs.
78
+ """
79
+ if not all([office_id, project_id]):
80
+ raise ValueError("Office and Project ID must be provided.")
81
+
82
+ endpoint = "projects/locks"
83
+
84
+ params = {"office": office_id, "project-id": project_id}
85
+
86
+ response = api.get(endpoint, params, api_version=1)
87
+ return Data(response)
88
+
89
+
90
+ def create_lock(data: JSON, fail_if_exists: bool = True) -> None:
91
+ """
92
+ Create CWMS Lock.
93
+
94
+ Parameters
95
+ ----------
96
+ data : JSON
97
+ Lock successfully stored to CWMS. (Body)
98
+ fail_if_exists : bool, optional
99
+ Create will fail if provided ID already exists. Default: True. (Query)
100
+
101
+ Returns
102
+ -------
103
+ None
104
+
105
+ Raises
106
+ ------
107
+ ValueError
108
+ If any required parameters are None.
109
+ ClientError
110
+ If a 400-level error occurs.
111
+ NoDataFoundError
112
+ If a 404-level error occurs.
113
+ ServerError
114
+ If a 500-level error occurs.
115
+ """
116
+ if not data:
117
+ raise ValueError("Data must be provided and cannot be empty.")
118
+
119
+ endpoint = "projects/locks"
120
+ params = {"fail-if-exists": fail_if_exists}
121
+
122
+ api.post(endpoint, data, params, api_version=1)
123
+
124
+
125
+ def delete_lock(
126
+ name: str, office_id: str, method: DeleteMethod = DeleteMethod.DELETE_KEY
127
+ ) -> None:
128
+ """
129
+ Delete CWMS Lock
130
+
131
+ Parameters
132
+ ----------
133
+ name : str
134
+ Specifies the name of the lock to be deleted. (Path)
135
+ office_id : str
136
+ Specifies the owning office of the lock to be deleted. (Query)
137
+ method : DeleteMethod, optional
138
+ Specifies the delete method used. Defaults to "DELETE_KEY". (Query)
139
+
140
+ Returns
141
+ -------
142
+ None
143
+
144
+ Raises
145
+ ------
146
+ ValueError
147
+ If any required parameters are None.
148
+ ClientError
149
+ If a 400-level error occurs.
150
+ NoDataFoundError
151
+ If a 404-level error occurs.
152
+ ServerError
153
+ If a 500-level error occurs.
154
+ """
155
+ if not all([name, office_id]):
156
+ raise ValueError("Name and Office ID must be provided.")
157
+
158
+ endpoint = f"projects/locks/{name}"
159
+ params = {"office": office_id, "method": method.name}
160
+
161
+ api.delete(endpoint, params, api_version=1)
162
+
163
+
164
+ def update_lock(name: str, office_id: str, new_name: str) -> None:
165
+ """
166
+ Rename CWMS Lock
167
+
168
+ Parameters
169
+ ----------
170
+ name : str
171
+ Specifies the name of the lock to be renamed. (Path)
172
+ office_id : str
173
+ Specifies the owning office of the lock to be renamed. (Query)
174
+ new_name : str
175
+ Specifies the new lock name. (Query)
176
+
177
+ Returns
178
+ -------
179
+ None
180
+
181
+ Raises
182
+ ------
183
+ ValueError
184
+ If any required parameters are None.
185
+ ClientError
186
+ If a 400-level error occurs.
187
+ NoDataFoundError
188
+ If a 404-level error occurs.
189
+ ServerError
190
+ If a 500-level error occurs.
191
+ """
192
+ if not all([name, office_id, new_name]):
193
+ raise ValueError("Name, Office ID, and New Name must be provided.")
194
+
195
+ endpoint = f"projects/locks/{name}"
196
+ params = {"office": office_id, "name": new_name}
197
+
198
+ api.patch(endpoint, None, params, api_version=1)
@@ -192,4 +192,4 @@ def store_outlet(data: JSON, fail_if_exists: Optional[bool] = True) -> None:
192
192
 
193
193
  endpoint = "projects/outlets"
194
194
  params = {"fail-if-exists": fail_if_exists}
195
- api.post(endpoint, data, params)
195
+ api.post(endpoint, data, params, api_version=1)
@@ -87,7 +87,7 @@ def get_projects(
87
87
  "page": page,
88
88
  "page-size": page_size,
89
89
  }
90
- response = api.get(endpoint, params)
90
+ response = api.get(endpoint, params, api_version=1)
91
91
  return Data(response)
92
92
 
93
93
 
@@ -208,7 +208,7 @@ def rename_project(office_id: str, old_name: str, new_name: str) -> None:
208
208
 
209
209
  endpoint = f"projects/{old_name}"
210
210
  params = {"office": office_id, "name": new_name}
211
- api.patch(endpoint=endpoint, params=params)
211
+ api.patch(endpoint=endpoint, params=params, api_version=1)
212
212
 
213
213
 
214
214
  def store_project(data: JSON, fail_if_exists: Optional[bool] = True) -> None:
@@ -243,7 +243,7 @@ def store_project(data: JSON, fail_if_exists: Optional[bool] = True) -> None:
243
243
 
244
244
  endpoint = "projects"
245
245
  params = {"fail-if-exists": fail_if_exists}
246
- api.post(endpoint, data, params)
246
+ api.post(endpoint, data, params, api_version=1)
247
247
 
248
248
 
249
249
  def status_update(
@@ -306,4 +306,4 @@ def status_update(
306
306
  "begin": (begin.isoformat() if begin else None),
307
307
  "end": (end.isoformat() if end else None),
308
308
  }
309
- api.post(endpoint, None, params)
309
+ api.post(endpoint, None, params, api_version=1)
@@ -0,0 +1,234 @@
1
+ # Copyright (c) 2026
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
+ import cwms.api as api
7
+ from cwms.cwms_types import JSON, Data, DeleteMethod
8
+
9
+
10
+ def get_water_contract(
11
+ office_id: str, project_id: str, water_user: str, contract_name: str
12
+ ) -> Data:
13
+ """
14
+ Return a specified water contract
15
+
16
+ Parameters
17
+ ----------
18
+ office_id : str
19
+ The office Id the contract is associated with. (Path)
20
+ project_id : str
21
+ The project Id the contract is associated with. (Path)
22
+ water_user : str
23
+ The water user the contract is associated with. (Path)
24
+ contract_name : str
25
+ The name of the contract to retrieve. (Path)
26
+
27
+ Returns
28
+ -------
29
+ Data
30
+ The JSON response from CWMS Data API wrapped in a Data object.
31
+
32
+ Raises
33
+ ------
34
+ ValueError
35
+ If any required path parameters are None.
36
+ ClientError
37
+ If a 400-level error occurs.
38
+ NoDataFoundError
39
+ If a 404-level error occurs.
40
+ ServerError
41
+ If a 500-level error occurs.
42
+ """
43
+ if not all([office_id, project_id, water_user, contract_name]):
44
+ raise ValueError(
45
+ "Office, project_id, water_user, and contract_name must be provided."
46
+ )
47
+
48
+ endpoint = f"projects/{office_id}/{project_id}/water-user/{water_user}/contracts/{contract_name}"
49
+
50
+ response = api.get(endpoint, api_version=1)
51
+ return Data(response)
52
+
53
+
54
+ def get_water_contracts(office_id: str, project_id: str, water_user: str) -> Data:
55
+ """
56
+ Return all water contracts for the specified water user, project, and office
57
+
58
+ Parameters
59
+ ----------
60
+ office_id : str
61
+ The office Id the contract is associated with. (Path)
62
+ project_id : str
63
+ The project Id the contract is associated with. (Path)
64
+ water_user : str
65
+ The water user the contract is associated with. (Path)
66
+
67
+ Returns
68
+ -------
69
+ Data
70
+ The JSON response from CWMS Data API wrapped in a Data object.
71
+
72
+ Raises
73
+ ------
74
+ ValueError
75
+ If any required path parameters are None.
76
+ ClientError
77
+ If a 400-level error occurs.
78
+ NoDataFoundError
79
+ If a 404-level error occurs.
80
+ ServerError
81
+ If a 500-level error occurs.
82
+ """
83
+ if not all([office_id, project_id, water_user]):
84
+ raise ValueError("Office, project_id, and water_user must be provided.")
85
+
86
+ endpoint = f"projects/{office_id}/{project_id}/water-user/{water_user}/contracts"
87
+
88
+ response = api.get(endpoint, api_version=1)
89
+ return Data(response)
90
+
91
+
92
+ def create_water_contract(
93
+ water_user: str,
94
+ data: JSON,
95
+ fail_if_exists: bool = True,
96
+ ignore_nulls: bool = False,
97
+ ) -> None:
98
+ """
99
+ Create a new water contract
100
+
101
+ Parameters
102
+ ----------
103
+ water_user : str
104
+ The water user the contract is associated with. (Path)
105
+ data : JSON
106
+ Water contract successfully stored to CWMS. (Body)
107
+ fail_if_exists : bool, optional
108
+ If true, the contract will not be stored if it already exists.
109
+ Default: True (Query)
110
+ ignore_nulls : bool, optional
111
+ If true, null fields will be ignored when storing the contract.
112
+ Default: False (Query)
113
+
114
+ Returns
115
+ -------
116
+ None
117
+
118
+ Raises
119
+ ------
120
+ ValueError
121
+ If any required path parameters are None.
122
+ ClientError
123
+ If a 400-level error occurs.
124
+ NoDataFoundError
125
+ If a 404-level error occurs.
126
+ ServerError
127
+ If a 500-level error occurs.
128
+ """
129
+ if not water_user:
130
+ raise ValueError("Water User must be provided.")
131
+ if not data:
132
+ raise ValueError("Data must be provided and cannot be empty.")
133
+
134
+ # Note the office ID and project ID are not used by CDA
135
+ endpoint = f"projects/{water_user}/{water_user}/water-user/{water_user}/contracts"
136
+ params = {
137
+ "fail-if-exists": fail_if_exists,
138
+ "ignore-nulls": ignore_nulls,
139
+ }
140
+
141
+ api.post(endpoint, data, params, api_version=1)
142
+
143
+
144
+ def delete_water_contract(
145
+ office_id: str,
146
+ project_id: str,
147
+ water_user: str,
148
+ contract_name: str,
149
+ method: DeleteMethod = DeleteMethod.DELETE_KEY,
150
+ ) -> None:
151
+ """
152
+ Delete a specified water contract
153
+
154
+ Parameters
155
+ ----------
156
+ office_id : str
157
+ The office Id the contract is associated with. (Path)
158
+ project_id : str
159
+ The project Id the contract is associated with. (Path)
160
+ water_user : str
161
+ The water user the contract is associated with. (Path)
162
+ contract_name : str
163
+ The name of the contract to be deleted. (Path)
164
+ method : DeleteMethod, optional
165
+ Specifies the delete method used. Defaults to DELETE_KEY. (Query)
166
+
167
+ Returns
168
+ -------
169
+ None
170
+
171
+ Raises
172
+ ------
173
+ ValueError
174
+ If any required path parameters are None.
175
+ ClientError
176
+ If a 400-level error occurs.
177
+ NoDataFoundError
178
+ If a 404-level error occurs.
179
+ ServerError
180
+ If a 500-level error occurs.
181
+ """
182
+ if not all([office_id, project_id, water_user, contract_name]):
183
+ raise ValueError(
184
+ "Office, Project ID, Water User, and Contract Name must be provided."
185
+ )
186
+
187
+ endpoint = f"projects/{office_id}/{project_id}/water-user/{water_user}/contracts/{contract_name}"
188
+ params = {"method": method.name}
189
+
190
+ api.delete(endpoint, params, api_version=1)
191
+
192
+
193
+ def update_water_contract(
194
+ contract_name: str,
195
+ new_contract_name: str,
196
+ data: JSON,
197
+ ) -> None:
198
+ """
199
+ Updates a water contract in CWMS.
200
+
201
+ Parameters
202
+ ----------
203
+ contract_name : str
204
+ The name of the contract to be updated. (Path)
205
+ new_contract_name : str
206
+ The new name of the contract. (Query)
207
+ data : JSON
208
+ Water contract successfully updated in CWMS. (Body)
209
+
210
+ Returns
211
+ -------
212
+ None
213
+
214
+ Raises
215
+ ------
216
+ ValueError
217
+ If any required path parameters are None.
218
+ ClientError
219
+ If a 400-level error occurs.
220
+ NoDataFoundError
221
+ If a 404-level error occurs.
222
+ ServerError
223
+ If a 500-level error occurs.
224
+ """
225
+ if not all([contract_name, new_contract_name]):
226
+ raise ValueError("Contract Name and New Contract Name must be provided.")
227
+ if not data:
228
+ raise ValueError("Data must be provided and cannot be empty.")
229
+
230
+ endpoint = f"projects/{contract_name}/{contract_name}/water-user/{contract_name}/contracts/{contract_name}"
231
+
232
+ params = {"contract-name": new_contract_name}
233
+
234
+ api.patch(endpoint, data, params, api_version=1)
@@ -0,0 +1,198 @@
1
+ # Copyright (c) 2026
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
+ import cwms.api as api
7
+ from cwms.cwms_types import JSON, Data
8
+
9
+
10
+ def get_water_user(office_id: str, project_id: str, water_user: str) -> Data:
11
+ """
12
+ Gets a specified water user.
13
+
14
+ Parameters
15
+ ----------
16
+ office_id : str
17
+ The office Id the contract is associated with. (Path)
18
+ project_id : str
19
+ The project Id the contract is associated with. (Path)
20
+ water_user : str
21
+ The water user the contract is associated with. (Path)
22
+
23
+ Returns
24
+ -------
25
+ Data
26
+ The JSON response from CWMS Data API wrapped in a Data object.
27
+
28
+ Raises
29
+ ------
30
+ ValueError
31
+ If any required path parameters are None.
32
+ ClientError
33
+ If a 400-level error occurs.
34
+ NoDataFoundError
35
+ If a 404-level error occurs.
36
+ ServerError
37
+ If a 500-level error occurs.
38
+ """
39
+ if not all([office_id, project_id, water_user]):
40
+ raise ValueError("Office, project_id, and water_user must be provided.")
41
+
42
+ endpoint = f"projects/{office_id}/{project_id}/water-user/{water_user}"
43
+
44
+ response = api.get(endpoint, api_version=1)
45
+ return Data(response)
46
+
47
+
48
+ def get_water_users(office_id: str, project_id: str) -> Data:
49
+ """
50
+ Gets a specified water user.
51
+
52
+ Parameters
53
+ ----------
54
+ office_id : str
55
+ The office Id the contract is associated with. (Path)
56
+ project_id : str
57
+ The project Id the contract is associated with. (Path)
58
+
59
+ Returns
60
+ -------
61
+ Data
62
+ The JSON response from CWMS Data API wrapped in a Data object.
63
+
64
+ Raises
65
+ ------
66
+ ValueError
67
+ If any required path parameters are None.
68
+ ClientError
69
+ If a 400-level error occurs.
70
+ NoDataFoundError
71
+ If a 404-level error occurs.
72
+ ServerError
73
+ If a 500-level error occurs.
74
+ """
75
+ if not all([office_id, project_id]):
76
+ raise ValueError("Office and Project ID must be provided.")
77
+
78
+ endpoint = f"projects/{office_id}/{project_id}/water-user"
79
+
80
+ response = api.get(endpoint, api_version=1)
81
+ return Data(response)
82
+
83
+
84
+ def create_water_user(data: JSON, fail_if_exists: bool = True) -> None:
85
+ """
86
+ Stores a water user to CWMS.
87
+
88
+ Parameters
89
+ ----------
90
+ data : JSON
91
+ Water user successfully stored to CWMS. (Body)
92
+ fail_if_exists : bool, optional
93
+ If true, the operation will fail if the water user already exists.
94
+ Default: true (Query)
95
+
96
+ Returns
97
+ -------
98
+ None
99
+
100
+ Raises
101
+ ------
102
+ ValueError
103
+ If any required path parameters are None.
104
+ ClientError
105
+ If a 400-level error occurs.
106
+ NoDataFoundError
107
+ If a 404-level error occurs.
108
+ ServerError
109
+ If a 500-level error occurs.
110
+ """
111
+ if not data:
112
+ raise ValueError("Data must be provided and cannot be empty.")
113
+
114
+ endpoint = "projects/office/project/water-user"
115
+ params = {"fail-if-exists": fail_if_exists}
116
+
117
+ api.post(endpoint, data, params, api_version=1)
118
+
119
+
120
+ def delete_water_user(office_id: str, project_id: str, water_user: str) -> None:
121
+ """
122
+ Deletes a specified water user.
123
+
124
+ Parameters
125
+ ----------
126
+ office_id : str
127
+ The office Id the contract is associated with. (Path)
128
+ project_id : str
129
+ The project Id the contract is associated with. (Path)
130
+ water_user : str
131
+ The water user the contract is associated with. (Path)
132
+
133
+ Returns
134
+ -------
135
+ None
136
+
137
+ Raises
138
+ ------
139
+ ValueError
140
+ If any required path parameters are None.
141
+ ClientError
142
+ If a 400-level error occurs.
143
+ NoDataFoundError
144
+ If a 404-level error occurs.
145
+ ServerError
146
+ If a 500-level error occurs.
147
+ """
148
+ if not all([office_id, project_id, water_user]):
149
+ raise ValueError("Office, Project ID, and Water User must be provided.")
150
+
151
+ endpoint = f"projects/{office_id}/{project_id}/water-user/{water_user}"
152
+
153
+ api.delete(endpoint, api_version=1)
154
+
155
+
156
+ def update_water_user(
157
+ office_id: str, project_id: str, water_user: str, data: JSON, name: str
158
+ ) -> None:
159
+ """
160
+ Updates a water user in CWMS.
161
+
162
+ Parameters
163
+ ----------
164
+ office_id : str
165
+ The office Id the contract is associated with. (Path)
166
+ project_id : str
167
+ The project Id the contract is associated with. (Path)
168
+ water_user : str
169
+ The water user the contract is associated with. (Path)
170
+ data : JSON
171
+ Water user entity data in JSON format. (Body)
172
+ name : str
173
+ Specifies the new name of the water user entity. (Query)
174
+
175
+ Returns
176
+ -------
177
+ None
178
+
179
+ Raises
180
+ ------
181
+ ValueError
182
+ If any required path parameters are None.
183
+ ClientError
184
+ If a 400-level error occurs.
185
+ NoDataFoundError
186
+ If a 404-level error occurs.
187
+ ServerError
188
+ If a 500-level error occurs.
189
+ """
190
+ if not all([office_id, project_id, water_user, name]):
191
+ raise ValueError("Office, Project ID, Water User, and Name must be provided.")
192
+ if not data:
193
+ raise ValueError("Data must be provided and cannot be empty.")
194
+
195
+ endpoint = f"projects/{office_id}/{project_id}/water-user/{water_user}"
196
+ params = {"name": name}
197
+
198
+ api.patch(endpoint, data, params, api_version=1)
@@ -3,7 +3,7 @@ name = "cwms-python"
3
3
  repository = "https://github.com/HydrologicEngineeringCenter/cwms-python"
4
4
 
5
5
  # Managed by Release Please; runtime versions come from package metadata.
6
- version = "1.0.9"
6
+ version = "1.1.0"
7
7
 
8
8
  packages = [
9
9
  { include = "cwms" },
@@ -28,6 +28,11 @@ requests = [
28
28
  {version = "^2.32.4", python = ">=3.9,<3.10"},
29
29
  {version = "^2.33.0", python = ">=3.10"},
30
30
  ]
31
+ # Keep the Python 3.9 fallback while requiring patched releases on newer Python.
32
+ urllib3 = [
33
+ {version = ">=2.6.3,<2.7", python = ">=3.9,<3.10"},
34
+ {version = "^2.7.0", python = ">=3.10"},
35
+ ]
31
36
 
32
37
  [tool.poetry.group.dev.dependencies]
33
38
  black = [
@@ -46,6 +51,10 @@ pytest-cov = "^4.1.0"
46
51
  pandas-stubs = "^2.2.1.240316"
47
52
  types-requests = "^2.31.0.20240311"
48
53
  yamlfix = "^1.16.0"
54
+ setuptools = [
55
+ {version = ">=82.0.1,<83", python = ">=3.9,<3.10"},
56
+ {version = ">=83.0.0", python = ">=3.10"},
57
+ ]
49
58
 
50
59
  [build-system]
51
60
  requires = ["poetry-core"]
File without changes
File without changes
File without changes