9 from overrides import overrides
12 # This module is commonly used by others in here and should avoid
13 # taking any unnecessary dependencies back on them.
15 logger = logging.getLogger(__name__)
18 class ActionNoYes(argparse.Action):
28 msg = 'You must provide a default with Yes/No action'
31 if len(option_strings) != 1:
32 msg = 'Only single argument is allowed with YesNo action'
35 opt = option_strings[0]
36 if not opt.startswith('--'):
37 msg = 'Yes/No arguments must be prefixed with --'
42 opts = ['--' + opt, '--no_' + opt]
54 def __call__(self, parser, namespace, values, option_strings=None):
56 option_strings.startswith('--no-') or
57 option_strings.startswith('--no_')
59 setattr(namespace, self.dest, False)
61 setattr(namespace, self.dest, True)
64 def valid_bool(v: Any) -> bool:
66 If the string is a valid bool, return its value.
71 >>> valid_bool("true")
84 Traceback (most recent call last):
86 argparse.ArgumentTypeError: 12345
89 if isinstance(v, bool):
91 from string_utils import to_bool
95 raise argparse.ArgumentTypeError(v)
98 def valid_ip(ip: str) -> str:
100 If the string is a valid IPv4 address, return it. Otherwise raise
101 an ArgumentTypeError.
103 >>> valid_ip("1.2.3.4")
106 >>> valid_ip("localhost")
107 Traceback (most recent call last):
109 argparse.ArgumentTypeError: localhost is an invalid IP address
112 from string_utils import extract_ip_v4
113 s = extract_ip_v4(ip.strip())
116 msg = f"{ip} is an invalid IP address"
118 raise argparse.ArgumentTypeError(msg)
121 def valid_mac(mac: str) -> str:
123 If the string is a valid MAC address, return it. Otherwise raise
124 an ArgumentTypeError.
126 >>> valid_mac('12:23:3A:4F:55:66')
129 >>> valid_mac('12-23-3A-4F-55-66')
133 Traceback (most recent call last):
135 argparse.ArgumentTypeError: big is an invalid MAC address
138 from string_utils import extract_mac_address
139 s = extract_mac_address(mac)
142 msg = f"{mac} is an invalid MAC address"
144 raise argparse.ArgumentTypeError(msg)
147 def valid_percentage(num: str) -> float:
149 If the string is a valid percentage, return it. Otherwise raise
150 an ArgumentTypeError.
152 >>> valid_percentage("15%")
155 >>> valid_percentage('40')
158 >>> valid_percentage('115')
159 Traceback (most recent call last):
161 argparse.ArgumentTypeError: 115 is an invalid percentage; expected 0 <= n <= 100.0
166 if 0.0 <= n <= 100.0:
168 msg = f"{num} is an invalid percentage; expected 0 <= n <= 100.0"
170 raise argparse.ArgumentTypeError(msg)
173 def valid_filename(filename: str) -> str:
175 If the string is a valid filename, return it. Otherwise raise
176 an ArgumentTypeError.
178 >>> valid_filename('/tmp')
181 >>> valid_filename('wfwefwefwefwefwefwefwefwef')
182 Traceback (most recent call last):
184 argparse.ArgumentTypeError: wfwefwefwefwefwefwefwefwef was not found and is therefore invalid.
188 if os.path.exists(s):
190 msg = f"{filename} was not found and is therefore invalid."
192 raise argparse.ArgumentTypeError(msg)
195 def valid_date(txt: str) -> datetime.date:
196 """If the string is a valid date, return it. Otherwise raise
197 an ArgumentTypeError.
199 >>> valid_date('6/5/2021')
200 datetime.date(2021, 6, 5)
202 # Note: dates like 'next wednesday' work fine, they are just
203 # hard to test for without knowing when the testcase will be
205 >>> valid_date('next wednesday') # doctest: +ELLIPSIS
208 from string_utils import to_date
212 msg = f'Cannot parse argument as a date: {txt}'
214 raise argparse.ArgumentTypeError(msg)
217 def valid_datetime(txt: str) -> datetime.datetime:
218 """If the string is a valid datetime, return it. Otherwise raise
219 an ArgumentTypeError.
221 >>> valid_datetime('6/5/2021 3:01:02')
222 datetime.datetime(2021, 6, 5, 3, 1, 2)
224 # Again, these types of expressions work fine but are
225 # difficult to test with doctests because the answer is
226 # relative to the time the doctest is executed.
227 >>> valid_datetime('next christmas at 4:15am') # doctest: +ELLIPSIS
230 from string_utils import to_datetime
231 dt = to_datetime(txt)
234 msg = f'Cannot parse argument as datetime: {txt}'
236 raise argparse.ArgumentTypeError(msg)
239 def valid_duration(txt: str) -> datetime.timedelta:
240 """If the string is a valid time duration, return a
241 datetime.timedelta representing the period of time. Otherwise
242 maybe raise an ArgumentTypeError or potentially just treat the
243 time window as zero in length.
245 >>> valid_duration('3m')
246 datetime.timedelta(seconds=180)
248 >>> valid_duration('your mom')
249 datetime.timedelta(0)
252 from datetime_utils import parse_duration
254 secs = parse_duration(txt)
255 except Exception as e:
256 raise argparse.ArgumentTypeError(e)
258 return datetime.timedelta(seconds=secs)
261 if __name__ == '__main__':
263 doctest.ELLIPSIS_MARKER = '-ANYTHING-'