This is a small extension to the native Python3 argparse.ArgumentParser. It adds some functionality I find I always end up requiring.
pip install wintersdeep.argparseOften you want the user to be able to configure log levels, thats a pain in itself. Then when you select debug and get overwhelmed you realise you only want it on one or more loggers, and not all of them. Often this is done by tweaking the code to acheive what you need in the moment. This helps fix that by making adjusting log levels at the command line simple - take a look:
from wintersdeep.argparse import ArgumentParser
argument_parser = ArgumentParser()
argument_parser.add_log_level_arguments() # there are options here, but the defaults will work fine.
arguments = argument_parser.parse_args()
if log_levels := arguments.log_level:
log_levels.apply()A user can now type -l[log-level] to set the global/default log level, or -l[logger]=[log-level] to set a specific log level, or any mix of the two.
python3 -m path.to.app -lwarning -lpath.to.app.problematic=debug -lpath.to.app.suspect=infoIt alters the behaviour of the choices argument on the add_argument method such that it supports the use of Mapping objects such as dict.
When using a Mapping object as a choices constraint the user will be able to choose only from the keys of the Mapping object specified. The arguments value in the resulting Namespace will be that which is associated with the key in the provided Mapping - e.g. if the user specified the key "abc" the value in the resulting namespace would be taken from choices_mapping["abc"].
As a simple example, the below code accepts any number of integer arguments and prints them to stdout using the CLI specified formatting. The formatter argument -f is constrained using choices which is provided as the formatters dict object. Users will only be able to choose one of its keys (bin, hex or oct) and the value in the resulting namespace is the assoicated lambda expression.
from wintersdeep.argparse import ArgumentParser
formatters = {
"bin": lambda n: f"{n:b}",
"oct": lambda n: f"{n:o}",
"hex": lambda n: f"{n:x}"
}
argument_parser = ArgumentParser()
argument_parser.add_argument("N", nargs="+", type=int)
argument_parser.add_argument("-f", choices=formatters, default_key='hex')
arguments = argument_parser.parse_args()
for n in arguments.N:
print( arguments.formatter(n) )- Allows usage of
Mappingtype objects as achoicesconstraint onArgumentParser::add_argument. Accepted input will be constrained to key values in the given map and the value in the resultingNamespacewill be that keys associated value. - Added a
default_keykeyword argument; you can specify eitherdefaultordefault_keybut not both. When usingdefault_keythe default value will be the value associated with the specifieddefault_keyin thechoicesmap.default_keyallows you to ensure documentation and behaviour remain syncronised and readable.defaultcan still be used as normal if preferred and is required when the default option should not be user selectable.
-
The
actionprocess wasn't applied to thedefaultvalue (specified or derived fromdefault_key).This is by intention and is consistent with the native
ArgumentParserbehaviour - the default value is provided "as-is" when the option is not specifed. -
The
Actionreturned byadd_argumentis not the type specified byadd_argumentsactionparameterThe libraries behaviours are implemented by shimming
add_argument, and inserting a customactionwhenchoicesare aMappingtype.If you have set your own
actionthenadd_argumentwill preserve this and invoke it after the value has been translated, so you should still get the expected behaviour. However this does mean theActionclass return byadd_argumentwill not be of the type you originally specified. Your originalactioncan be found in the[action].next_actionproperty.For example, the below script extends the original sample to use an
AddPrefixaction to prefix a string to the user specified formatter. This works identically to the native behaviour, except thevaluesargument will be the value from thechoicesmapping.from argparse import Action from wintersdeep.argparse import ArgumentParser # A custom action to add a prefix to all formatters. class AddPrefix(Action): def __call__(self, parser, namespace, values, option_string=None): # by the time this action receives `values` it has been converted into its Mapping associated value (in context a lambda function from `formatters`. prefix_fn = lambda x: f"Your number is {values(x)}" setattr(namespace, self.dest, prefix_fn) formatters = { "bin": lambda n: f"{n:b}", "oct": lambda n: f"{n:o}", "hex": lambda n: f"{n:x}" } argument_parser = ArgumentParser() argument_parser.add_argument("N", nargs="+", type=int) argument_parser.add_argument("-f", action=AddPrefix, choices=formatters, required=True) arguments = argument_parser.parse_args() for n in arguments.N: print( arguments.formatter(n) ) print(action.__class__.__name__) # "MappingChoicesAction" print(action.next_action.__class__) # <class '__main__.AddPrefix'>