imap-data-access 0.15.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.
- imap_data_access/__init__.py +99 -0
- imap_data_access/cli.py +392 -0
- imap_data_access/file_validation.py +682 -0
- imap_data_access/io.py +279 -0
- imap_data_access/processing_input.py +386 -0
- imap_data_access-0.15.1.dist-info/METADATA +252 -0
- imap_data_access-0.15.1.dist-info/RECORD +11 -0
- imap_data_access-0.15.1.dist-info/WHEEL +5 -0
- imap_data_access-0.15.1.dist-info/entry_points.txt +2 -0
- imap_data_access-0.15.1.dist-info/licenses/LICENSE +21 -0
- imap_data_access-0.15.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Data Access for the IMAP Mission.
|
|
2
|
+
|
|
3
|
+
The Interstellar Mapping and Acceleration Probe (IMAP) is a NASA mission to study the
|
|
4
|
+
heliosphere. This package contains the data access tools for the IMAP mission. It
|
|
5
|
+
provides a convenient way to query the IMAP data archive and download data files.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from imap_data_access.file_validation import (
|
|
12
|
+
AncillaryFilePath,
|
|
13
|
+
ImapFilePath,
|
|
14
|
+
ScienceFilePath,
|
|
15
|
+
SPICEFilePath,
|
|
16
|
+
)
|
|
17
|
+
from imap_data_access.io import download, query, upload
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"query",
|
|
21
|
+
"download",
|
|
22
|
+
"upload",
|
|
23
|
+
"ImapFilePath",
|
|
24
|
+
"ScienceFilePath",
|
|
25
|
+
"SPICEFilePath",
|
|
26
|
+
"AncillaryFilePath",
|
|
27
|
+
"VALID_INSTRUMENTS",
|
|
28
|
+
"VALID_DATALEVELS",
|
|
29
|
+
"VALID_FILE_EXTENSION",
|
|
30
|
+
"FILENAME_CONVENTION",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
__version__ = "0.15.1"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
config = {
|
|
37
|
+
"DATA_ACCESS_URL": os.getenv("IMAP_DATA_ACCESS_URL")
|
|
38
|
+
or "https://api.dev.imap-mission.com",
|
|
39
|
+
"DATA_DIR": Path(os.getenv("IMAP_DATA_DIR") or Path.cwd() / "data"),
|
|
40
|
+
"API_KEY": os.getenv("IMAP_API_KEY"),
|
|
41
|
+
}
|
|
42
|
+
"""Settings configuration dictionary.
|
|
43
|
+
|
|
44
|
+
DATA_ACCESS_URL : This is the URL of the data access API.
|
|
45
|
+
DATA_DIR : This is where the file data is stored and organized by instrument and level.
|
|
46
|
+
The default location is a 'data/' folder in the current working directory,
|
|
47
|
+
"but this can be set on the command line using the --data-dir option, or through
|
|
48
|
+
the environment variable IMAP_DATA_DIR.
|
|
49
|
+
API_KEY : This is the API key used to authenticate with the data access API.
|
|
50
|
+
It can be set on the command line using the --api-key option, or through the
|
|
51
|
+
environment variable IMAP_API_KEY. It is only necessary for uploading files.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
VALID_INSTRUMENTS = {
|
|
56
|
+
"codice",
|
|
57
|
+
"glows",
|
|
58
|
+
"hit",
|
|
59
|
+
"hi",
|
|
60
|
+
"idex",
|
|
61
|
+
"lo",
|
|
62
|
+
"mag",
|
|
63
|
+
"swapi",
|
|
64
|
+
"swe",
|
|
65
|
+
"ultra",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
VALID_DATALEVELS = {
|
|
69
|
+
"l0",
|
|
70
|
+
"l1",
|
|
71
|
+
"l1a",
|
|
72
|
+
"l1b",
|
|
73
|
+
"l1c",
|
|
74
|
+
"l1ca",
|
|
75
|
+
"l1cb",
|
|
76
|
+
"l1d",
|
|
77
|
+
"l2",
|
|
78
|
+
"l2a",
|
|
79
|
+
"l2b",
|
|
80
|
+
"l3",
|
|
81
|
+
"l3a",
|
|
82
|
+
"l3b",
|
|
83
|
+
"l3c",
|
|
84
|
+
"l3d",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
VALID_FILE_EXTENSION = {"pkts", "cdf"}
|
|
88
|
+
|
|
89
|
+
FILENAME_CONVENTION = (
|
|
90
|
+
"<mission>_<instrument>_<datalevel>_<descriptor>_"
|
|
91
|
+
"<startdate>(-<repointing>)_<version>.<extension>"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
ANCILLARY_FILENAME_CONVENTION = (
|
|
95
|
+
"<mission>_<instrument>_<description>_"
|
|
96
|
+
"<start_date>(_<end_date>)_<version>.<extension>"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
VALID_ANCILLARY_FILE_EXTENSION = {"cdf", "csv", "json"}
|
imap_data_access/cli.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Command line interface to the IMAP Data Access API.
|
|
4
|
+
|
|
5
|
+
This module serves as a command line utility to invoke the IMAP Data Access API.
|
|
6
|
+
It provides the ability to interact with the Science Data Center (SDC)
|
|
7
|
+
by querying, downloading, and uploading files to the data center.
|
|
8
|
+
|
|
9
|
+
Use
|
|
10
|
+
---
|
|
11
|
+
imap-data-access <command> [<args>]
|
|
12
|
+
imap-data-access --help
|
|
13
|
+
imap-data-access download <file_path>
|
|
14
|
+
imap-data-access query <query-parameters>
|
|
15
|
+
imap-data-access upload <file_path>
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import imap_data_access
|
|
24
|
+
from imap_data_access.file_validation import ScienceFilePath
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _download_parser(args: argparse.Namespace):
|
|
28
|
+
"""Download a file from the IMAP SDC.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
args : argparse.Namespace
|
|
33
|
+
An object containing the parsed arguments and their values
|
|
34
|
+
"""
|
|
35
|
+
output_path = imap_data_access.download(args.file_path)
|
|
36
|
+
print(f"Successfully downloaded the file to: {output_path}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _print_query_results_table(query_results: list[dict]):
|
|
40
|
+
"""Print the query results in a table.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
query_results : list
|
|
45
|
+
A list of dictionaries containing the query results
|
|
46
|
+
"""
|
|
47
|
+
num_files = len(query_results)
|
|
48
|
+
print(f"Found [{num_files}] matching files")
|
|
49
|
+
if num_files == 0:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Use the keys of the first item in query_results for the header
|
|
53
|
+
headers = [
|
|
54
|
+
"Instrument",
|
|
55
|
+
"Data Level",
|
|
56
|
+
"Descriptor",
|
|
57
|
+
"Start Date",
|
|
58
|
+
"Repointing",
|
|
59
|
+
"Version",
|
|
60
|
+
"Filename",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Calculate the maximum width for each column based on the header and the data
|
|
64
|
+
column_widths = {}
|
|
65
|
+
for header in headers[:-1]:
|
|
66
|
+
column_widths[header] = max(
|
|
67
|
+
len(header),
|
|
68
|
+
*(len(str(item.get(header.lower(), ""))) for item in query_results),
|
|
69
|
+
)
|
|
70
|
+
# Calculate the maximum width for each column based on the header and the data
|
|
71
|
+
|
|
72
|
+
column_widths["Filename"] = max(
|
|
73
|
+
len("Filename"),
|
|
74
|
+
*(
|
|
75
|
+
len(os.path.basename(item.get("file_path", "")))
|
|
76
|
+
for item in query_results
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Create the format string dynamically based on the number of columns
|
|
81
|
+
format_string = (
|
|
82
|
+
"| "
|
|
83
|
+
+ " | ".join([f"{{:<{column_widths[header]}}}" for header in headers])
|
|
84
|
+
+ " |"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Add hyphens for a separator between header and data
|
|
88
|
+
hyphens = "|" + "-" * (sum(column_widths.values()) + 3 * len(headers) - 1) + "|"
|
|
89
|
+
print(hyphens)
|
|
90
|
+
|
|
91
|
+
# Print header
|
|
92
|
+
print(format_string.format(*headers))
|
|
93
|
+
print(hyphens)
|
|
94
|
+
|
|
95
|
+
# Print data
|
|
96
|
+
for item in query_results:
|
|
97
|
+
values = [
|
|
98
|
+
str(item.get("instrument", "")),
|
|
99
|
+
str(item.get("data_level", "")),
|
|
100
|
+
str(item.get("descriptor", "")),
|
|
101
|
+
str(item.get("start_date", "")),
|
|
102
|
+
str(item.get("repointing", "")) or "",
|
|
103
|
+
str(item.get("version", "")),
|
|
104
|
+
os.path.basename(item.get("file_path", "")),
|
|
105
|
+
]
|
|
106
|
+
print(format_string.format(*values))
|
|
107
|
+
|
|
108
|
+
# Close the table
|
|
109
|
+
print(hyphens)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _query_parser(args: argparse.Namespace):
|
|
113
|
+
"""Query the IMAP SDC.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
args : argparse.Namespace
|
|
118
|
+
An object containing the parsed arguments and their values
|
|
119
|
+
"""
|
|
120
|
+
# Filter to get the arguments of interest from the namespace
|
|
121
|
+
valid_args = [
|
|
122
|
+
"instrument",
|
|
123
|
+
"data_level",
|
|
124
|
+
"descriptor",
|
|
125
|
+
"start_date",
|
|
126
|
+
"end_date",
|
|
127
|
+
"repointing",
|
|
128
|
+
"version",
|
|
129
|
+
"extension",
|
|
130
|
+
"filename",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
query_params = {
|
|
134
|
+
key: value
|
|
135
|
+
for key, value in vars(args).items()
|
|
136
|
+
if key in valid_args and value is not None
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Checking to see if a filename was passed.
|
|
140
|
+
if args.filename is not None:
|
|
141
|
+
del query_params["filename"]
|
|
142
|
+
if query_params:
|
|
143
|
+
raise TypeError("Too many arguments, '--filename' should be ran by itself")
|
|
144
|
+
|
|
145
|
+
file_path = ScienceFilePath(args.filename)
|
|
146
|
+
query_params = {
|
|
147
|
+
"instrument": file_path.instrument,
|
|
148
|
+
"data_level": file_path.data_level,
|
|
149
|
+
"descriptor": file_path.descriptor,
|
|
150
|
+
"start_date": file_path.start_date,
|
|
151
|
+
"repointing": file_path.repointing,
|
|
152
|
+
"version": file_path.version,
|
|
153
|
+
"extension": file_path.extension,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
query_results = imap_data_access.query(**query_params)
|
|
157
|
+
|
|
158
|
+
if args.output_format == "table":
|
|
159
|
+
_print_query_results_table(query_results)
|
|
160
|
+
elif args.output_format == "json":
|
|
161
|
+
# Dump the content directly
|
|
162
|
+
print(query_results)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _upload_parser(args: argparse.Namespace):
|
|
166
|
+
"""Upload a file to the IMAP SDC.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
args : argparse.Namespace
|
|
171
|
+
An object containing the parsed arguments and their values
|
|
172
|
+
"""
|
|
173
|
+
imap_data_access.upload(args.file_path)
|
|
174
|
+
print("Successfully uploaded the file to the IMAP SDC")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# PLR0915: too many statements
|
|
178
|
+
def main(): # noqa: PLR0915
|
|
179
|
+
"""Parse the command line arguments.
|
|
180
|
+
|
|
181
|
+
Run the command line interface to the IMAP Data Access API.
|
|
182
|
+
"""
|
|
183
|
+
api_key_help = (
|
|
184
|
+
"API key to authenticate with the IMAP SDC. "
|
|
185
|
+
"This can also be set using the IMAP_API_KEY environment variable. "
|
|
186
|
+
"It is only necessary for uploading files."
|
|
187
|
+
)
|
|
188
|
+
data_dir_help = (
|
|
189
|
+
"Directory to use for reading and writing IMAP data. "
|
|
190
|
+
"The default is a 'data/' folder in the "
|
|
191
|
+
"current working directory. This can also be "
|
|
192
|
+
"set using the IMAP_DATA_DIR environment variable."
|
|
193
|
+
)
|
|
194
|
+
description = (
|
|
195
|
+
"This command line program accesses the IMAP SDC APIs to query, download, "
|
|
196
|
+
"and upload data files."
|
|
197
|
+
)
|
|
198
|
+
download_help = (
|
|
199
|
+
"Download a file from the IMAP SDC to the locally configured data directory. "
|
|
200
|
+
"Run 'download -h' for more information. "
|
|
201
|
+
)
|
|
202
|
+
help_menu_for_download = (
|
|
203
|
+
"Download a file from the IMAP SDC to the locally configured data directory. "
|
|
204
|
+
)
|
|
205
|
+
file_path_help = (
|
|
206
|
+
"This must be the full path to the file."
|
|
207
|
+
"\nE.g. imap/mag/l0/2025/01/imap_mag_l0_raw_20250101_v001.pkts"
|
|
208
|
+
)
|
|
209
|
+
query_help = (
|
|
210
|
+
"Query the IMAP SDC for files matching the query parameters. "
|
|
211
|
+
"The query parameters are optional, but at least one must be provided. "
|
|
212
|
+
"Run 'query -h' for more information."
|
|
213
|
+
)
|
|
214
|
+
help_menu_for_query = (
|
|
215
|
+
"Query the IMAP SDC for files matching the query parameters. "
|
|
216
|
+
"The query parameters are optional, but at least one must be provided. "
|
|
217
|
+
)
|
|
218
|
+
upload_help = (
|
|
219
|
+
"Upload a file to the IMAP SDC. This must be the full path to the file."
|
|
220
|
+
"\nE.g. imap/mag/l0/2025/01/imap_mag_l0_raw_20250101_v001.pkts. "
|
|
221
|
+
"Run 'upload -h' for more information."
|
|
222
|
+
)
|
|
223
|
+
help_menu_for_upload = (
|
|
224
|
+
"Upload a file to the IMAP SDC. This must be the full path to the file."
|
|
225
|
+
"\nE.g. imap/mag/l0/2025/01/imap_mag_l0_raw_20250101_v001.pkts. "
|
|
226
|
+
)
|
|
227
|
+
url_help = (
|
|
228
|
+
"URL of the IMAP SDC API. "
|
|
229
|
+
"The default is https://api.dev.imap-mission.com. This can also be "
|
|
230
|
+
"set using the IMAP_DATA_ACCESS_URL environment variable."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
parser = argparse.ArgumentParser(prog="imap-data-access", description=description)
|
|
234
|
+
parser.add_argument(
|
|
235
|
+
"--version",
|
|
236
|
+
action="version",
|
|
237
|
+
version=f"%(prog)s {imap_data_access.__version__}",
|
|
238
|
+
help="Show programs version number and exit. No other parameters needed.",
|
|
239
|
+
)
|
|
240
|
+
parser.add_argument("--api-key", type=str, required=False, help=api_key_help)
|
|
241
|
+
parser.add_argument("--data-dir", type=Path, required=False, help=data_dir_help)
|
|
242
|
+
parser.add_argument("--url", type=str, required=False, help=url_help)
|
|
243
|
+
# Logging level
|
|
244
|
+
parser.add_argument(
|
|
245
|
+
"--debug",
|
|
246
|
+
help="Print lots of debugging statements.",
|
|
247
|
+
action="store_const",
|
|
248
|
+
dest="loglevel",
|
|
249
|
+
const=logging.DEBUG,
|
|
250
|
+
default=logging.WARNING,
|
|
251
|
+
)
|
|
252
|
+
parser.add_argument(
|
|
253
|
+
"-v",
|
|
254
|
+
"--verbose",
|
|
255
|
+
help="Add verbose output",
|
|
256
|
+
action="store_const",
|
|
257
|
+
dest="loglevel",
|
|
258
|
+
const=logging.INFO,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Download command
|
|
262
|
+
subparsers = parser.add_subparsers(required=True)
|
|
263
|
+
parser_download = subparsers.add_parser(
|
|
264
|
+
"download", help=download_help, description=help_menu_for_download
|
|
265
|
+
)
|
|
266
|
+
parser_download.add_argument("file_path", type=Path, help=file_path_help)
|
|
267
|
+
parser_download.set_defaults(func=_download_parser)
|
|
268
|
+
|
|
269
|
+
# Query command (with optional arguments)
|
|
270
|
+
query_parser = subparsers.add_parser(
|
|
271
|
+
"query", help=query_help, description=help_menu_for_query
|
|
272
|
+
)
|
|
273
|
+
query_parser.add_argument(
|
|
274
|
+
"--instrument",
|
|
275
|
+
type=str,
|
|
276
|
+
required=False,
|
|
277
|
+
help="Name of the instrument",
|
|
278
|
+
choices=[
|
|
279
|
+
"codice",
|
|
280
|
+
"glows",
|
|
281
|
+
"hi",
|
|
282
|
+
"hit",
|
|
283
|
+
"idex",
|
|
284
|
+
"lo",
|
|
285
|
+
"mag",
|
|
286
|
+
"swapi",
|
|
287
|
+
"swe",
|
|
288
|
+
"ultra",
|
|
289
|
+
],
|
|
290
|
+
)
|
|
291
|
+
query_parser.add_argument(
|
|
292
|
+
"--data-level",
|
|
293
|
+
type=str,
|
|
294
|
+
required=False,
|
|
295
|
+
help="Data level of the product (l0, l1a, l2, etc.)",
|
|
296
|
+
)
|
|
297
|
+
query_parser.add_argument(
|
|
298
|
+
"--descriptor",
|
|
299
|
+
type=str,
|
|
300
|
+
required=False,
|
|
301
|
+
help="Descriptor of the product (raw, burst, etc.)",
|
|
302
|
+
)
|
|
303
|
+
query_parser.add_argument(
|
|
304
|
+
"--start-date",
|
|
305
|
+
type=str,
|
|
306
|
+
required=False,
|
|
307
|
+
help="Start date for files in YYYYMMDD format",
|
|
308
|
+
)
|
|
309
|
+
query_parser.add_argument(
|
|
310
|
+
"--end-date",
|
|
311
|
+
type=str,
|
|
312
|
+
required=False,
|
|
313
|
+
help="End date for a range of file timestamps in YYYYMMDD format",
|
|
314
|
+
)
|
|
315
|
+
query_parser.add_argument(
|
|
316
|
+
"--repointing", type=int, required=False, help="Repointing number (int)"
|
|
317
|
+
)
|
|
318
|
+
query_parser.add_argument(
|
|
319
|
+
"--version",
|
|
320
|
+
type=str,
|
|
321
|
+
required=False,
|
|
322
|
+
help="Version of the product in the format 'v001'."
|
|
323
|
+
" Must have one other parameter to run."
|
|
324
|
+
" Passing 'latest' will return latest version of a file",
|
|
325
|
+
)
|
|
326
|
+
query_parser.add_argument(
|
|
327
|
+
"--extension", type=str, required=False, help="File extension (cdf, pkts)"
|
|
328
|
+
)
|
|
329
|
+
query_parser.add_argument(
|
|
330
|
+
"--output-format",
|
|
331
|
+
type=str,
|
|
332
|
+
required=False,
|
|
333
|
+
help="How to format the output, default is 'table'",
|
|
334
|
+
choices=["table", "json"],
|
|
335
|
+
default="table",
|
|
336
|
+
)
|
|
337
|
+
query_parser.add_argument(
|
|
338
|
+
"--filename",
|
|
339
|
+
type=str,
|
|
340
|
+
required=False,
|
|
341
|
+
help="Name of a file to be searched for. For convention standards see https://imap-"
|
|
342
|
+
"processing.readthedocs.io/en/latest/development-guide/style-guide/naming-conventions"
|
|
343
|
+
".html#data-product-file-naming-conventions",
|
|
344
|
+
)
|
|
345
|
+
query_parser.set_defaults(func=_query_parser)
|
|
346
|
+
|
|
347
|
+
# Upload command
|
|
348
|
+
parser_upload = subparsers.add_parser(
|
|
349
|
+
"upload", help=upload_help, description=help_menu_for_upload
|
|
350
|
+
)
|
|
351
|
+
parser_upload.add_argument("file_path", type=Path, help=file_path_help)
|
|
352
|
+
parser_upload.set_defaults(func=_upload_parser)
|
|
353
|
+
|
|
354
|
+
# Parse the arguments and set the values
|
|
355
|
+
try:
|
|
356
|
+
args = parser.parse_args()
|
|
357
|
+
except TypeError:
|
|
358
|
+
parser.exit(
|
|
359
|
+
status=1,
|
|
360
|
+
message="Please provide input parameters, "
|
|
361
|
+
"or use '-h' for more information.",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
logging.basicConfig(level=args.loglevel)
|
|
365
|
+
|
|
366
|
+
if args.data_dir:
|
|
367
|
+
# We got an explicit data directory from the command line
|
|
368
|
+
data_path = args.data_dir.resolve()
|
|
369
|
+
if not data_path.exists():
|
|
370
|
+
parser.error(f"Data directory {args.data_dir} does not exist")
|
|
371
|
+
# Set the data directory to the user-supplied value
|
|
372
|
+
imap_data_access.config["DATA_DIR"] = data_path
|
|
373
|
+
|
|
374
|
+
if args.url:
|
|
375
|
+
# We got an explicit url from the command line
|
|
376
|
+
imap_data_access.config["DATA_ACCESS_URL"] = args.url
|
|
377
|
+
|
|
378
|
+
if args.api_key:
|
|
379
|
+
# We got an explicit api key from the command line
|
|
380
|
+
imap_data_access.config["API_KEY"] = args.api_key
|
|
381
|
+
|
|
382
|
+
# Now process through the respective function for the invoked command
|
|
383
|
+
# (set with set_defaults on the subparsers above)
|
|
384
|
+
try:
|
|
385
|
+
args.func(args)
|
|
386
|
+
except Exception as e:
|
|
387
|
+
# Make sure we are exiting with non-zero exit code and printing the message
|
|
388
|
+
parser.exit(status=1, message=str(e) + "\n")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
if __name__ == "__main__":
|
|
392
|
+
main()
|