SDB PROGRAM LEARNER
SDB PROGRAM LEARNER

Reputation: 53

How to solve command line problem using python argparse?

I am working on a command-line project where I get some problem in the handling of the add subcommand, as indicated with comments below:

import argparse
import sys
def todo(args):
    if args.o =='add':
        print("Added Todo: "+args.x)
        f=open("todo.txt", "a+")
        c= str(count+1)
        p= '\n'+'. '+args.x
        f.write(p)
        f.close()
    elif args.o =='report':
        return 
    elif args.o =='del NUMBER':
        return 
    elif args.o =='done NUMBER':
        return 
    elif args.o =='help':
        print ("Usage :-"+
               "\n"+'$ ./todo add "todo item"           # add a new todo'+
               "\n"+'$ ./todo ls                        # Show remaining todos'+
               "\n"+'$ ./todo del NUMBER                # delete a todo'+
               "\n"+'$ ./todo done NUMBER               # complete a todo'+
               "\n"+'$ ./todo help                      # Show Usage'+
               "\n"+'$ ./todo report                    # Statistics')
    elif args.o =='ls':
        f=open("todo.txt", "r")
        print(f.read())
        f.close()
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('./todo', type=str, default="./todo")
    parser.add_argument('o', type=str, default="add")
    args = parser.parse_args()
    if args.o =='add':                     # <- starting here
        parser.add_argument('x', type=str, default=None)
        args = parser.parse_args()
        sys.stdout.write(str(todo(args)))  # <- ending here
    else:
        a = parser.parse_args()
        sys.stdout.write(str(todo(args)))

When I run this script in Powershell I get some error like

PS E:\python projects\fellowship challenge\python> python todo.py ./todo add " I am soham Das Biswas"
usage: todo.py [-h] ./todo o
todo.py: error: unrecognized arguments:  I am soham Das Biswas

How can I fix this?

Upvotes: 0

Views: 909

Answers (2)

hpaulj
hpaulj

Reputation: 231385

With

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('./todo', type=str, default="./todo")
parser.add_argument('o', type=str, default="add")
args = parser.parse_args()
print(args)

some sample runs:

1301:~/mypy$ python3 stack65328753.py -h
usage: stack65328753.py [-h] ./todo o

positional arguments:
  ./todo
  o

optional arguments:
  -h, --help  show this help message and exit

With 2 strings:

1303:~/mypy$ python3 stack65328753.py todo add
Namespace(o='add', **{'./todo': 'todo'})

with an extra:

1304:~/mypy$ python3 stack65328753.py todo add "extra string"
usage: stack65328753.py [-h] ./todo o
stack65328753.py: error: unrecognized arguments: extra string

This defines 2 arguments, both positionals. They are identified by position, not any sort of 'flag' string. The first string is assigned to the "./todo" attribute of args, and the second to "o". There's nothing to take a third string.

You can access the second value with args.o, but args../todo does not work. Instead you'd have to getattr(args, "./todo"). So using an "fancy" name like that is usually not a good idea.

Since these are required positionals, there's no point to specifying a default.

Change the arguments to optionals:

parser = argparse.ArgumentParser()
parser.add_argument("--dir", default="./todo")
parser.add_argument('-o', type=str, default="add")
args = parser.parse_args()
print(args)
print(args.dir, args.o)

and runs:

1315:~/mypy$ python3 stack65328753.py -h
usage: stack65328753.py [-h] [--dir DIR] [-o O]

optional arguments:
  -h, --help  show this help message and exit
  --dir DIR
  -o O

1316:~/mypy$ python3 stack65328753.py
Namespace(dir='./todo', o='add')
./todo add

1316:~/mypy$ python3 stack65328753.py --dir ./mydir 
Namespace(dir='./mydir', o='add')
./mydir add

1316:~/mypy$ python3 stack65328753.py --dir ./mydir -o subtract
Namespace(dir='./mydir', o='subtract')
./mydir subtract

1316:~/mypy$ python3 stack65328753.py --dir ./mydir -o "an extra string"
Namespace(dir='./mydir', o='an extra string')
./mydir an extra string

You try to add a "x" argument based on the args.o value

args = parser.parse_args()
if args.o =='add':                     # <- starting here
    parser.add_argument('x', type=str, default=None)
    args = parser.parse_args()

But the first parse_args() is the one that raises the unrecognized error and exits. So you never go on to this addition.

parser = argparse.ArgumentParser()
parser.add_argument("--dir", default="./todo")
parser.add_argument('-o', type=str, default="add")
args, extras = parser.parse_known_args()
print(args, extras)
print(args.dir, args.o)

if args.o == "add":
    parser.add_argument('x')
    args = parser.parse_args()
    print(args)

The help isn't changed, because it's the first parse that acts on that:

1318:~/mypy$ python3 stack65328753.py -h
usage: stack65328753.py [-h] [--dir DIR] [-o O]

optional arguments:
  -h, --help  show this help message and exit
  --dir DIR
  -o O


1323:~/mypy$ python3 stack65328753.py --dir ./mydir -o "an extra string"
Namespace(dir='./mydir', o='an extra string') []
./mydir an extra string

The parse_known_args puts the extra string in the extras. Now it goes on to add the x argument:

1323:~/mypy$ python3 stack65328753.py --dir ./mydir -o add "an extra string"
Namespace(dir='./mydir', o='add') ['an extra string']
./mydir add
Namespace(dir='./mydir', o='add', x='an extra string')

An alternative would be just to

args.x = extras

which will be (possibly empty) list.

With a problem like this, I strongly advise using that print(args) to see what parser does. And debug the parser before embedding it in a larger script. And for a start don't try to be too fancy. Use optionals for things that are optional, not required, and positionals for required things. There are ways of changing that, but it makes the inputs harder to understand, both for you and your users.

Upvotes: 1

Gena Kukartsev
Gena Kukartsev

Reputation: 1705

You may be misunderstanding how to define the arguments. You are probably assuming that you can use arguments in your example as positional arguments in a function call but that's not how argument parser works. Here is what happens in your example

python todo.py ./todo add " I am soham Das Biswas"

You defined two parameters: "./todo" and "o". The part ./todo add assigns the value of add to the ./todo argument.

The " I am soham Das Biswas" does not get assigned to "o" because you do not have "o" mentioned in the call. If you want to assign something to "o", you need something like o value_i_want_to_assign.

What do you want your code to do?

Upvotes: 0

Related Questions