I want to call a Django management command from one of my tests. I'm using django.core.management.call_command for this. And it doesn't work.
I have a command with 4 required arguments. When I call it, it complains all arguments are missing even though I'm passing them:
call_command('my_command', url='12', project='abc', website='zbb', title='12345')
I get the base command error that --url, --project, --website and --title are missing.
I did not specify a different destination for these arguments.
I looked at the call_command source and pinpointed the problem to the following line in call_command:
if command.use_argparse:
# Use the `dest` option name from the parser option
opt_mapping = {sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
for s_opt in parser._actions if s_opt.option_strings}
arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
defaults = parser.parse_args(args=args) ****** THIS *****
defaults = dict(defaults._get_kwargs(), **arg_options)
# Move positional args out of options to mimic legacy optparse
args = defaults.pop('args', ())
args is the positional arguments passed to call_commands, which is empty. I'm only passing named arguments. parser.parse_args complains the required variables are missing.
This is in Django 1.8.3.
Here is my command's add_arguments function (I just removed the help strings for brevity):
def add_arguments(self, parser):
parser.add_argument('--url', action='store', required=True)
parser.add_argument('--project', action='store', required=True)
parser.add_argument('--continue-processing', action='store_true', default=False)
parser.add_argument('--website', action='store', required=True)
parser.add_argument('--title', action='store', required=True)
parser.add_argument('--duplicate', action='store_true',default=False)
Based on that piece of code which you posted, I've concluded in call_command argument is required
that the required named arguments have to be passed in through *args, not just the positionals.
**kwargs bypasses the parser. So it doesn't see anything you defined there. **kwargs may override the *args values, but *args still needs something for each required argument. If you don't want to do that, then turn off the required attribute.
Related
I have this very simple ArgumentParser instance, with an optional positional argument and an option, which writes a constant to the same destination:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store_const', dest='path', const='<all>')
parser.add_argument('path', nargs='?')
# Prints None instead of '<all>'
print(parser.parse_args(['-a']).path)
But no matter what, parsing the command line ['-a'] does not yield a Namespace instance with path set to that constant. Instead, the default from the positional argument is used.
What am I doing wrong?
My use case is that the user should be able to specify a path (actually a list of paths). This list of paths defaults to the current working directory. But instead of using that default, -a can be passed, which should result in some configured root directory to be used. The full code for this part of the argument parser is this:
all_sentinel = object()
parser = argparse.ArgumentParser()
paths_group = parser.add_mutually_exclusive_group()
paths_group.add_argument('-a', action='store_const', dest='paths', const=all_sentinel)
paths_group.add_argument('paths', nargs='*', default=['.'])
A positional with nargs='?' has some special handling of its default (here None).
Normally defaults are assigned to the Namespace at the start of parsing, and overwritten by the actions, such as the optional.
Because an empty list of values satisfies the nargs, that positional is always 'seen'. But rather than assign [] or some other 'blank' to it, the parser assigns the default. So the positional's default overwrites the value set by '-a'.
nargs='*' gets the same kind of special handling.
I suspect that if you had another positional argument before the '-a', that you wouldn't see this effect. The '?*' positional would be processed before the '-a', and not overwrite its value.
Optionals are only processed if the flag occurs. Positionals are always processed, regardless of the nargs. The 'optional' positionals are processed, but with some extra handling of the defaults. But when they are processed relative to flagged arguments can vary.
That's some tricky behavior that I'm aware of simply because I've studied the code in detail, and answered a lot questions here and on the Python bug/issues.
Sharing the dest often does work, but that's more by default than design. It's the result of other design choices. argparse makes no promises in that regard. So if it isn't reliable, don't use it.
Python Version: Python 3.5.1
Django Version: Django 1.10.2
I am trying to write my own django custom command and I noticed that to take in an argument, it always ends up as a list.
See https://docs.python.org/3/library/argparse.html
Notice that the arguments for integers is a list of integer.
I wanted to have an argument that takes in a relative path or absolute path to a directory written in obviously str format.
My question is:
is it even possible to only accept the argument as a single str object for the parser object?
if it's possible, what do I need to change?
My current code is
def add_arguments(self, parser):
parser.add_argument('path', nargs='+', type=str)
# Named (optional) arguments
parser.add_argument(
'--whiteware',
action='store_true',
dest='whiteware',
default=True,
help='Affects whiteware variants only',
)
def handle(self, *args, **options):
directory_in_str = options['path']
print(directory_in_str)
Your issue is with the way you are creating the command line argument path.
From the documentation,
nargs - The number of command-line arguments that should be consumed.
and nargs='+' implies one or more space separated arguments, which would be casted into a list by argparse.
Now, if you are expecting a string, you can just do:
parser.add_argument('path', type=str) #type is str by default, no need to specify this explicitly.
Note that nargs is extremely useful when you want to restrict the choice types, etc.
For example:
parser.add_argument('path', nargs='+', choices=['a', 'b', 'c'])
This way, you can provide a bunch of options which would be available as a list for consumption.
Or even:
parser.add_argument('path', choices=['a', 'b', 'c'])
If you want a single option as a string.
You can read more on argparse options here in the documentation
I have a small program that uses argparse and a positional argument. I'm trying to allow that argument to be set by using an environment variable, but are not getting it to work.
I have seen this post: Setting options from environment variables when using argparse which mentions the same problem, but not for positional arguments.
This is the code so far:
import argparse
import os
class EnvDefault(argparse.Action):
def __init__(self, envvar, required=True, default=None, **kwargs):
if not default and envvar:
if envvar in os.environ:
default = os.environ[envvar]
if required and default:
required = False
super(EnvDefault, self).__init__(default=default, required=required, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('testvar', help="Test variable", action=EnvDefault, envvar='TEST_VAR')
parser.add_argument('--othervar', help="Other variable", action='store_true')
args = parser.parse_args()
if not args.testvar: exit(parser.print_usage())
print args.testvar
Which returns this:
$ TEST_VAR="bla" ./test.py
usage: test.py [-h] [--othervar] testvar
test.py: error: too few arguments
You need to make positional argument optional, try nargs='?':
...
parser.add_argument('testvar', help="Test variable", action=EnvDefault,
envvar='TEST_VAR', nargs='?')
...
Note that output changes slightly:
$ python test.py
usage: test.py [-h] [--othervar] [testvar]
Note: There's one side effect - it doesn't return error, even if env variable is not set.
The too few error message indicates that you have slightly older version of Python/argparse. Here's the code that generates the message. It occurs at the end of parsing:
# if we didn't use all the Positional objects, there were too few
# arg strings supplied.
if positionals:
self.error(_('too few arguments'))
# make sure all required actions were present
for action in self._actions:
if action.required:
if action not in seen_actions:
name = _get_action_name(action)
self.error(_('argument %s is required') % name)
positionals starts a list of all the positional arguments, which are removed as they get matched up with argument strings. So it is testing if any were not matched.
Note that the test of the required attribute occurs after this positional test, so changing it, as your Action does, does not help.
The only way to make a positional optional is with nargs - ? or *. An empty list of strings matches those, so such a positional is always consumed.
Check the docs for these. The const parameter might be useful.
The latest version drops that if positionals test, using only the required test to generate a list of arguments that were not used. Your special Action might work in that code.
I am trying to use argparse to process several optional arguments. Each of the arguments will have a single optional argument as well. For example I have a script called runner.py. I want to call runner.py --functionals --capacity --performance and I want it to use the const values I have set. This part is working. I also want to be able to specify arguments such as --functionals test1 --performance test2 and --capacity test3. Instead of const, now I except the arguments to have the given values. for ex. functionals should be test1, performance test2 etc. What results in the latter case is I get: -c: error: argument --performance: not allowed with argument --functionals
Code for the parser looks like:
def get_parser():
parser = argparse.ArgumentParser(add_help=False)
required_arguments = parser.add_argument_group(title = "required arguments")
test_arguments = parser.add_mutually_exclusive_group()
test_arguments.add_argument(
'--capacity',
nargs='?',
)
test_arguments.add_argument(
'--functionals',
nargs='?',
)
test_arguments.add_argument(
'--performance',
nargs='?',
)
return parser
My mistake was that I was using a mutually exclusive group. I should have been using an regular argument group.
I'm trying to write a Python program that could be extended by third parties. The program will be run from the command line with whatever arguments are supplied.
In order to allow third parties to create their own modules, I've created the following (simplified) base class:
class MyBaseClass(object):
def __init__(self):
self.description = ''
self.command = ''
def get_args(self):
# code that I can't figure out to specify argparse arguments here
# args = []
# arg.append(.....)
return args
Any arguments that they supply via get_args() will be added to a subparser for that particular module. I want them to be able to specify any type of argument.
I'm not sure of the best way to declare and then get the arguments from the subclassed modules into my main program. I successfully find all subclasses of MyBaseClass and loop through them to create the subparsers, but I cannot find a clean way to add the individual arguments to the subparser.
Here is the current code from the main program:
for module in find_modules():
m = module()
subparser_dict[module.__name__] = subparsers.add_parser(m.command, help=m.help)
for arg in m.get_args():
subparser_dict[module.__name__].add_argument(...)
How can I best specify the arguments in the external modules via get_args() or similar and then add them to the subparser? One of my failed attempts looked like the following, which doesn't work because it tries to pass every possible option to add_argument() whether it has a value or is None:
subparser_dict[module.__name__].add_argument(arg['long-arg'],
action=arg['action'],
nargs=arg['nargs'],
const=arg['const'],
default=arg['default'],
type=arg['type'],
choices=arg['choices'],
required=arg['required'],
help=arg['help'],
metavar=arg['metavar'],
dest=arg['dest'],
)
Without trying to fully understand your module structure, I think you want to be able to provide the arguments to a add_argument call as objects that you can import.
You could, for example, provide a list of positional arguments, and dictionary of keyword arguments:
args=['-f','--foo']
kwargs={'type':int, 'nargs':'*', 'help':'this is a help line'}
parser=argparse.ArgumentParser()
parser.add_argument(*args, **kwargs)
parser.print_help()
producing
usage: ipython [-h] [-f [FOO [FOO ...]]]
optional arguments:
-h, --help show this help message and exit
-f [FOO [FOO ...]], --foo [FOO [FOO ...]]
this is a help line
In argparse.py, the add_argument method (of a super class of ArgumentParser), has this general signature
def add_argument(self, *args, **kwargs):
The code of this method manipulates these arguments, adds the args to the kwargs, adds default values, and eventually passes kwargs to the appropriate Action class, returning the new action. (It also 'registers' the action with the parser or subparser). It's the __init__ of the Action subclasses that lists the arguments and their default values.
I would just return an ArgumentParser instance from your get_args method. Then you can create a new ArgumentParser to join all other argument parsers using the parents argument: https://docs.python.org/3/library/argparse.html#parents.