cwms-cli 0.8.1__tar.gz → 0.9.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.8.1 → cwms_cli-0.9.0}/PKG-INFO +2 -1
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/commands_cwms.py +5 -9
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/users.py +36 -8
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/usgs/getusgs_cda.py +107 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/deps.py +36 -6
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/pyproject.toml +1 -1
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/LICENSE +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/README.md +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/__main__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/_generated/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/_generated/ownership_data.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/callbacks/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/blob.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/clob.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/.gitignore +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/README.md +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/__main__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/config.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/doclinks.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/examples/complete_config.json +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/parser.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/data/.gitignore +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/data/sample_brok.csv +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/data/sample_config.json +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/test_dateutils.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/test_expressions.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/test_fileio.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/test_main.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/transform.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/utils/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/utils/dateutils.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/utils/expression.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/utils/fileio.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/utils/logging.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/writer.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/env.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/shef/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/shef/import_critfile.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/shef/import_infile.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/shef/shef_parameters.csv +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/dss/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/dss/cli.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/dss/naming.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/dss/transfer.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/README.md +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/__main__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/location/location.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/location/location_ids.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/location/location_ids_bygroup.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/root.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/timeseries/timeseries.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/timeseries/timeseries_data.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/load/timeseries/timeseries_ids.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/ownership.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/requirements.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/usgs/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/usgs/__main__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/usgs/getUSGS_ratings_cda.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/usgs/getusgs_measurements_cda.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/usgs/rating_ini_file_import.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/auth.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/callback_success.html +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/click_help.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/colors.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/env_store.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/friendly_errors.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/interaction.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/intervals.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/io.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/links.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/logging/__init__.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/logging/formatters.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/paths.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/ssl_errors.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/update.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/utils/version.py +0 -0
- {cwms_cli-0.8.1 → cwms_cli-0.9.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.9.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,6 +16,7 @@ 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
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
19
20
|
Provides-Extra: dss
|
|
20
21
|
Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
21
22
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
@@ -534,15 +534,13 @@ def update_cli_cmd(target_version: Optional[str], pre: bool, yes: bool) -> None:
|
|
|
534
534
|
@click.group(
|
|
535
535
|
"blob",
|
|
536
536
|
help="Manage CWMS Blobs (upload, download, delete, update, list)",
|
|
537
|
-
epilog=textwrap.dedent(
|
|
538
|
-
"""
|
|
537
|
+
epilog=textwrap.dedent("""
|
|
539
538
|
Example Usage:\n
|
|
540
539
|
- Store a PDF/image as a CWMS blob with optional description\n
|
|
541
540
|
- Download a blob by id to your local filesystem\n
|
|
542
541
|
- Update a blob's name/description/mime-type\n
|
|
543
542
|
- Bulk list blobs for an office
|
|
544
|
-
"""
|
|
545
|
-
),
|
|
543
|
+
"""),
|
|
546
544
|
)
|
|
547
545
|
def blob_group():
|
|
548
546
|
pass
|
|
@@ -755,14 +753,12 @@ def list_cmd(**kwargs):
|
|
|
755
753
|
@click.group(
|
|
756
754
|
"clob",
|
|
757
755
|
help="Manage CWMS Clobs (upload, download, delete, update, list)",
|
|
758
|
-
epilog=textwrap.dedent(
|
|
759
|
-
"""
|
|
756
|
+
epilog=textwrap.dedent("""
|
|
760
757
|
Example Usage:\n
|
|
761
758
|
- Download a clob by id to your local filesystem\n
|
|
762
759
|
- Update a clob's name/description/mime-type\n
|
|
763
760
|
- Bulk list clobs for an office
|
|
764
|
-
"""
|
|
765
|
-
),
|
|
761
|
+
"""),
|
|
766
762
|
)
|
|
767
763
|
@requires(reqs.cwms)
|
|
768
764
|
def clob_group():
|
|
@@ -1017,7 +1013,7 @@ def users_roles_list_user(user_name, office, api_root, api_key, api_key_loc):
|
|
|
1017
1013
|
multiple=True,
|
|
1018
1014
|
default=None,
|
|
1019
1015
|
callback=csv_to_list,
|
|
1020
|
-
help="enter admin, readonly, readwrite, or individual role name(s) to add. Repeat the option or pass a comma/pipe-separated list.",
|
|
1016
|
+
help="enter admin, readonly, readwrite, batchadmin, or individual role name(s) to add. Repeat the option or pass a comma/pipe-separated list.",
|
|
1021
1017
|
)
|
|
1022
1018
|
@requires(reqs.cwms)
|
|
1023
1019
|
def users_roles_add(office, api_root, api_key, api_key_loc, user_name, roles):
|
|
@@ -58,11 +58,45 @@ def _fetch_roles(cwms_module) -> list[str]:
|
|
|
58
58
|
_handle_api_error(error, cwms_module)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
# Role shortcut expansions used by `_expand_role_shortcuts` and
|
|
62
|
+
# referenced during validation so expanded roles are accepted even when the
|
|
63
|
+
# CWMS role catalog returned by the API does not include all shortcut values.
|
|
64
|
+
ROLE_SHORTCUTS: dict[str, list[str]] = {
|
|
65
|
+
"admin": [
|
|
66
|
+
"All Users",
|
|
67
|
+
"CWMS Users",
|
|
68
|
+
"TS ID Creator",
|
|
69
|
+
"CWMS User Admins",
|
|
70
|
+
"CWMS PD Users",
|
|
71
|
+
"Data Acquisition Mgr",
|
|
72
|
+
],
|
|
73
|
+
"readonly": ["All Users", "CWMS Users"],
|
|
74
|
+
"readwrite": ["All Users", "CWMS Users", "TS ID Creator"],
|
|
75
|
+
"batchadmin": [
|
|
76
|
+
"All Users",
|
|
77
|
+
"CWMS Users",
|
|
78
|
+
"TS ID Creator",
|
|
79
|
+
"Data Acquisition Mgr",
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
61
84
|
def _fetch_users(
|
|
62
85
|
cwms_module, office=None, username_like=None, page_size: int = 5000
|
|
63
86
|
) -> list[dict]:
|
|
64
87
|
users: list[dict] = []
|
|
65
88
|
|
|
89
|
+
# If a username_like filter was provided, ensure it is wrapped with
|
|
90
|
+
# leading and trailing wildcards so CDA receives a contains-style query.
|
|
91
|
+
if username_like:
|
|
92
|
+
_ul = str(username_like).strip()
|
|
93
|
+
if _ul:
|
|
94
|
+
if not _ul.startswith("*"):
|
|
95
|
+
_ul = "*" + _ul
|
|
96
|
+
if not _ul.endswith("*"):
|
|
97
|
+
_ul = _ul + "*"
|
|
98
|
+
username_like = _ul
|
|
99
|
+
|
|
66
100
|
try:
|
|
67
101
|
response = cwms_module.get_users(
|
|
68
102
|
office_id=office, username_like=username_like, page_size=page_size
|
|
@@ -135,19 +169,13 @@ def _split_roles(
|
|
|
135
169
|
|
|
136
170
|
|
|
137
171
|
def _expand_role_shortcuts(roles: list[str]) -> list[str]:
|
|
138
|
-
emap = {
|
|
139
|
-
"admin": ["All Users", "CWMS Users", "TS ID Creator", "CWMS User Admins"],
|
|
140
|
-
"readonly": ["All Users", "CWMS Users"],
|
|
141
|
-
"readwrite": ["All Users", "CWMS Users", "TS ID Creator"],
|
|
142
|
-
}
|
|
143
|
-
|
|
144
172
|
expanded_roles: list[str] = []
|
|
145
173
|
for role in roles:
|
|
146
174
|
key = role.strip().casefold()
|
|
147
175
|
if key == "all":
|
|
148
176
|
expanded_roles.append(role) # Keep "all" as is, handled elsewhere
|
|
149
|
-
elif key in
|
|
150
|
-
expanded_roles.extend(
|
|
177
|
+
elif key in ROLE_SHORTCUTS:
|
|
178
|
+
expanded_roles.extend(ROLE_SHORTCUTS[key])
|
|
151
179
|
else:
|
|
152
180
|
expanded_roles.append(role)
|
|
153
181
|
return expanded_roles
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import re
|
|
2
3
|
from datetime import datetime, timedelta
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
@@ -375,6 +376,35 @@ def CWMS_writeData(USGS_ts, USGS_data, USGS_data_method, days_back):
|
|
|
375
376
|
office = row["office-id"]
|
|
376
377
|
values["quality-code"] = 0
|
|
377
378
|
|
|
379
|
+
# Resample to TSID interval when needed.
|
|
380
|
+
values = resample_to_tsid_interval(values, ts_id)
|
|
381
|
+
|
|
382
|
+
# Drop rows with missing values produced by resampling
|
|
383
|
+
# (CWMS CDA rejects NaN/Na values in the values array).
|
|
384
|
+
try:
|
|
385
|
+
n_before = len(values)
|
|
386
|
+
values = values.dropna(subset=["value"]).copy()
|
|
387
|
+
n_after = len(values)
|
|
388
|
+
if n_after != n_before:
|
|
389
|
+
logging.info(
|
|
390
|
+
f"Dropped {n_before - n_after} rows with missing values for {ts_id} after resampling"
|
|
391
|
+
)
|
|
392
|
+
except Exception:
|
|
393
|
+
# If anything goes wrong, continue and let the store call fail
|
|
394
|
+
# so the caller sees the original error.
|
|
395
|
+
pass
|
|
396
|
+
|
|
397
|
+
# Ensure quality-code is numeric and has no NaN entries.
|
|
398
|
+
if "quality-code" in values.columns:
|
|
399
|
+
try:
|
|
400
|
+
values["quality-code"] = (
|
|
401
|
+
values["quality-code"].fillna(0).astype(int)
|
|
402
|
+
)
|
|
403
|
+
except Exception:
|
|
404
|
+
values["quality-code"] = values["quality-code"].fillna(
|
|
405
|
+
0
|
|
406
|
+
)
|
|
407
|
+
|
|
378
408
|
# write values to CWMS database
|
|
379
409
|
try:
|
|
380
410
|
data = cwms.timeseries_df_to_json(
|
|
@@ -422,3 +452,80 @@ def CWMS_writeData(USGS_ts, USGS_data, USGS_data_method, days_back):
|
|
|
422
452
|
logging.info(
|
|
423
453
|
f"The following ts_ids errored because multiple method TSID were present for the USGS station. A USGS method TSID needs to be defined in the time series group in CWMS or an incorrect TSID is defined. {mult_ids}"
|
|
424
454
|
)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def resample_to_tsid_interval(values: pd.DataFrame, ts_id: str) -> pd.DataFrame:
|
|
458
|
+
"""Resample the incoming values DataFrame to the interval encoded in ``ts_id``.
|
|
459
|
+
|
|
460
|
+
The DataFrame is expected to have columns ``date-time`` and ``value``. The
|
|
461
|
+
function returns a new DataFrame with the same columns. If the TSID encodes
|
|
462
|
+
an irregular interval (contains '~' or '0') the original DataFrame is
|
|
463
|
+
returned unchanged. Otherwise the function infers the incoming data
|
|
464
|
+
frequency and resamples only when it differs from the TSID interval.
|
|
465
|
+
"""
|
|
466
|
+
if values is None or values.empty:
|
|
467
|
+
return values
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
interval_segment = ts_id.split(".")[3]
|
|
471
|
+
except Exception:
|
|
472
|
+
return values
|
|
473
|
+
|
|
474
|
+
if "~" in interval_segment or interval_segment == "0":
|
|
475
|
+
return values
|
|
476
|
+
|
|
477
|
+
m = re.match(r"^(\d+)(.*)", interval_segment)
|
|
478
|
+
if m:
|
|
479
|
+
interval_number = int(m.group(1))
|
|
480
|
+
interval_type = m.group(2)
|
|
481
|
+
else:
|
|
482
|
+
return values
|
|
483
|
+
|
|
484
|
+
if interval_type == "Minutes":
|
|
485
|
+
target_str = f"{interval_number}min"
|
|
486
|
+
else:
|
|
487
|
+
# use lower-case single-letter codes (pandas prefers lower-case symbols)
|
|
488
|
+
target_str = f"{interval_number}{interval_type[0].lower()}"
|
|
489
|
+
|
|
490
|
+
df = values.copy()
|
|
491
|
+
# Coerce date-time to datetimes; drop rows without a valid timestamp because
|
|
492
|
+
# they cannot be resampled. Sort and deduplicate the index to ensure
|
|
493
|
+
# deterministic resampling behavior.
|
|
494
|
+
df["date-time"] = pd.to_datetime(df["date-time"], errors="coerce")
|
|
495
|
+
df = df.dropna(subset=["date-time"]).copy()
|
|
496
|
+
if df.empty:
|
|
497
|
+
return df
|
|
498
|
+
df = df.set_index("date-time")
|
|
499
|
+
df = df.sort_index()
|
|
500
|
+
# If duplicate timestamps exist, keep the first occurrence (matching prior
|
|
501
|
+
# behavior of using .first() during resample).
|
|
502
|
+
df = df[~df.index.duplicated(keep="first")]
|
|
503
|
+
|
|
504
|
+
current_delta = None
|
|
505
|
+
try:
|
|
506
|
+
inferred = pd.infer_freq(df.index)
|
|
507
|
+
if inferred:
|
|
508
|
+
current_delta = pd.to_timedelta(inferred)
|
|
509
|
+
except Exception:
|
|
510
|
+
current_delta = None
|
|
511
|
+
|
|
512
|
+
if current_delta is None:
|
|
513
|
+
diffs = df.index.to_series().diff().dropna()
|
|
514
|
+
if not diffs.empty:
|
|
515
|
+
current_delta = diffs.median()
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
target_delta = pd.to_timedelta(target_str)
|
|
519
|
+
except Exception:
|
|
520
|
+
target_delta = None
|
|
521
|
+
|
|
522
|
+
if target_delta is not None and (
|
|
523
|
+
current_delta is None or current_delta != target_delta
|
|
524
|
+
):
|
|
525
|
+
logging.info(
|
|
526
|
+
f"Resampling {ts_id}: from {current_delta} to {target_delta} (target={target_str})"
|
|
527
|
+
)
|
|
528
|
+
df_resampled = df.resample(target_str).first()
|
|
529
|
+
return df_resampled.reset_index()
|
|
530
|
+
|
|
531
|
+
return df.reset_index()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import importlib.metadata
|
|
3
3
|
import os
|
|
4
|
+
from typing import Callable
|
|
4
5
|
|
|
5
6
|
import click
|
|
6
7
|
|
|
@@ -57,6 +58,15 @@ def requires(*requirements):
|
|
|
57
58
|
missing = []
|
|
58
59
|
version_issues = []
|
|
59
60
|
|
|
61
|
+
# choose a version parsing function: prefer packaging, fallback to pkg_resources
|
|
62
|
+
try:
|
|
63
|
+
from packaging.version import parse as _parse_version
|
|
64
|
+
except Exception:
|
|
65
|
+
try:
|
|
66
|
+
from pkg_resources import parse_version as _parse_version
|
|
67
|
+
except Exception:
|
|
68
|
+
_parse_version = None
|
|
69
|
+
|
|
60
70
|
for req in requirements:
|
|
61
71
|
mod = req["module"]
|
|
62
72
|
pkg = req.get("package", mod)
|
|
@@ -78,12 +88,32 @@ def requires(*requirements):
|
|
|
78
88
|
if min_version:
|
|
79
89
|
try:
|
|
80
90
|
actual_version = importlib.metadata.version(pkg)
|
|
81
|
-
if
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
if _parse_version is not None:
|
|
92
|
+
try:
|
|
93
|
+
if _parse_version(actual_version) < _parse_version(
|
|
94
|
+
min_version
|
|
95
|
+
):
|
|
96
|
+
version_issues.append(
|
|
97
|
+
f"- python package `{pkg}` version `{actual_version}` found, "
|
|
98
|
+
f"but `{min_version}` or higher is required.\n\t"
|
|
99
|
+
f"Update the package to the required minimum version to use this command."
|
|
100
|
+
)
|
|
101
|
+
except Exception:
|
|
102
|
+
# Fall back to string comparison if parsing fails
|
|
103
|
+
if actual_version < min_version:
|
|
104
|
+
version_issues.append(
|
|
105
|
+
f"- python package `{pkg}` version `{actual_version}` found, "
|
|
106
|
+
f"but `{min_version}` or higher is required.\n\t"
|
|
107
|
+
f"Update the package to the required minimum version to use this command."
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
# No parser available — fall back to lexical comparison
|
|
111
|
+
if actual_version < min_version:
|
|
112
|
+
version_issues.append(
|
|
113
|
+
f"- python package `{pkg}` version `{actual_version}` found, "
|
|
114
|
+
f"but `{min_version}` or higher is required.\n\t"
|
|
115
|
+
f"Update the package to the required minimum version to use this command."
|
|
116
|
+
)
|
|
87
117
|
except importlib.metadata.PackageNotFoundError:
|
|
88
118
|
version_issues.append(
|
|
89
119
|
f"- `{pkg}` is installed but version could not be verified"
|
|
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
|
{cwms_cli-0.8.1 → cwms_cli-0.9.0}/cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cwms_cli-0.8.1 → cwms_cli-0.9.0}/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
|
|
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
|