-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #185 from jarq6c/nwis-client-cli
Add `nwis_client` CLI
- Loading branch information
Showing
7 changed files
with
252 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = "3.1.0" | ||
__version__ = "3.2.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import click | ||
from hydrotools.nwis_client import IVDataService | ||
from hydrotools.nwis_client import _version as CLIENT_VERSION | ||
from typing import Tuple | ||
import pandas as pd | ||
from dataclasses import dataclass | ||
|
||
class TimestampParamType(click.ParamType): | ||
name = "timestamp" | ||
|
||
def convert(self, value, param, ctx): | ||
if isinstance(value, pd.Timestamp): | ||
return value | ||
|
||
try: | ||
return pd.Timestamp(value) | ||
except ValueError: | ||
self.fail(f"{value!r} is not a valid timestamp", param, ctx) | ||
|
||
@dataclass | ||
class CSVDefaults: | ||
comments: str = """# USGS IV Service Data | ||
# | ||
# value_date: Datetime of measurement (UTC) (character string) | ||
# variable: USGS variable name (character string) | ||
# usgs_site_code: USGS Gage Site Code (character string) | ||
# measurement_unit: Units of measurement (character string) | ||
# value: Measurement value (float) | ||
# qualifiers: Qualifier string (character string) | ||
# series: Series number in case multiple time series are returned (integer) | ||
# | ||
""" | ||
|
||
def write_to_csv( | ||
data: pd.DataFrame, | ||
ofile: click.File, | ||
comments: bool = True, | ||
header: bool = True | ||
) -> None: | ||
# Get default options | ||
defaults = CSVDefaults() | ||
|
||
# Comments | ||
if comments: | ||
output = defaults.comments | ||
|
||
# Add version, link, and write time | ||
now = pd.Timestamp.utcnow() | ||
output += f"# Generated at {now}\n" | ||
output += f"# nwis_client version: {CLIENT_VERSION.__version__}\n" | ||
output += "# Source code: https://github.com/NOAA-OWP/hydrotools\n# \n" | ||
|
||
# Write comments to file | ||
ofile.write(output) | ||
|
||
# Write data to file | ||
data.to_csv(ofile, mode="a", index=False, float_format="{:.2f}".format, header=header, chunksize=20000) | ||
|
||
@click.command() | ||
@click.argument("sites", nargs=-1, required=False) | ||
@click.option("-o", "--output", nargs=1, type=click.File("w"), help="Output file path", default="-") | ||
@click.option("-s", "--startDT", "startDT", nargs=1, type=TimestampParamType(), help="Start datetime") | ||
@click.option("-e", "--endDT", "endDT", nargs=1, type=TimestampParamType(), help="End datetime") | ||
@click.option("-p", "--parameterCd", "parameterCd", nargs=1, type=str, default="00060", help="Parameter code") | ||
@click.option('--comments/--no-comments', default=True, help="Enable/disable comments in output, enabled by default") | ||
@click.option('--header/--no-header', default=True, help="Enable/disable header in output, enabled by default") | ||
def run( | ||
sites: Tuple[str], | ||
output: click.File, | ||
startDT: pd.Timestamp = None, | ||
endDT: pd.Timestamp = None, | ||
parameterCd: str = "00060", | ||
comments: bool = True, | ||
header: bool = True | ||
) -> None: | ||
"""Retrieve data from the USGS IV Web Service API and write in CSV format. | ||
Example: | ||
nwis-client 01013500 02146470 | ||
""" | ||
# Get sites | ||
if not sites: | ||
print("Reading sites from stdin: ") | ||
sites = click.get_text_stream("stdin").read().split() | ||
|
||
# Setup client | ||
client = IVDataService(value_time_label="value_time") | ||
|
||
# Retrieve data | ||
df = client.get( | ||
sites=sites, | ||
startDT=startDT, | ||
endDT=endDT, | ||
parameterCd=parameterCd | ||
) | ||
|
||
# Write to CSV | ||
write_to_csv(data=df, ofile=output, comments=comments, header=header) | ||
|
||
if __name__ == "__main__": | ||
run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import pytest | ||
from hydrotools.nwis_client import cli | ||
from pathlib import Path | ||
import subprocess | ||
from tempfile import TemporaryDirectory | ||
|
||
def test_cli(): | ||
"""Normaly would use click.testing.CLiRunner. However, this does not appear to be async friendly.""" | ||
with TemporaryDirectory() as tdir: | ||
# Test default parameters | ||
result1 = subprocess.run([ | ||
"nwis-client", "01013500", "02146470", "-o", f"{tdir}/test_output_1.csv" | ||
]) | ||
assert result1.returncode == 0 | ||
assert Path(f"{tdir}/test_output_1.csv").exists() | ||
|
||
# Test other parameters | ||
result2 = subprocess.run([ | ||
"nwis-client", | ||
'-s', '2022-01-01', | ||
'-e', '2022-01-02', | ||
'-p', '00065', | ||
'01013500', '02146470', "-o", f"{tdir}/test_output_2.csv"]) | ||
assert result2.returncode == 0 | ||
assert Path(f"{tdir}/test_output_2.csv").exists() | ||
|
||
def test_comments_header(): | ||
"""Normaly would use click.testing.CLiRunner. However, this does not appear to be async friendly.""" | ||
with TemporaryDirectory() as tdir: | ||
# Output file | ||
ofile = Path(tdir) / "test_output.csv" | ||
|
||
# Disable comments and header | ||
result2 = subprocess.run([ | ||
"nwis-client", | ||
'--no-comments', '--no-header', | ||
'01013500', '02146470', "-o", str(ofile)]) | ||
assert result2.returncode == 0 | ||
assert ofile.exists() | ||
|
||
# File should only have two lines | ||
with ofile.open('r') as fi: | ||
count = len([l for l in fi]) | ||
assert count == 2 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters