cwms-cli 0.7.4__tar.gz → 0.8.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.
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/PKG-INFO +30 -2
- cwms_cli-0.8.0/README.md +66 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/__main__.py +29 -3
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/_generated/ownership_data.py +11 -1
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/blob.py +3 -2
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/clob.py +28 -8
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/commands_cwms.py +60 -11
- cwms_cli-0.8.0/cwmscli/commands/env.py +508 -0
- cwms_cli-0.8.0/cwmscli/dss/__init__.py +5 -0
- cwms_cli-0.8.0/cwmscli/dss/cli.py +352 -0
- cwms_cli-0.8.0/cwmscli/dss/naming.py +318 -0
- cwms_cli-0.8.0/cwmscli/dss/transfer.py +215 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/location/location_ids.py +8 -0
- cwms_cli-0.8.0/cwmscli/load/root.py +335 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/timeseries/timeseries.py +3 -2
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/requirements.py +16 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/__init__.py +1 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/auth.py +3 -6
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/click_help.py +20 -22
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/deps.py +3 -2
- cwms_cli-0.8.0/cwmscli/utils/env_store.py +133 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/friendly_errors.py +88 -0
- cwms_cli-0.8.0/cwmscli/utils/links.py +5 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/logging/__init__.py +1 -1
- cwms_cli-0.8.0/cwmscli/utils/paths.py +23 -0
- cwms_cli-0.8.0/cwmscli/utils/update.py +155 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/pyproject.toml +8 -5
- cwms_cli-0.7.4/README.md +0 -42
- cwms_cli-0.7.4/cwmscli/load/root.py +0 -190
- cwms_cli-0.7.4/cwmscli/utils/links.py +0 -1
- cwms_cli-0.7.4/cwmscli/utils/update.py +0 -61
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/LICENSE +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/_generated/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/callbacks/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/.gitignore +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/README.md +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/__main__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/config.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/doclinks.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/examples/complete_config.json +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/parser.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/data/.gitignore +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/data/sample_brok.csv +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/data/sample_config.json +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/test_dateutils.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/test_expressions.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/test_fileio.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/tests/test_main.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/transform.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/utils/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/utils/dateutils.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/utils/expression.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/utils/fileio.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/utils/logging.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/csv2cwms/writer.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/shef/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/shef/import_critfile.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/shef/import_infile.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/shef/shef_parameters.csv +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/commands/users.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/README.md +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/__main__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/location/location.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/location/location_ids_bygroup.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/timeseries/timeseries_data.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/load/timeseries/timeseries_ids.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/ownership.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/usgs/__init__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/usgs/__main__.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/usgs/getUSGS_ratings_cda.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/usgs/getusgs_cda.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/usgs/getusgs_measurements_cda.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/usgs/rating_ini_file_import.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/callback_success.html +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/colors.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/intervals.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/io.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/logging/formatters.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/ssl_errors.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/cwmscli/utils/version.py +0 -0
- {cwms_cli-0.7.4 → cwms_cli-0.8.0}/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.
|
|
3
|
+
Version: 0.8.0
|
|
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
|
|
@@ -16,8 +16,12 @@ 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
|
+
Provides-Extra: dss
|
|
19
20
|
Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
20
21
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
22
|
+
Requires-Dist: cwms-python (>=1.0.7) ; extra == "dss"
|
|
23
|
+
Requires-Dist: hec-python-library (>=0.9.5) ; extra == "dss"
|
|
24
|
+
Requires-Dist: hecdss (>=0.1.24) ; extra == "dss"
|
|
21
25
|
Requires-Dist: requests (>=2.32.0,<3.0.0) ; python_version == "3.9"
|
|
22
26
|
Requires-Dist: requests (>=2.33.0,<3.0.0) ; python_version >= "3.10"
|
|
23
27
|
Project-URL: Repository, https://github.com/HydrologicEngineeringCenter/cwms-cli
|
|
@@ -39,7 +43,7 @@ Note: You may need to run `python -m pip install cwms-cli` if PIP is not in your
|
|
|
39
43
|
|
|
40
44
|
### Update
|
|
41
45
|
```sh
|
|
42
|
-
pip install cwms-cli
|
|
46
|
+
python -m pip install --upgrade cwms-cli
|
|
43
47
|
```
|
|
44
48
|
|
|
45
49
|
Or as of version `0.3.0+`
|
|
@@ -47,6 +51,12 @@ Or as of version `0.3.0+`
|
|
|
47
51
|
cwms-cli update
|
|
48
52
|
```
|
|
49
53
|
|
|
54
|
+
`cwms-cli update` displays the Python executable, environment, package metadata
|
|
55
|
+
location, and editable project location when applicable before asking for
|
|
56
|
+
confirmation. It runs pip through the same Python interpreter that is running
|
|
57
|
+
`cwms-cli`, avoiding accidental updates to a different Python installation or
|
|
58
|
+
virtual environment.
|
|
59
|
+
|
|
50
60
|
To install a specific version:
|
|
51
61
|
```sh
|
|
52
62
|
cwms-cli update --target-version 0.7.1 --yes
|
|
@@ -66,3 +76,21 @@ from cwmscli.usgs.getusgs_measurements_cda import getusgs_measurements_cda
|
|
|
66
76
|
from cwmscli.usgs.getUSGS_ratings_cda import getusgs_rating_cda
|
|
67
77
|
```
|
|
68
78
|
|
|
79
|
+
## Development environment
|
|
80
|
+
|
|
81
|
+
The repository includes a Linux/Python 3.12 development container exercised by
|
|
82
|
+
a dedicated CI job. Open the repository with the VS Code Dev Containers
|
|
83
|
+
extension, or use the Dev Container CLI:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
devcontainer up --workspace-folder .
|
|
87
|
+
devcontainer exec --workspace-folder . poetry run pytest -q
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The container installs project dependencies with Poetry and uses
|
|
91
|
+
`/home/vscode/.venv`, so it does not reuse a host operating system's `.venv`.
|
|
92
|
+
Poetry itself is kept in a separate `/opt/poetry` environment. Use the container
|
|
93
|
+
for changes involving time zones, paths, native libraries, or other
|
|
94
|
+
operating-system-dependent behavior. Standard Python 3.9 and Python 3.12
|
|
95
|
+
installations remain covered by the existing CI matrix.
|
|
96
|
+
|
cwms_cli-0.8.0/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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/cli.html#cwms-cli) - 📖 Read the docs: https://cwms-cli.readthedocs.io/en/latest/
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install cwms-cli
|
|
11
|
+
```
|
|
12
|
+
Note: You may need to run `python -m pip install cwms-cli` if PIP is not in your path.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Update
|
|
16
|
+
```sh
|
|
17
|
+
python -m pip install --upgrade cwms-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or as of version `0.3.0+`
|
|
21
|
+
```sh
|
|
22
|
+
cwms-cli update
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`cwms-cli update` displays the Python executable, environment, package metadata
|
|
26
|
+
location, and editable project location when applicable before asking for
|
|
27
|
+
confirmation. It runs pip through the same Python interpreter that is running
|
|
28
|
+
`cwms-cli`, avoiding accidental updates to a different Python installation or
|
|
29
|
+
virtual environment.
|
|
30
|
+
|
|
31
|
+
To install a specific version:
|
|
32
|
+
```sh
|
|
33
|
+
cwms-cli update --target-version 0.7.1 --yes
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Command line implementation
|
|
37
|
+
|
|
38
|
+
View the help in terminal:
|
|
39
|
+
```sh
|
|
40
|
+
cwms-cli --help
|
|
41
|
+
```
|
|
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
|
+
|
|
50
|
+
## Development environment
|
|
51
|
+
|
|
52
|
+
The repository includes a Linux/Python 3.12 development container exercised by
|
|
53
|
+
a dedicated CI job. Open the repository with the VS Code Dev Containers
|
|
54
|
+
extension, or use the Dev Container CLI:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
devcontainer up --workspace-folder .
|
|
58
|
+
devcontainer exec --workspace-folder . poetry run pytest -q
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The container installs project dependencies with Poetry and uses
|
|
62
|
+
`/home/vscode/.venv`, so it does not reuse a host operating system's `.venv`.
|
|
63
|
+
Poetry itself is kept in a separate `/opt/poetry` environment. Use the container
|
|
64
|
+
for changes involving time zones, paths, native libraries, or other
|
|
65
|
+
operating-system-dependent behavior. Standard Python 3.9 and Python 3.12
|
|
66
|
+
installations remain covered by the existing CI matrix.
|
|
@@ -7,10 +7,17 @@ import click
|
|
|
7
7
|
from click.core import ParameterSource
|
|
8
8
|
|
|
9
9
|
from cwmscli.commands import commands_cwms
|
|
10
|
+
from cwmscli.commands.env import env_group
|
|
11
|
+
from cwmscli.dss import dss_group
|
|
10
12
|
from cwmscli.load import __main__ as load
|
|
11
13
|
from cwmscli.usgs import usgs_group
|
|
12
14
|
from cwmscli.utils.click_help import add_version_to_help_tree
|
|
13
|
-
from cwmscli.utils.friendly_errors import
|
|
15
|
+
from cwmscli.utils.friendly_errors import (
|
|
16
|
+
cda_stack_trace,
|
|
17
|
+
format_cda_stack_trace,
|
|
18
|
+
to_user_facing_error,
|
|
19
|
+
)
|
|
20
|
+
from cwmscli.utils.links import BUG_REPORT_URL
|
|
14
21
|
from cwmscli.utils.logging import (
|
|
15
22
|
LoggingConfig,
|
|
16
23
|
apply_logging_policies,
|
|
@@ -88,7 +95,9 @@ cli.add_command(commands_cwms.update_cli_cmd)
|
|
|
88
95
|
cli.add_command(commands_cwms.blob_group)
|
|
89
96
|
cli.add_command(commands_cwms.clob_group)
|
|
90
97
|
cli.add_command(commands_cwms.users_group)
|
|
98
|
+
cli.add_command(env_group)
|
|
91
99
|
cli.add_command(load.load_group)
|
|
100
|
+
cli.add_command(dss_group)
|
|
92
101
|
add_version_to_help_tree(cli)
|
|
93
102
|
|
|
94
103
|
|
|
@@ -111,9 +120,19 @@ def main() -> None:
|
|
|
111
120
|
except SystemExit:
|
|
112
121
|
raise
|
|
113
122
|
except click.ClickException as e:
|
|
123
|
+
debug = debug or logging.getLogger().isEnabledFor(logging.DEBUG)
|
|
124
|
+
if debug:
|
|
125
|
+
server_stack_trace = cda_stack_trace(e)
|
|
126
|
+
if server_stack_trace is not None:
|
|
127
|
+
click.echo(format_cda_stack_trace(server_stack_trace), err=True)
|
|
128
|
+
raise SystemExit(e.exit_code)
|
|
114
129
|
e.show()
|
|
115
130
|
raise SystemExit(e.exit_code)
|
|
116
131
|
except Exception as e:
|
|
132
|
+
# The environment switch supports failures before Click configures logging.
|
|
133
|
+
# Once CLI setup has run, --log-level DEBUG enables the same behavior.
|
|
134
|
+
debug = debug or logging.getLogger().isEnabledFor(logging.DEBUG)
|
|
135
|
+
|
|
117
136
|
if is_cert_verify_error(e) and not debug:
|
|
118
137
|
# Keep this short, no stack trace.
|
|
119
138
|
logging.error(
|
|
@@ -122,14 +141,21 @@ def main() -> None:
|
|
|
122
141
|
click.echo(ssl_help_text(), err=True)
|
|
123
142
|
raise SystemExit(2)
|
|
124
143
|
|
|
125
|
-
if
|
|
144
|
+
if debug:
|
|
145
|
+
server_stack_trace = cda_stack_trace(e)
|
|
146
|
+
if server_stack_trace is not None:
|
|
147
|
+
click.echo(format_cda_stack_trace(server_stack_trace), err=True)
|
|
148
|
+
raise SystemExit(1)
|
|
149
|
+
else:
|
|
126
150
|
friendly_error = to_user_facing_error(e)
|
|
127
151
|
if friendly_error is not None:
|
|
128
152
|
logging.debug("Suppressed traceback for CLI exception", exc_info=e)
|
|
129
153
|
friendly_error.show()
|
|
130
154
|
raise SystemExit(friendly_error.exit_code)
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
click.echo(f"Unexpected error. Report it at {BUG_REPORT_URL}", err=True)
|
|
157
|
+
# Preserve raw exception behavior when CDA did not provide a server stack
|
|
158
|
+
# trace so an issue report includes useful diagnostic details.
|
|
133
159
|
raise
|
|
134
160
|
|
|
135
161
|
|
|
@@ -15,6 +15,16 @@ OWNERSHIP_DATA = {
|
|
|
15
15
|
"name": "Charles Graham"
|
|
16
16
|
}
|
|
17
17
|
],
|
|
18
|
+
"cwms-cli env": [
|
|
19
|
+
{
|
|
20
|
+
"email": "mitchell.s.weier@usace.army.mil",
|
|
21
|
+
"name": "Mitch Weier"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"email": "charles.r.graham@usace.army.mil",
|
|
25
|
+
"name": "Charles Graham"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
18
28
|
"cwms-cli load": [
|
|
19
29
|
{
|
|
20
30
|
"email": "charles.r.graham@usace.army.mil",
|
|
@@ -25,7 +35,7 @@ OWNERSHIP_DATA = {
|
|
|
25
35
|
"name": "Eric Novotny"
|
|
26
36
|
}
|
|
27
37
|
],
|
|
28
|
-
"cwms-cli
|
|
38
|
+
"cwms-cli shef": [
|
|
29
39
|
{
|
|
30
40
|
"email": "eric.v.novotny@usace.army.mil",
|
|
31
41
|
"name": "Eric Novotny"
|
|
@@ -613,12 +613,12 @@ def download_cmd(
|
|
|
613
613
|
try:
|
|
614
614
|
blob_content = cwms.get_blob(office_id=office, blob_id=bid)
|
|
615
615
|
target = dest or _default_download_dest(bid)
|
|
616
|
-
_save_blob_content(
|
|
616
|
+
saved_target = _save_blob_content(
|
|
617
617
|
blob_content,
|
|
618
618
|
dest=target,
|
|
619
619
|
media_type_hint=_blob_media_type(cwms, office, bid),
|
|
620
620
|
)
|
|
621
|
-
logging.info(f"Downloaded blob to: {
|
|
621
|
+
logging.info(f"Downloaded blob to: {saved_target}")
|
|
622
622
|
except requests.HTTPError as e:
|
|
623
623
|
detail = getattr(e.response, "text", "") or str(e)
|
|
624
624
|
logging.error(f"Failed to download (HTTP): {detail}")
|
|
@@ -632,6 +632,7 @@ def download_cmd(
|
|
|
632
632
|
sys.exit(1)
|
|
633
633
|
except Exception as e:
|
|
634
634
|
logging.error(format_local_download_error(e, BLOB_DOCS_URL))
|
|
635
|
+
# Local write/path failures are not CDA credential scope problems.
|
|
635
636
|
if not isinstance(e, (OSError, ValueError)):
|
|
636
637
|
log_scoped_read_hint(
|
|
637
638
|
credential_kind=credential_kind,
|
|
@@ -12,10 +12,15 @@ from cwms import api as cwms_api
|
|
|
12
12
|
from cwmscli.utils import (
|
|
13
13
|
format_local_download_error,
|
|
14
14
|
get_api_key,
|
|
15
|
+
get_saved_login_token,
|
|
15
16
|
has_invalid_chars,
|
|
17
|
+
init_cwms_session,
|
|
16
18
|
log_scoped_read_hint,
|
|
17
19
|
validate_default_download_dest,
|
|
18
20
|
)
|
|
21
|
+
from cwmscli.utils.click_help import DOCS_BASE_URL
|
|
22
|
+
|
|
23
|
+
CLOB_DOCS_URL = f"{DOCS_BASE_URL}/cli/clob.html"
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
def _join_api_url(api_root: str, path: str) -> str:
|
|
@@ -28,6 +33,16 @@ def _resolve_optional_api_key(api_key: Optional[str], anonymous: bool) -> Option
|
|
|
28
33
|
return get_api_key(api_key, None)
|
|
29
34
|
|
|
30
35
|
|
|
36
|
+
def _resolve_credential_kind(api_key: Optional[str], anonymous: bool) -> Optional[str]:
|
|
37
|
+
if anonymous:
|
|
38
|
+
return None
|
|
39
|
+
if get_saved_login_token():
|
|
40
|
+
return "token"
|
|
41
|
+
if _resolve_optional_api_key(api_key, anonymous):
|
|
42
|
+
return "api_key"
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
31
46
|
def _write_clob_content(content: str, dest: str) -> str:
|
|
32
47
|
os.makedirs(os.path.dirname(dest) or ".", exist_ok=True)
|
|
33
48
|
with open(dest, "w", encoding="utf-8", newline="") as f:
|
|
@@ -36,12 +51,17 @@ def _write_clob_content(content: str, dest: str) -> str:
|
|
|
36
51
|
|
|
37
52
|
|
|
38
53
|
def _default_download_dest(clob_id: str) -> str:
|
|
39
|
-
return validate_default_download_dest(
|
|
54
|
+
return validate_default_download_dest(
|
|
55
|
+
clob_id,
|
|
56
|
+
resource_name="Clob",
|
|
57
|
+
docs_url=CLOB_DOCS_URL,
|
|
58
|
+
)
|
|
40
59
|
|
|
41
60
|
|
|
42
61
|
def _clob_endpoint_id(clob_id: str) -> tuple[str, Optional[str]]:
|
|
43
62
|
normalized = clob_id.upper()
|
|
44
63
|
if has_invalid_chars(normalized):
|
|
64
|
+
# Path-like IDs use the placeholder route and keep the real ID in query params.
|
|
45
65
|
return "ignored", normalized
|
|
46
66
|
return normalized, None
|
|
47
67
|
|
|
@@ -190,8 +210,8 @@ def download_cmd(
|
|
|
190
210
|
f"DRY RUN: would GET {api_root} clob with clob-id={clob_id} office={office}."
|
|
191
211
|
)
|
|
192
212
|
return
|
|
193
|
-
|
|
194
|
-
cwms
|
|
213
|
+
credential_kind = _resolve_credential_kind(api_key, anonymous)
|
|
214
|
+
init_cwms_session(cwms, api_root=api_root, api_key=api_key, anonymous=anonymous)
|
|
195
215
|
bid = clob_id.upper()
|
|
196
216
|
logging.debug(f"Office={office} clobID={bid}")
|
|
197
217
|
|
|
@@ -215,7 +235,7 @@ def download_cmd(
|
|
|
215
235
|
detail = getattr(e.response, "text", "") or str(e)
|
|
216
236
|
logging.error(f"Failed to download (HTTP): {detail}")
|
|
217
237
|
log_scoped_read_hint(
|
|
218
|
-
|
|
238
|
+
credential_kind=credential_kind,
|
|
219
239
|
anonymous=anonymous,
|
|
220
240
|
office=office,
|
|
221
241
|
action="download",
|
|
@@ -223,7 +243,7 @@ def download_cmd(
|
|
|
223
243
|
)
|
|
224
244
|
sys.exit(1)
|
|
225
245
|
except Exception as e:
|
|
226
|
-
logging.error(format_local_download_error(e,
|
|
246
|
+
logging.error(format_local_download_error(e, CLOB_DOCS_URL))
|
|
227
247
|
sys.exit(1)
|
|
228
248
|
|
|
229
249
|
|
|
@@ -305,8 +325,8 @@ def list_cmd(
|
|
|
305
325
|
api_key: str,
|
|
306
326
|
anonymous: bool = False,
|
|
307
327
|
):
|
|
308
|
-
|
|
309
|
-
cwms
|
|
328
|
+
credential_kind = _resolve_credential_kind(api_key, anonymous)
|
|
329
|
+
init_cwms_session(cwms, api_root=api_root, api_key=api_key, anonymous=anonymous)
|
|
310
330
|
try:
|
|
311
331
|
df = list_clobs(
|
|
312
332
|
office=office,
|
|
@@ -319,7 +339,7 @@ def list_cmd(
|
|
|
319
339
|
)
|
|
320
340
|
except Exception:
|
|
321
341
|
log_scoped_read_hint(
|
|
322
|
-
|
|
342
|
+
credential_kind=credential_kind,
|
|
323
343
|
anonymous=anonymous,
|
|
324
344
|
office=office,
|
|
325
345
|
action="list",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
import subprocess
|
|
4
|
-
import sys
|
|
5
4
|
import textwrap
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import Optional
|
|
@@ -26,7 +25,9 @@ from cwmscli.utils.auth import DEFAULT_REDIRECT_HOST, DEFAULT_REDIRECT_PORT
|
|
|
26
25
|
from cwmscli.utils.deps import requires
|
|
27
26
|
from cwmscli.utils.update import (
|
|
28
27
|
build_update_package_spec,
|
|
28
|
+
get_update_environment,
|
|
29
29
|
launch_windows_update,
|
|
30
|
+
looks_like_externally_managed_environment,
|
|
30
31
|
looks_like_missing_version,
|
|
31
32
|
)
|
|
32
33
|
from cwmscli.utils.version import get_cwms_cli_version
|
|
@@ -416,6 +417,7 @@ def csv2cwms_cmd(**kwargs):
|
|
|
416
417
|
def update_cli_cmd(target_version: Optional[str], pre: bool, yes: bool) -> None:
|
|
417
418
|
current_version = get_cwms_cli_version()
|
|
418
419
|
package_spec = build_update_package_spec(target_version)
|
|
420
|
+
update_environment = get_update_environment()
|
|
419
421
|
|
|
420
422
|
click.echo(
|
|
421
423
|
"Current cwms-cli version: " f"{colors.c(current_version, 'cyan', bright=True)}"
|
|
@@ -428,12 +430,35 @@ def update_cli_cmd(target_version: Optional[str], pre: bool, yes: bool) -> None:
|
|
|
428
430
|
else:
|
|
429
431
|
click.echo("Requested cwms-cli version: latest available release")
|
|
430
432
|
|
|
431
|
-
|
|
433
|
+
click.echo("Update environment:")
|
|
434
|
+
click.echo(f" Python executable: {update_environment.python_executable}")
|
|
435
|
+
click.echo(
|
|
436
|
+
f" Environment: {update_environment.environment_prefix} "
|
|
437
|
+
f"({update_environment.environment_type})"
|
|
438
|
+
)
|
|
439
|
+
click.echo(f" Package metadata location: {update_environment.package_location}")
|
|
440
|
+
if update_environment.editable_project_location:
|
|
441
|
+
click.echo(
|
|
442
|
+
" Editable project location: "
|
|
443
|
+
f"{update_environment.editable_project_location}"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
cmd = [
|
|
447
|
+
update_environment.python_executable,
|
|
448
|
+
"-m",
|
|
449
|
+
"pip",
|
|
450
|
+
"install",
|
|
451
|
+
"--upgrade",
|
|
452
|
+
package_spec,
|
|
453
|
+
]
|
|
432
454
|
if pre:
|
|
433
455
|
cmd.append("--pre")
|
|
434
456
|
|
|
435
457
|
if not yes:
|
|
436
|
-
proceed = click.confirm(
|
|
458
|
+
proceed = click.confirm(
|
|
459
|
+
"Proceed with updating cwms-cli in this environment via pip?",
|
|
460
|
+
default=True,
|
|
461
|
+
)
|
|
437
462
|
if not proceed:
|
|
438
463
|
click.echo(colors.warn("Update canceled."))
|
|
439
464
|
return
|
|
@@ -472,6 +497,16 @@ def update_cli_cmd(target_version: Optional[str], pre: bool, yes: bool) -> None:
|
|
|
472
497
|
|
|
473
498
|
if result.returncode != 0:
|
|
474
499
|
pip_output = "\n".join(part for part in [result.stdout, result.stderr] if part)
|
|
500
|
+
if looks_like_externally_managed_environment(pip_output):
|
|
501
|
+
raise click.ClickException(
|
|
502
|
+
colors.err(
|
|
503
|
+
"The selected Python installation is externally managed, so pip "
|
|
504
|
+
"refused to update cwms-cli. Install cwms-cli in a virtual "
|
|
505
|
+
"environment or with pipx, then run that installation's "
|
|
506
|
+
"cwms-cli update command. cwms-cli will not use "
|
|
507
|
+
"--break-system-packages automatically."
|
|
508
|
+
)
|
|
509
|
+
)
|
|
475
510
|
if target_version and looks_like_missing_version(pip_output, package_spec):
|
|
476
511
|
raise click.ClickException(
|
|
477
512
|
colors.err(
|
|
@@ -492,13 +527,15 @@ def update_cli_cmd(target_version: Optional[str], pre: bool, yes: bool) -> None:
|
|
|
492
527
|
@click.group(
|
|
493
528
|
"blob",
|
|
494
529
|
help="Manage CWMS Blobs (upload, download, delete, update, list)",
|
|
495
|
-
epilog=textwrap.dedent(
|
|
530
|
+
epilog=textwrap.dedent(
|
|
531
|
+
"""
|
|
496
532
|
Example Usage:\n
|
|
497
533
|
- Store a PDF/image as a CWMS blob with optional description\n
|
|
498
534
|
- Download a blob by id to your local filesystem\n
|
|
499
535
|
- Update a blob's name/description/mime-type\n
|
|
500
536
|
- Bulk list blobs for an office
|
|
501
|
-
"""
|
|
537
|
+
"""
|
|
538
|
+
),
|
|
502
539
|
)
|
|
503
540
|
def blob_group():
|
|
504
541
|
pass
|
|
@@ -671,10 +708,15 @@ def update_cmd(**kwargs):
|
|
|
671
708
|
show_default=True,
|
|
672
709
|
help="Sort descending instead of ascending.",
|
|
673
710
|
)
|
|
674
|
-
@click.option(
|
|
711
|
+
@click.option(
|
|
712
|
+
"--limit",
|
|
713
|
+
type=click.IntRange(min=1),
|
|
714
|
+
default=None,
|
|
715
|
+
help="Max rows to show.",
|
|
716
|
+
)
|
|
675
717
|
@click.option(
|
|
676
718
|
"--page-size",
|
|
677
|
-
type=
|
|
719
|
+
type=click.IntRange(min=1),
|
|
678
720
|
default=None,
|
|
679
721
|
help="Max rows to request from the blob endpoint. Defaults to --limit if set, otherwise no pagination (all results in one page).",
|
|
680
722
|
)
|
|
@@ -706,12 +748,14 @@ def list_cmd(**kwargs):
|
|
|
706
748
|
@click.group(
|
|
707
749
|
"clob",
|
|
708
750
|
help="Manage CWMS Clobs (upload, download, delete, update, list)",
|
|
709
|
-
epilog=textwrap.dedent(
|
|
751
|
+
epilog=textwrap.dedent(
|
|
752
|
+
"""
|
|
710
753
|
Example Usage:\n
|
|
711
754
|
- Download a clob by id to your local filesystem\n
|
|
712
755
|
- Update a clob's name/description/mime-type\n
|
|
713
756
|
- Bulk list clobs for an office
|
|
714
|
-
"""
|
|
757
|
+
"""
|
|
758
|
+
),
|
|
715
759
|
)
|
|
716
760
|
@requires(reqs.cwms)
|
|
717
761
|
def clob_group():
|
|
@@ -837,10 +881,15 @@ def update_cmd(**kwargs):
|
|
|
837
881
|
show_default=True,
|
|
838
882
|
help="Sort descending instead of ascending.",
|
|
839
883
|
)
|
|
840
|
-
@click.option(
|
|
884
|
+
@click.option(
|
|
885
|
+
"--limit",
|
|
886
|
+
type=click.IntRange(min=1),
|
|
887
|
+
default=None,
|
|
888
|
+
help="Max rows to show.",
|
|
889
|
+
)
|
|
841
890
|
@click.option(
|
|
842
891
|
"--page-size",
|
|
843
|
-
type=
|
|
892
|
+
type=click.IntRange(min=1),
|
|
844
893
|
default=None,
|
|
845
894
|
help="Max rows to request from the clob endpoint. Defaults to --limit when set.",
|
|
846
895
|
)
|