Python: How to make command line arguments available for objects? - python

I am relatively new to Python and I am trying to solve the following problem at the moment. I am writing a script which parses command line arguments with argparse. Besides some other things I have a verbosity flag, which I want to make available for some objects. Currently I am passing it to the constructor and I am wondering if there is a more elegant way for doing this.
class MyClass(object):
def __init__(self, verbosity=False):
self.verbosity = verbosity
def do_something(self):
if self.verbosity:
print("Doing something ...")
.
import argparse
import myclass
def main():
parser = argparse.ArgumentParser(description='Testing.')
parser.add_argument('-v', '--verbosity', action='store_true',
help="Produce verbose output")
args = parser.parse_args()
object = myclass.MyClass(verbosity=args.verbosity)
object.do_something()
if __name__ == "__main__":
main()
Any help would really be appreciated.

I would say what you're doing is the cleanest approach, at least in this simple case. You could pass the args namespace that argparse gives you to the MyClass initializer, but then you'll need to construct a similar object if you want to run tests on your class, or to use it as a library from another script.
If you want the same command line options to be available from multiple classes or scripts, you could create a config.py that looks something like this:
import argparse
__all__ = ['options']
parser = argparse.ArgumentParser(description='Testing.')
parser.add_argument('-v', '--verbosity', action='store_true',
help="Produce verbose output")
args = parser.parse_args()
options = vars(args)
Then from your script you can do from config import options and get the parsed argument from options['verbosity']. You could also change the value of options - for something like:
if self.too_much_output():
options['verbosity'] = False
and have that change be available to any code that's using options.
(The idea to use vars(args) comes from this answer to another argparse-related question.)

Related

Good practice for defining variables for paths for entire python package interactively via a script

So I'm not really sure how to do this and if this is a good idea at all. But I have a directory of some python modules and they all have references in there to some specific directories that get created while the sequential execution of one python module after the other.
So in every module I declare globally the variables:
path_to_zips = "../zips"
path_to_unzipped = "../unzipped"
And I thought there must be a better way to handle this. I know that I can declare the paths in a script and then do a
from paths_script import *
But what I would like to do is do give the user the chance to run a script (let's call it set_paths) that would kind of interactively set those paths and if he doesn't run it there will be some defaults.
So I came up with this
import argparse
# parse some arguments
parser = argparse.ArgumentParser(description="Set the Paths for the entire project")
# get optionsal arguments
parser.add_argument("-data", "--data", dest="data_dir", metavar="", help="folder for all the Zips and SAFEs", default="../data", type=str)
parser.add_argument("-slc", "--slc", dest="slc_dir", metavar="", help="folder for all the SLCs", default="../data/SLC", type=str)
parser.add_argument("-dem", "--dem", dest="dem_dir", metavar="", help="folder for all the DEMs", default="../data/DEM", type=str)
parser.add_argument("-tup", "--tuple", dest="tuple_dir", metavar="", help="folder for all the tuples", default="../data/tuples", type=str)
args =parser.parse_args()
def parse_dates(args):
return args.data_dir, args.slc_dir, args.dem_dir, args.tuple_dir
def main():
# how to define them globally?
data_dir, slc_dir, dem_dir, tuples_dir = parse_dates(args)
if __name__ == "__main__":
main()
But then again I don't know how to treat those *_dir variables to make them importable into another script like:
from set_paths import *; print(data_dir)
Maybe someone understands the issue and has any idea;)

Importing and using a function that takes argparse as a parameter

I'm trying to import a program and use a couple functions in it, but I'm running into an issue pertaining to argparse.
In the functions I would like to use, the creator passes his parser args to the function like so.
args = parser.parse_args()
def write_flash(esp, args):
if args.compress is None and not args.no_compress:
args.compress = not args.no_stub
# verify file sizes fit in flash
flash_end = flash_size_bytes(args.flash_size)
for address, argfile in args.addr_filename:
argfile.seek(0,2) # seek to end
if address + argfile.tell() > flash_end:
I'm wondering how I can use this function in another program I'm writing. Do I somehow create a parser.parse_args() object with the same arguments as him? One thing I thought of is using subprocess.popen to run it like so:
p = subprocess.Popen(['python', 'esptool.py', '--port',
'COM3', 'write_flash', '0x00000', 'boot_v1.7.bin', '0xfc000', 'esp_init_data_ default_v08.bin', '0xfb000',
'blank.bin', '0x01000', 'user1.1024.new.2.bin'])
But this seems less than ideal. I'm really lost in general on how to approach argparse in general and any help would be much appreciated, thank you.
parse_args() returns a Namespace object. You can just create one yourself.
from argparse import Namespace`
args = Namespace()
args.compress = True
args.no_stub = 3
print(args)
and then pass it.

get python argparser attributes into script namespace

I used to do this in my python scripts:
import sys
my_int = int(sys.argv[1])
...more commands...
How can I replicate THIS EXACT functionality with the argparse module? Do I need to extract all of the variables from the parser? That would be tedious.
I don't want an answer like this:
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("--my_int", type=int, dest="my_int")
args = parser.parse_args()
main(**vars(args))
because I want to be able to interrupt my script in ipython and have all the variables still be defined.
locals().update(vars(args))
is the answer

Is there a way to add an already created parser as a subparser in argparse?

Normally, to add a subparser in argparse you have to do:
parser = ArgumentParser()
subparsers = parser.add_subparser()
subparser = subparsers.add_parser()
The problem I'm having is I'm trying to add another command line script, with its own parser, as a subcommand of my main script. Is there an easy way to do this?
EDIT: To clarify, I have a file script.py that looks something like this:
def initparser():
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
parser.add_argument('--bar')
return parser
def func(args):
#args is a Namespace, this function does stuff with it
if __name__ == '__main__':
initparser().parse_args()
So I can run this like:
python script.py --foo --bar
I'm trying to write a module app.py that's a command line interface with several subcommands, so i can run something like:
python app.py script --foo --bar
Rather than copy and pasting all of the initparser() logic over to app.py, I'd like to be able to directly use the parser i create from initparser() as a sub-parser. Is this possible?
You could use the parents parameter
p=argparse.ArgumentParser()
s=p.add_subparsers()
ss=s.add_parser('script',parents=[initparser()],add_help=False)
p.parse_args('script --foo sst'.split())
ss is a parser that shares all the arguments defined for initparser. The add_help=False is needed on either ss or initparser so -h is not defined twice.
You might want to take a look at the shlex module as it sounds to me like you're trying to hack the ArgumentParser to do something that it wasn't actually intended to do.
Having said that, it's a little difficult to figure out a good answer without examples of what it is, exactly, that you're trying to parse.
I think your problem can be addressed by a declarative wrapper for argparse. The one I wrote is called Argh. It helps with separating definition of commands (with all arguments-related stuff) from assembling (including subparsers) and dispatching.
This is a way old question, but I wanted to throw out another alternative. And that is to think in terms of inversion of control. By this I mean the root ArgumentParser would manage the creation of the subparsers:
# root_argparser.py
from argparse import ArgumentParser, Namespace
__ARG_PARSER = ArgumentParser('My Script')
__SUBPARSERS = __ARG_PARSER.add_subparsers(dest='subcommand')
__SUBPARSERS.required = True
def get_subparser(name: str, **kwargs) -> ArgumentParser:
return __SUBPARSERS.add_parser(name, **kwargs)
def parse_args(**kwargs) -> Namespace:
return __ARG_PARSER.parse_args(**kwargs)
# my_script.py
from argparse import ArgumentParser
from root_argparse import get_subparser
__ARG_PARSER = get_subparser('script')
__ARG_PARSER.add_argument('--foo')
__ARG_PARSER.add_argument('--bar')
def do_stuff(...):
...
# main.py
from root_argparse import parse_args
import my_script
if __name__ == '__main__':
args = parse_args()
# do stuff with args
Seems to work okay from some quick testing I did.

Python: argument parser that handles global options to sub-commands properly

argparse fails at dealing with sub-commands receiving global options:
import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')
will have p.parse_args('--arg test'.split()) work,
but fails on p.parse_args('test --arg'.split()).
Anyone aware of a python argument parser that handles global options to sub-commands properly?
You can easily add this argument to both parsers (main parser and subcommand parser):
import argparse
main = argparse.ArgumentParser()
subparser = main.add_subparsers().add_parser('test')
for p in [main,subparser]:
p.add_argument('--arg', action='store_true')
print main.parse_args('--arg test'.split()).arg
print main.parse_args('test --arg'.split()).arg
Edit: As #hpaulj pointed in comment, there is also parents argument which you can pass to ArgumentParser constructor or to add_parser method. You can list in this value parsers which are bases for new one.
import argparse
base = argparse.ArgumentParser(add_help=False)
base.add_argument('--arg', action='store_true')
main = argparse.ArgumentParser(parents=[base])
subparser = main.add_subparsers().add_parser('test', parents=[base])
print main.parse_args('--arg test'.split()).arg
print main.parse_args('test --arg'.split()).arg
More examples/docs:
looking for best way of giving command line arguments in python, where some params are req for some option and some params are req for other options
Python argparse - Add argument to multiple subparsers (I'm not sure if this question is not overlaping with this one too much)
http://docs.python.org/dev/library/argparse.html#parents
Give docopt a try:
>>> from docopt import docopt
>>> usage = """
... usage: prog.py command [--test]
... prog.py another [--test]
...
... --test Perform the test."""
>>> docopt(usage, argv='command --test')
{'--test': True,
'another': False,
'command': True}
>>> docopt(usage, argv='--test command')
{'--test': True,
'another': False,
'command': True}
There's a ton of argument-parsing libs in the Python world. Here are a few that I've seen, all of which should be able to handle address the problem you're trying to solve (based on my fuzzy recollection of them when I played with them last):
opster—I think this is what mercurial uses, IIRC
docopt—This one is new, but uses an interesting approach
cliff—This is a relatively new project by Doug Hellmann (PSF member, virtualenvwrapper author, general hacker extraordinaire) is a bit more than just an argument parser, but is designed from the ground up to handle multi-level commands
clint—Another project that aims to be "argument parsing and more", this one by Kenneth Reitz (of Requests fame).
Here's a dirty workaround --
import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')
def my_parse_args(ss):
#parse the info the subparser knows about; don't issue an error on unknown stuff
namespace,leftover=p.parse_known_args(ss)
#reparse the unknown as global options and add it to the namespace.
if(leftover):
s.add_parser('null',add_help=False)
p.parse_args(leftover+['null'],namespace=namespace)
return namespace
#print my_parse_args('-h'.split()) #This works too, but causes the script to stop.
print my_parse_args('--arg test'.split())
print my_parse_args('test --arg'.split())
This works -- And you could modify it pretty easily to work with sys.argv (just remove the split string "ss"). You could even subclass argparse.ArgumentParser and replace the parse_args method with my_parse_args and then you'd never know the difference -- Although subclassing to replace a single method seems overkill to me.
I think however, that this is a lit bit of a non-standard way to use subparsers. In general, global options are expected to come before subparser options, not after.
The parser has a specific syntax: command <global options> subcommand <subcommand ptions>, you are trying to feed the subcommand with an option and but you didn't define one.

Categories