Get arguments from ArgumentParser without calling parse_args - python

I have the following code and I want to use to extract the config parameter.
parser = argparse.ArgumentParser()
parser.add_argument(
"--config",
type=str,
default="src/config.yml",
dest="config"
)
My issue is that I cannot use parser.parse_args() (because I'm running the script from uvicorn and the parse_args is raising an error. Is there a way to retrieve the config parameter without the use of parse_args?
Other answers I've seen make use of parse_args.

If you want to parse an argument array that is not passed via sys.argv, for example one that you created, simply pass an array to the parse_args() function.
my_args = ["--config", "my_value"]
parsed_args = parser.parse_args(my_args)
print(parsed_args.config) # Prints "my_value"

Related

Argparse module with multiple command and arguments

I'm trying to parse commands with arguments using the python 3 Built-in argparse module.
I have read the argparse documentation partially, however, I could not find anything that meets my requirements.
I parse the arguments as input (I have my reasons).
I have multiple commands, for each, there are both essential and optional arguments.
For example:
restart --name (the name is replaced)
restart is the command and name is the essential argument.
Currently my code would count the "--" in the input and call the function with corresponding booleans (if --all given, is_all boolean parameter will be true)
I can also add an optional argument --all (all is not replaced).
Sounds like you are looking for something like this
def get_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("--arg1", required=False, default=None)
parser.add_argument("--arg2", required=False, default=None)
return parser.parse_args()
args = get_arguments()
if args.arg1:
# do something
Really hard to answer this without seeing your code or example of what you want.
I'm assuming you're doing something like a shell of sorts. I'm going to also assume that each line entered has a command, each with their own arguments.
from argparse import ArgumentParser
def get_parser(cmd):
'''Returns a parser object for a given command'''
# Instantiate the parser object, add the appropriate arguments for the command
return parser # This is an example -- you need to instantiate it
def main():
while True:
try:
in_line = input('> ')
if not in_line.strip(): # Quit if empty
break
args = in_line.split()
parser = get_parser(args[0])
opts = parser.parser_args(args)
# Do stuff with opts depending on command
except EOFError:
break
except SystemExit:
pass # Prevent failures from killing the program by trapping sys.exit()

How to provide a python argparse.parser with arguments from inside the code, without command-line arguments?

I have a code that takes the command-line arguments into a parser and modifies some configuration settings. Something like this:
command:
python mycode.py --config-file "some_file.yaml" SOMETHING.subsetting_a 2 SOMETHING.subsetting_b 3
and then it does:
import argparse
parser = argparse.ArgumentParser(description="Some description here")
parser.add_argument(
"--config-file",
default="",
metavar="FILE",
help="path to config file",
type=str,
)
//some more 'add_argument' lines here
args = parser.parse_args()
But as I am using jupyter notebook, it would be easier to provide the arguments directly to the parser, as if they come from the command-line. How can I create a string containing the command (as mentioned above) and pass it to parser?
parse_args's first optional argument is the list of arguments to parse, the signature is:
ArgumentParser.parse_args(args=None, namespace=None)
It just takes args from sys.argv if you don't provide it.
So just call it as:
args = parser.parse_args(['mycode.py', '--config-file', "some_file.yaml", 'SOMETHING.subsetting_a', '2', 'SOMETHING.subsetting_a'])
(with the list containing whatever you like instead) and it will use it instead of sys.argv.
Note: As #ShadowRanger mentioned, there is no need to use sys.argv. See his response.
One way is to use sys.argv to mimic the command-line arguments:
import sys
sys.argv = [
"--config-file" , "some_file.yaml",
"SOMETHING.subsetting_a" , "2",
"SOMETHING.subsetting_b" , "3"]
args = parser.parse_args(sys.argv)
The content of args is something liek this:
> Namespace(config_file='some_file.yaml', opts=['SOMETHING.subsetting_a', '2', 'SOMETHING.subsetting_b', '3')
which is similar to the output of print(parser.parse_args()).

Parsing multiple subparsers, but with global arguments

I have read quite a few questions and answers on how to define, parse and run multiple subparsers to run sth like
tool.py func_a -a 12 func_b -b 15 input.txt output.txt
^-- main parser args
^--------------- subparser b
^---------------------------- subparser a
They usually suggest something like:
def func_a(args):
pass
def func_b(args):
pass
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
# Define subparsers
subp_a = subparsers.add_parser(func_a.__name__)
subp_a.set_defaults(func=func_a)
subp_a.add_argument('-a')
subp_b = subparsers.add_parser(func_b.__name__)
subp_b.set_defaults(func=func_b)
subp_b.add_argument('-b')
# Define global parameters
parser.add_argument('input', type=argparse.FileType('r'))
parser.add_argument('output', type=argparse.FileType('wb', 0))
# Parse and run through all arguments
rest = sys.argv[1:]
while rest:
args, rest = parser.parse_known_args(rest)
args.func(args)
However this implementation has one problem: The arguments input and output are defined for the main parser and should only be used once. However, each call of parser.parse_known_args(rest) expects the values to be set and consequently removes it from rest.
This means the first call to parse_known_args retrieves the values and each subsequent call fails due to missing arguments.
Is there a solution to overcome this without manually copying the values into the rest list?
Yes, the first parse_known_args consumes the filenames along with the 1st subparser level.
You could define 2 parsers, one with the 'global' positionals, one without. First parse with the 'with' parser, hanging onto its args (for the filenames). Then work your way down the subparser stack with the 'without' parser.
Here's a simpler example:
parser = argparse.ArgumentParser()
parser.add_argument('foo')
subp = parser.add_subparsers(dest='cmd')
cmd1 = subp.add_parser('cmd1')
cmd1.add_argument('-a')
cmd2 = subp.add_parser('cmd2')
# parser.add_argument('bar') # confusing location
parser1 = argparse.ArgumentParser()
subp = parser1.add_subparsers(dest='cmd')
cmd1 = subp.add_parser('cmd1', parents=[cmd1], add_help=False)
cmd2 = subp.add_parser('cmd2')
# Parse and run through all arguments
args, rest = parser.parse_known_args()
print args, rest
while rest:
args, rest = parser1.parse_known_args(rest)
print args, rest
I put the parser foo before subp because it creates less confusion there. A positional at the end (bar) may be confused with arguments for one of the subparsers, or one of the nested subparsers - especially when generating error messages.
To get an idea of how parse_args will allocate argument strings to the various positionals, try a script like:
parser = argparse.ArgumentParser()
parser.add_argument('foo')
parser.add_argument('cmd', nargs=argparse.PARSER, choices=['cmd1','cmd2'])
parser.add_argument('bar')
print parser.parse_args()
To parser, the subparsers argument looks just a like a positional that takes at least one string (kind of like '+'), and the 1st must match choices. It will receive all the 'remaining' strings, consistent with the demands of the other positionals.

Python argparse: command-line argument that can be either named or positional

I am trying to make a Python program that uses the argparse module to parse command-line options.
I want to make an optional argument that can either be named or positional. For example, I want myScript --username=batman to do the same thing as myScript batman. I also want myScript without a username to be valid. Is this possible? If so, how can it be done?
I tried various things similar to the code below without any success.
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-u", "--user-name", default="admin")
group.add_argument("user-name", default="admin")
args = parser.parse_args()
EDIT: The above code throws an exception saying ValueError: mutually exclusive arguments must be optional.
I am using Python 2.7.2 on OS X 10.8.4.
EDIT: I tried Gabriel Jacobsohn's suggestion but I couldn't get it working correctly in all cases.
I tried this:
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-u", "--user-name", default="admin", nargs="?")
group.add_argument("user_name", default="admin", nargs="?")
args = parser.parse_args()
print(args)
and running myScript batman would print Namespace(user_name='batman'), but myScript -u batman and myScript --user-name=batman would print Namespace(user_name='admin').
I tried changing the name user-name to user_name in the 1st add_argument line and that sometimes resulted in both batman and admin in the namespace or an error, depending on how I ran the program.
I tried changing the name user_name to user-name in the 2nd add_argument line but that would print either Namespace(user-name='batman', user_name='admin') or Namespace(user-name='admin', user_name='batman'), depending on how I ran the program.
The way the ArgumentParser works, it always checks for any trailing positional arguments after it has parsed the optional arguments. So if you have a positional argument with the same name as an optional argument, and it doesn't appear anywhere on the command line, it's guaranteed to override the optional argument (either with its default value or None).
Frankly this seems like a bug to me, at least when used in a mutually exclusive group, since if you had specified the parameter explicitly it would have been an error.
That said, my suggested solution, is to give the positional argument a different name.
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-u','--username')
group.add_argument('static_username',nargs='?',default='admin')
Then when parsing, you use the optional username if present, otherwise fallback to the positional static_username.
results = parser.parse_args()
username = results.username or results.static_username
I realise this isn't a particularly neat solution, but I don't think any of the answers will be.
Here is a solution that I think does everything you want:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--user-name", default="admin")
# Gather all extra args into a list named "args.extra"
parser.add_argument("extra", nargs='*')
args = parser.parse_args()
# Set args.user_name to first extra arg if it is not given and len(args.extra) > 0
if args.user_name == parser.get_default("user_name") and args.extra:
args.user_name = args.extra.pop(0)
print args
If you run myScript -u batman or myScript --user-name=batman, args.user_name is set to 'batman'. If you do myScript batman, args.user_name is again set to 'batman'. And finally, if you just do myScript, args.user_name is set to 'admin'.
Also, as an added bonus, you now have all of the extra arguments that were passed to the script stored in args.extra. Hope this helps.
Try to use the "nargs" parameter of the add_argument method.
This way it works for me.
Now you can add the username twice,
so you have to check it and raise an error, if you want.
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--user-name", default="admin")
parser.add_argument("user_name", default="admin", nargs="?")
args = parser.parse_args()
print(args)

Python arguments not being parsed when passed

I have the following code at the minute:
parser = argparse.ArgumentParser(prog='Tempus')
ex_group = parser.add_mutually_exclusive_group(required=True)
## Miscellaneous but needed args
parser.add_argument('--mode', type=str, choices=['xml', 'text', 'term'],
dest='mode', required=True, help='export mode')
parser.add_argument('-v', '--verbose', action='store_true',
dest='verbose', help='enable verbose/debug mode')
# Input methods
ex_group.add_argument('--i', action='store_true',
dest='interactive', help='enter interactive mode')
ex_group.add_argument('--p', metavar='I', type=float,
dest='integer', help='percentage to use')
args = parser.parse_args()
However when I pass the arguments that are needed, in any order, I get the error:
Tempus: error: argument --mode is required
And this happens, even when I pass that argument. Any way to sort this?
Thank you!
EDIT: Thanks everyone, I got it working, turns out executing it without first specifying the python exe before it doesn't pass arguments.
Just another quick question: is it possible to create an argument similar to --mode in my code, but let an additional argument be passed to one of the choices?
For example, have a command such as --input which can take two arguments 'integer' and 'interactive', but I can also pass a number to the --input integer command? So the command would read: --input integer 23 for example? Is this possible?
Try
1. to print sys.argv before parsing or
2. to explicitely add arguments inside the script as in http://docs.python.org/dev/library/argparse.html#parsing-arguments
So you can exclude any confusion about which arguments argparse actually sees.

Categories

Resources