9 from overrides import overrides
11 # This module is commonly used by others in here and should avoid
12 # taking any unnecessary dependencies back on them.
14 logger = logging.getLogger(__name__)
17 class ActionNoYes(argparse.Action):
18 def __init__(self, option_strings, dest, default=None, required=False, help=None):
20 msg = 'You must provide a default with Yes/No action'
23 if len(option_strings) != 1:
24 msg = 'Only single argument is allowed with YesNo action'
27 opt = option_strings[0]
28 if not opt.startswith('--'):
29 msg = 'Yes/No arguments must be prefixed with --'
34 opts = ['--' + opt, '--no_' + opt]
46 def __call__(self, parser, namespace, values, option_strings=None):
47 if option_strings.startswith('--no-') or option_strings.startswith('--no_'):
48 setattr(namespace, self.dest, False)
50 setattr(namespace, self.dest, True)
53 def valid_bool(v: Any) -> bool:
55 If the string is a valid bool, return its value.
60 >>> valid_bool("true")
73 Traceback (most recent call last):
75 argparse.ArgumentTypeError: 12345
78 if isinstance(v, bool):
80 from string_utils import to_bool
85 raise argparse.ArgumentTypeError(v)
88 def valid_ip(ip: str) -> str:
90 If the string is a valid IPv4 address, return it. Otherwise raise
93 >>> valid_ip("1.2.3.4")
96 >>> valid_ip("localhost")
97 Traceback (most recent call last):
99 argparse.ArgumentTypeError: localhost is an invalid IP address
102 from string_utils import extract_ip_v4
104 s = extract_ip_v4(ip.strip())
107 msg = f"{ip} is an invalid IP address"
109 raise argparse.ArgumentTypeError(msg)
112 def valid_mac(mac: str) -> str:
114 If the string is a valid MAC address, return it. Otherwise raise
115 an ArgumentTypeError.
117 >>> valid_mac('12:23:3A:4F:55:66')
120 >>> valid_mac('12-23-3A-4F-55-66')
124 Traceback (most recent call last):
126 argparse.ArgumentTypeError: big is an invalid MAC address
129 from string_utils import extract_mac_address
131 s = extract_mac_address(mac)
134 msg = f"{mac} is an invalid MAC address"
136 raise argparse.ArgumentTypeError(msg)
139 def valid_percentage(num: str) -> float:
141 If the string is a valid percentage, return it. Otherwise raise
142 an ArgumentTypeError.
144 >>> valid_percentage("15%")
147 >>> valid_percentage('40')
150 >>> valid_percentage('115')
151 Traceback (most recent call last):
153 argparse.ArgumentTypeError: 115 is an invalid percentage; expected 0 <= n <= 100.0
158 if 0.0 <= n <= 100.0:
160 msg = f"{num} is an invalid percentage; expected 0 <= n <= 100.0"
162 raise argparse.ArgumentTypeError(msg)
165 def valid_filename(filename: str) -> str:
167 If the string is a valid filename, return it. Otherwise raise
168 an ArgumentTypeError.
170 >>> valid_filename('/tmp')
173 >>> valid_filename('wfwefwefwefwefwefwefwefwef')
174 Traceback (most recent call last):
176 argparse.ArgumentTypeError: wfwefwefwefwefwefwefwefwef was not found and is therefore invalid.
180 if os.path.exists(s):
182 msg = f"{filename} was not found and is therefore invalid."
184 raise argparse.ArgumentTypeError(msg)
187 def valid_date(txt: str) -> datetime.date:
188 """If the string is a valid date, return it. Otherwise raise
189 an ArgumentTypeError.
191 >>> valid_date('6/5/2021')
192 datetime.date(2021, 6, 5)
194 # Note: dates like 'next wednesday' work fine, they are just
195 # hard to test for without knowing when the testcase will be
197 >>> valid_date('next wednesday') # doctest: +ELLIPSIS
200 from string_utils import to_date
205 msg = f'Cannot parse argument as a date: {txt}'
207 raise argparse.ArgumentTypeError(msg)
210 def valid_datetime(txt: str) -> datetime.datetime:
211 """If the string is a valid datetime, return it. Otherwise raise
212 an ArgumentTypeError.
214 >>> valid_datetime('6/5/2021 3:01:02')
215 datetime.datetime(2021, 6, 5, 3, 1, 2)
217 # Again, these types of expressions work fine but are
218 # difficult to test with doctests because the answer is
219 # relative to the time the doctest is executed.
220 >>> valid_datetime('next christmas at 4:15am') # doctest: +ELLIPSIS
223 from string_utils import to_datetime
225 dt = to_datetime(txt)
228 msg = f'Cannot parse argument as datetime: {txt}'
230 raise argparse.ArgumentTypeError(msg)
233 def valid_duration(txt: str) -> datetime.timedelta:
234 """If the string is a valid time duration, return a
235 datetime.timedelta representing the period of time. Otherwise
236 maybe raise an ArgumentTypeError or potentially just treat the
237 time window as zero in length.
239 >>> valid_duration('3m')
240 datetime.timedelta(seconds=180)
242 >>> valid_duration('your mom')
243 datetime.timedelta(0)
246 from datetime_utils import parse_duration
249 secs = parse_duration(txt)
250 except Exception as e:
251 raise argparse.ArgumentTypeError(e)
253 return datetime.timedelta(seconds=secs)
256 if __name__ == '__main__':
259 doctest.ELLIPSIS_MARKER = '-ANYTHING-'