forked from ESCOMP/CTSM
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfig_utils.py
More file actions
225 lines (192 loc) · 6.85 KB
/
config_utils.py
File metadata and controls
225 lines (192 loc) · 6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"""
General-purpose utilities and functions for handling command-line
config files in ctsm python codes.
"""
import logging
import configparser
from ctsm.utils import abort
logger = logging.getLogger(__name__)
# This string is used in the out-of-the-box ctsm.cfg and modify.cfg files
# to denote a value that needs to be filled in
_CONFIG_PLACEHOLDER = "FILL_THIS_IN"
# This string is used in the out-of-the-box ctsm.cfg and modify.cfg files
# to denote a value that can be filled in, but doesn't absolutely need to be
_CONFIG_UNSET = "UNSET"
def convert_lon_0to360(lon_in):
"""
Description
-----------
Convert a longitude from [-180, 180] format (i.e., centered around Prime Meridian) to [0, 360]
format (i.e., centered around International Date Line).
"""
if not -180 <= lon_in <= 180:
raise ValueError(f"lon_in needs to be in the range [-180, 180]: {lon_in}")
lon_out = lon_in % 360
logger.info(
"Converting longitude from [-180, 180] to [0, 360]: %s to %s",
str(lon_in),
str(lon_out),
)
return lon_out
def convert_lons_if_needed(lon_1, lon_2, lon_type):
"""
Description
-----------
Given two longitudes, if their type is 180 (i.e., between -180 and 180), convert them to 0-360
"""
if lon_type == 180:
lon_1 = convert_lon_0to360(lon_1)
lon_2 = convert_lon_0to360(lon_2)
elif lon_type != 360:
raise ValueError(f"lon_type must be either 180 or 360, not {lon_type}")
return lon_1, lon_2
def check_lon1_lt_lon2(lon1, lon2, lon_type):
"""
Description
-----------
Given two longitudes, check that lon1 is < lon2. Useful for avoiding CTSM Issue #2017, but note
that to use this function properly for that purpose, you need to have already converted
longitudes from lon_type 180 to 360.
"""
if lon1 < lon2:
return
msg = f"--lon1 ({lon1}) must be < --lon2 ({lon2})\n"
msg += "See CTSM issue #2017: https://github.com/ESCOMP/CTSM/issues/2017"
if lon_type == 180:
msg = "After converting to --lon-type 360, " + msg
raise ValueError(msg)
def get_config_value(
config,
section,
item,
file_path,
allowed_values=None,
default=None,
is_list=False,
convert_to_type=None,
can_be_unset=False,
):
"""Get a given item from a given section of the config object
Give a helpful error message if we can't find the given section or item
Note that the file_path argument is only used for the sake of the error message
If allowed_values is present, it should be a list of strings giving allowed values
The function _handle_config_value determines what to do if we read:
- a list or
- a str that needs to be converted to int / float / bool
- _CONFIG_UNSET: anything with the value "UNSET" will become "None"
"""
try:
val = config.get(section, item)
except configparser.NoSectionError:
abort("ERROR: Config file {} must contain section '{}'".format(file_path, section))
except configparser.NoOptionError:
abort(
"ERROR: Config file {} must contain item '{}' in section '{}'".format(
file_path, item, section
)
)
if val == _CONFIG_PLACEHOLDER:
abort("Error: {} needs to be specified in config file {}".format(item, file_path))
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
return val
def get_config_value_or_array(
config,
section,
item,
convert_to_type=None,
):
"""Get a config value as a single value or as an array if it's expressed as an array
for cases when you don't know how it's going to be expressed"""
val = config.get(section, item)
vallist = val.split()
if convert_to_type is not None:
if (
convert_to_type is not float
and convert_to_type is not int
and convert_to_type is not str
):
abort(
"get_config_value_or_array can only have convert_to_type as float, int or str not "
+ str(convert_to_type)
)
is_list = bool(len(vallist) > 1)
val = _handle_config_value(
var=val,
default=None,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=False,
allowed_values=None,
)
return val
def _handle_config_value(
var, default, item, is_list, convert_to_type, can_be_unset, allowed_values
):
"""
Description
-----------
Assign the default value or the user-specified one to var.
Convert from default type (str) to reqested type (int or float).
If is_list is True, then default should be a list
"""
if var == _CONFIG_UNSET:
if can_be_unset:
return default # default may be None
abort("Must set a value for .cfg file variable: {}".format(item))
# convert string to list of strings; if there is just one element,
# we will get a list of size one, which we will convert back to a
# scalar later if needed
var = var.split()
if convert_to_type is bool:
try:
var = [_convert_to_bool(v) for v in var]
except ValueError:
abort("Non-boolean value found for .cfg file variable: {}".format(item))
elif convert_to_type is not None:
try:
var = [convert_to_type(v) for v in var]
except ValueError:
abort("Wrong type for .cfg file variable: {}".format(item))
if allowed_values is not None:
for val in var:
if val not in allowed_values:
print("val = ", val, " in var not in allowed_values")
errmsg = (
"{} is not an allowed value for {} in .cfg file. "
"Check allowed_values".format(val, item)
)
abort(errmsg)
if not is_list:
if len(var) > 1:
abort("More than 1 element found for .cfg file variable: {}".format(item))
var = var[0]
return var
def _convert_to_bool(var):
"""
Function for converting different forms of
boolean strings to boolean value.
Args:
var (str): String bool input
Raises:
if the argument is not an acceptable boolean string
(such as yes or no ; true or false ; y or n ; t or f ; 0 or 1).
ValueError: The string should be one of the mentioned values.
Returns:
var_out (bool): Boolean value corresponding to the input.
"""
if var.lower() in ("yes", "true", "t", "y", "1", "on"):
var_out = True
elif var.lower() in ("no", "false", "f", "n", "0", "off"):
var_out = False
else:
raise ValueError("Boolean value expected. [true or false] or [y or n]")
return var_out