arparse python argument from directory

138 Views Asked by At

I have a file structure like this:

project/ main_prog.py tools/ script.py md_script/ __init__.py md_script.py

I search in tools for local python modules. In this example it's md_script. And i want to use it as positional argument like install in my code, but when i use it, I'v got an error:

./jsh.py md_script
usage: jsh.py [-h] {install,call,list,log,restore} ... [md_script]
jsh.py: error: invalid choice: 'md_script' (choose from 'install', 'call', 'list', 'log', 'restore')

python3.4 on ubuntu14.10 Here is my code:

parser = argparse.ArgumentParser(prog='jsh.py',
    description='Some help.', epilog='Example of usage: some help')
subparsers = parser.add_subparsers()
parser_install = subparsers.add_parser('install', help = 'Install new project.')
parser_install.add_argument('install', nargs='?', help = 'Name of project to be installed')
if os.path.isdir(full/path/to/tools/):
   name_arg = next(os.walk(full/path/to/tools))[1]
   tools_arg = parser.add_argument_group('Tools', 'Modules from tools')
   for element in name_arg:
      tools_arg.add_argument(element, nargs='?', help='md_script description')
args = parser.parse_args()
try:
  if not len(sys.argv) > 1:
     parser.print_help()
  elif 'install' in args:
     do_some_stuff
  elif element in args:
     do_some_md_script_stuff
  else:
     parser.print_help()
2

There are 2 best solutions below

7
On

The usage line shows what's wrong:

usage: jsh.py [-h] {install,call,list,log,restore} ... [md_script]

You need to use something like

jsh.py install md_script

You specified subparsers, so you have to give it a subparser name.


From the usage it also looks like you created other subparsers, call, list, etc that you don't show in the code.

You also define positional arguments after creating subparser. That's where the [md_script] comes from. Be careful about making a lot of nargs='?' positionals (including the argument for the install subparser). This could make things confusing for your users. In fact it seems to confusing you. Remember that subparser is in effect a positional argument (one that requires 1 string).

I'd suggest experimenting with a simplier parser before creating one this complicated.


So from your comments and examples I see that you goal is let the user name a module, so your script can invoke it in some way or other. For that populating the subparsers with these names makes sense.

I wonder why you also create an optional positional argument with the same name:

module_pars = subparsers.add_parser(element, help = 'Modules from tools')
module_pars.add_argument(element, nargs='?', help=element+' description')

Is that because you are using the presence of the attribute as evidence that this subparser was invoked?

elif element in args:
    do_some_md_script_stuff

The argparse documentation has a couple of other ideas.

One particularly effective way of handling sub-commands is to combine the use of the add_subparsers() method with calls to set_defaults() so that each subparser knows which Python function it should execute.

and

However, if it is necessary to check the name of the subparser that was invoked, the dest keyword argument to the add_subparsers() call will work:

These avoid the messiness of a '?' positional argument, freeing you to use subparser arguments for real information.

subparsers = parser.add_subparsers(dest='module')
....
for element in name_arg:
    # module_pars = 'parser_'+element   # this does nothing
    module_pars = subparsers.add_parser(element, help = 'Modules from tools')
    module_pars.set_defaults(func = do_some_md_script_stuff)
    # or module_pars.set_default(element='I am here')
    module_pars.add_argument('real_argument')

Now you can check:

if args.module='md_script':
    do_some_md_script_stuff(args)

or

if hasattr(args, 'func'):
    func(args)

With the alternative set_defaults, your original test should still work:

if element in args:
    do_some_md_script_stuff
1
On

I did it like this. It's exactly what I want to.

if os.path.isdir(TOOLS_PATH):
   name_arg = next(os.walk(TOOLS_PATH))[1]
   for element in name_arg:
      module_pars = 'parser_'+element
      module_pars = subparsers.add_parser(element, help = 'Modules from tools')
      module_pars.add_argument(element, nargs='?', help=element+' description')

I didn't test it, because i dont have a test module, but ./jsh.py md_script goes into elif element in args: print('md_script') and print string. So it looks like it works. Thanks for all replies.

Edit: I tested it. In add_argument i must change nargs='?' for nargs='*' to catch more than one argument. And to catch arguments from command line I used this:

elif args:
   for element in name_arg:
      if element in args:
         element_arg = sys.argv[2:]
         done_cmd,msg = opt_exec_module(element,*element_arg)
         my_logger(done_cmd,msg)

Not very elegant but it works.