robertspierre
robertspierre

Reputation: 4467

python argparse always set defaults related to an optional argument

I use python 3, i want to set argparse to always set the default key in relation to a predefined optional argument of my choice.

E.g. I pass some input files to the script. An optional argument specify if the input file should be rotated by the script. I get a list of input files but I also want a list of the same size containing 'True' where the user want to rotate the input file, or the default false when the user has not made a choice

For example

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--input', action='append', required=True, help='Input pdf files.')
parser.add_argument('--rotate', action='store_true', default=False, help='Rotate the input file')
args = parser.parse_args()
print(args)

I call the script with this command:

python argparse_test.py --input 1.pdf --input 2.pdf --rotate

This is the output

Namespace(input=['1.pdf', '2.pdf'], rotate=True)

But I want this output instead

Namespace(input=['1.pdf', '2.pdf'], rotate=[False,True])

Please note that --rotate can be ANY operation that must be performed by the python script i'm writing on the --input file before the --rotate itself. So i need to know which --input file --rotate is refering to

Upvotes: 0

Views: 1386

Answers (1)

hpaulj
hpaulj

Reputation: 231738

So

python argparse_test.py --input 1.pdf --input 2.pdf --rotate

is supposed to mean - set rotate to True for 2.pdf because it follows that name, but leave it False for 1.pdf because there isn't a --rotate flag following it?

It can't work that way. flagged arguments like --rotate and --input can occur in any order. And store_true arguments can't operate in the append mode like your --input.

Some alternatives that might work:

--input as nargs=2
python argparse_test.py --input 1.pdf 0 --input 2.pdf 1
Namespace(input=[['1.pdf','0'], ['2.pdf','1']])

--input as nargs='+'
python argparse_test.py --input 1.pdf --input 2.pdf 1
Namespace(input=[['1.pdf'], ['2.pdf','1']])

In other words, the user gives an added string after the file name to indicate whether it is to be rotated or not.

I was going to suggest an append_const for rotate, but there's no way to insert a default into the list.

I can imagine defining a pair of custom action classes - see below.

What I think Waylan was suggesting was to have 2 append lists, one for files that should be rotated, and another for ones that shouldn't.

How would you convey requirements like this in the usage and help message? What may seem logical to you in the middle of development, might not be so obvious to another user, or to yourself six months from now.


Custom Action classes that seem to behave as desired. This isn't a trivial customization, but not a complicated or obscure one either. It does require some detailed understanding of the argparse code.

import argparse

class Input_action(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string=None):
        # assume attribute has already been set to []
        items = getattr(namespace, self.dest)
        items.append(values)
        setattr(namespace, self.dest, items)

        dest = 'file_rotate'
        values = False
        items = getattr(namespace, dest)
        items.append(values)
        setattr(namespace, dest, items)

class Rotate_action(argparse._StoreTrueAction):

    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, self.const)
        dest = 'file_rotate'  # not same as self.dest
        items = getattr(namespace, dest)
        if items:
            items[-1] = True
        setattr(namespace, dest, items)

parser = argparse.ArgumentParser()
parser.add_argument('-i','--input', action=Input_action)
parser.add_argument('-r','--rotate', action=Rotate_action)

parser.print_help()

# simpler to initialize these attributes here than in the Actions
ns = argparse.Namespace(input=[], file_rotate=[])
print parser.parse_args(namespace=ns)

output:

1030:~/mypy$ python stack28967342.py -i one -r -i two -i three -r
usage: stack28967342.py [-h] [-i INPUT] [-r]

optional arguments:
  -h, --help            show this help message and exit
  -i INPUT, --input INPUT
  -r, --rotate
Namespace(file_rotate=[True, False, True], input=['one', 'two', 'three'], rotate=True)

Note the 2 lists in the resulting Namespace. The rotate attribute isn't needed, but removing it requires more work. The help does not indicate anything about the special pairing of options.


Here's an alternate Action pair that works with one list and composite object with name and rotate attribute. It might be easier to generalize this approach.

class MyObj(argparse._AttributeHolder):
    "simple class to hold name and various attributes"
    def __init__(self, filename):
        self.name = filename
        self.rotate = False
        # define other defaults here

class Input_action(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string=None):
        items = argparse._ensure_value(namespace, self.dest, [])
        items.append(MyObj(values))
        setattr(namespace, self.dest, items)

class Rotate_action(argparse._StoreTrueAction):

    def __call__(self, parser, namespace, values, option_string=None):
        # with default=SUPPRESS, rotate does not appear in namespace
        dest = 'input'  # could make this a parameter
        items = getattr(namespace, dest)
        if items:
            items[-1].rotate = True
        # no need to set, since I'm just modifying an existing object

parser = argparse.ArgumentParser()
parser.add_argument('-i','--input', action=Input_action,
    help='create a default input object with this name')
parser.add_argument('-r','--rotate', action=Rotate_action, default=argparse.SUPPRESS,
    help = 'set rotate attribute of preceeding input object')

parser.print_help()
print parser.parse_args()

producing args like

Namespace(input=[MyObj(name='one', rotate=True), MyObj(name='two', rotate=False), MyObj(name='three', rotate=True)])

Upvotes: 1

Related Questions