cwms-cli 0.7.0__tar.gz → 0.7.2__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 (76) hide show
  1. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/PKG-INFO +1 -1
  2. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/blob.py +20 -9
  3. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/clob.py +13 -10
  4. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/shef/import_infile.py +18 -8
  5. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/__init__.py +61 -0
  6. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/pyproject.toml +1 -1
  7. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/LICENSE +0 -0
  8. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/README.md +0 -0
  9. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/__init__.py +0 -0
  10. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/__main__.py +0 -0
  11. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/_generated/__init__.py +0 -0
  12. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/_generated/ownership_data.py +0 -0
  13. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/callbacks/__init__.py +0 -0
  14. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/commands_cwms.py +0 -0
  15. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/.gitignore +0 -0
  16. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/README.md +0 -0
  17. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/__init__.py +0 -0
  18. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/__main__.py +0 -0
  19. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/config.py +0 -0
  20. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/doclinks.py +0 -0
  21. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/examples/complete_config.json +0 -0
  22. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/parser.py +0 -0
  23. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/__init__.py +0 -0
  24. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/data/.gitignore +0 -0
  25. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json +0 -0
  26. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/data/sample_brok.csv +0 -0
  27. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/data/sample_config.json +0 -0
  28. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py +0 -0
  29. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/test_dateutils.py +0 -0
  30. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/test_expressions.py +0 -0
  31. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/test_fileio.py +0 -0
  32. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/tests/test_main.py +0 -0
  33. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/transform.py +0 -0
  34. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/utils/__init__.py +0 -0
  35. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/utils/dateutils.py +0 -0
  36. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/utils/expression.py +0 -0
  37. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/utils/fileio.py +0 -0
  38. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/utils/logging.py +0 -0
  39. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/csv2cwms/writer.py +0 -0
  40. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/shef/__init__.py +0 -0
  41. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/shef/import_critfile.py +0 -0
  42. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/shef/shef_parameters.csv +0 -0
  43. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/commands/users.py +0 -0
  44. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/README.md +0 -0
  45. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/__init__.py +0 -0
  46. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/__main__.py +0 -0
  47. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/location/location.py +0 -0
  48. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/location/location_ids.py +0 -0
  49. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/location/location_ids_bygroup.py +0 -0
  50. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/root.py +0 -0
  51. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/timeseries/timeseries.py +0 -0
  52. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/timeseries/timeseries_data.py +0 -0
  53. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/load/timeseries/timeseries_ids.py +0 -0
  54. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/ownership.py +0 -0
  55. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/requirements.py +0 -0
  56. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/usgs/__init__.py +0 -0
  57. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/usgs/__main__.py +0 -0
  58. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/usgs/getUSGS_ratings_cda.py +0 -0
  59. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/usgs/getusgs_cda.py +0 -0
  60. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/usgs/getusgs_measurements_cda.py +0 -0
  61. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/usgs/rating_ini_file_import.py +0 -0
  62. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/auth.py +0 -0
  63. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/callback_success.html +0 -0
  64. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/click_help.py +0 -0
  65. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/colors.py +0 -0
  66. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/deps.py +0 -0
  67. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/friendly_errors.py +0 -0
  68. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/intervals.py +0 -0
  69. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/io.py +0 -0
  70. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/links.py +0 -0
  71. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/logging/__init__.py +0 -0
  72. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/logging/formatters.py +0 -0
  73. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/ssl_errors.py +0 -0
  74. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/update.py +0 -0
  75. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/version.py +0 -0
  76. {cwms_cli-0.7.0 → cwms_cli-0.7.2}/cwmscli/utils/version_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cwms-cli
3
- Version: 0.7.0
3
+ Version: 0.7.2
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
@@ -11,10 +11,12 @@ from typing import Optional, Sequence, Tuple, Union
11
11
 
12
12
  from cwmscli.utils import (
13
13
  colors,
14
+ format_local_download_error,
14
15
  get_api_key,
15
16
  has_invalid_chars,
16
17
  init_cwms_session,
17
18
  log_scoped_read_hint,
19
+ validate_default_download_dest,
18
20
  )
19
21
  from cwmscli.utils.click_help import DOCS_BASE_URL
20
22
  from cwmscli.utils.deps import requires
@@ -109,6 +111,14 @@ def _save_blob_content(
109
111
  return dest
110
112
 
111
113
 
114
+ def _default_download_dest(blob_id: str) -> str:
115
+ return validate_default_download_dest(
116
+ blob_id,
117
+ resource_name="Blob",
118
+ docs_url=BLOB_DOCS_URL,
119
+ )
120
+
121
+
112
122
  def _blob_media_type(cwms_module, office: str, blob_id: str) -> Optional[str]:
113
123
  try:
114
124
  result = cwms_module.get_blobs(office_id=office, blob_id_like=blob_id)
@@ -602,7 +612,7 @@ def download_cmd(
602
612
 
603
613
  try:
604
614
  blob_content = cwms.get_blob(office_id=office, blob_id=bid)
605
- target = dest or bid
615
+ target = dest or _default_download_dest(bid)
606
616
  _save_blob_content(
607
617
  blob_content,
608
618
  dest=target,
@@ -621,14 +631,15 @@ def download_cmd(
621
631
  )
622
632
  sys.exit(1)
623
633
  except Exception as e:
624
- logging.error(f"Failed to download: {e}")
625
- log_scoped_read_hint(
626
- credential_kind=credential_kind,
627
- anonymous=anonymous,
628
- office=office,
629
- action="download",
630
- resource="blob content",
631
- )
634
+ logging.error(format_local_download_error(e, BLOB_DOCS_URL))
635
+ if not isinstance(e, (OSError, ValueError)):
636
+ log_scoped_read_hint(
637
+ credential_kind=credential_kind,
638
+ anonymous=anonymous,
639
+ office=office,
640
+ action="download",
641
+ resource="blob content",
642
+ )
632
643
  sys.exit(1)
633
644
 
634
645
 
@@ -9,7 +9,13 @@ import pandas as pd
9
9
  import requests
10
10
  from cwms import api as cwms_api
11
11
 
12
- from cwmscli.utils import get_api_key, has_invalid_chars, log_scoped_read_hint
12
+ from cwmscli.utils import (
13
+ format_local_download_error,
14
+ get_api_key,
15
+ has_invalid_chars,
16
+ log_scoped_read_hint,
17
+ validate_default_download_dest,
18
+ )
13
19
 
14
20
 
15
21
  def _join_api_url(api_root: str, path: str) -> str:
@@ -29,6 +35,10 @@ def _write_clob_content(content: str, dest: str) -> str:
29
35
  return dest
30
36
 
31
37
 
38
+ def _default_download_dest(clob_id: str) -> str:
39
+ return validate_default_download_dest(clob_id, resource_name="Clob")
40
+
41
+
32
42
  def _clob_endpoint_id(clob_id: str) -> tuple[str, Optional[str]]:
33
43
  normalized = clob_id.upper()
34
44
  if has_invalid_chars(normalized):
@@ -198,7 +208,7 @@ def download_cmd(
198
208
  content = str(payload)
199
209
  else:
200
210
  content = _get_special_clob_text(office=office, clob_id=query_id)
201
- target = dest or bid
211
+ target = dest or _default_download_dest(bid)
202
212
  _write_clob_content(content, target)
203
213
  logging.info(f"Downloaded clob to: {target}")
204
214
  except requests.HTTPError as e:
@@ -213,14 +223,7 @@ def download_cmd(
213
223
  )
214
224
  sys.exit(1)
215
225
  except Exception as e:
216
- logging.error(f"Failed to download: {e}")
217
- log_scoped_read_hint(
218
- api_key=resolved_api_key,
219
- anonymous=anonymous,
220
- office=office,
221
- action="download",
222
- resource="clob content",
223
- )
226
+ logging.error(format_local_download_error(e, ""))
224
227
  sys.exit(1)
225
228
 
226
229
 
@@ -388,17 +388,23 @@ def _contextual_parse(
388
388
  current_send_code = "ZZZ"
389
389
  current_shef_loc: Optional[str] = None
390
390
  location_map: dict[str, str] = {} # cwms_location_name -> shef_id
391
+ version_send_codes: dict[str, str] = {} # cwms_version -> send_code
391
392
 
392
393
  for raw in text.splitlines():
393
394
  s = raw.strip()
394
395
  if not s or s.startswith("#"):
395
396
  continue
396
397
 
397
- # TS * = SEND_CODE
398
- m = re.match(r"^TS\s+\*\s*=\s*(\S+)\s*$", s, re.IGNORECASE)
398
+ # TS * = SEND_CODE or TS VersionName = SEND_CODE
399
+ m = re.match(r"^TS\s+(\S+)\s*=\s*(\S+)\s*$", s, re.IGNORECASE)
399
400
  if m:
400
- current_send_code = m.group(1)
401
- log.debug("Send code %s", current_send_code)
401
+ ts_key, ts_val = m.group(1), m.group(2)
402
+ if ts_key == "*":
403
+ current_send_code = ts_val
404
+ log.debug("Send code → %s", ts_val)
405
+ else:
406
+ version_send_codes[ts_key] = ts_val
407
+ log.debug("Version send code: %s → %s", ts_key, ts_val)
402
408
  continue
403
409
 
404
410
  # PE pattern = CODE[;units=UNIT] (positional — update mappings in place)
@@ -458,6 +464,7 @@ def _contextual_parse(
458
464
  cwms_location = cwms_parts[0]
459
465
  cwms_parameter = cwms_parts[1]
460
466
  cwms_duration = cwms_parts[4]
467
+ cwms_version = cwms_parts[5]
461
468
 
462
469
  # Resolve SHEF location
463
470
  # 1. Exact match in location_map
@@ -491,10 +498,12 @@ def _contextual_parse(
491
498
  shef_loc = mapped_value
492
499
  if not shef_loc:
493
500
  shef_loc = current_shef_loc
494
-
495
501
  if not shef_loc:
496
- log.warning("No SHEF location found for TSID: %s — skipping.", tsid_str)
497
- continue
502
+ shef_loc = cwms_location
503
+ log.debug(
504
+ "No LOCATION mapping for %s — using CWMS location as SHEF ID",
505
+ cwms_location,
506
+ )
498
507
 
499
508
  # Resolve PE code (first matching rule wins)
500
509
  pe_code = _resolve_pe_code(cwms_parameter, pe_mappings)
@@ -511,12 +520,13 @@ def _contextual_parse(
511
520
  if not units and pe_code in pe_units:
512
521
  units = pe_units[pe_code]
513
522
 
523
+ send_code = version_send_codes.get(cwms_version, current_send_code)
514
524
  entry = {
515
525
  "tsid": tsid_str,
516
526
  "shef_loc": shef_loc,
517
527
  "pe_code": pe_code,
518
528
  "duration": str(duration_value),
519
- "send_code": current_send_code,
529
+ "send_code": send_code,
520
530
  }
521
531
  if units:
522
532
  entry["units"] = units
@@ -1,4 +1,5 @@
1
1
  import logging as py_logging
2
+ import re
2
3
  import time
3
4
  from pathlib import Path
4
5
  from typing import Optional, Union
@@ -235,6 +236,66 @@ def log_scoped_read_hint(
235
236
  )
236
237
 
237
238
 
239
+ def format_local_download_error(error: Exception, docs_url: str) -> str:
240
+ if isinstance(error, (OSError, ValueError)):
241
+ message = (
242
+ f"{colors.c('Failed to download:', 'red', bright=True)} {error}. "
243
+ f"If this is a local destination/path issue, pass "
244
+ f"{colors.c('--dest', 'cyan', bright=True)} explicitly."
245
+ )
246
+ if docs_url:
247
+ message = (
248
+ f"{message} {colors.c('Docs:', 'blue', bright=True)} "
249
+ f"{colors.c(docs_url, 'blue', bright=True)}"
250
+ )
251
+ return message
252
+ return f"{colors.c('Failed to download:', 'red', bright=True)} {error}"
253
+
254
+
255
+ def validate_default_download_dest(
256
+ raw_id: str,
257
+ *,
258
+ resource_name: str,
259
+ docs_url: str = "",
260
+ ) -> str:
261
+ if raw_id is None:
262
+ raise ValueError(
263
+ f"{resource_name} ID must include a non-root destination name. "
264
+ f"Pass --dest explicitly if needed."
265
+ )
266
+
267
+ if raw_id.startswith("//") or raw_id.startswith("\\\\"):
268
+ raise ValueError(
269
+ f"{resource_name} ID must resolve to a relative local path. "
270
+ f"Pass --dest explicitly if needed."
271
+ )
272
+
273
+ target = raw_id.lstrip("/\\")
274
+ if not target:
275
+ message = (
276
+ f"{resource_name} ID must include a non-root destination name. "
277
+ f"Pass --dest explicitly if needed."
278
+ )
279
+ if docs_url:
280
+ message = f"{message} Docs: {docs_url}"
281
+ raise ValueError(message)
282
+
283
+ if re.match(r"^[A-Za-z]:", target):
284
+ raise ValueError(
285
+ f"{resource_name} ID must resolve to a relative local path. "
286
+ f"Pass --dest explicitly if needed."
287
+ )
288
+
289
+ parts = re.split(r"[\\/]", target)
290
+ if any(part in {"", ".", ".."} for part in parts):
291
+ raise ValueError(
292
+ f"{resource_name} ID must resolve to a relative local path. "
293
+ f"Pass --dest explicitly if needed."
294
+ )
295
+
296
+ return target
297
+
298
+
238
299
  def common_api_options(f):
239
300
  f = log_level_option(f)
240
301
  f = office_option(f)
@@ -2,7 +2,7 @@
2
2
  name = "cwms-cli"
3
3
  repository = "https://github.com/HydrologicEngineeringCenter/cwms-cli"
4
4
 
5
- version = "0.7.0"
5
+ version = "0.7.2"
6
6
 
7
7
 
8
8
  packages = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes