Argparse add option strings - python-3.x

That question will be strange in some point...
I'm trying to add the option strings in the parser after adding one argument.
For example
import argparse
p = argparse.ArgumentParser()
p.add_argument(dest = 'myvar')
p._actions[1].option_strings = ['-foo']
p.parse_args('-foo 1')
This example doesn't work, it says:
: error: the following arguments are required: -foo
Even though I'm supplying the argument...
Anyone know why this is occurring?
Is there some way to add the option strings after the add_argument method?

After looking at the source code of argparse, I found you also have to register the action by the option string it has:
import argparse
p = argparse.ArgumentParser()
p.add_argument(dest = 'myvar')
p._actions[1].option_strings = ['-foo']
# You also need to do this
p._option_string_actions['-foo'] = p._actions[1]
args = p.parse_args(['-foo', '100'])
print('myvar is', args.myvar) # myvar is 1
Note the change when calling parse_args.
I hope this help!!

Related

Python argparse with possibly empty string value

I would like to use argparse to pass some values throughout my main function. When calling the python file, I would always like to include the flag for the argument, while either including or excluding its string argument. This is because some external code, where the python file is being called, becomes a lot simpler if this would be possible.
When adding the arguments by calling parser.add_argument, I've tried setting the default value to default=None and also setting it to default=''. I can't make this work on my own it seems..
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--projects_to_build', default='')
args = parser.parse_args()
This call works fine:
py .\python_file.py -p proj_1,proj_2,proj_3
This call does not:
py .\python_file.py -p
python_file.py: error: argument -p/--projects_to_build: expected one argument
You need to pass a nargs value of '?' with const=''
parser.add_argument('-p', '--projects_to_build', nargs='?', const='')
You should also consider adding required=True so you don't have to pass default='' as well.

argparse like docker cli

Hello i am writing cmd tool,
i want to have behaviour like docker does:
docker container run --help
print the help for this particular command.
im stucked with code:
parser.add_argument("method", help=getHelp())
but the method can be anything like
add
remove
update
and how to later add a method in add like:
add ram
add cpu
i can add subparser for add but how to add later a subparser for ram ?
How can i achieve that with argparse in python?
Is it even possible?
Can somebody show me example of third deep command with its own arguments ?
import argparse
import pprint
import random
def get_comments(args):
return [{'post_id': args.post_id,
'comment_id': str(random.randrange(1, 1000)),
'comment': "< comment's body >"}
for _ in range(random.randrange(1, 10))]
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
list_parser = subparsers.add_parser('list')
list_subparsers = list_parser.add_subparsers(dest='type')
comments_parser = list_subparsers.add_parser('comments')
comments_parser.add_argument('post_id')
comments_parser.set_defaults(func=get_comments)
accounts_parser = list_subparsers.add_parser('accounts')
show_parser = subparsers.add_parser('show')
args = parser.parse_args()
print(args)
print(args.command)
#result = args.func(args)
print(parser)
#pprint.pprint(args)

Nature of optional arguments argparse Python

As I have started to learn argparse module, I try to get my head around optional arguments in Python argparse. I would like to add my own arguments, as I present below:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--sense", help = "What you are seeking")
arg = parser.parse_args()
if arg.sense:
print("42")
However, the help doesn't seem to accept the new argument in output:
"""python3 file.py -h"""
usage: file.py [-h]
optional arguments:
-h, --help show this help message and exit
What surprises me is the fact, that when I copied the code from a tutorial:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("verbosity turned on")
https://docs.python.org/3/howto/argparse.html
the help changed. Is there a list of possible optional arguments? Am I allowed to create new ones?

Overriding namespace with yaml

Problem statement
I want the options supported in a python module to be overridable with an .yaml file, because in some cases there are too many options to be specified with non-default values.
I implemented the logic as follows.
parser = argparse.ArgumentParser()
# some parser.add statements that comes with default values
parser.add_argument("--config_path", default=None, type=str,
help="A yaml file for overriding parameters specification in this module.")
args = parser.parse_args()
# Override parameters
if args.config_path is not None:
with open(args.config_path, "r") as f:
yml_config = yaml.safe_load(f)
for k, v in yml_config.items():
if k in args.__dict__:
args.__dict__[k] = v
else:
sys.stderr.write("Ignored unknown parameter {} in yaml.\n".format(k))
The problem is, for some options I have specific functions/lambda expressions to convert the input strings, such as:
parser.add_argument("--tokens", type=lambda x: x.split(","))
In order to apply corresponding functions when parsing option specifications in YAML, adding so many if statements does not seem a good solution. Maintaining a dictionary that changes accordingly when new options are introduced in parser object seems redundant. Is there any solution to get the type for each argument in parser object?
If the elements that you add to the parser with add_argument start with -- then they
are actually optional and usually called options. You can find these walking over
the result of the _get_optonal_actions() method of the parser instance.
If you config.yaml looks like:
tokens: a,b,c
, then you can do:
import sys
import argparse
import ruamel.yaml
sys.argv[1:] = ['--config-path', 'config.yaml'] # simulate commandline
yaml = ruamel.yaml.YAML(typ='safe')
parser = argparse.ArgumentParser()
parser.add_argument("--config-path", default=None, type=str,
help="A yaml file for overriding parameters specification in this module.")
parser.add_argument("--tokens", type=lambda x: x.split(","))
args = parser.parse_args()
def find_option_type(key, parser):
for opt in parser._get_optional_actions():
if ('--' + key) in opt.option_strings:
return opt.type
raise ValueError
if args.config_path is not None:
with open(args.config_path, "r") as f:
yml_config = yaml.load(f)
for k, v in yml_config.items():
if k in args.__dict__:
typ = find_option_type(k, parser)
args.__dict__[k] = typ(v)
else:
sys.stderr.write("Ignored unknown parameter {} in yaml.\n".format(k))
print(args)
which gives:
Namespace(config_path='config.yaml', tokens=['a', 'b', 'c'])
Please note:
I am using the new API of ruamel.yaml. Loading this way is actually faster
than using from ruamel import yaml; yaml.safe_load() although your config files
are probably not big enough to notice.
I am using the file extension .yaml, as recommended in the official FAQ. You should do so as well, unless you cannot (e.g. if your
filesystem doesn't allow that).
I use a dash in the option string --config-path instead of an underscore, this is
somewhat more natural to type and automatically converted to an underscore to get valid
identifier name
You might want to consider a different approach where you parse sys.argv by hand for
--config-path, then set the defaults for all the options from the YAML config file and
then call .parse_args(). Doing things in that order allow you to override, on
the commandline, what has a value in the config file. If you do things your way, you always
have to edit a config file if it has all correct values except for one.

Call a function dynamically from a variable - Python

Ok so I have two files, filename1.py and filename2.py and they both have a function with same name funB. The third file process.py has function that calls function from either files. I seem to be struggling in calling the correct function.
In process.py:
from directoryA.filename1 import funB
from directoryA.filename2 import funB
def funA:
#do stuff to determine which filename and save it in variable named 'd'
d = 'filename2'
# here i want to call funB with *args based on what 'd' is
So i have tried eval() like so:
call_right_funB = eval(d.funB(*args))
but it seems not to work.
Any help is appreciated.
The problem is, you can't use eval() with a combination of a string and a method like that. What you have written is:
call_right_funB = eval('filename'.funB(*args))
What you can do is:
call_right_funB = eval(d + '.funB(*args)')
But this is not very pythonic approach.
I would recommend creating a dictionary switch. Even though you have to import entire module:
import directoryA.filename1
import directoryA.filename2
dic_switch = {1: directoryA.filename1, 2: directoryA.filename2}
switch_variable = 1
call_right_funB = dic_switch[switch_variable].funB(*args)
Hope it helps.

Resources