Skip to content

Commit 6f092a9

Browse files
Merge pull request #74 from LouisPauchet/main
Adding a CLI for metocean-api to be used directly in command line
2 parents a34b0a8 + 0829a90 commit 6f092a9

File tree

4 files changed

+220
-3
lines changed

4 files changed

+220
-3
lines changed

docs/index.rst

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
contain the root `toctree` directive.
55
66
Welcome to metocean-api's documentation!
7-
=====================================
7+
========================================
88

99
**metocean-api** is a Python tool designed to extract time series of metocean (meteorological and oceanographic) data from various sources, including global, regional, and coastal hindcasts and reanalysis.
1010
The extracted data can be saved in CSV format or NetCDF for further analysis and usage.
@@ -271,6 +271,65 @@ To combine several csv-files produced by **metocean-api**:
271271
'NORA3_atm_sub_lon1.32_lat53.324_20210101_20210331.csv'],
272272
output_file='combined_NORA3_lon1.32_lat53.324.csv')
273273
274+
275+
276+
Command Line Interface (CLI) - metocean-cli
277+
===========================================
278+
279+
**Download Command**
280+
281+
282+
The ``download`` command is used to download metocean data for a specified product, location, and time range.
283+
284+
By default, we are using the existing cached data, if you want to ignore the cached data (product change, redownload...) please use ``--no_cache``
285+
286+
**Usage:**
287+
288+
.. code-block:: bash
289+
290+
metocean-cli download <product> <lat> <lon> <start_time> <stop_time> <file_format> [--no_cache] [--max_retry MAX_RETRY] [-o OUTPUT]
291+
292+
**Arguments:**
293+
294+
- ``<product>``: Name of the product to download.
295+
- ``<lat>``: Latitude of the location.
296+
- ``<lon>``: Longitude of the location.
297+
- ``<start_time>``: Start time for data in ISO 8601 format (e.g., ``"2023-01-01"``).
298+
- ``<stop_time>``: Stop time for data in ISO 8601 format (e.g., ``"2023-01-02"``).
299+
- ``<file_format>``: Format of the output file: ``"csv"``, ``"netcdf"``, or ``"both"``.
300+
- ``--no_cache``: Optional flag to removed the cached data at the end of the processing.
301+
- ``--max_retry MAX_RETRY``: Optional argument to specify the maximum number of retry attempts for the download. Default is ``5``.
302+
- ``-o OUTPUT``, ``--output OUTPUT``: Path to the output file where the downloaded data will be saved.
303+
304+
**Example:**
305+
306+
.. code-block:: bash
307+
308+
metocean-cli download NORA3_offshore_wind 54.01 6.58 "2023-01-01" "2023-01-02" csv
309+
310+
**Combine Command**
311+
312+
The ``combine`` command is used to combine multiple metocean data files into a single output file.
313+
314+
**Usage:**
315+
316+
.. code-block:: bash
317+
318+
metocean-cli combine <files>... -o OUTPUT
319+
320+
**Arguments:**
321+
322+
- ``<files>...``: List of file paths to combine. You can specify multiple files separated by spaces.
323+
- ``-o OUTPUT``, ``--output OUTPUT``: Path to the output file where the combined data will be saved.
324+
325+
**Example:**
326+
327+
.. code-block:: bash
328+
329+
metocean-cli combine 'NORA3_wind_sub_lon1.32_lat53.324_20210101_20210131.csv' 'NORA3_atm_sub_lon1.32_lat53.324_20210101_20210331.csv' -o combined.csv
330+
331+
332+
274333
.. toctree::
275334
:maxdepth: 2
276335
:caption: Contents:

metocean_api/cli/metocean_cli.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import argparse
2+
import sys
3+
import time
4+
import os
5+
from typing import Optional, List
6+
7+
from metocean_api import ts
8+
9+
class FileNotCreatedError(Exception):
10+
"""Exception raised when a file is not created successfully."""
11+
def __init__(self, message="The file was not created successfully."):
12+
self.message = message
13+
super().__init__(self.message)
14+
15+
def download(product: str, lat: float, lon: float, start_time: str, stop_time: str,
16+
file_format: str, use_cache: bool = True, max_retry: int = 5,
17+
output_file: Optional[str] = None) -> None:
18+
"""
19+
Download metocean data for a specified product, location, and time range.
20+
21+
Args:
22+
product (str): The product to download.
23+
lat (float): Latitude of the location.
24+
lon (float): Longitude of the location.
25+
start_time (str): Start time in ISO 8601 format.
26+
stop_time (str): Stop time in ISO 8601 format.
27+
file_format (str): File format to download ('csv', 'netcdf', or 'both').
28+
use_cache (bool): Whether to use cached data. Defaults to True.
29+
max_retry (int): Maximum number of retry attempts. Defaults to 5.
30+
output_file (Optional[str]): Path to the output file. Required for execution.
31+
"""
32+
print(f"Downloading {product} data from {start_time} to {stop_time} at ({lat}, {lon}) in {file_format} format.")
33+
print(f"Use cache: {use_cache}, Max retry: {max_retry}")
34+
35+
file_format_options = {
36+
'save_csv': False,
37+
'save_nc': False
38+
}
39+
40+
if file_format == 'csv':
41+
file_format_options['save_csv'] = True
42+
elif file_format == 'netcdf':
43+
file_format_options['save_nc'] = True
44+
elif file_format == 'both':
45+
file_format_options['save_csv'] = True
46+
file_format_options['save_nc'] = True
47+
48+
retry_count = 0
49+
success = False
50+
51+
while retry_count < max_retry and not success:
52+
try:
53+
# Attempt to create TimeSeries object and import data
54+
df_ts = ts.TimeSeries(lon=lon, lat=lat,
55+
start_time=start_time, end_time=stop_time,
56+
product=product, datafile=output_file) # type: ignore
57+
58+
output_file = df_ts.datafile
59+
60+
df_ts.import_data(**file_format_options, use_cache=use_cache)
61+
success = True
62+
63+
except Exception as e:
64+
print(f"An error occurred: {e}")
65+
retry_count += 1
66+
print(f"Retrying... Attempt {retry_count}/{max_retry}")
67+
time.sleep(5) # Wait for a moment before retrying
68+
69+
if not success:
70+
print("Failed to download data after several attempts.")
71+
else:
72+
# Verify if the output file was created
73+
if os.path.exists(output_file): # type: ignore
74+
print(f"Data successfully downloaded to {output_file}")
75+
else:
76+
print("Download process completed but output file was not created.")
77+
78+
def combine(files: List[str], output_file: str) -> None:
79+
"""
80+
Combine multiple files into a single output file.
81+
82+
Args:
83+
files (List[str]): List of file paths to combine.
84+
output_file (str): Path to the output file.
85+
"""
86+
print(f"Combining files: {files} into {output_file}")
87+
df = ts.ts_mod.combine_data(list_files=files, output_file=output_file)
88+
89+
def main():
90+
parser = argparse.ArgumentParser(
91+
prog=f'metocean-cli',
92+
description='Metocean-api CLI: A command-line tool to extract time series of metocean data from global/regional/coastal hindcasts/reanalysis',
93+
epilog='''
94+
MET-OM/metocean-api Extract time series of metocean data from global/regional/coastal hindcasts/reanalysis
95+
Copyright (C) <year> KonstantinChri
96+
97+
This library is free software; you can redistribute it and/or
98+
modify it under the terms of the GNU Lesser General Public
99+
License as published by the Free Software Foundation; either
100+
version 2.1 of the License, or (at your option) any later version.
101+
102+
This library is distributed in the hope that it will be useful,
103+
but WITHOUT ANY WARRANTY; without even the implied warranty of
104+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
105+
Lesser General Public License for more details.
106+
107+
You should have received a copy of the GNU Lesser General Public
108+
License along with this library; if not, write to the Free Software
109+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
110+
USA
111+
''',
112+
formatter_class=argparse.RawDescriptionHelpFormatter
113+
)
114+
115+
subparsers = parser.add_subparsers(dest='command', help='Sub-command help')
116+
117+
# Download command
118+
download_parser = subparsers.add_parser('download', help='Download metocean time series data')
119+
download_parser.add_argument('product', type=str, help='Name of the product to download. The list of the product is available at https://metocean-api.readthedocs.io/en/latest/#available-datasets-in-metocean-api')
120+
download_parser.add_argument('lat', type=float, help='Latitude of the location')
121+
download_parser.add_argument('lon', type=float, help='Longitude of the location')
122+
download_parser.add_argument('start_time', type=str, help='Start time for data in ISO 8601 format')
123+
download_parser.add_argument('stop_time', type=str, help='Stop time for data in ISO 8601 format')
124+
download_parser.add_argument('file_format', type=str, choices=['csv', 'netcdf', 'both'],
125+
help='Format of the output file: "csv", "netcdf", or "both"')
126+
download_parser.add_argument('--no_cache', default=False,
127+
help='Clear cached data at the end of the processing - not recommanded in case of faillure or large dataset')
128+
download_parser.add_argument('--max_retry', type=int, default=5,
129+
help='Maximum number of retry attempts for the download')
130+
download_parser.add_argument('-o', '--output', type=str, default=None, required=False,
131+
help='Path to the output file')
132+
133+
# Combine command
134+
combine_parser = subparsers.add_parser('combine', help='Combine multiple metocean data files generated using metocean-api')
135+
combine_parser.add_argument('files', type=str, nargs='+',
136+
help='List of file paths to combine')
137+
combine_parser.add_argument('-o', '--output', type=str, required=True,
138+
help='Path to the output combined file')
139+
140+
args = parser.parse_args()
141+
142+
if args.command == 'download':
143+
download(args.product, args.lat, args.lon, args.start_time, args.stop_time,
144+
args.file_format, not args.no_cache, args.max_retry, args.output)
145+
elif args.command == 'combine':
146+
combine(args.files, args.output)
147+
else:
148+
parser.print_help()
149+
150+
if __name__ == '__main__':
151+
main()

metocean_api/ts/ts_mod.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import numpy as np
44
from .internal import products
55
from .internal.aux_funcs import read_commented_lines
6+
from typing import Optional, List
67

7-
def combine_data(list_files, output_file=False):
8+
9+
def combine_data(list_files: List[str], output_file: Optional[str] = None):
810
for i in range(len(list_files)):
911
df = pd.read_csv(list_files[i], comment="#", index_col=0, parse_dates=True)
1012
top_header = read_commented_lines(list_files[i])

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030
include_package_data = True,
3131
setup_requires = ['setuptools_scm'],
3232
tests_require = ['pytest','pytest-cov'],
33-
scripts = []
33+
scripts = [],
34+
entry_points={
35+
'console_scripts': [
36+
'metocean-cli=metocean_api.cli.metocean_cli:main',
37+
],
38+
},
3439
)
3540

3641

0 commit comments

Comments
 (0)