cwms-cli 0.3.4__tar.gz → 0.3.5__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.3.4 → cwms_cli-0.3.5}/PKG-INFO +1 -1
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/__main__.py +29 -2
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/location/location.py +12 -4
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/location/location_ids.py +36 -15
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/location/location_ids_bygroup.py +22 -17
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/root.py +5 -4
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/timeseries/timeseries.py +6 -2
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/timeseries/timeseries_ids.py +8 -5
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/__init__.py +10 -0
- cwms_cli-0.3.5/cwmscli/utils/links.py +1 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/logging/__init__.py +27 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/pyproject.toml +1 -1
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/LICENSE +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/README.md +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/__init__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/callbacks/__init__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/blob.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/commands_cwms.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/.gitignore +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/README.md +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/__init__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/__main__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/config.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/doclinks.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/examples/complete_config.json +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/parser.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/__init__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/data/.gitignore +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/data/sample_brok.csv +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/data/sample_config.json +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/test_dateutils.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/test_expressions.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/test_fileio.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/test_main.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/transform.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/utils/__init__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/utils/dateutils.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/utils/expression.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/utils/fileio.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/utils/logging.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/writer.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/shef_critfile_import.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/README.md +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/__init__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/load/__main__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/requirements.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/usgs/__init__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/usgs/__main__.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/usgs/getUSGS_ratings_cda.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/usgs/getusgs_cda.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/usgs/getusgs_measurements_cda.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/usgs/rating_ini_file_import.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/click_help.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/colors.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/deps.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/intervals.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/io.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/logging/formatters.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/utils/ssl_errors.py +0 -0
- {cwms_cli-0.3.4 → cwms_cli-0.3.5}/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.
|
|
3
|
+
Version: 0.3.5
|
|
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
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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
|
|
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)'
|
|
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
|
-
|
|
24
|
+
logger.info(
|
|
19
25
|
f"[load locations] source={source_cda} ({source_office}) -> target={target_cda}"
|
|
20
26
|
)
|
|
21
|
-
|
|
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 [
|
|
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"
|
|
48
|
+
if kinds == ["ALL"] and not like:
|
|
35
49
|
locations = cwms.get_locations(office_id=source_office).json
|
|
36
50
|
else:
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
78
|
+
logger.info("Fetched %s locations from source", len(locations))
|
|
58
79
|
|
|
59
80
|
if dry_run:
|
|
60
81
|
for loc in locations:
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"^(
|
|
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
|
-
|
|
37
|
+
logger.info(
|
|
35
38
|
f"[load location group] source={source_cda} ({source_office}) -> target={target_cda})"
|
|
36
39
|
)
|
|
37
|
-
|
|
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
|
-
|
|
57
|
+
logger.info("Fetched Location Group '%s' from source:", group_id)
|
|
55
58
|
if hasattr(grp, "df"):
|
|
56
|
-
|
|
59
|
+
logger.info("%s", grp.df)
|
|
57
60
|
else:
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
logger.info("Group members found: %s", len(member_ids))
|
|
75
78
|
if not member_ids:
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
f"[dry-run] would store Location(name={loc
|
|
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
|
-
|
|
117
|
+
logger.info("Store: %s", loc["name"])
|
|
115
118
|
cwms.store_location(data=loc, fail_if_exists=False)
|
|
116
119
|
if verbose:
|
|
117
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
+
logger.info("Timeseries ID copy operation completed.")
|
|
@@ -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)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cwms_cli-0.3.4 → cwms_cli-0.3.5}/cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py
RENAMED
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|