cwms-cli 0.1.1__py3-none-any.whl

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 (41) hide show
  1. cwms_cli-0.1.1.dist-info/METADATA +40 -0
  2. cwms_cli-0.1.1.dist-info/RECORD +41 -0
  3. cwms_cli-0.1.1.dist-info/WHEEL +4 -0
  4. cwms_cli-0.1.1.dist-info/entry_points.txt +3 -0
  5. cwms_cli-0.1.1.dist-info/licenses/LICENSE +21 -0
  6. cwmscli/__init__.py +12 -0
  7. cwmscli/__main__.py +15 -0
  8. cwmscli/callbacks/__init__.py +18 -0
  9. cwmscli/commands/blob.py +439 -0
  10. cwmscli/commands/commands_cwms.py +227 -0
  11. cwmscli/commands/csv2cwms/.gitignore +3 -0
  12. cwmscli/commands/csv2cwms/README.md +51 -0
  13. cwmscli/commands/csv2cwms/__init__.py +5 -0
  14. cwmscli/commands/csv2cwms/__main__.py +265 -0
  15. cwmscli/commands/csv2cwms/examples/complete_config.json +19 -0
  16. cwmscli/commands/csv2cwms/examples/hourly.json +243 -0
  17. cwmscli/commands/csv2cwms/examples/minutes.json +315 -0
  18. cwmscli/commands/csv2cwms/tests/__init__.py +0 -0
  19. cwmscli/commands/csv2cwms/tests/data/.gitignore +1 -0
  20. cwmscli/commands/csv2cwms/tests/data/expected_brok_output.json +278 -0
  21. cwmscli/commands/csv2cwms/tests/data/sample_brok.csv +9 -0
  22. cwmscli/commands/csv2cwms/tests/data/sample_config.json +45 -0
  23. cwmscli/commands/csv2cwms/tests/skip_test_integration_pipeline.py +35 -0
  24. cwmscli/commands/csv2cwms/tests/test_dateutils.py +68 -0
  25. cwmscli/commands/csv2cwms/tests/test_expressions.py +49 -0
  26. cwmscli/commands/csv2cwms/tests/test_fileio.py +43 -0
  27. cwmscli/commands/csv2cwms/utils/__init__.py +5 -0
  28. cwmscli/commands/csv2cwms/utils/dateutils.py +105 -0
  29. cwmscli/commands/csv2cwms/utils/expression.py +39 -0
  30. cwmscli/commands/csv2cwms/utils/fileio.py +26 -0
  31. cwmscli/commands/csv2cwms/utils/logging.py +80 -0
  32. cwmscli/commands/csv2cwms/utils/terminal.py +45 -0
  33. cwmscli/commands/shef_critfile_import.py +146 -0
  34. cwmscli/requirements.py +25 -0
  35. cwmscli/usgs/__init__.py +161 -0
  36. cwmscli/usgs/getUSGS_ratings_cda.py +346 -0
  37. cwmscli/usgs/getusgs_cda.py +345 -0
  38. cwmscli/usgs/getusgs_measurements_cda.py +961 -0
  39. cwmscli/usgs/rating_ini_file_import.py +130 -0
  40. cwmscli/utils/__init__.py +68 -0
  41. cwmscli/utils/deps.py +102 -0
@@ -0,0 +1,227 @@
1
+ import click
2
+
3
+ from cwmscli import requirements as reqs
4
+ from cwmscli.callbacks import csv_to_list
5
+ from cwmscli.commands import csv2cwms
6
+ from cwmscli.utils import api_key_loc_option, common_api_options
7
+ from cwmscli.utils.deps import requires
8
+
9
+
10
+ @click.command(
11
+ "shefcritimport",
12
+ help="Import SHEF crit file into timeseries group for SHEF file processing",
13
+ )
14
+ @click.option(
15
+ "-f",
16
+ "--filename",
17
+ required=True,
18
+ type=str,
19
+ help="filename of SHEF crit file to be processed",
20
+ )
21
+ @common_api_options
22
+ @api_key_loc_option
23
+ @requires(reqs.cwms)
24
+ def shefcritimport(filename, office, api_root, api_key, api_key_loc):
25
+ from cwmscli.commands.shef_critfile_import import import_shef_critfile
26
+
27
+ api_key = get_api_key(api_key, api_key_loc)
28
+ import_shef_critfile(
29
+ file_path=filename,
30
+ office_id=office,
31
+ api_root=api_root,
32
+ api_key=api_key,
33
+ )
34
+
35
+
36
+ @click.command("csv2cwms", help="Store CSV TimeSeries data to CWMS using a config file")
37
+ @common_api_options
38
+ @click.option(
39
+ "--input-keys",
40
+ "input_keys",
41
+ default="all",
42
+ show_default=True,
43
+ help='Input keys. Defaults to all keys/files with --input-keys=all. These are the keys under "input_files" in a given config file. This option lets you run a single file from a config that contains multiple files. Example: --input-keys=file1',
44
+ )
45
+ @click.option(
46
+ "-lb",
47
+ "--lookback",
48
+ type=int,
49
+ default=24 * 5,
50
+ show_default=True,
51
+ help="Lookback period in HOURS",
52
+ )
53
+ @click.option("-v", "--verbose", is_flag=True, help="Verbose logging")
54
+ @click.option(
55
+ "-c",
56
+ "--config",
57
+ "config_path",
58
+ required=True,
59
+ type=click.Path(exists=True),
60
+ help="Path to JSON config file",
61
+ )
62
+ @click.option(
63
+ "-df",
64
+ "--data-file",
65
+ "data_file",
66
+ type=str,
67
+ help="Override CSV file (else use config)",
68
+ )
69
+ @click.option("--log", show_default=True, help="Path to the log file.")
70
+ @click.option("--dry-run", is_flag=True, help="Log only (no HTTP calls)")
71
+ @click.option("--begin", type=str, help="YYYY-MM-DDTHH:MM (local to --tz)")
72
+ @click.option("-tz", "--timezone", "tz", default="GMT", show_default=True)
73
+ @click.option(
74
+ "--ignore-ssl-errors", is_flag=True, help="Ignore TLS errors (testing only)"
75
+ )
76
+ @click.version_option(version=csv2cwms.__version__)
77
+ @requires(reqs.cwms)
78
+ def csv2cwms_cmd(**kwargs):
79
+ from cwmscli.commands.csv2cwms.__main__ import main as csv2_main
80
+
81
+ # Handle the version for this specific command
82
+ if kwargs.pop("version", False):
83
+ from cwmscli.commands.csv2cwms import __version__
84
+
85
+ click.echo(f"csv2cwms v{__version__}")
86
+ return
87
+ csv2_main(**kwargs)
88
+
89
+
90
+ # region Blob
91
+ # ================================================================================
92
+ # BLOB
93
+ # ================================================================================
94
+ @click.group(
95
+ "blob",
96
+ help="Manage CWMS Blobs (upload, download, delete, update, list)",
97
+ epilog="""
98
+ * Store a PDF/image as a CWMS blob with optional description
99
+ * Download a blob by id to your local filesystem
100
+ * Update a blob's name/description
101
+ * Bulk list blobs for an office
102
+ """,
103
+ )
104
+ @requires(reqs.cwms)
105
+ def blob_group():
106
+ pass
107
+
108
+
109
+ # ================================================================================
110
+ # Upload
111
+ # ================================================================================
112
+ @blob_group.command("upload", help="Upload a file as a blob")
113
+ @click.option(
114
+ "--input-file",
115
+ required=True,
116
+ type=click.Path(exists=True, dir_okay=False, readable=True, path_type=str),
117
+ help="Path to the file to upload.",
118
+ )
119
+ @click.option("--blob-id", required=True, type=str, help="Blob ID to create.")
120
+ @click.option("--description", default=None, help="Optional description JSON or text.")
121
+ @click.option(
122
+ "--media-type",
123
+ default=None,
124
+ help="Override media type (guessed from file if omitted).",
125
+ )
126
+ @click.option(
127
+ "--overwrite/--no-overwrite",
128
+ default=False,
129
+ show_default=True,
130
+ help="If true, replace existing blob.",
131
+ )
132
+ @click.option("--dry-run", is_flag=True, help="Show request; do not send.")
133
+ @common_api_options
134
+ def blob_upload(**kwargs):
135
+ from cwmscli.commands.blob import upload_cmd
136
+
137
+ upload_cmd(**kwargs)
138
+
139
+
140
+ # ================================================================================
141
+ # Download
142
+ # ================================================================================
143
+ @blob_group.command("download", help="Download a blob by ID")
144
+ # TODO: test XML
145
+ @click.option("--blob-id", required=True, type=str, help="Blob ID to download.")
146
+ @click.option(
147
+ "--dest",
148
+ default=None,
149
+ help="Destination file path. Defaults to blob-id.",
150
+ )
151
+ @common_api_options
152
+ def blob_download(**kwargs):
153
+ from cwmscli.commands.blob import download_cmd
154
+
155
+ download_cmd(**kwargs)
156
+
157
+
158
+ # ================================================================================
159
+ # Delete
160
+ # ================================================================================
161
+ @blob_group.command("delete", help="[Not implemented] Delete a blob by ID")
162
+ @click.option("--blob-id", required=True, type=str, help="Blob ID to delete.")
163
+ @common_api_options
164
+ def delete_cmd(**kwargs):
165
+ from cwmscli.commands.blob import delete_cmd
166
+
167
+ delete_cmd(**kwargs)
168
+
169
+
170
+ # ================================================================================
171
+ # Update
172
+ # ================================================================================
173
+ @blob_group.command("update", help="[Not implemented] Update/patch a blob by ID")
174
+ @click.option("--blob-id", required=True, type=str, help="Blob ID to update.")
175
+ @click.option(
176
+ "--input-file",
177
+ required=False,
178
+ type=click.Path(exists=True, dir_okay=False, readable=True, path_type=str),
179
+ help="Optional file content to upload with update.",
180
+ )
181
+ @common_api_options
182
+ def update_cmd(**kwargs):
183
+ from cwmscli.commands.blob import update_cmd
184
+
185
+ update_cmd(**kwargs)
186
+
187
+
188
+ # ================================================================================
189
+ # List
190
+ # ================================================================================
191
+ @blob_group.command("list", help="List blobs with optional filters and sorting")
192
+ # TODO: Add link to regex docs when new CWMS-DATA site is deployed to PROD
193
+ @click.option(
194
+ "--blob-id-like", help="LIKE filter for blob ID (e.g., ``*PNG``)."
195
+ ) # Escape the wildcard/asterisk for RTD generation with double backticks
196
+ @click.option(
197
+ "--columns",
198
+ multiple=True,
199
+ callback=csv_to_list,
200
+ help="Columns to show (repeat or comma-separate).",
201
+ )
202
+ @click.option(
203
+ "--sort-by",
204
+ multiple=True,
205
+ callback=csv_to_list,
206
+ help="Columns to sort by (repeat or comma-separate).",
207
+ )
208
+ @click.option(
209
+ "--desc/--asc",
210
+ default=False,
211
+ show_default=True,
212
+ help="Sort descending instead of ascending.",
213
+ )
214
+ @click.option("--limit", type=int, default=None, help="Max rows to show.")
215
+ @click.option(
216
+ "--to-csv",
217
+ type=click.Path(dir_okay=False, writable=True, path_type=str),
218
+ help="If set, write results to this CSV file.",
219
+ )
220
+ @common_api_options
221
+ def list_cmd(**kwargs):
222
+ from cwmscli.commands.blob import list_cmd
223
+
224
+ list_cmd(**kwargs)
225
+
226
+
227
+ # endregion
@@ -0,0 +1,3 @@
1
+ # CSV2CWMS Ignore Files
2
+ !timeseries_map.csv
3
+ *.log*
@@ -0,0 +1,51 @@
1
+ # CSV2CWMS
2
+
3
+ Writes CSV timeseries data to CDA using a configuration file.
4
+
5
+ To View the Help: `cwms-cli csv2cwms --help`
6
+
7
+ ## USAGE (--help)
8
+
9
+ Usage: cwms-cli csv2cwms [OPTIONS]
10
+
11
+ Store CSV TimeSeries data to CWMS using a config file
12
+
13
+ Options:
14
+ -o, --office TEXT Office to grab data for [required]
15
+ -a, --api_root TEXT Api Root for CDA. Can be user defined or placed
16
+ in a env variable CDA_API_ROOT [required]
17
+ -k, --api_key TEXT api key for CDA. Can be user defined or place in
18
+ env variable CDA_API_KEY. one of api_key or
19
+ api_key_loc are required
20
+ -l, --location TEXT Location ID. Use "-p=all" for all locations.
21
+ [default: all]
22
+ -lb, --lookback INTEGER Lookback period in HOURS [default: 120]
23
+ -v, --verbose Verbose logging
24
+ -c, --config PATH Path to JSON config file [required]
25
+ [default: all]
26
+ -lb, --lookback INTEGER Lookback period in HOURS [default: 120]
27
+ -v, --verbose Verbose logging
28
+ [default: all]
29
+ [default: all]
30
+ -lb, --lookback INTEGER Lookback period in HOURS [default: 120]
31
+ -v, --verbose Verbose logging
32
+ -c, --config PATH Path to JSON config file [required]
33
+ -df, --data-file TEXT Override CSV file (else use config)
34
+ --log TEXT Path to the log file.
35
+ -dp, --data-path DIRECTORY Directory where csv files are stored [default:
36
+ .]
37
+ --dry-run Log only (no HTTP calls)
38
+ --begin TEXT YYYY-MM-DDTHH:MM (local to --tz)
39
+ -tz, --timezone TEXT [default: GMT]
40
+ --ignore-ssl-errors Ignore TLS errors (testing only)
41
+ --version Show the version and exit.
42
+ --help Show this message and exit.
43
+
44
+ ## Features
45
+
46
+ - Allow for specifying one or more date formats that might be seen per input csv file
47
+ - Allow mathematical operations across multiple columns and storing into one timeseries
48
+ - Store one column of data with a user-specified precision and units to a timeseries identifier
49
+ - Dry runs to test what data might look like prior to database storage
50
+ - Verbose logging via the -v flag
51
+ - Colored terminal output for user readability
@@ -0,0 +1,5 @@
1
+ __version__ = "0.0.1"
2
+ __name__ = "csv2cwms"
3
+ __author__ = "Charles Graham - USACE"
4
+ __license__ = "MIT"
5
+ __description__ = "Parses Comma Separated Value (CSV) files and uses a config to store TimeSeries to CDA"
@@ -0,0 +1,265 @@
1
+ # Script Entry File
2
+ import os
3
+ import sys
4
+ import time
5
+ import traceback
6
+ from datetime import datetime, timedelta
7
+
8
+ import cwms
9
+
10
+ # Add the current directory to the path
11
+ # This is necessary for the script to be run as a standalone script
12
+ sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
13
+
14
+
15
+ # Handle imports for local and package use
16
+ # This is necessary for the script to be run as a package or as a standalone script
17
+ # The script can be run as a standalone script by running `python -m scada_ts` from the parent directory
18
+ # or as a package by running `python scada_ts` from the parent directory
19
+ try:
20
+ # Relative imports for modules
21
+ from . import __author__, __license__, __version__
22
+ from .utils import (
23
+ colorize,
24
+ colorize_count,
25
+ determine_interval,
26
+ eval_expression,
27
+ load_csv,
28
+ logger,
29
+ parse_date,
30
+ read_config,
31
+ safe_zoneinfo,
32
+ setup_logger,
33
+ )
34
+ except ImportError:
35
+ from __init__ import __author__, __license__, __version__
36
+ from utils import (
37
+ colorize,
38
+ colorize_count,
39
+ determine_interval,
40
+ eval_expression,
41
+ load_csv,
42
+ logger,
43
+ parse_date,
44
+ read_config,
45
+ safe_zoneinfo,
46
+ setup_logger,
47
+ )
48
+
49
+ # Load environment variables
50
+ API_KEY = os.getenv("CDA_API_KEY")
51
+ OFFICE = os.getenv("CDA_OFFICE", "SWT")
52
+ HOST = os.getenv("CDA_HOST")
53
+
54
+ if [API_KEY, OFFICE, HOST].count(None) > 0:
55
+ raise ValueError(
56
+ "Environment variables CDA_API_KEY, CDA_OFFICE, and CDA_HOST must be set."
57
+ )
58
+
59
+
60
+ def parse_file(file_path, begin_time, date_format, timezone="GMT"):
61
+ csv_data = load_csv(file_path)
62
+ header = csv_data[0]
63
+ data = csv_data[1:]
64
+ ts_data = {}
65
+ logger.debug(f"Begin time: {begin_time}")
66
+ for row in data:
67
+ # Skip empty rows or rows without a timestamp
68
+ if not row:
69
+ continue
70
+ row_datetime = parse_date(row[0], tz_str=timezone, date_format=date_format)
71
+ # Guarantee only one entry per timestamp
72
+ ts_data[int(row_datetime.timestamp())] = row
73
+ return {"header": header, "data": ts_data}
74
+
75
+
76
+ def load_timeseries(file_data, file_key, config):
77
+ header = file_data.get("header", [])
78
+ data = file_data.get("data", {})
79
+
80
+ if not header or not data:
81
+ raise ValueError(
82
+ "No data found in the CSV file for the range selected. Please ensure you set the timezone of the CSV file with --tz America/Chicago or similar."
83
+ )
84
+
85
+ ts_config = config["input_files"][file_key]["timeseries"]
86
+ file_ts = []
87
+
88
+ # Interval in seconds
89
+ interval = config.get("interval")
90
+ if not interval:
91
+ interval = determine_interval(data, 10)
92
+ logger.warning(
93
+ f"Interval not found in configuration. Determined interval: {interval} seconds."
94
+ )
95
+ start_epoch = min(data.keys())
96
+ end_epoch = max(data.keys())
97
+
98
+ # Map column names to indexes (case-insensitive)
99
+ header_map = {col.strip().lower(): i for i, col in enumerate(header)}
100
+ logger.debug(f"Header map (column name -> index): {header_map}")
101
+
102
+ for name, meta in ts_config.items():
103
+ expr = meta["columns"]
104
+ units = meta.get("units", "")
105
+ precision = meta.get("precision", 2)
106
+ values = []
107
+ epoch = start_epoch
108
+ while epoch <= end_epoch:
109
+ row = data.get(epoch)
110
+ if row:
111
+ value = eval_expression(expr, row, header_map)
112
+ value = round(value, precision) if value is not None else None
113
+ quality = 3 if value is not None else 5
114
+ else:
115
+ value = None
116
+ quality = 5
117
+ logger.debug(
118
+ f"[{name}] {datetime.fromtimestamp(epoch)} -> {value} (quality: {quality})"
119
+ )
120
+ values.append([epoch * 1000, value, quality])
121
+ # Convert seconds to minutes
122
+ epoch += interval
123
+
124
+ ts_obj = {"name": name, "units": units, "values": values}
125
+ valid = sum(1 for _, v, _ in values if v is not None)
126
+ total = len(values)
127
+ logger.info(
128
+ f"Built timeseries {colorize(name, 'blue')} with {colorize_count(valid, total)} valid points."
129
+ )
130
+ logger.debug(
131
+ f"Timeseries {name} data range: {colorize(datetime.fromtimestamp(start_epoch), 'blue')} to {colorize(datetime.fromtimestamp(end_epoch), 'blue')}"
132
+ )
133
+ file_ts.append(ts_obj)
134
+
135
+ return file_ts
136
+
137
+
138
+ def config_check(config):
139
+ """Checks a configuration file for required keys"""
140
+ if not config.get("interval"):
141
+ logger.warning(
142
+ "Configuration file does not contain an 'interval' key (and value in seconds), this is recommended per CSV file to avoid ambiguity."
143
+ )
144
+ if config.get("projects"):
145
+ logger.warning(
146
+ "Configuration file contains a 'projects' key, this has been renamed to 'input_files' for clarity. Continuing for backwards compatibility."
147
+ )
148
+ config["input_files"] = config.pop("projects")
149
+ if not config.get("input_files"):
150
+ raise ValueError("Configuration file must contain an 'input_files' key.")
151
+ for file_key, file_data in config.get("input_files").items():
152
+ # Only check the specified keys or if all keys are specified
153
+ if file_key != "all" and file_key != file_key.lower():
154
+ continue
155
+ if not file_data.get("timeseries"):
156
+ raise ValueError(
157
+ f"Configuration file must contain a 'timeseries' key for file '{file_key}'."
158
+ )
159
+ for ts_name, ts_data in file_data.get("timeseries").items():
160
+ if not ts_data.get("columns"):
161
+ raise ValueError(
162
+ f"Configuration file must contain a 'columns' key for timeseries '{ts_name}' in file '{file_key}'."
163
+ )
164
+
165
+
166
+ def main(*args, **kwargs):
167
+ """
168
+ Main function to execute the scada_ts script.
169
+ This function serves as the entry point for the script.
170
+ """
171
+ start_time = time.time()
172
+ tz = safe_zoneinfo(kwargs.get("tz"))
173
+ if kwargs.get("begin"):
174
+ try:
175
+ begin_time = datetime.strptime(
176
+ kwargs.get("begin"), "%Y-%m-%dT%H:%M"
177
+ ).replace(tzinfo=tz)
178
+ except ValueError:
179
+ raise ValueError("--begin must be in format YYYY-MM-DDTHH:MM")
180
+ else:
181
+ begin_time = datetime.now(tz)
182
+
183
+ cwms.api.init_session(
184
+ api_root=kwargs.get("api_root"), api_key=kwargs.get("api_key")
185
+ )
186
+ # Setup the logger if a path is provided
187
+ setup_logger(kwargs.get("log"), verbose=kwargs.get("verbose"))
188
+ logger.info(f"Begin time: {begin_time}")
189
+ logger.debug(f"Timezone: {tz}")
190
+ # Override environment variables if provided in CLI
191
+ if kwargs.get("coop"):
192
+ HOST = os.getenv("CDA_COOP_HOST")
193
+ if not HOST:
194
+ raise ValueError(
195
+ "Environment variable CDA_COOP_HOST must be set to use --coop flag."
196
+ )
197
+ config_path = kwargs.get("config_path")
198
+ config = read_config(config_path)
199
+ config_check(config)
200
+ INPUT_FILES = config.get("input_files", {})
201
+ # Override file names if one is specified in CLI
202
+ if kwargs.get("input_keys"):
203
+ if kwargs.get("input_keys") == "all":
204
+ INPUT_FILES = config.get("input_files", {}).keys()
205
+ else:
206
+ INPUT_FILES = kwargs.get("input_keys").split(",")
207
+ logger.info(f"Started for {','.join(INPUT_FILES)} input files.")
208
+ # Input checks
209
+ # if kwargs.get("file_name") != "all" and kwargs.get("file_name") not in INPUT_FILES:
210
+ # raise ValueError(
211
+ # f"Invalid file name '{kwargs.get("file_name")}'. Valid options are: {', '.join(INPUT_FILES)}"
212
+ # )
213
+
214
+ # Loop the file names and post the data
215
+ for file_name in INPUT_FILES:
216
+ # Grab the csv file path from the config
217
+ CONFIG_ITEM = config.get("input_files", {}).get(file_name, {})
218
+ DATA_FILE = CONFIG_ITEM.get("data_path", "")
219
+ if not DATA_FILE:
220
+ logger.warning(
221
+ # TODO: List URL to example in doc site once available
222
+ f"No data file specified for input-keys '{file_name}' in {config_path}. {colorize(f'Skipping {file_name}', 'red')}. Please provide a valid CSV file path by ensuring the 'data_path' key is set in the config."
223
+ )
224
+ continue
225
+ csv_data = parse_file(
226
+ DATA_FILE,
227
+ begin_time,
228
+ CONFIG_ITEM.get("date_format"),
229
+ kwargs.get("tz"),
230
+ )
231
+ try:
232
+ ts_min_data = load_timeseries(csv_data, file_name, config)
233
+ except ValueError as e:
234
+ logger.error(f"Error loading timeseries for {file_name}: {e}")
235
+ continue
236
+
237
+ if kwargs.get("dry_run"):
238
+ logger.info("DRY RUN enabled. No data will be posted")
239
+ for ts_object in ts_min_data:
240
+ try:
241
+ ts_object.update({"office-id": kwargs.get("office")})
242
+ logger.info(
243
+ "Store Rule: " + CONFIG_ITEM.get("store_rule", "")
244
+ if CONFIG_ITEM.get("store_rule", "")
245
+ else f"No Store Rule specified, will default to REPLACE_ALL in {config_path}."
246
+ )
247
+ if kwargs.get("dry_run"):
248
+ logger.info(f"DRY RUN: {ts_object}")
249
+ else:
250
+ cwms.store_timeseries(
251
+ data=ts_object,
252
+ store_rule=CONFIG_ITEM.get("store_rule", "REPLACE_ALL"),
253
+ )
254
+ logger.info(f"Stored {ts_object['name']} values")
255
+ except Exception as e:
256
+ logger.error(
257
+ f"Error posting data for {file_name}: {e}\n{traceback.format_exc()}"
258
+ )
259
+
260
+ logger.debug(f"\tExecution time: {round(time.time() - start_time, 3)} seconds.")
261
+ logger.debug(f"\tMemory usage: {round(os.sys.getsizeof(locals()) / 1024, 2)} KB")
262
+
263
+
264
+ if __name__ == "__main__":
265
+ main()
@@ -0,0 +1,19 @@
1
+ {
2
+ "interval": 3600,
3
+ "input_files": {
4
+ "BROK": {
5
+ "data_path": "cwmscli/commands/csv2cwms/tests/data/sample_brok.csv",
6
+ "date_format": [
7
+ "%m/%d/%Y %H:%M:%S",
8
+ "%m/%d/%Y %H:%M"
9
+ ],
10
+ "timeseries": {
11
+ "BROK.Elev.Inst.15Minutes.0.Rev-SCADA-cda": {
12
+ "columns": "Headwater",
13
+ "units": "ft",
14
+ "precision": 2
15
+ }
16
+ }
17
+ }
18
+ }
19
+ }