cwms-cli 0.3.4__tar.gz → 0.3.6__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 (62) hide show
  1. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/PKG-INFO +1 -1
  2. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/__main__.py +29 -2
  3. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/location/location.py +12 -4
  4. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/location/location_ids.py +36 -15
  5. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/location/location_ids_bygroup.py +22 -17
  6. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/root.py +5 -4
  7. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/timeseries/timeseries.py +6 -2
  8. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/timeseries/timeseries_ids.py +8 -5
  9. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/usgs/__init__.py +7 -3
  10. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/__init__.py +10 -0
  11. cwms_cli-0.3.6/cwmscli/utils/links.py +1 -0
  12. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/logging/__init__.py +27 -0
  13. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/pyproject.toml +1 -1
  14. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/LICENSE +0 -0
  15. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/README.md +0 -0
  16. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/__init__.py +0 -0
  17. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/callbacks/__init__.py +0 -0
  18. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/blob.py +0 -0
  19. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/commands_cwms.py +0 -0
  20. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/.gitignore +0 -0
  21. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/README.md +0 -0
  22. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/__init__.py +0 -0
  23. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/__main__.py +0 -0
  24. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/config.py +0 -0
  25. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/doclinks.py +0 -0
  26. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/examples/complete_config.json +0 -0
  27. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/parser.py +0 -0
  28. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/__init__.py +0 -0
  29. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/data/.gitignore +0 -0
  30. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json +0 -0
  31. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/data/sample_brok.csv +0 -0
  32. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/data/sample_config.json +0 -0
  33. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py +0 -0
  34. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/test_dateutils.py +0 -0
  35. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/test_expressions.py +0 -0
  36. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/test_fileio.py +0 -0
  37. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/tests/test_main.py +0 -0
  38. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/transform.py +0 -0
  39. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/utils/__init__.py +0 -0
  40. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/utils/dateutils.py +0 -0
  41. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/utils/expression.py +0 -0
  42. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/utils/fileio.py +0 -0
  43. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/utils/logging.py +0 -0
  44. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/csv2cwms/writer.py +0 -0
  45. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/commands/shef_critfile_import.py +0 -0
  46. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/README.md +0 -0
  47. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/__init__.py +0 -0
  48. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/load/__main__.py +0 -0
  49. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/requirements.py +0 -0
  50. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/usgs/__main__.py +0 -0
  51. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/usgs/getUSGS_ratings_cda.py +0 -0
  52. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/usgs/getusgs_cda.py +0 -0
  53. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/usgs/getusgs_measurements_cda.py +0 -0
  54. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/usgs/rating_ini_file_import.py +0 -0
  55. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/click_help.py +0 -0
  56. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/colors.py +0 -0
  57. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/deps.py +0 -0
  58. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/intervals.py +0 -0
  59. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/io.py +0 -0
  60. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/logging/formatters.py +0 -0
  61. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/ssl_errors.py +0 -0
  62. {cwms_cli-0.3.4 → cwms_cli-0.3.6}/cwmscli/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cwms-cli
3
- Version: 0.3.4
3
+ Version: 0.3.6
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
@@ -4,12 +4,18 @@ import sys
4
4
  from typing import Optional
5
5
 
6
6
  import click
7
+ from click.core import ParameterSource
7
8
 
8
9
  from cwmscli.commands import commands_cwms
9
10
  from cwmscli.load import __main__ as load
10
11
  from cwmscli.usgs import usgs_group
11
12
  from cwmscli.utils.click_help import add_version_to_help_tree
12
- from cwmscli.utils.logging import LoggingConfig, setup_logging
13
+ from cwmscli.utils.logging import (
14
+ LoggingConfig,
15
+ apply_logging_policies,
16
+ current_environment,
17
+ setup_logging,
18
+ )
13
19
  from cwmscli.utils.ssl_errors import is_cert_verify_error, ssl_help_text
14
20
  from cwmscli.utils.version import get_cwms_cli_version
15
21
 
@@ -40,8 +46,29 @@ from cwmscli.utils.version import get_cwms_cli_version
40
46
  ),
41
47
  default="INFO",
42
48
  )
43
- def cli(log_file: Optional[str], no_color: bool, log_level: str) -> None:
49
+ @click.option(
50
+ "-q",
51
+ "--quiet",
52
+ is_flag=True,
53
+ default=False,
54
+ help="Suppress routine output; warnings and errors still print.",
55
+ )
56
+ @click.pass_context
57
+ def cli(
58
+ ctx: click.Context,
59
+ log_file: Optional[str],
60
+ no_color: bool,
61
+ log_level: str,
62
+ quiet: bool,
63
+ ) -> None:
44
64
  level = getattr(logging, log_level.upper(), logging.INFO)
65
+ level = apply_logging_policies(
66
+ level,
67
+ quiet=quiet,
68
+ environment=current_environment(),
69
+ explicit_log_level=ctx.get_parameter_source("log_level")
70
+ == ParameterSource.COMMANDLINE,
71
+ )
45
72
 
46
73
  # Disable colors if stdout isn't a TTY (piped/redirected)
47
74
  tty = sys.stdout.isatty()
@@ -10,6 +10,7 @@ from cwmscli.load.root import (
10
10
  validate_cda_targets,
11
11
  )
12
12
  from cwmscli.utils.deps import requires
13
+ from cwmscli.utils.links import CDA_REGEXP_GUIDE_URL
13
14
 
14
15
 
15
16
  @load_group.group(
@@ -22,14 +23,21 @@ def location(ctx):
22
23
 
23
24
  @location.command(
24
25
  "ids-all",
25
- help="Copy ALL locations from a source CDA to a target CDA.",
26
+ help=(
27
+ "Copy locations from a source CDA catalog to a target CDA. "
28
+ "The --like and --location-kind-like filters use CDA regex semantics. "
29
+ f"Regex guide: {CDA_REGEXP_GUIDE_URL}"
30
+ ),
26
31
  )
27
32
  @shared_source_target_options
28
33
  @click.option(
29
34
  "--like",
30
35
  default=None,
31
36
  type=str,
32
- help="LIKE filter for location name (e.g. 'Turbine*').",
37
+ help=(
38
+ "Regular expression passed directly to the source CDA catalog. "
39
+ "Examples: '^Black Butte$' for an exact match, '^Black Butte.*' for a prefix match. "
40
+ ),
33
41
  )
34
42
  @click.option(
35
43
  "--location-kind-like",
@@ -37,13 +45,13 @@ def location(ctx):
37
45
  default=["ALL"],
38
46
  multiple=True,
39
47
  help=(
40
- "Filter by LOCATION_KIND using LIKE; may be passed multiple times.\n\n"
48
+ "Filter by LOCATION_KIND using CDA regex semantics; may be passed multiple times.\n\n"
41
49
  "Default is to pull all Location kinds.\n\n"
42
50
  "Common kinds: SITE, EMBANKMENT, OVERFLOW, TURBINE, STREAM, PROJECT, "
43
51
  "STREAMGAGE, BASIN, OUTLET, LOCK, GATE.\n\n"
44
52
  "Examples:\n"
45
53
  " --location-kind-like PROJECT --location-kind-like STREAM\n"
46
- " --location-kind-like '(SITE|STREAM)' # Posix regex"
54
+ " --location-kind-like '(SITE|STREAM)'"
47
55
  ),
48
56
  )
49
57
  @requires(reqs.cwms)
@@ -1,8 +1,14 @@
1
+ import logging
2
+ import re
1
3
  from typing import Iterable, Optional
2
4
 
3
5
  import click
4
6
  import cwms
5
7
 
8
+ from cwmscli.utils.links import CDA_REGEXP_GUIDE_URL
9
+
10
+ logger = logging.getLogger(__name__)
11
+
6
12
 
7
13
  def load_locations(
8
14
  source_cda: str,
@@ -15,50 +21,65 @@ def load_locations(
15
21
  location_kind_like: Optional[Iterable[str]] = "ALL",
16
22
  ):
17
23
  if verbose:
18
- click.echo(
24
+ logger.info(
19
25
  f"[load locations] source={source_cda} ({source_office}) -> target={target_cda}"
20
26
  )
21
- click.echo(
27
+ logger.info(
22
28
  f" like={like or '-'} kinds={list(location_kind_like) or '-'} dry_run={dry_run}"
23
29
  )
30
+ if like or (
31
+ location_kind_like
32
+ and list(location_kind_like) != ["ALL"]
33
+ and list(location_kind_like) != []
34
+ ):
35
+ logger.info(" CDA regex guide: %s", CDA_REGEXP_GUIDE_URL)
24
36
 
25
37
  cwms.init_session(api_root=source_cda, api_key=None)
26
38
 
27
39
  cat_kwargs = {"office_id": source_office}
28
40
  if like:
29
41
  cat_kwargs["like"] = like
30
- kinds = list(location_kind_like) if location_kind_like else [None]
42
+ kinds = list(location_kind_like) if location_kind_like else ["ALL"]
43
+ if "ALL" in kinds:
44
+ kinds = ["ALL"]
31
45
 
32
46
  locations = []
33
47
 
34
- if "ALL" in kinds:
48
+ if kinds == ["ALL"] and not like:
35
49
  locations = cwms.get_locations(office_id=source_office).json
36
50
  else:
37
- locations = []
51
+ seen_location_ids = set()
38
52
  for kind in kinds:
39
53
  cat_kwargs_k = dict(cat_kwargs)
40
54
  if kind != "ALL":
41
55
  cat_kwargs_k["location_kind_like"] = kind
42
56
 
43
57
  if verbose >= 2:
44
- click.echo(f" > catalog query: {cat_kwargs_k}")
58
+ logger.debug(" > catalog query: %s", cat_kwargs_k)
45
59
 
46
60
  resp = cwms.get_locations_catalog(**cat_kwargs_k)
47
61
  if resp.df.empty:
48
62
  continue
49
63
 
50
- loc_ids = resp.df["name"].tolist()
51
- locations_resp = cwms.get_locations(
52
- office_id=source_office, location_ids=loc_ids
53
- )
54
- locations.extend(locations_resp.json or [])
64
+ for location_id in resp.df["name"].tolist():
65
+ if location_id in seen_location_ids:
66
+ continue
67
+ seen_location_ids.add(location_id)
68
+ if verbose >= 2:
69
+ logger.debug(" > location fetch: %s", location_id)
70
+ detail_resp = cwms.get_locations(
71
+ office_id=source_office,
72
+ location_ids=rf"^{re.escape(location_id)}$",
73
+ )
74
+ if detail_resp and detail_resp.json:
75
+ locations.extend(detail_resp.json)
55
76
 
56
77
  if verbose:
57
- click.echo(f"Fetched {len(locations)} locations from source")
78
+ logger.info("Fetched %s locations from source", len(locations))
58
79
 
59
80
  if dry_run:
60
81
  for loc in locations:
61
- click.echo(
82
+ logger.info(
62
83
  f"[dry-run] would store Location(name={loc['name']}) to {target_cda} ({source_office})"
63
84
  )
64
85
  return
@@ -72,7 +93,7 @@ def load_locations(
72
93
  if loc["active"] is True:
73
94
  result = cwms.store_location(data=loc, fail_if_exists=False)
74
95
  if verbose:
75
- click.echo(result)
96
+ logger.info("%s", result)
76
97
  except Exception as e:
77
98
  errors += 1
78
99
  click.echo(f"Error storing location {loc}: \n\t{e}", err=True)
@@ -80,4 +101,4 @@ def load_locations(
80
101
  if errors:
81
102
  raise click.ClickException(f"Completed with {errors} error(s).")
82
103
  if verbose:
83
- click.echo("Done.")
104
+ logger.info("Done.")
@@ -1,17 +1,20 @@
1
1
  # cwmscli/load/location_group.py
2
+ import logging
2
3
  import re
3
4
  from typing import Optional
4
5
 
5
6
  import click
6
7
  import cwms
7
8
 
9
+ logger = logging.getLogger(__name__)
10
+
8
11
 
9
12
  def exact_or_regex(ids: list[str]) -> str:
10
13
  if not ids:
11
14
  return r"^$"
12
15
  if len(ids) == 1:
13
16
  return rf"^{re.escape(ids[0])}$"
14
- return r"^(?:" + "|".join(re.escape(x) for x in ids) + r")$"
17
+ return r"^(" + "|".join(re.escape(x) for x in ids) + r")$"
15
18
 
16
19
 
17
20
  def copy_from_group(
@@ -31,10 +34,10 @@ def copy_from_group(
31
34
  category_office_id = category_office_id or source_office
32
35
 
33
36
  if verbose:
34
- click.echo(
37
+ logger.info(
35
38
  f"[load location group] source={source_cda} ({source_office}) -> target={target_cda})"
36
39
  )
37
- click.echo(
40
+ logger.info(
38
41
  f" group={group_id} category={category_id} "
39
42
  f"group_office={group_office_id} category_office={category_office_id} "
40
43
  f"filter_office={filter_office} dry_run={dry_run}"
@@ -51,11 +54,11 @@ def copy_from_group(
51
54
  category_office_id=category_office_id,
52
55
  )
53
56
  if verbose:
54
- click.echo(f"Fetched Location Group '{group_id}' from source:")
57
+ logger.info("Fetched Location Group '%s' from source:", group_id)
55
58
  if hasattr(grp, "df"):
56
- click.echo(grp.df)
59
+ logger.info("%s", grp.df)
57
60
  else:
58
- click.echo(grp.json)
61
+ logger.info("%s", grp.json)
59
62
  except Exception as e:
60
63
  raise click.ClickException(
61
64
  f"Failed to read location group '{group_id}' in category '{category_id}': {e}"
@@ -63,7 +66,7 @@ def copy_from_group(
63
66
 
64
67
  df = getattr(grp, "df", None)
65
68
  if df is None or df.empty:
66
- click.echo("No members found in the specified location group.")
69
+ logger.info("No members found in the specified location group.")
67
70
  return
68
71
 
69
72
  if filter_office and "office-id" in df.columns:
@@ -71,9 +74,9 @@ def copy_from_group(
71
74
 
72
75
  member_ids = sorted(df["location-id"].dropna().unique().tolist())
73
76
  if verbose:
74
- click.echo(f"Group members found: {len(member_ids)}")
77
+ logger.info("Group members found: %s", len(member_ids))
75
78
  if not member_ids:
76
- click.echo("No valid location IDs to copy.")
79
+ logger.info("No valid location IDs to copy.")
77
80
  return
78
81
 
79
82
  try:
@@ -85,7 +88,7 @@ def copy_from_group(
85
88
  pattern = exact_or_regex(batch)
86
89
  resp = cwms.get_locations(office_id=source_office, location_ids=pattern)
87
90
  if verbose and getattr(resp, "df", None) is not None:
88
- click.echo(f"Fetched {len(resp.df)} matching Locations in batch")
91
+ logger.info("Fetched %s matching Locations in batch", len(resp.df))
89
92
  if resp and resp.json:
90
93
  locations.extend(resp.json)
91
94
 
@@ -93,12 +96,12 @@ def copy_from_group(
93
96
  raise click.ClickException(f"Failed to fetch locations from source: {e}")
94
97
 
95
98
  if verbose:
96
- click.echo(f"Fetched {len(locations)} Location objects from source")
99
+ logger.info("Fetched %s Location objects from source", len(locations))
97
100
 
98
101
  if dry_run:
99
102
  for loc in locations:
100
- click.echo(
101
- f"[dry-run] would store Location(name={loc.name}) to {target_cda} ({source_office})"
103
+ logger.info(
104
+ f"[dry-run] would store Location(name={loc['name']}) to {target_cda} ({source_office})"
102
105
  )
103
106
  return
104
107
 
@@ -111,16 +114,18 @@ def copy_from_group(
111
114
  for loc in locations:
112
115
  try:
113
116
  if verbose:
114
- click.echo(f"Store: {loc['name']}")
117
+ logger.info("Store: %s", loc["name"])
115
118
  cwms.store_location(data=loc, fail_if_exists=False)
116
119
  if verbose:
117
- click.echo("\tStored successfully.")
120
+ logger.info("\tStored successfully.")
118
121
  except Exception as e:
119
122
  errors += 1
120
123
  click.echo(f"Error storing location {loc}: \n\t{e}", err=True)
121
124
 
122
- click.echo(
123
- f"Successfully stored {len(locations) - errors} / {len(locations)} locations."
125
+ logger.info(
126
+ "Successfully stored %s / %s locations.",
127
+ len(locations) - errors,
128
+ len(locations),
124
129
  )
125
130
 
126
131
  if errors:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import functools
4
+ import logging
4
5
  from dataclasses import dataclass
5
6
  from typing import Optional
6
7
  from urllib.parse import urlparse
@@ -10,6 +11,8 @@ import click
10
11
  from cwmscli import requirements as reqs
11
12
  from cwmscli.utils.deps import requires
12
13
 
14
+ logger = logging.getLogger(__name__)
15
+
13
16
  CONTEXT = dict(
14
17
  help_option_names=["-h", "--help"],
15
18
  max_content_width=160,
@@ -56,16 +59,14 @@ def validate_cda_targets(func):
56
59
  "Type cwms-cli load --help for arg options."
57
60
  )
58
61
  elif same_root and not same_office:
59
- click.secho(
62
+ logger.warning(
60
63
  "Warning: source and target use the same CDA root URL but different offices. "
61
64
  "This is allowed, but double-check intent.",
62
- fg="yellow",
63
65
  )
64
66
 
65
- click.secho(
67
+ logger.info(
66
68
  f"Source: {source_cda} (office={source_office or '-'})\n"
67
69
  f"Target: {target_cda} (office={source_office or '-'})",
68
- fg="green" if not same_root else "yellow",
69
70
  )
70
71
  return func(*args, **kwargs)
71
72
 
@@ -9,6 +9,7 @@ from cwmscli.load.root import (
9
9
  validate_cda_targets,
10
10
  )
11
11
  from cwmscli.utils.deps import requires
12
+ from cwmscli.utils.links import CDA_REGEXP_GUIDE_URL
12
13
 
13
14
 
14
15
  @load_group.group(
@@ -21,7 +22,10 @@ def timeseries(ctx):
21
22
 
22
23
  @timeseries.command(
23
24
  "ids-all",
24
- help="Copy ALL timeseries IDs for locations in a target CDA from a source CDA.",
25
+ help=(
26
+ "Copy ALL timeseries IDs for locations in a target CDA from a source CDA. "
27
+ f"Regex guide for --timeseries-id-regex: {CDA_REGEXP_GUIDE_URL}"
28
+ ),
25
29
  )
26
30
  @shared_source_target_options
27
31
  @click.option(
@@ -29,7 +33,7 @@ def timeseries(ctx):
29
33
  "timeseries_id_regex",
30
34
  default=None,
31
35
  type=str,
32
- help="regex filter for timeseries ID (e.g. 'LocID.*').",
36
+ help="Regex filter for timeseries ID (e.g. '^LocID.*').",
33
37
  )
34
38
  @requires(reqs.cwms)
35
39
  @validate_cda_targets
@@ -1,10 +1,13 @@
1
1
  # cwmscli/load/timeseries_ids.py
2
+ import logging
2
3
  from turtle import pd
3
4
  from typing import Optional
4
5
 
5
6
  import click
6
7
  import pandas as pd
7
8
 
9
+ logger = logging.getLogger(__name__)
10
+
8
11
 
9
12
  def load_timeseries_ids(
10
13
  source_cda: str,
@@ -18,7 +21,7 @@ def load_timeseries_ids(
18
21
  import cwms
19
22
 
20
23
  if verbose:
21
- click.echo(
24
+ logger.info(
22
25
  f"Loading timeseries IDs from source CDA '{source_cda}' (office '{source_office}') "
23
26
  f"to target CDA '{target_cda}'."
24
27
  )
@@ -38,13 +41,13 @@ def load_timeseries_ids(
38
41
  ts_lo_ids = pd.merge(ts_ids, locs, how="inner", on=["location-id", "office-id"])
39
42
 
40
43
  if verbose:
41
- click.echo(f"Found {len(ts_lo_ids)} timeseries IDs to copy.")
44
+ logger.info("Found %s timeseries IDs to copy.", len(ts_lo_ids))
42
45
 
43
46
  errors = 0
44
47
  for i, row in ts_lo_ids.iterrows():
45
48
  ts_id = row["time-series-id"]
46
49
  if dry_run:
47
- click.echo(
50
+ logger.info(
48
51
  f"[dry-run] would store Timeseries ID(name={ts_id}) to {target_cda} ({source_office})"
49
52
  )
50
53
  continue
@@ -60,7 +63,7 @@ def load_timeseries_ids(
60
63
  data=t_id_json, fail_if_exists=False
61
64
  )
62
65
  if verbose:
63
- click.echo(result)
66
+ logger.info("%s", result)
64
67
  except Exception as e:
65
68
  errors += 1
66
69
  click.echo(f"Error storing location {ts_id}: \n\t{e}", err=True)
@@ -68,4 +71,4 @@ def load_timeseries_ids(
68
71
  if errors:
69
72
  raise click.ClickException(f"Completed with {errors} error(s).")
70
73
  if verbose:
71
- click.echo("Timeseries ID copy operation completed.")
74
+ logger.info("Timeseries ID copy operation completed.")
@@ -46,7 +46,7 @@ def getusgs_timeseries(office, days_back, api_root, api_key, api_key_loc, backfi
46
46
  from cwmscli.usgs.getusgs_cda import getusgs_cda
47
47
 
48
48
  if backfill is not None:
49
- backfill_list = backfill.replace(" ", "").split(",")
49
+ backfill_list = [item.strip() for item in backfill.split(",") if item.strip()]
50
50
  else:
51
51
  backfill_list = None
52
52
 
@@ -78,7 +78,9 @@ def getusgs_ratings(office, days_back, api_root, api_key, api_key_loc, rating_su
78
78
  from cwmscli.usgs.getUSGS_ratings_cda import getusgs_rating_cda
79
79
 
80
80
  if rating_subset is not None:
81
- rating_list = rating_subset.replace(" ", "").split(",")
81
+ rating_list = [
82
+ item.strip() for item in rating_subset.split(",") if item.strip()
83
+ ]
82
84
  else:
83
85
  rating_list = None
84
86
 
@@ -158,7 +160,9 @@ def getusgs_measurements(
158
160
  if "group" in backfill:
159
161
  backfill_group = True
160
162
  elif type(backfill) == str:
161
- backfill_list = backfill.replace(" ", "").split(",")
163
+ backfill_list = [
164
+ item.strip() for item in backfill.split(",") if item.strip()
165
+ ]
162
166
  api_key = get_api_key(api_key, api_key_loc)
163
167
  getusgs_measurement_cda(
164
168
  api_root=api_root,
@@ -2,9 +2,11 @@ import logging as py_logging
2
2
  from typing import Optional
3
3
 
4
4
  import click
5
+ from click.core import ParameterSource
5
6
 
6
7
  from cwmscli.utils import colors
7
8
  from cwmscli.utils.click_help import DOCS_BASE_URL
9
+ from cwmscli.utils.logging import apply_logging_policies, current_environment
8
10
 
9
11
 
10
12
  def to_uppercase(ctx, param, value):
@@ -19,6 +21,14 @@ def _set_log_level(ctx, param, value):
19
21
  level = getattr(py_logging, value.upper(), None)
20
22
  if level is None:
21
23
  raise click.BadParameter(f"Invalid log level: {value}")
24
+ quiet = bool(ctx.find_root().params.get("quiet", False))
25
+ level = apply_logging_policies(
26
+ level,
27
+ quiet=quiet,
28
+ environment=current_environment(),
29
+ explicit_log_level=ctx.get_parameter_source(param.name)
30
+ == ParameterSource.COMMANDLINE,
31
+ )
22
32
  py_logging.getLogger().setLevel(level)
23
33
  return value
24
34
 
@@ -0,0 +1 @@
1
+ CDA_REGEXP_GUIDE_URL = "https://cwms-cli.readthedocs.io/en/latest/cli/cda_regex.html"
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import os
4
5
  import sys
5
6
  from dataclasses import dataclass
6
7
  from typing import Optional
@@ -18,6 +19,32 @@ class LoggingConfig:
18
19
  color: bool = True
19
20
 
20
21
 
22
+ PRODUCTION_ENV_ALIASES = frozenset({"prod", "production"})
23
+
24
+
25
+ def apply_logging_policies(
26
+ level: int,
27
+ *,
28
+ quiet: bool,
29
+ environment: Optional[str],
30
+ explicit_log_level: bool = False,
31
+ ) -> int:
32
+ effective_level = level
33
+ normalized_env = (environment or "").strip().lower()
34
+
35
+ if normalized_env in PRODUCTION_ENV_ALIASES and not explicit_log_level:
36
+ effective_level = max(effective_level, logging.WARNING)
37
+
38
+ if quiet:
39
+ effective_level = max(effective_level, logging.WARNING)
40
+
41
+ return effective_level
42
+
43
+
44
+ def current_environment() -> Optional[str]:
45
+ return os.getenv("ENVIRONMENT")
46
+
47
+
21
48
  class ColorLevelFormatter(logging.Formatter):
22
49
  def __init__(self, fmt: str, datefmt: str, enable_color: bool) -> None:
23
50
  super().__init__(fmt=fmt, datefmt=datefmt)
@@ -2,7 +2,7 @@
2
2
  name = "cwms-cli"
3
3
  repository = "https://github.com/HydrologicEngineeringCenter/cwms-cli"
4
4
 
5
- version = "0.3.4"
5
+ version = "0.3.6"
6
6
 
7
7
 
8
8
  packages = [
File without changes
File without changes
File without changes
File without changes
File without changes