cwms-cli 0.1.1__tar.gz → 0.2.1__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_cli-0.1.1 → cwms_cli-0.2.1}/PKG-INFO +13 -4
- cwms_cli-0.2.1/README.md +26 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/__init__.py +2 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/__main__.py +2 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/blob.py +105 -107
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/commands_cwms.py +39 -10
- cwms_cli-0.2.1/cwmscli/load/README.md +7 -0
- cwms_cli-0.2.1/cwmscli/load/__init__.py +0 -0
- cwms_cli-0.2.1/cwmscli/load/__main__.py +5 -0
- cwms_cli-0.2.1/cwmscli/load/location/location.py +131 -0
- cwms_cli-0.2.1/cwmscli/load/location/location_ids.py +83 -0
- cwms_cli-0.2.1/cwmscli/load/location/location_ids_bygroup.py +127 -0
- cwms_cli-0.2.1/cwmscli/load/root.py +123 -0
- cwms_cli-0.2.1/cwmscli/load/timeseries/timeseries.py +55 -0
- cwms_cli-0.2.1/cwmscli/load/timeseries/timeseries_ids.py +71 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/usgs/__init__.py +24 -16
- cwms_cli-0.2.1/cwmscli/usgs/__main__.py +7 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/usgs/getUSGS_ratings_cda.py +26 -12
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/usgs/getusgs_cda.py +7 -2
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/usgs/getusgs_measurements_cda.py +12 -12
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/usgs/rating_ini_file_import.py +0 -1
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/utils/__init__.py +6 -6
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/utils/deps.py +13 -1
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/pyproject.toml +3 -3
- cwms_cli-0.1.1/README.md +0 -17
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/LICENSE +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/callbacks/__init__.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/.gitignore +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/README.md +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/__init__.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/__main__.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/examples/complete_config.json +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/examples/hourly.json +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/examples/minutes.json +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/__init__.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/data/.gitignore +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/data/sample_brok.csv +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/data/sample_config.json +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/test_dateutils.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/test_expressions.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/tests/test_fileio.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/utils/__init__.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/utils/dateutils.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/utils/expression.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/utils/fileio.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/utils/logging.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/csv2cwms/utils/terminal.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/commands/shef_critfile_import.py +0 -0
- {cwms_cli-0.1.1 → cwms_cli-0.2.1}/cwmscli/requirements.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cwms-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Command line utilities for Corps Water Management Systems (CWMS) python scripts. This is a collection of shared scripts across the enterprise Water Management Enterprise System (WMES) teams.
|
|
5
5
|
License: LICENSE
|
|
6
6
|
License-File: LICENSE
|
|
@@ -22,19 +22,28 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
|
|
23
23
|
# cwms-cli
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
A collection of scripts to create, read, update, list, and delete data through CWMS Data API (CDA) and other commonly used API in the US Army Corps of Engineers water management. CWMS-CLI wraps these API in a friendly to use terminal based interface.
|
|
26
26
|
|
|
27
|
-
[](https://cwms-cli.readthedocs.io/en/latest/)
|
|
27
|
+
[](https://cwms-cli.readthedocs.io/en/latest/) - 📖 Read the docs: https://cwms-cli.readthedocs.io/en/latest/
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
|
|
32
|
+
pip3 install git+https://github.com/HydrologicEngineeringCenter/cwms-cli.git@main
|
|
33
33
|
```
|
|
34
|
+
Note: If you are on Windows OS, you may just need to use the command `pip`
|
|
34
35
|
|
|
35
36
|
## Command line implementation
|
|
36
37
|
|
|
38
|
+
View the help in terminal:
|
|
37
39
|
```sh
|
|
38
40
|
cwms-cli --help
|
|
39
41
|
```
|
|
40
42
|
|
|
43
|
+
## run from within python
|
|
44
|
+
```python
|
|
45
|
+
from cwmscli.usgs.getusgs_cda import getusgs_cda
|
|
46
|
+
from cwmscli.usgs.getusgs_measurements_cda import getusgs_measurements_cda
|
|
47
|
+
from cwmscli.usgs.getUSGS_ratings_cda import getusgs_rating_cda
|
|
48
|
+
```
|
|
49
|
+
|
cwms_cli-0.2.1/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# cwms-cli
|
|
2
|
+
|
|
3
|
+
A collection of scripts to create, read, update, list, and delete data through CWMS Data API (CDA) and other commonly used API in the US Army Corps of Engineers water management. CWMS-CLI wraps these API in a friendly to use terminal based interface.
|
|
4
|
+
|
|
5
|
+
[](https://cwms-cli.readthedocs.io/en/latest/) - 📖 Read the docs: https://cwms-cli.readthedocs.io/en/latest/
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip3 install git+https://github.com/HydrologicEngineeringCenter/cwms-cli.git@main
|
|
11
|
+
```
|
|
12
|
+
Note: If you are on Windows OS, you may just need to use the command `pip`
|
|
13
|
+
|
|
14
|
+
## Command line implementation
|
|
15
|
+
|
|
16
|
+
View the help in terminal:
|
|
17
|
+
```sh
|
|
18
|
+
cwms-cli --help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## run from within python
|
|
22
|
+
```python
|
|
23
|
+
from cwmscli.usgs.getusgs_cda import getusgs_cda
|
|
24
|
+
from cwmscli.usgs.getusgs_measurements_cda import getusgs_measurements_cda
|
|
25
|
+
from cwmscli.usgs.getUSGS_ratings_cda import getusgs_rating_cda
|
|
26
|
+
```
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
3
|
from cwmscli.commands import commands_cwms
|
|
4
|
+
from cwmscli.load import __main__ as load
|
|
4
5
|
from cwmscli.usgs import usgs_group
|
|
5
6
|
|
|
6
7
|
|
|
@@ -13,3 +14,4 @@ cli.add_command(usgs_group, name="usgs")
|
|
|
13
14
|
cli.add_command(commands_cwms.shefcritimport)
|
|
14
15
|
cli.add_command(commands_cwms.csv2cwms_cmd)
|
|
15
16
|
cli.add_command(commands_cwms.blob_group)
|
|
17
|
+
cli.add_command(load.load_group)
|
|
@@ -7,10 +7,6 @@ import re
|
|
|
7
7
|
import sys
|
|
8
8
|
from typing import Optional, Sequence
|
|
9
9
|
|
|
10
|
-
import cwms
|
|
11
|
-
import pandas as pd
|
|
12
|
-
import requests
|
|
13
|
-
|
|
14
10
|
from cwmscli.utils import get_api_key
|
|
15
11
|
from cwmscli.utils.deps import requires
|
|
16
12
|
|
|
@@ -27,22 +23,21 @@ DATA_URL_RE = re.compile(r"^data:(?P<mime>[^;]+);base64,(?P<data>.+)$", re.I | r
|
|
|
27
23
|
"link": "https://docs.python.org/3/library/imghdr.html",
|
|
28
24
|
}
|
|
29
25
|
)
|
|
30
|
-
def _determine_ext(data: bytes
|
|
26
|
+
def _determine_ext(data: bytes) -> str:
|
|
31
27
|
"""
|
|
32
28
|
Attempt to determine the file extension from the data itself.
|
|
33
29
|
Requires the imghdr module (lazy import) to inspect the bytes for image types.
|
|
34
30
|
If not an image, defaults to .bin
|
|
35
31
|
|
|
36
32
|
Args:
|
|
37
|
-
data: The binary data
|
|
38
|
-
write_type: The mode in which the data will be written ('wb' for binary, 'w' for text).
|
|
33
|
+
data: The binary data to inspect.
|
|
39
34
|
|
|
40
35
|
Returns:
|
|
41
36
|
The determined file extension, including the leading dot (e.g., '.png', '.jpg').
|
|
42
37
|
"""
|
|
43
38
|
import imghdr
|
|
44
39
|
|
|
45
|
-
kind = imghdr.what(None, data)
|
|
40
|
+
kind: Optional[str] = imghdr.what(None, data)
|
|
46
41
|
if kind == "jpeg":
|
|
47
42
|
kind = "jpg"
|
|
48
43
|
return f".{kind}" if kind else ".bin"
|
|
@@ -51,7 +46,7 @@ def _determine_ext(data: bytes | str, write_type: str) -> str:
|
|
|
51
46
|
def _save_base64(
|
|
52
47
|
b64_or_dataurl: str,
|
|
53
48
|
dest: str,
|
|
54
|
-
media_type_hint: str
|
|
49
|
+
media_type_hint: Optional[str] = None,
|
|
55
50
|
) -> str:
|
|
56
51
|
m = DATA_URL_RE.match(b64_or_dataurl.strip())
|
|
57
52
|
if m:
|
|
@@ -80,8 +75,8 @@ def _save_base64(
|
|
|
80
75
|
ext = ".jpg"
|
|
81
76
|
# last resort, try to determine from the data itself
|
|
82
77
|
# requires imghdr to dig into the bytes to determine image type
|
|
83
|
-
if not ext:
|
|
84
|
-
ext = _determine_ext(data
|
|
78
|
+
if not ext and isinstance(data, bytes):
|
|
79
|
+
ext = _determine_ext(data)
|
|
85
80
|
dest = base + ext
|
|
86
81
|
|
|
87
82
|
os.makedirs(os.path.dirname(dest) or ".", exist_ok=True)
|
|
@@ -91,8 +86,11 @@ def _save_base64(
|
|
|
91
86
|
|
|
92
87
|
|
|
93
88
|
def store_blob(**kwargs):
|
|
89
|
+
import cwms
|
|
90
|
+
import requests
|
|
91
|
+
|
|
94
92
|
file_data = kwargs.get("file_data")
|
|
95
|
-
blob_id = kwargs.get("blob_id").upper()
|
|
93
|
+
blob_id = kwargs.get("blob_id", "").upper()
|
|
96
94
|
# Attempt to determine what media type should be used for the mime-type if one is not presented based on the file extension
|
|
97
95
|
media = kwargs.get("media_type") or get_media_type(kwargs.get("input_file"))
|
|
98
96
|
|
|
@@ -146,13 +144,15 @@ def store_blob(**kwargs):
|
|
|
146
144
|
|
|
147
145
|
|
|
148
146
|
def retrieve_blob(**kwargs):
|
|
149
|
-
|
|
147
|
+
import cwms
|
|
148
|
+
import requests
|
|
149
|
+
|
|
150
|
+
blob_id = kwargs.get("blob_id", "").upper()
|
|
150
151
|
if not blob_id:
|
|
151
152
|
logging.warning(
|
|
152
153
|
"Valid blob_id required to download a blob. cwms-cli blob download --blob-id=myid. Run the list directive to see options for your office."
|
|
153
154
|
)
|
|
154
155
|
sys.exit(0)
|
|
155
|
-
blob_id = blob_id.upper()
|
|
156
156
|
logging.debug(f"Office: {kwargs.get('office')} Blob ID: {blob_id}")
|
|
157
157
|
try:
|
|
158
158
|
blob = cwms.get_blob(
|
|
@@ -174,14 +174,17 @@ def retrieve_blob(**kwargs):
|
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
def delete_blob(**kwargs):
|
|
177
|
+
import cwms
|
|
178
|
+
import requests
|
|
179
|
+
|
|
177
180
|
blob_id = kwargs.get("blob_id").upper()
|
|
178
181
|
logging.debug(f"Office: {kwargs.get('office')} Blob ID: {blob_id}")
|
|
179
182
|
|
|
180
183
|
try:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
cwms.delete_blob(
|
|
185
|
+
office_id=kwargs.get("office"),
|
|
186
|
+
blob_id=kwargs.get("blob_id").upper(),
|
|
187
|
+
)
|
|
185
188
|
logging.info(f"Successfully deleted blob with ID: {blob_id}")
|
|
186
189
|
except requests.HTTPError as e:
|
|
187
190
|
details = getattr(e.response, "text", "") or str(e)
|
|
@@ -199,8 +202,11 @@ def list_blobs(
|
|
|
199
202
|
sort_by: Optional[Sequence[str]] = None,
|
|
200
203
|
ascending: bool = True,
|
|
201
204
|
limit: Optional[int] = None,
|
|
202
|
-
)
|
|
205
|
+
):
|
|
203
206
|
logging.info(f"Listing blobs for office: {office!r}...")
|
|
207
|
+
import cwms
|
|
208
|
+
import pandas as pd
|
|
209
|
+
|
|
204
210
|
result = cwms.get_blobs(office_id=office, blob_id_like=blob_id_like)
|
|
205
211
|
|
|
206
212
|
# Accept either a DataFrame or a JSON/dict-like response
|
|
@@ -229,7 +235,7 @@ def list_blobs(
|
|
|
229
235
|
if limit is not None:
|
|
230
236
|
df = df.head(limit)
|
|
231
237
|
|
|
232
|
-
logging.info(f"Found {len(df):,}
|
|
238
|
+
logging.info(f"Found {len(df):,} blob(s)")
|
|
233
239
|
# List the blobs in the logger
|
|
234
240
|
for _, row in df.iterrows():
|
|
235
241
|
logging.info(f"Blob ID: {row['id']}, Description: {row.get('description')}")
|
|
@@ -241,77 +247,6 @@ def get_media_type(file_path: str) -> str:
|
|
|
241
247
|
return mime_type or "application/octet-stream"
|
|
242
248
|
|
|
243
249
|
|
|
244
|
-
def main(
|
|
245
|
-
directive: str,
|
|
246
|
-
input_file: str,
|
|
247
|
-
blob_id: str,
|
|
248
|
-
description: Optional[str],
|
|
249
|
-
media_type: Optional[str],
|
|
250
|
-
office: str,
|
|
251
|
-
api_root: str,
|
|
252
|
-
api_key: str,
|
|
253
|
-
overwrite: Optional[bool] = True,
|
|
254
|
-
dry_run: Optional[bool] = False,
|
|
255
|
-
):
|
|
256
|
-
"""
|
|
257
|
-
Upload, Download, Delete, or Update blob data in CWMS.
|
|
258
|
-
|
|
259
|
-
DIRECTIVE is the action to perform (upload, download, delete, update).
|
|
260
|
-
INPUT_FILE is the path to the file on disk.
|
|
261
|
-
BLOB_ID is the blob ID to store under.
|
|
262
|
-
"""
|
|
263
|
-
|
|
264
|
-
cwms.api.init_session(api_root=api_root, api_key=api_key)
|
|
265
|
-
file_data = None
|
|
266
|
-
if directive in ["upload", "update"]:
|
|
267
|
-
if not input_file or not os.path.isfile(input_file):
|
|
268
|
-
logging.warning(
|
|
269
|
-
"Valid input_file required for upload/update. Use --input-file to specify."
|
|
270
|
-
)
|
|
271
|
-
sys.exit(0)
|
|
272
|
-
try:
|
|
273
|
-
file_size = os.path.getsize(input_file)
|
|
274
|
-
with open(input_file, "rb") as f:
|
|
275
|
-
file_data = f.read()
|
|
276
|
-
logging.info(f"Read file: {input_file} ({file_size} bytes)")
|
|
277
|
-
except Exception as e:
|
|
278
|
-
logging.error(f"Failed to read file: {e}")
|
|
279
|
-
sys.exit(1)
|
|
280
|
-
|
|
281
|
-
# Determine what should be done based on directive
|
|
282
|
-
if directive == "upload":
|
|
283
|
-
store_blob(
|
|
284
|
-
office=office,
|
|
285
|
-
api_root=api_root,
|
|
286
|
-
input_file=input_file,
|
|
287
|
-
blob_id=blob_id,
|
|
288
|
-
description=description,
|
|
289
|
-
media_type=media_type,
|
|
290
|
-
file_data=file_data,
|
|
291
|
-
overwrite=overwrite,
|
|
292
|
-
dry_run=dry_run,
|
|
293
|
-
)
|
|
294
|
-
elif directive == "list":
|
|
295
|
-
list_blobs(office=office, blob_id_like=blob_id, sort_by="blob_id")
|
|
296
|
-
elif directive == "download":
|
|
297
|
-
retrieve_blob(
|
|
298
|
-
office=office,
|
|
299
|
-
blob_id=blob_id,
|
|
300
|
-
)
|
|
301
|
-
elif directive == "delete":
|
|
302
|
-
# TODO: Delete endpoint does not exist in cwms-python yet
|
|
303
|
-
logging.warning(
|
|
304
|
-
"[NOT IMPLEMENTED] Delete Blob is not supported yet!\n\thttps://github.com/HydrologicEngineeringCenter/cwms-python/issues/192"
|
|
305
|
-
)
|
|
306
|
-
pass
|
|
307
|
-
elif directive == "update":
|
|
308
|
-
# TODO: Patch endpoint does not exist in cwms-python yet
|
|
309
|
-
logging.warning(
|
|
310
|
-
"[NOT IMPLEMENTED] Update Blob is not supported yet! Consider overwriting instead if a rename is not needed.\n\thttps://github.com/HydrologicEngineeringCenter/cwms-python/issues/192"
|
|
311
|
-
)
|
|
312
|
-
pass
|
|
313
|
-
|
|
314
|
-
|
|
315
250
|
def upload_cmd(
|
|
316
251
|
input_file: str,
|
|
317
252
|
blob_id: str,
|
|
@@ -323,7 +258,10 @@ def upload_cmd(
|
|
|
323
258
|
api_root: str,
|
|
324
259
|
api_key: str,
|
|
325
260
|
):
|
|
326
|
-
cwms
|
|
261
|
+
import cwms
|
|
262
|
+
import requests
|
|
263
|
+
|
|
264
|
+
cwms.init_session(api_root=api_root, api_key=get_api_key(api_key, ""))
|
|
327
265
|
try:
|
|
328
266
|
file_size = os.path.getsize(input_file)
|
|
329
267
|
with open(input_file, "rb") as f:
|
|
@@ -351,7 +289,7 @@ def upload_cmd(
|
|
|
351
289
|
params = {"fail-if-exists": not overwrite}
|
|
352
290
|
|
|
353
291
|
if dry_run:
|
|
354
|
-
logging.info(f"
|
|
292
|
+
logging.info(f"DRY RUN: would POST {api_root}blobs with params={params}")
|
|
355
293
|
logging.info(
|
|
356
294
|
json.dumps(
|
|
357
295
|
{
|
|
@@ -365,7 +303,7 @@ def upload_cmd(
|
|
|
365
303
|
return
|
|
366
304
|
|
|
367
305
|
try:
|
|
368
|
-
cwms.store_blobs(blob, fail_if_exists=overwrite)
|
|
306
|
+
cwms.store_blobs(blob, fail_if_exists=not overwrite)
|
|
369
307
|
logging.info(f"Uploaded blob: {blob_id_up}")
|
|
370
308
|
logging.info(f"View: {api_root}blobs/{blob_id_up}?office={office}")
|
|
371
309
|
except requests.HTTPError as e:
|
|
@@ -377,8 +315,18 @@ def upload_cmd(
|
|
|
377
315
|
sys.exit(1)
|
|
378
316
|
|
|
379
317
|
|
|
380
|
-
def download_cmd(
|
|
381
|
-
|
|
318
|
+
def download_cmd(
|
|
319
|
+
blob_id: str, dest: str, office: str, api_root: str, api_key: str, dry_run: bool
|
|
320
|
+
):
|
|
321
|
+
import cwms
|
|
322
|
+
import requests
|
|
323
|
+
|
|
324
|
+
if dry_run:
|
|
325
|
+
logging.info(
|
|
326
|
+
f"DRY RUN: would GET {api_root} blob with blob-id={blob_id} office={office}."
|
|
327
|
+
)
|
|
328
|
+
return
|
|
329
|
+
cwms.init_session(api_root=api_root, api_key=get_api_key(api_key, ""))
|
|
382
330
|
bid = blob_id.upper()
|
|
383
331
|
logging.debug(f"Office={office} BlobID={bid}")
|
|
384
332
|
|
|
@@ -396,18 +344,65 @@ def download_cmd(blob_id: str, dest: str, office: str, api_root: str, api_key: s
|
|
|
396
344
|
sys.exit(1)
|
|
397
345
|
|
|
398
346
|
|
|
399
|
-
def delete_cmd(blob_id: str, office: str, api_root: str, api_key: str):
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
347
|
+
def delete_cmd(blob_id: str, office: str, api_root: str, api_key: str, dry_run: bool):
|
|
348
|
+
import cwms
|
|
349
|
+
|
|
350
|
+
if dry_run:
|
|
351
|
+
logging.info(
|
|
352
|
+
f"DRY RUN: would DELETE {api_root} blob with blob-id={blob_id} office={office}"
|
|
353
|
+
)
|
|
354
|
+
return
|
|
355
|
+
cwms.init_session(api_root=api_root, api_key=api_key)
|
|
356
|
+
cwms.delete_blob(office_id=office, blob_id=blob_id)
|
|
357
|
+
logging.info(f"Deleted blob: {blob_id} for office: {office}")
|
|
404
358
|
|
|
405
359
|
|
|
406
|
-
def update_cmd(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
360
|
+
def update_cmd(
|
|
361
|
+
input_file: str,
|
|
362
|
+
blob_id: str,
|
|
363
|
+
description: str,
|
|
364
|
+
media_type: str,
|
|
365
|
+
overwrite: bool,
|
|
366
|
+
dry_run: bool,
|
|
367
|
+
office: str,
|
|
368
|
+
api_root: str,
|
|
369
|
+
api_key: str,
|
|
370
|
+
):
|
|
371
|
+
import cwms
|
|
372
|
+
|
|
373
|
+
if dry_run:
|
|
374
|
+
logging.info(
|
|
375
|
+
f"DRY RUN: would PATCH {api_root} blob with blob-id={blob_id} office={office}"
|
|
376
|
+
)
|
|
377
|
+
return
|
|
378
|
+
file_data = None
|
|
379
|
+
if input_file:
|
|
380
|
+
try:
|
|
381
|
+
file_size = os.path.getsize(input_file)
|
|
382
|
+
with open(input_file, "rb") as f:
|
|
383
|
+
file_data = f.read()
|
|
384
|
+
logging.info(f"Read file: {input_file} ({file_size} bytes)")
|
|
385
|
+
except Exception as e:
|
|
386
|
+
logging.error(f"Failed to read file: {e}")
|
|
387
|
+
sys.exit(1)
|
|
388
|
+
# Setup minimum required payload
|
|
389
|
+
blob = {"office-id": office, "id": blob_id.upper()}
|
|
390
|
+
if description:
|
|
391
|
+
blob["description"] = description
|
|
392
|
+
if media_type:
|
|
393
|
+
blob["media-type-id"] = media_type
|
|
394
|
+
else:
|
|
395
|
+
logging.info("Media type not specified; Retrieving existing media type...")
|
|
396
|
+
blob_metadata = cwms.get_blobs(office_id=office, blob_id_like=blob_id)
|
|
397
|
+
blob["media-type-id"] = blob_metadata.df.get(
|
|
398
|
+
"media-type-id", "application/octet-stream"
|
|
399
|
+
)[0]
|
|
400
|
+
logging.info(f"Using existing media type: {blob['media-type-id']}")
|
|
401
|
+
|
|
402
|
+
if file_data:
|
|
403
|
+
blob["value"] = base64.b64encode(file_data).decode("utf-8")
|
|
404
|
+
cwms.init_session(api_root=api_root, api_key=api_key)
|
|
405
|
+
cwms.update_blob(blob, fail_if_not_exists=not overwrite)
|
|
411
406
|
|
|
412
407
|
|
|
413
408
|
def list_cmd(
|
|
@@ -421,7 +416,10 @@ def list_cmd(
|
|
|
421
416
|
api_root: str,
|
|
422
417
|
api_key: str,
|
|
423
418
|
):
|
|
424
|
-
cwms
|
|
419
|
+
import cwms
|
|
420
|
+
import pandas as pd
|
|
421
|
+
|
|
422
|
+
cwms.init_session(api_root=api_root, api_key=get_api_key(api_key, None))
|
|
425
423
|
df = list_blobs(
|
|
426
424
|
office=office,
|
|
427
425
|
blob_id_like=blob_id_like,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
|
|
1
3
|
import click
|
|
2
4
|
|
|
3
5
|
from cwmscli import requirements as reqs
|
|
@@ -23,6 +25,7 @@ from cwmscli.utils.deps import requires
|
|
|
23
25
|
@requires(reqs.cwms)
|
|
24
26
|
def shefcritimport(filename, office, api_root, api_key, api_key_loc):
|
|
25
27
|
from cwmscli.commands.shef_critfile_import import import_shef_critfile
|
|
28
|
+
from cwmscli.utils import get_api_key
|
|
26
29
|
|
|
27
30
|
api_key = get_api_key(api_key, api_key_loc)
|
|
28
31
|
import_shef_critfile(
|
|
@@ -94,14 +97,16 @@ def csv2cwms_cmd(**kwargs):
|
|
|
94
97
|
@click.group(
|
|
95
98
|
"blob",
|
|
96
99
|
help="Manage CWMS Blobs (upload, download, delete, update, list)",
|
|
97
|
-
epilog=
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
epilog=textwrap.dedent(
|
|
101
|
+
"""
|
|
102
|
+
Example Usage:\n
|
|
103
|
+
- Store a PDF/image as a CWMS blob with optional description\n
|
|
104
|
+
- Download a blob by id to your local filesystem\n
|
|
105
|
+
- Update a blob's name/description/mime-type\n
|
|
106
|
+
- Bulk list blobs for an office
|
|
107
|
+
"""
|
|
108
|
+
),
|
|
103
109
|
)
|
|
104
|
-
@requires(reqs.cwms)
|
|
105
110
|
def blob_group():
|
|
106
111
|
pass
|
|
107
112
|
|
|
@@ -124,13 +129,14 @@ def blob_group():
|
|
|
124
129
|
help="Override media type (guessed from file if omitted).",
|
|
125
130
|
)
|
|
126
131
|
@click.option(
|
|
127
|
-
"--overwrite
|
|
132
|
+
"--overwrite",
|
|
128
133
|
default=False,
|
|
129
134
|
show_default=True,
|
|
130
135
|
help="If true, replace existing blob.",
|
|
131
136
|
)
|
|
132
137
|
@click.option("--dry-run", is_flag=True, help="Show request; do not send.")
|
|
133
138
|
@common_api_options
|
|
139
|
+
@requires(reqs.cwms)
|
|
134
140
|
def blob_upload(**kwargs):
|
|
135
141
|
from cwmscli.commands.blob import upload_cmd
|
|
136
142
|
|
|
@@ -148,7 +154,9 @@ def blob_upload(**kwargs):
|
|
|
148
154
|
default=None,
|
|
149
155
|
help="Destination file path. Defaults to blob-id.",
|
|
150
156
|
)
|
|
157
|
+
@click.option("--dry-run", is_flag=True, help="Show request; do not send.")
|
|
151
158
|
@common_api_options
|
|
159
|
+
@requires(reqs.cwms)
|
|
152
160
|
def blob_download(**kwargs):
|
|
153
161
|
from cwmscli.commands.blob import download_cmd
|
|
154
162
|
|
|
@@ -158,9 +166,11 @@ def blob_download(**kwargs):
|
|
|
158
166
|
# ================================================================================
|
|
159
167
|
# Delete
|
|
160
168
|
# ================================================================================
|
|
161
|
-
@blob_group.command("delete", help="
|
|
169
|
+
@blob_group.command("delete", help="Delete a blob by ID")
|
|
162
170
|
@click.option("--blob-id", required=True, type=str, help="Blob ID to delete.")
|
|
171
|
+
@click.option("--dry-run", is_flag=True, help="Show request; do not send.")
|
|
163
172
|
@common_api_options
|
|
173
|
+
@requires(reqs.cwms)
|
|
164
174
|
def delete_cmd(**kwargs):
|
|
165
175
|
from cwmscli.commands.blob import delete_cmd
|
|
166
176
|
|
|
@@ -170,15 +180,33 @@ def delete_cmd(**kwargs):
|
|
|
170
180
|
# ================================================================================
|
|
171
181
|
# Update
|
|
172
182
|
# ================================================================================
|
|
173
|
-
@blob_group.command("update", help="
|
|
183
|
+
@blob_group.command("update", help="Update/patch a blob by ID")
|
|
174
184
|
@click.option("--blob-id", required=True, type=str, help="Blob ID to update.")
|
|
185
|
+
@click.option("--dry-run", is_flag=True, help="Show request; do not send.")
|
|
186
|
+
@click.option(
|
|
187
|
+
"--description",
|
|
188
|
+
default=None,
|
|
189
|
+
help="New description JSON or text.",
|
|
190
|
+
)
|
|
191
|
+
@click.option(
|
|
192
|
+
"--media-type",
|
|
193
|
+
default=None,
|
|
194
|
+
help="New media type (guessed from file if omitted).",
|
|
195
|
+
)
|
|
175
196
|
@click.option(
|
|
176
197
|
"--input-file",
|
|
177
198
|
required=False,
|
|
178
199
|
type=click.Path(exists=True, dir_okay=False, readable=True, path_type=str),
|
|
179
200
|
help="Optional file content to upload with update.",
|
|
180
201
|
)
|
|
202
|
+
@click.option(
|
|
203
|
+
"--overwrite/--no-overwrite",
|
|
204
|
+
default=False,
|
|
205
|
+
show_default=True,
|
|
206
|
+
help="If true, replace existing blob.",
|
|
207
|
+
)
|
|
181
208
|
@common_api_options
|
|
209
|
+
@requires(reqs.cwms)
|
|
182
210
|
def update_cmd(**kwargs):
|
|
183
211
|
from cwmscli.commands.blob import update_cmd
|
|
184
212
|
|
|
@@ -218,6 +246,7 @@ def update_cmd(**kwargs):
|
|
|
218
246
|
help="If set, write results to this CSV file.",
|
|
219
247
|
)
|
|
220
248
|
@common_api_options
|
|
249
|
+
@requires(reqs.cwms)
|
|
221
250
|
def list_cmd(**kwargs):
|
|
222
251
|
from cwmscli.commands.blob import list_cmd
|
|
223
252
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Loader Scripts
|
|
2
|
+
|
|
3
|
+
This command supports loading data in some form or fashion.
|
|
4
|
+
|
|
5
|
+
Initially it was intended to load data across from one CDA instance (GET) to another (POST). But could be expanded to other forms of loading including backloading jobs that potentially wrap existing other commands.
|
|
6
|
+
|
|
7
|
+
It differs from the other scripts in that it is effectively a `CDA2CDA` script which is usually only needed for `loading` data in initial setups. (Dev/Docker Compose)
|
|
File without changes
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# cwmscli/load/__main__.py
|
|
2
|
+
# Side-effect imports to register subcommands under `load_group`
|
|
3
|
+
from cwmscli.load.location import location as _locations
|
|
4
|
+
from cwmscli.load.root import load_group # export for callers
|
|
5
|
+
from cwmscli.load.timeseries import timeseries as _timeseries
|