Python argparse: Mutually exclusive required group with a required option - python

I am trying to have a required mutually exclusive group with one required parameter. Below is the code which I have put
#!/usr/bin/python
import argparse
import sys
# Check for the option provided as part of arguments
def parseArgv():
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", choices=[1,2,3,4],
help = "Increase verbosity")
group.add_argument("-q", "--quiet", action="store_true", help = "Run quietly")
name = parser.add_mutually_exclusive_group(required=True)
name.add_argument("-n", "--name", help = "Name of the virtual machine")
name.add_argument("-t", "--template", help = "Name of the template to use \
for creating vm. If path is not provided then it will be looked \
under template directory.")
parser.add_argument("-s", "--save", help = "Save the machine template. If \
path is not provided then it will be saved under template directory.");
#parser.add_argument("-k", "--kick_start", required = True, help = "Name of the \
# kick start file. If path is not provided then it will be look into http \
# directory.")
if len(sys.argv) == 1:
parser.print_help()
args = parser.parse_args()
if __name__ == '__main__':
parseArgv()
Now the output of this program as follow
$ python test.py
usage: test.py [-h] [-v {1,2,3,4} | -q] (-n NAME | -t TEMPLATE) [-s SAVE]
optional arguments:
-h, --help show this help message and exit
-v {1,2,3,4}, --verbose {1,2,3,4}
Increase verbosity
-q, --quiet Run quietly
-n NAME, --name NAME Name of the virtual machine
-t TEMPLATE, --template TEMPLATE
Name of the template to use for creating vm. If path
is not provided then it will be looked under template
directory.
-s SAVE, --save SAVE Save the machine template. If path is not provided
then it will be saved under template directory.
usage: test.py [-h] [-v {1,2,3,4} | -q] (-n NAME | -t TEMPLATE) [-s SAVE]
test.py: error: one of the arguments -n/--name -t/--template is required
But if I un-comment the from line 20 - 22 then the output change as below
$ python test.py
usage: test.py [-h] [-v {1,2,3,4} | -q] (-n NAME | -t TEMPLATE) [-s SAVE] -k
KICK_START
optional arguments:
-h, --help show this help message and exit
-v {1,2,3,4}, --verbose {1,2,3,4}
Increase verbosity
-q, --quiet Run quietly
-n NAME, --name NAME Name of the virtual machine
-t TEMPLATE, --template TEMPLATE
Name of the template to use for creating vm. If path
is not provided then it will be looked under template
directory.
-s SAVE, --save SAVE Save the machine template. If path is not provided
then it will be saved under template directory.
-k KICK_START, --kick_start KICK_START
Name of the kick start file. If path is not provided
then it will be look into http directory.
usage: test.py [-h] [-v {1,2,3,4} | -q] (-n NAME | -t TEMPLATE) [-s SAVE] -k
KICK_START
test.py: error: argument -k/--kick_start is required
But I want that either -n / -t along with -k become mandatory. How to achieve the same.

You have already achieved it! Argparse only prints the first error it finds, so while it may look like it's only checking -k, it actually recuires -n/-t too. You can see this by actually giving it the -k argument.
If you provide the -k argument, the error message will change from test.py: error: argument -k/--kick_start is required to test.py: error: one of the arguments -n/--name -t/--template is required.

Related

Mounting a directory within a docker build

I'm wondering how to use a simple script with a docker container.
The script is:
example python script
# Example python script
import argparse
import pathlib
def run(
*,
input: pathlib.Path | str,
output: pathlib.Path | str,
) -> None:
pathlib.Path(output).write_text(pathlib.Path(input).read_text().upper())
def main() -> int:
desc = "example script"
parser = argparse.ArgumentParser(
description=desc,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"-i",
"--input",
help=("input file"),
required=True,
)
parser.add_argument(
"-o",
"--output",
help=("output file"),
)
parser.add_argument(
"-x",
"--overwrite",
help=("Whether to overwrite previously created file."),
action="store_true",
)
args = parser.parse_args()
if not pathlib.Path(args.input).exists():
raise FileNotFoundError(f"input file {args.input} not found")
if not args.output:
raise argparse.ArgumentError(f"output not given")
if pathlib.Path(args.output).exists() and not args.overwrite:
raise FileExistsError(f"{args.output} already exists. ")
run(input=args.input, output=args.output)
if __name__ == "__main__":
raise SystemExit(main())
The script works fine on my system (without docker).
example docker file
The Dockerfile is:
FROM python:3.10.6-bullseye
COPY . .
ENTRYPOINT ["python", "example.py"]
This works (ish) after the following:
# build
docker build -t demo .
# run
docker run demo --help
Which outputs:
usage: example.py [-h] -i INPUT [-o OUTPUT] [-x]
example.
options:
-h, --help show this help message and exit
-i INPUT, --input INPUT
input file
-o OUTPUT, --output OUTPUT
output file
-x, --overwrite Whether to overwrite previously created file.
But I'm not sure how to use it with the -i and -o arguments.
what I'd like to do
I'd like to be able to do the following:
echo "text" > input.txt
# Create output from input
docker run demo -i input.txt -o output.txt
# Create output from input and say it's ok to overwrite
docker run demo -i input.txt -o output.txt -x
And after this there by a output.txt file created which has TEXT in it.
Error
I've tried to do this with the above command, and it doesn't work.
Eg:
echo "this" > input.txt
docker run demo -i input.txt -o output.txt -x
After this there is no output.txt file created which has THIS in it.
Attempted solution (--mount within the shell command)
Using the following seems to work - but it feels as though It's a lot in a shell command :
docker run \
--mount type=bind,source="$(pwd)",target=/check \
--workdir=/check demo:latest \
-i input.txt -o output.txt -x
Is there a way to do the --mount within the dockerfile itself?
I am doing a similar thing by running a compiler inside the docker container.
Obviously the docker image gets built whenever there is a new version of the compiler or the underlying image.
The container gets to run whenever I want to compile something. And here I have to mount source and target directories, but my docker command looks smaller than yours:
docker run --rm -v /sourcecode:/project:ro -v /compiled:/output:rw -v cache:/cache:rw compilerimagename
All the rest is defined within the image.

Argparse with subparsers not working and I cannot figure it out

I am using argparse with subparsers to do different actions. Each action has slightly different arguments.
I have set it up as the documentations instructs, with one action in subparser (parser_2) and the other subparser (parser_3) when i do help for usage of each it says the correct parameters
This is for cdf:
positional arguments:
repo name the repo to perform tasks on
optional arguments:
-h, --help show this help message and exit
--state {open,closed}
Print issues in a repository having status of
all(default), open, or closed
this is for clsiss:
usage: subparsprob.py clsiss [-h] repo issnums [issnums ...]
positional arguments:
repo name the repo to perform tasks on
issnums put the issue number(s) separated by blank
optional arguments:
-h, --help show this help message and exit
however when i actually run the commands i get usage errors:
for clsiss executing from command line:
PS C:\xxx> python subparsprob.py clsiss repo 1
usage: subparsprob.py clsiss [-h] repo issnums [issnums ...]
subparsprob.py clsiss: error: argument issnums: invalid int value: 'repo'
for cdf (executing from command line):
PS C:\xxx> python subparsprob.py cdf repo
usage: subparsprob.py clsiss [-h] repo issnums [issnums ...]
subparsprob.py clsiss: error: argument issnums: invalid int value: 'repo'
please help, I am using the correct arguments and number of argument but cannot figure out why the usage is wrong when i actually try to run it
i am still getting the same error , here is the entire code, I cannot figure it out. Please help
#!/usr/bin/python3
import argparse
import sys
import os
argv = sys.argv[1:]
# from issueGithub import IssueGithub, Taskname
def main():
parser=argparse.ArgumentParser(description='Invoke various github
actions')
subparsers = parser.add_subparsers(help='sub-commands for Github
options',dest='action_name')
parser_2 =subparsers.add_parser('clsiss',help='close issues')
parser_2.add_argument('repo',type=str,help="name the repo to perform
tasks
on")
parser_2.add_argument('issnums',type=int,nargs='+',help="put the issue
number(s) separated by blank")
parser_3 = subparsers.add_parser('cdf',help='create default
tasks/issues')
parser_3.add_argument('repo',type=str,help="name the repo to perform
tasks
on")
parser_3.add_argument('--state',choices=['open','closed'], default='all',
help='Print issues in a repository
args = parser.parse_args()
args2 = parser_2.parse_args()
args3 = parser_3.parse_args()
print("Args are")
print(args)
print(args2)
print(args3)
if __name__ =="__main__":
main()
With a copy-n-paste of your code:
1301:~/mypy$ python3 stack58684096.py -h
usage: stack58684096.py [-h] {clsiss,cdf} ...
Invoke various actions
positional arguments:
{clsiss,cdf} sub-commands for options
clsiss close issues
cdf create default tasks/issues
optional arguments:
-h, --help show this help message and exit
I don't get your errors:
1302:~/mypy$ python3 stack58684096.py clsiss repo 1
Namespace(action_name='clsiss', issnums=[1], repo='repo')
1302:~/mypy$ python3 stack58684096.py cdf repo
Namespace(action_name='cdf', repo='repo', state='all')
1302:~/mypy$ python3 stack58684096.py clsiss -h
usage: stack58684096.py clsiss [-h] repo issnums [issnums ...]
positional arguments:
repo name the repo to perform tasks on
issnums put the issue number(s) separated by blank
optional arguments:
-h, --help show this help message and exit
Your errors - sort of:
1302:~/mypy$ python3 stack58684096.py clsiss repo
usage: stack58684096.py clsiss [-h] repo issnums [issnums ...]
stack58684096.py clsiss: error: the following arguments are required: issnums
1304:~/mypy$ python3 stack58684096.py clsiss repo repo
usage: stack58684096.py clsiss [-h] repo issnums [issnums ...]
stack58684096.py clsiss: error: argument issnums: invalid int value: 'repo'

How to override the cli name in argparse -h usage information?

I am bundling my python app into an .AppImage file. Now, when I run it with flag -h I would expect it to print something along these lines:
$ ./mytool.AppImage -h
usage: mytool [-h] [-d DIR] [-f] [-e] [BLA [BLA ...]]
...
But due to the nature of the AppImage bundling process I get:
$ ./mytool.AppImage -h
usage: AppRun [-h] [-d DIR] [-f] [-e] [BLA [BLA ...]]
...
That is, AppRun instead of mytool.
So my question is:
How can I force override the app name so that regardless of how the app is called it will always print the same name in the usage string?
As per hpaulj's comment, this can solved by simply setting the prog parameter of the argparse.ArgumentParser constructor:
parser = argparse.ArgumentParser(
prog='mytool',
description='Some description...'
)

Python: ArgParse with a Main command and Sub command

Hi I'm using ArgParse to handle my arguments. I would like the code to work like this
# Main function
$ myApp -i INPUT -o OUTPUT -s STUFF
# Configure function
$ myApp config -a conf1 -b conf2
import argparse
from argparse import RawTextHelpFormatter
parser = argparse.ArgumentParser(description='myApp',formatter_class=RawTextHelpFormatter)
parser.add_argument('-i',help='input',required=True)
parser.add_argument('-o',help='output',required=True)
parser.add_argument('-s',help='stuff',default=None,required=False)
args = parser.parse_args()
subp = parser.add_subparsers()
conf_parser = subp.add_parser('config', help='configure')
conf_parser.add_argument('-a',help='a config file',default=None,required=False)
conf_parser.add_argument('-b',help='b config file',default=None,required=False)
conf_args = conf_arser.parse_args()
Here's the output
python sandbox/test1.py --help
usage: test1.py [-h] -i I -o O [-s S]
myApp
optional arguments:
-h, --help show this help message and exit
-i I input
-o O output
-s S stuff
I'm not getting the config args to show. I'm not sure what I'm doing wrong here.
Thanks!
Solved it
import argparse
from argparse import RawTextHelpFormatter
parser = argparse.ArgumentParser(description='myApp',formatter_class=RawTextHelpFormatter)
parser.add_argument('-i',help='input',required=True)
parser.add_argument('-o',help='output',required=True)
parser.add_argument('-s',help='stuff',default=None,required=False)
subp = parser.add_subparsers(help='configure')
conf_parser = subp.add_parser('config')
conf_parser.add_argument('-a',help='a config file',default=None,required=False)
conf_parser.add_argument('-b',help='b config file',default=None,required=False)
args = parser.parse_args()
python sandbox/test1.py --help
usage: test1.py [-h] -i I -o O [-s S] {config} ...
myApp
positional arguments:
{config} configure
optional arguments:
-h, --help show this help message and exit
-i I input
-o O output
-s S stuff
python sandbox/test1.py config --help
usage: test1.py config [-h] [-a A] [-b B]
optional arguments:
-h, --help show this help message and exit
-a A a config file
-b B b config file

how to load python script in interactive shell

I am trying sudo python get_gps.py -c and expecting it to load the script and then present the interactive shell to debug the script live as opposed to typing it in manually.
From the docs:
$ python --help
usage: /usr/bin/python2.7 [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-B : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x
-c cmd : program passed in as string (terminates option list)
-d : debug output from parser; also PYTHONDEBUG=x
-E : ignore PYTHON* environment variables (such as PYTHONPATH)
-h : print this help message and exit (also --help)
-i : inspect interactively after running script; forces a prompt even
if stdin does not appear to be a terminal; also PYTHONINSPECT=x
use -i option

Categories