netcdf2ascii 7.84 KB
Newer Older
1 2
#!/apps/base/python3/bin/python3

3
"""
4 5 6 7
Author: Michael Giansiracusa
Email: giansiracumt@ornl.gov

Purpose:
8
    This is a wrapper to decide which nc 2 ascii script to call, icartt for arial data, cdf for all others
9 10 11

Arguments
    required arguments
12
        in_dir : str
13
            fully qualified path to files that will be processed.
14
        out_dir : str
15
            fully qualified path where to put output files.
16
        var_list : str
17 18
            fully qualified path to a file that contains all
            variables to extract each on it's own line.
19
        file_list : str
20
            fully qualified path to a file that contains all
21
            files in the in_dir to process.
22
    optional arguments
23
        merged_output : bool flag
24 25 26 27 28 29
            output files merged into one if provided
        replaceMissing : bool flag
            replace missing values (-9999, -9995) with ""/None
        DQRfilter : str
            filter data by variable using dqr status.
            should be suspect, incorrect, or suspect,incorrect
30

31 32

Output:
33
    One cdf file for each file in input directory is put in the out_dir or
34 35 36 37 38
    one merged file with all rows and columns converted from input cdf files.
    The first row of each file will be the column names; the first 3 column
    names will always be time_stamp, basetime, and time_offset. The time_stamp
    column is calculated by adding the basetime and time_offset and converting
    to a calendar date of the form YYYY-mm-dd HH:MM:SS
39 40 41 42 43
"""

# Operations
import os
import sys
44 45 46
import json
from nc2icartt import nc2icartt
from nc2csv import nc2csv
Michael Giansiracusa's avatar
Michael Giansiracusa committed
47
import logging
48 49 50 51

# Input parsing
import argparse

52 53 54
sys.path.insert(0, "/apps/base/python3.5/lib/python3.5/site-packages")
sys.path.insert(1, "/apps/adc/retrievals/ARM-utils/packages")

Michael Giansiracusa's avatar
Michael Giansiracusa committed
55 56 57 58
logging.basicConfig(level=logging.WARNING,
                    format='[%(asctime)s] %(levelname)s (%(funcName)s %(lineno)s) : %(message)s',
                    datefmt='%Y-%m-%d %H:%M')
main_logger = logging.getLogger("main")
59 60
try:
    import comms
Michael Giansiracusa's avatar
Michael Giansiracusa committed
61
    from config import *
62 63
except Exception:
    print("Could not import comms/config. No monitoring messages will be sent.")
64

65 66 67 68 69 70 71

help_description = """
This program converts netcdf files into csv files.
"""

example = """
EXAMPLE: py3 netcdf2ascii.py
72 73 74 75
            --in_dir "/path_to/input"
            --out_dir "/path_to/output"
            --var_list "/path_to/var_list.txt"
            --file_list "/path_to/file_list.txt"
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
"""

""" setup_arguments summary

Author: Michael Giansiracusa
Email: giansiracumt@ornl.gov

Purpose:
    Parse arguments from command line.

Args:
    None

Returns:
    args object with attributes
91 92 93 94
            in_dir : str
            out_dir : str
            var_list : str
            file_list : str
95 96 97 98
"""


def parse_arguments():
Michael Giansiracusa's avatar
Michael Giansiracusa committed
99 100 101 102
    '''Instantiates an argument parser and returns loads in artument flags.

    :return (argparse.Namespace): Ojbect with arguments and flags as dot properties.
    '''
103 104 105 106
    parser = argparse.ArgumentParser(description=help_description, epilog=example,
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    requiredArguments = parser.add_argument_group("required arguments")
Michael Giansiracusa's avatar
Michael Giansiracusa committed
107
    optionalArguments = parser.add_argument_group("optional arguments")
108

Michael Giansiracusa's avatar
Michael Giansiracusa committed
109 110 111 112
    # The type is a function to validate that the path exists, only for the required arguments.
    requiredArguments.add_argument("-i", "--indir", type=str, dest="in_dir", required=True,
                                   help="The directory where input files are.")
    requiredArguments.add_argument("-o", "--outdir", type=str, dest="out_dir", required=True,
113
                                   help="The directory to put output file in.")
Michael Giansiracusa's avatar
Michael Giansiracusa committed
114
    requiredArguments.add_argument("-f", "--filelist", type=str, dest="file_list", required=True,
115
                                   help="The name of a file that contains the names of files to convert.")
Michael Giansiracusa's avatar
Michael Giansiracusa committed
116 117
    requiredArguments.add_argument("-v", "--varlist", type=str, dest="var_list",
                                   help="The name of a file that contains the names of variables to extract.")
118

Michael Giansiracusa's avatar
Michael Giansiracusa committed
119
    optionalArguments.add_argument("--mergedoutput", action="store_true", dest="merged_output",
120
                        help="merge files into one, possibly with max size")
Michael Giansiracusa's avatar
Michael Giansiracusa committed
121 122
    # DQR filter can take the following forms; --DQRfilter nofilter,incorrect,suspect
    optionalArguments.add_argument("--DQRfilter", type=str, dest="DQRfilter", default="",
123
                        help="filter out data with dqr, <suspect> or <incorrect> or <suspect,incorrect>")
Michael Giansiracusa's avatar
Michael Giansiracusa committed
124 125
    optionalArguments.add_argument("-mp", type=int, dest="maxProcesses", default=3,
                        help="number of processes in multiprocessing pool")
126 127 128 129 130 131 132

    args, unknownArgs = parser.parse_known_args()
    if len(sys.argv) <= 1:
        parser.print_help()

    return args, unknownArgs

133 134 135 136 137
error_msg = "Error {}: Problem with {} extraction.\n" \
            "main_args: {}"
success_msg = "Successful {}: {} data extracted\n" \
              "main_args: {}"

138
def netcdf2ascii(main_args):
Michael Giansiracusa's avatar
Michael Giansiracusa committed
139
    main_logger.debug("main_args: {}".format(main_args))
140
    try:
Michael Giansiracusa's avatar
Michael Giansiracusa committed
141 142
        if is_arial(get_first_file(main_args.file_list, main_args.in_dir)):
            main_logger.info('Processing arial files...')
143
            proc_type = "arial"
144
            exit_code = nc2icartt(main_args)
Michael Giansiracusa's avatar
Michael Giansiracusa committed
145
            main_logger.info("netcdf2ascii.icart: exit({})".format(exit_code))
146
        else:
Michael Giansiracusa's avatar
Michael Giansiracusa committed
147
            main_logger.info('Processing netcdf files...')
148
            proc_type = "netcdf"
149
            exit_code = nc2csv(main_args)
Michael Giansiracusa's avatar
Michael Giansiracusa committed
150 151 152 153 154 155 156 157 158 159
            main_logger.info("netcdf2ascii.nc2csv: exit({})".format(exit_code))
    except (FileNotFoundError, NotADirectoryError, ConnectionError):
        main_logger.info("netcdf2ascii.nc2csv: exit(1)")
        try:
            comms.slack_send_direct(error_msg.format(exit_code, proc_type, main_args))
        except Exception:
            pass
        exit(1)
    except BlockingIOError:
        main_logger.critical("Unknown exception: exit(7)")
160
        try:
Michael Giansiracusa's avatar
Michael Giansiracusa committed
161
            comms.slack_send_direct(error_msg.format(exit_code, proc_type, main_args))
162
        except Exception:
Michael Giansiracusa's avatar
Michael Giansiracusa committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
            pass
        exit(7)

def valid_argument(arg: str) -> str:
    '''Checks to make sure the input argument exists. If not raise argparse.ArgumentTypeError.

    :param arg (str): One of the required input arguments.
    :return str: The input argument path as a string if it exists.
    '''
    if os.path.exists(arg):
        return arg
    else:
        raise argparse.ArgumentTypeError("Input argument does not exist: {}".format(arg))

def is_arial(file_name):
    main_logger.debug(file_name)
    main_logger.debug(file_name.split("."))
    main_logger.debug(file_name.split(".")[0])
    main_logger.debug(file_name.split(".")[0][-2])
    facility = file_name.split(".")[0][-2]
    return "F" == facility or "U" == facility

def get_first_file(file_list, in_dir):
    files = list()
    main_logger.debug('file_list: {}'.format(file_list))
    try:
        if os.path.isfile(file_list):
            if os.path.isdir(in_dir):
                for file_name in open(file_list, "r"):
                    infile_path = os.path.join(in_dir, file_name.strip("\n"))
                    if os.path.isfile(infile_path):
                        return file_name
                    else:
                        main_logger.warning("File to convert does not exist: {}".format(infile_path))
                main_logger.critical("No valid files in file list.")
                raise FileNotFoundError
            else:
                main_logger.critical("Input directory does not exist.")
                raise NotADirectoryError
        else:
            main_logger.critical("File list does not exist.")
            raise FileNotFoundError
    except TypeError:
        main_logger.critical("Missing argument; file_list and/or in_dir are null.")
207 208 209 210 211

if __name__ == "__main__":
    if len(sys.argv) > 1:
        main_args,unknownArgs = parse_arguments()

Michael Giansiracusa's avatar
Michael Giansiracusa committed
212
        netcdf2ascii(main_args)