9 # This module is commonly used by others in here and should avoid
10 # taking any unnecessary dependencies back on them.
12 logger = logging.getLogger(__name__)
15 class ActionNoYes(argparse.Action):
25 msg = 'You must provide a default with Yes/No action'
28 if len(option_strings) != 1:
29 msg = 'Only single argument is allowed with YesNo action'
32 opt = option_strings[0]
33 if not opt.startswith('--'):
34 msg = 'Yes/No arguments must be prefixed with --'
39 opts = ['--' + opt, '--no_' + opt]
50 def __call__(self, parser, namespace, values, option_strings=None):
52 option_strings.startswith('--no-') or
53 option_strings.startswith('--no_')
55 setattr(namespace, self.dest, False)
57 setattr(namespace, self.dest, True)
60 def valid_bool(v: Any) -> bool:
62 If the string is a valid bool, return its value.
67 >>> valid_bool("true")
80 Traceback (most recent call last):
82 argparse.ArgumentTypeError: 12345
85 if isinstance(v, bool):
87 from string_utils import to_bool
91 raise argparse.ArgumentTypeError(v)
94 def valid_ip(ip: str) -> str:
96 If the string is a valid IPv4 address, return it. Otherwise raise
99 >>> valid_ip("1.2.3.4")
102 >>> valid_ip("localhost")
103 Traceback (most recent call last):
105 argparse.ArgumentTypeError: localhost is an invalid IP address
108 from string_utils import extract_ip_v4
109 s = extract_ip_v4(ip.strip())
112 msg = f"{ip} is an invalid IP address"
114 raise argparse.ArgumentTypeError(msg)
117 def valid_mac(mac: str) -> str:
119 If the string is a valid MAC address, return it. Otherwise raise
120 an ArgumentTypeError.
122 >>> valid_mac('12:23:3A:4F:55:66')
125 >>> valid_mac('12-23-3A-4F-55-66')
129 Traceback (most recent call last):
131 argparse.ArgumentTypeError: big is an invalid MAC address
134 from string_utils import extract_mac_address
135 s = extract_mac_address(mac)
138 msg = f"{mac} is an invalid MAC address"
140 raise argparse.ArgumentTypeError(msg)
143 def valid_percentage(num: str) -> float:
145 If the string is a valid percentage, return it. Otherwise raise
146 an ArgumentTypeError.
148 >>> valid_percentage("15%")
151 >>> valid_percentage('40')
154 >>> valid_percentage('115')
155 Traceback (most recent call last):
157 argparse.ArgumentTypeError: 115 is an invalid percentage; expected 0 <= n <= 100.0
162 if 0.0 <= n <= 100.0:
164 msg = f"{num} is an invalid percentage; expected 0 <= n <= 100.0"
166 raise argparse.ArgumentTypeError(msg)
169 def valid_filename(filename: str) -> str:
171 If the string is a valid filename, return it. Otherwise raise
172 an ArgumentTypeError.
174 >>> valid_filename('/tmp')
177 >>> valid_filename('wfwefwefwefwefwefwefwefwef')
178 Traceback (most recent call last):
180 argparse.ArgumentTypeError: wfwefwefwefwefwefwefwefwef was not found and is therefore invalid.
184 if os.path.exists(s):
186 msg = f"{filename} was not found and is therefore invalid."
188 raise argparse.ArgumentTypeError(msg)
191 def valid_date(txt: str) -> datetime.date:
192 """If the string is a valid date, return it. Otherwise raise
193 an ArgumentTypeError.
195 >>> valid_date('6/5/2021')
196 datetime.date(2021, 6, 5)
198 # Note: dates like 'next wednesday' work fine, they are just
199 # hard to test for without knowing when the testcase will be
201 >>> valid_date('next wednesday') # doctest: +ELLIPSIS
204 from string_utils import to_date
208 msg = f'Cannot parse argument as a date: {txt}'
210 raise argparse.ArgumentTypeError(msg)
213 def valid_datetime(txt: str) -> datetime.datetime:
214 """If the string is a valid datetime, return it. Otherwise raise
215 an ArgumentTypeError.
217 >>> valid_datetime('6/5/2021 3:01:02')
218 datetime.datetime(2021, 6, 5, 3, 1, 2)
220 # Again, these types of expressions work fine but are
221 # difficult to test with doctests because the answer is
222 # relative to the time the doctest is executed.
223 >>> valid_datetime('next christmas at 4:15am') # doctest: +ELLIPSIS
226 from string_utils import to_datetime
227 dt = to_datetime(txt)
230 msg = f'Cannot parse argument as datetime: {txt}'
232 raise argparse.ArgumentTypeError(msg)
235 def valid_duration(txt: str) -> datetime.timedelta:
236 """If the string is a valid time duration, return a
237 datetime.timedelta representing the period of time. Otherwise
238 maybe raise an ArgumentTypeError or potentially just treat the
239 time window as zero in length.
241 >>> valid_duration('3m')
242 datetime.timedelta(seconds=180)
244 >>> valid_duration('your mom')
245 datetime.timedelta(seconds=0)
248 from datetime_utils import parse_duration
250 secs = parse_duration(txt)
251 except Exception as e:
252 raise argparse.ArgumentTypeError(e)
254 return datetime.timedelta(seconds=secs)
257 if __name__ == '__main__':
259 doctest.ELLIPSIS_MARKER = '-ANYTHING-'