Disk Usage Script - python

Somewhere things stopped working. The intent was to show current disk usage if no argument given, show the directory's usage if provided as an argument, and show all subdirectories' disk usage if an argument called --all was given. Here is what I got so far. For some reason it fails now when supplying the "-d" for directory.
#!/usr/bin/env python
import os
import sys
import subprocess
import getopt
from humanize import naturalsize
def get_size(start_path = '.'):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
return total_size
def get_immediate_subdirectories(a_dir):
return [name for name in os.listdir(a_dir)
if os.path.isdir(os.path.join(a_dir, name))]
def version():
print ("Version: 1.0")
def usage():
print ("Usage: disk_usage.py [-a|--all][-n|--ncdu][-v|--version][directory name]")
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "adhnv", ["all","directory","help","ncdu","version",])
except getopt.GetoptError as err:
# print help information and exit:
print(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
for o, a in opts:
if o in ("-a", "--all"):
for subdir in get_immediate_subdirectories('.'):
print ("%s\t\t %s" % (naturalsize(get_size(subdir)),subdir))
sys.exit()
elif o in ("-d", "--directory"):
print (naturalsize(get_size(start_path=sys.argv[1:])))
elif o in ("-v", "--version"):
print (version())
sys.exit()
elif o in ("-h", "--help"):
usage()
sys.exit()
elif o in ("-n", "--ncdu"):
os.system('ncdu')
sys.exit()
else:
print (usage())
main()

I see a couple of problems with your usage of getopt. One (which sounds like you've figured-out for yourself) is that you're giving the wrong thing to get_size() by passing it start_path=sys.argv[1:] which assign start_path to a list of all the command arguments after the first.
The second issue is you're not defining the second and third arguments passed to getopt() properly. Quoting from the PyMOTW3:
The second argument is the option definition string for single character options. If one of the options requires an argument, its letter is followed by a colon.
(emphasis mine)
This means that in order to accept a value along with the -d option, you'd need to use "ad:hnv" instead of the "adhnv" you've got.
Similarly, for the --directory option in the list of long-style options:
The third argument, if used, should be a sequence of the long-style option names. Long style options can be more than a single character, such as --noarg or --witharg. The option names in the sequence should not include the “--” prefix. If any long option requires an argument, its name should have a suffix of “=”.
To support it there too requires you to pass ["all", "directory=", "help", "ncdu", "version"].
Unfortunately getopt doesn't support having an optional option argument. The reason is likely because it makes parsing ambiguous: For example, if -a takes an optional argument and -b is another option entirely, it unclear how interpret -ab.
If you can live with that limitation, the following incorporates changes to address the above issues:
import os
import sys
import subprocess
import getopt
from humanize import naturalsize
def get_size(start_path='.'):
# Might was to check validity of path here...
#if not (os.path.exists(start_path) and os.path.isdir(start_path)):
# raise ValueError('Invalid path argument: {!r}'.format(start_path))
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
return total_size
def get_immediate_subdirectories(a_dir):
return [name for name in os.listdir(a_dir)
if os.path.isdir(os.path.join(a_dir, name))]
def version():
print("Version: 1.0")
def usage():
print("Usage: disk_usage.py [-a|--all][-n|--ncdu][-v|--version][directory name]")
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "ad:hnv",
["all", "directory=", "help", "ncdu", "version",])
except getopt.GetoptError as err:
print(err)
usage()
if __name__ == '__main__':
return 2 # Unit test.
else:
sys.exit(2)
for opt, arg in opts:
if opt in ("-a", "--all"):
for subdir in get_immediate_subdirectories('.'):
print("%s\t\t %s" % (naturalsize(get_size(subdir)), subdir))
sys.exit()
elif opt in ("-d", "--directory"):
print(naturalsize(get_size(start_path=arg)))
elif opt in ("-v", "--version"):
print(version())
sys.exit()
elif opt in ("-h", "--help"):
usage()
sys.exit()
elif opt in ("-n", "--ncdu"):
os.system('ncdu')
sys.exit()
else:
print(usage())
else:
print(usage())
if __name__ == '__main__':
# Unit test.
sys.argv = ['disk_usage.py'] # no option given
main()
sys.argv = 'disk_usage.py -d.'.split() # short opt
main()
sys.argv = 'disk_usage.py -dsome_folder'.split() # short opt
main()
sys.argv = 'disk_usage.py -d some_folder'.split() # short opt
main()
sys.argv = 'disk_usage.py --directory some_folder'.split() # long opt
main()
sys.argv = 'disk_usage.py -d'.split() # Missing argument error
main()

I think I got it now. Line 42 included sys.argv[1:] which returned a list. It should actually be sys.argv[2] to reference the directory after supplying the -d option.

There are two problems here.
The first problem is that you're using getopt wrong:
opts, args = getopt.getopt(sys.argv[1:], "adhnv", ["all","directory","help","ncdu","version",])
This returns a list of options opts, and a list of args with the options filtered out. But you're continuing to use sys.argv instead of args:
print (naturalsize(get_size(start_path=sys.argv[1:])))
In an invocation like myprogram -d spam, sys.argv[1:] is going to be ['-d', 'spam']. But you don't want the -d. That's the whole reason you called getopts in the first place. So, use args here, not sys.argv.
The second problem is that you're taking a slice, sys.argv[1:], but treating it as if it were a single value, rather than a list of values.
If you want to allow multiple directories to be passed with -d and to work on all of them, you need a for arg in …: loop over the slice.
If you want just a single directory, then you need to just index the list, like [1], not [1:]. Although, if you want that, you probably don't want -d to be a boolean flag and the directory to be an arg; you want -d to be a flag with the directory as its value.
More generally, you probably shouldn't be using getopt here in the first place. As the docs say:
Note The getopt module is a parser for command line options whose API is designed to be familiar to users of the C getopt() function. Users who are unfamiliar with the C getopt() function or who would like to write less code and get better help and error messages should consider using the argparse module instead.
If you're a getopt wizard, then sure, use getopt for simple projects instead of learning something new, even if it's better. But for anyone else, there is really no good reason to learn getopt in the first place, much less to learn how to debug all the funky corners of it. argparse is just plain better in every way. And if you don't like argparse, there are half a dozen alternatives on PyPI.

Related

python getopts option not matching on operator '=='

following some examples from https://www.programcreek.com/python/example/121/getopt.getopt where they make option comparisons with == i.e (option == "--json"), I am trying to enter the if statement. However option will not match despite being of type string and "--json". Can anyone see what is going wrong?
import sys, getopt
argv = sys.argv[1:]
def main(argv):
try:
opts, args = getopt.getopt(argv,'',['json ='])
except:
print("failed to get arguments")
for (option, value) in opts:
print(option)
print(type(option))
if str(option) == "--json":
print ("enters here")
main(argv)
$ python test.py --json data
--json
<class 'str'>
You are using the longopts parameter incorrectly. Using an = in the option indicates that the option takes arguments but you are not passing your data argument to the --json option, you are passing it to the script as a global argument (because you omitted the = from the --json option).
Note also that you must remove the whitespace between the option and its argument. Finally, you should never use a bare except.
def main(argv):
try:
opts, args = getopt.getopt(argv, "", ["json="])
print(opts)
except Exception:
print("failed to get arguments")
for (option, value) in opts:
print(option)
print(type(option))
if str(option) == "--json":
print("enters here")
Usage:
$ python test.py --json='{"firstName":"John", "lastName":"Doe"}'
[('--json', '{firstName:John, lastName:Doe}')]
--json
<class 'str'>
enters here

Python getopts issue

import pywaves as pw
import sys, getopt
amount = 0
receive = ''
try:
options, remainder = getopt.getopt(
sys.argv[1:],
'r:a',
['receive',
'amount',
])
except getopt.GetoptError as err:
print('ERROR:', err)
sys.exit(1)
for opt, arg in options:
if opt in ('-a', '--amount'):
amount = arg
elif opt in ('-r', '--receive'):
receive = arg
print('OPTIONS :', options)
myAddress = pw.Address(privateKey='MYPRIVATEKEY')
otherAddress = pw.Address(receive)
myToken = pw.Asset('MYADDRESS')
myAmount = amount
myAddress.sendAsset(otherAddress, myToken, myAmount)
I tried run the code above and it seems my option "a" was not captured. What should I do to get it working?
I run the following command line
python this.py -r 3PFPovgPu3aBWA1krU544tPDTFiHgpvu7q1 -a 150
It returns
('OPTIONS :', [('-r', '3PFPovgPu3aBWA1krU544tPDTFiHgpvu7q1'), ('-a', '')])
I not sure why the "a" value was empty. How do I change my code to make it work properly?
You need to add a colon after the second parameter 'a'. So try
getopt.getopt(sys.argv[1:],'r:a:',['receive','amount'])
See the documentation for getopt, there it is said clearly:
Parses command line options and parameter list. args is the argument list to be parsed, without the leading reference to the running program. Typically, this means sys.argv[1:]. options is the string of option letters that the script wants to recognize, with options that require an argument followed by a colon (':'; i.e., the same format that Unix getopt() uses).

handling getopt dynamically through different python script

I am using getopt to handle optional arguments in all my scripts. I have lots of scripts and i have to manage getopt in all the scripts separately because all scripts have different options.
My Question is is there any dynamic way to handle these options through single class by defining all options and just update those by objects ?
Currently i am using getopt like this:
import getopt
if __name__=="__main__":
if len(sys.argv) < 2:
usage()
else:
try:
options, remainder = getopt.getopt(sys.argv[2:], 's:n:i:a:c:t:p:', ['s1=','n1=','iteration=','api_v=', 'c='.....,])
except getopt.GetoptError:
print "\nError: Provided options are not correct!\n"
usage()
for opt, arg in options:
if opt in ('-s', '--s1'):
s1 = arg
if opt in ('-n', '--n1'):
try:
n1= int(arg)
except ValueError:
print("Error : n1 not an integer!")
exit()
elif opt in ('-i', '--iteration'):
try:
timeout = int(arg)
except ValueError:
print("Error : iteration not an integer!")
exit()
elif opt in ('-a', '--abc'):
abc = arg
.....
....
#and list goes on.
This i have in almost all my scripts with different options. Is there any cleaner way to achieve the same thing?
Look at the docopt module. It allows you to very very easily define options for your scripts with little to no code, just docstrings describing them: http://docopt.org/
You may find the argparse module easier to use and read. You could create an ArgumentParser factory function in a separate module and just call that from all your scripts to return a parser with the correct options

getopt() not enforcing required arguments?

I'm having problems with this getopt() code in a script that I'm writing which does some simple file manipulation given 2 required parameters (input filename and output filename) and/or 2 optional/situational arguments (debug or help).
Code is:
def main(argv):
try:
opts, args = getopt.getopt(argv, "i:o:dh", ["input-file=", "output-file=", "debug", "help"])
except getopt.GetoptError:
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
usage()
sys.exit()
elif opt in ("-d", "--debug"):
global _debug
_debug = 1
elif opt in ("-i", "--input-file"):
u_input_file_name = arg
elif opt in ("-o", "--output-file"):
u_output_file_name = arg
According to the getopt() documentation:
options that require an argument followed by a colon (':'; i.e., the same format that Unix getopt() uses).
The problem is that as I understand it, the variables/args followed by a : should be enforced as required ... but the options i and o are not being enforced. Running this snippet garners an error about u_input_file_name being referenced before being assigned:
[tdelane#fbsd81-1 ~/python]$ ./inco_add_cm_mpscli.py -o google
Traceback (most recent call last):
File "./inco_add_cm_mpscli.py", line 57, in <module>
main(sys.argv[1:])
File "./inco_add_cm_mpscli.py", line 25, in main
infile = open(u_input_file_name, 'r')
UnboundLocalError: local variable 'u_input_file_name' referenced before assignment
What am I doing wrong?
An option followed by a colon only means that it needs an argument. It doesn't mean that the option is enforced. You should write your own code to enforce the existence of options/arguments.
Just as a note, I found that argparse is simpler and more useful than getopt, and it support required arguments.
http://docs.python.org/2/howto/argparse.html#id1
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
Command Line
$ python prog.py
usage: prog.py [-h] echo
prog.py: error: the following arguments are required: echo
In case this is useful to anyone. Here is a boiler plate that I use for creating a python script with command line options. It handles required options. If a required option is not specified, then the script will terminate with an error.
#!/usr/bin/python
import os
import sys
import getopt
import logging
# This will get the name of this file
script_name = os.path.basename(__file__)
default_loglevel = 'info'
##
# #brief Help document for this script. See the main function below.
#
help = f'''
{script_name} -c com_port [-o output_file] [--loglevel level]
Reads the temperature data from a radio. The temperature data is output in csv form.
examples:
Read table from radio attached to com4 and write the table to the file
output.csv.
{script_name} -c com4 -o output.csv
Read table from radio attached to com3 and write the table to stdout.
You can use IO redirection to send the contents where every you want.
# just print to the terminal
{script_name} -c com3
# redirect to another file
{script_name} -c com3 > somefile.csv
# filter out temperatures that are -100
{script_name} -c com3 | grep -v '^-100'
-c com_port
--com_port comport
Name of the COM port attached to the radio
-o output_file
--output output_file
If specified write the table data to the given file. If not specified
the data will be written to stdout.
--loglevel critical | error | warning | info | debug | notset
Control the verbosity of the script by setting the log level. Critical
is the least verbose and notset is the most verbose.
The default loglevel is {default_loglevel}.
These values correspond directly to the python logging module levels.
(i.e. https://docs.python.org/3/howto/logging.html#logging-levels)
-h
--help
print this message
'''
def print_help():
print(help, file=sys.stderr)
class RequiredOptions:
'''Just something to keep track of required options'''
def __init__(self, options=[]):
self.required_options = options
def add(self, option):
if option not in self.required_options:
self.required_options.append(option)
def resolve(self, option):
if option in self.required_options:
self.required_options.remove(option)
def optionsResolved(self):
if len(self.required_options):
return False
else:
return True
def main(argv):
# Use the logging module to print non table data. These prints will be sent
# to stderr. The verbosity of the script can by adjusted via the setLevel
# method.
#
logging.getLogger().setLevel(default_loglevel.upper())
try:
opts, args = getopt.getopt(argv,"hc:o:",["help", "com_port=", "output=","loglevel="])
except getopt.GetoptError as e:
print_help()
logging.exception(e)
sys.exit(2)
# This can be overridden with the --output option.
#
output_file = sys.stdout
# As the required options are encountered they are removed from this list.
# After all of the args have been processed, require_options should be
# empty.
#
required_options = RequiredOptions([ 'com_port' ])
for opt, arg in opts:
if opt in ('-h', '--help'):
print_help()
sys.exit(0)
elif opt in ("-o", "--output"):
output_file = open(arg, 'w')
elif opt in ("-c", "--com_port"):
com_port = arg
required_options.resolve('com_port')
elif opt in ("--loglevel"):
# Convert to uppercase
#
loglevel = arg.upper()
logging.getLogger().setLevel(loglevel)
else:
print_help()
# Verify that all of the required options have been specified
#
if not required_options.optionsResolved():
print_help()
logging.error("The following required options were not specified:" + ' '.join(required_options.required_options))
# indicate that there was an error by returning a non-zero value.
#
sys.exit(1)
# Now do your work
logging.debug('debug message')
logging.info('info message')
logging.warning('warn message')
logging.error('error message')
logging.critical('critical message')
if __name__ == "__main__":
main(sys.argv[1:])
I would just create global variable like argbit and use bitwise operation instead of flags for each arg. I used like:
argbit=1
for each arg loop:
case arg1: #mandatory
argbit <<= 1
do stuff and break
case arg2: #optional
do stuff and break
now based on your args it will be left shifted so at end just check its value
if argbit != value:
usage_and_exit()
if you have two mandatory args its value will be 4 like 2 ^ n.
Ran into the same problem, here's how I solved it
try:
required_argument1 # If argument is missing, it will error, we
# catch that error below in -except
required_argument2
required_argument3
except Error as e: # Whatever your error is (mine was a keyError)
print( 'argument ' + str(e) + is missing )

What's the best way to parse command line arguments? [duplicate]

This question already has answers here:
How to read/process command line arguments?
(22 answers)
Closed 8 months ago.
What's the easiest, tersest, and most flexible method or library for parsing Python command line arguments?
argparse is the way to go. Here is a short summary of how to use it:
1) Initialize
import argparse
# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')
2) Add Arguments
# Required positional argument
parser.add_argument('pos_arg', type=int,
help='A required integer positional argument')
# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
help='An optional integer positional argument')
# Optional argument
parser.add_argument('--opt_arg', type=int,
help='An optional integer argument')
# Switch
parser.add_argument('--switch', action='store_true',
help='A boolean switch')
3) Parse
args = parser.parse_args()
4) Access
print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)
5) Check Values
if args.pos_arg > 10:
parser.error("pos_arg cannot be larger than 10")
Usage
Correct use:
$ ./app 1 2 --opt_arg 3 --switch
Argument values:
1
2
3
True
Incorrect arguments:
$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'
$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10
Full help:
$ ./app -h
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
Optional app description
positional arguments:
pos_arg A required integer positional argument
opt_pos_arg An optional integer positional argument
optional arguments:
-h, --help show this help message and exit
--opt_arg OPT_ARG An optional integer argument
--switch A boolean switch
This answer suggests optparse which is appropriate for older Python versions. For Python 2.7 and above, argparse replaces optparse. See this answer for more information.
As other people pointed out, you are better off going with optparse over getopt. getopt is pretty much a one-to-one mapping of the standard getopt(3) C library functions, and not very easy to use.
optparse, while being a bit more verbose, is much better structured and simpler to extend later on.
Here's a typical line to add an option to your parser:
parser.add_option('-q', '--query',
action="store", dest="query",
help="query string", default="spam")
It pretty much speaks for itself; at processing time, it will accept -q or --query as options, store the argument in an attribute called query and has a default value if you don't specify it. It is also self-documenting in that you declare the help argument (which will be used when run with -h/--help) right there with the option.
Usually you parse your arguments with:
options, args = parser.parse_args()
This will, by default, parse the standard arguments passed to the script (sys.argv[1:])
options.query will then be set to the value you passed to the script.
You create a parser simply by doing
parser = optparse.OptionParser()
These are all the basics you need. Here's a complete Python script that shows this:
import optparse
parser = optparse.OptionParser()
parser.add_option('-q', '--query',
action="store", dest="query",
help="query string", default="spam")
options, args = parser.parse_args()
print 'Query string:', options.query
5 lines of python that show you the basics.
Save it in sample.py, and run it once with
python sample.py
and once with
python sample.py --query myquery
Beyond that, you will find that optparse is very easy to extend.
In one of my projects, I created a Command class which allows you to nest subcommands in a command tree easily. It uses optparse heavily to chain commands together. It's not something I can easily explain in a few lines, but feel free to browse around in my repository for the main class, as well as a class that uses it and the option parser
Using docopt
Since 2012 there is a very easy, powerful and really cool module for argument parsing called docopt. Here is an example taken from its documentation:
"""Naval Fate.
Usage:
naval_fate.py ship new <name>...
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
naval_fate.py ship shoot <x> <y>
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.py (-h | --help)
naval_fate.py --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Naval Fate 2.0')
print(arguments)
So this is it: 2 lines of code plus your doc string which is essential and you get your arguments parsed and available in your arguments object.
Using python-fire
Since 2017 there's another cool module called python-fire. It can generate a CLI interface for your code with you doing zero argument parsing. Here's a simple example from the documentation (this small program exposes the function double to the command line):
import fire
class Calculator(object):
def double(self, number):
return 2 * number
if __name__ == '__main__':
fire.Fire(Calculator)
From the command line, you can run:
> calculator.py double 10
20
> calculator.py double --number=15
30
The new hip way is argparse for these reasons. argparse > optparse > getopt
update: As of py2.7 argparse is part of the standard library and optparse is deprecated.
I prefer Click. It abstracts managing options and allows "(...) creating beautiful command line interfaces in a composable way with as little code as necessary".
Here's example usage:
import click
#click.command()
#click.option('--count', default=1, help='Number of greetings.')
#click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()
It also automatically generates nicely formatted help pages:
$ python hello.py --help
Usage: hello.py [OPTIONS]
Simple program that greets NAME for a total of COUNT times.
Options:
--count INTEGER Number of greetings.
--name TEXT The person to greet.
--help Show this message and exit.
Pretty much everybody is using getopt
Here is the example code for the doc :
import getopt, sys
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
except getopt.GetoptError:
# print help information and exit:
usage()
sys.exit(2)
output = None
verbose = False
for o, a in opts:
if o == "-v":
verbose = True
if o in ("-h", "--help"):
usage()
sys.exit()
if o in ("-o", "--output"):
output = a
So in a word, here is how it works.
You've got two types of options. Those who are receiving arguments, and those who are
just like switches.
sys.argv is pretty much your char** argv in C. Like in C you skip the first element which is the name of your program and parse only the arguments : sys.argv[1:]
Getopt.getopt will parse it according to the rule you give in argument.
"ho:v" here describes the short arguments : -ONELETTER. The : means that -o accepts one argument.
Finally ["help", "output="] describes long arguments ( --MORETHANONELETTER ).
The = after output once again means that output accepts one arguments.
The result is a list of couple (option,argument)
If an option doesn't accept any argument (like --help here) the arg part is an empty string.
You then usually want to loop on this list and test the option name as in the example.
I hope this helped you.
Use optparse which comes with the standard library. For example:
#!/usr/bin/env python
import optparse
def main():
p = optparse.OptionParser()
p.add_option('--person', '-p', default="world")
options, arguments = p.parse_args()
print 'Hello %s' % options.person
if __name__ == '__main__':
main()
Source: Using Python to create UNIX command line tools
However as of Python 2.7 optparse is deprecated, see: Why use argparse rather than optparse?
Lightweight command line argument defaults
Although argparse is great and is the right answer for fully documented command line switches and advanced features, you can use function argument defaults to handles straightforward positional arguments very simply.
import sys
def get_args(name='default', first='a', second=2):
return first, int(second)
first, second = get_args(*sys.argv)
print first, second
The 'name' argument captures the script name and is not used. Test output looks like this:
> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20
For simple scripts where I just want some default values, I find this quite sufficient. You might also want to include some type coercion in the return values or command line values will all be strings.
Just in case you might need to, this may help if you need to grab unicode arguments on Win32 (2K, XP etc):
from ctypes import *
def wmain(argc, argv):
print argc
for i in argv:
print i
return 0
def startup():
size = c_int()
ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
ref = c_wchar_p * size.value
raw = ref.from_address(ptr)
args = [arg for arg in raw]
windll.kernel32.LocalFree(ptr)
exit(wmain(len(args), args))
startup()
Argparse code can be longer than actual implementation code!
That's a problem I find with most popular argument parsing options is that if your parameters are only modest, the code to document them becomes disproportionately large to the benefit they provide.
A relative new-comer to the argument parsing scene (I think) is plac.
It makes some acknowledged trade-offs with argparse, but uses inline documentation and wraps simply around main() type function function:
def main(excel_file_path: "Path to input training file.",
excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
existing_model_path: "Path to an existing model to refine."=None,
batch_size_start: "The smallest size of any minibatch."=10.,
batch_size_stop: "The largest size of any minibatch."=250.,
batch_size_step: "The step for increase in minibatch size."=1.002,
batch_test_steps: "Flag. If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."
pass # Implementation code goes here!
if __name__ == '__main__':
import plac; plac.call(main)
I prefer optparse to getopt. It's very declarative: you tell it the names of the options and the effects they should have (e.g., setting a boolean field), and it hands you back a dictionary populated according to your specifications.
http://docs.python.org/lib/module-optparse.html
I think the best way for larger projects is optparse, but if you are looking for an easy way, maybe http://werkzeug.pocoo.org/documentation/script is something for you.
from werkzeug import script
# actions go here
def action_foo(name=""):
"""action foo does foo"""
pass
def action_bar(id=0, title="default title"):
"""action bar does bar"""
pass
if __name__ == '__main__':
script.run()
So basically every function action_* is exposed to the command line and a nice
help message is generated for free.
python foo.py
usage: foo.py <action> [<options>]
foo.py --help
actions:
bar:
action bar does bar
--id integer 0
--title string default title
foo:
action foo does foo
--name string
consoleargs deserves to be mentioned here. It is very easy to use. Check it out:
from consoleargs import command
#command
def main(url, name=None):
"""
:param url: Remote URL
:param name: File name
"""
print """Downloading url '%r' into file '%r'""" % (url, name)
if __name__ == '__main__':
main()
Now in console:
% python demo.py --help
Usage: demo.py URL [OPTIONS]
URL: Remote URL
Options:
--name -n File name
% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'
% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''
Here's a method, not a library, which seems to work for me.
The goals here are to be terse, each argument parsed by a single line, the args line up for readability, the code is simple and doesn't depend on any special modules (only os + sys), warns about missing or unknown arguments gracefully, use a simple for/range() loop, and works across python 2.x and 3.x
Shown are two toggle flags (-d, -v), and two values controlled by arguments (-i xxx and -o xxx).
import os,sys
def HelpAndExit():
print("<<your help output goes here>>")
sys.exit(1)
def Fatal(msg):
sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
sys.exit(1)
def NextArg(i):
'''Return the next command line argument (if there is one)'''
if ((i+1) >= len(sys.argv)):
Fatal("'%s' expected an argument" % sys.argv[i])
return(1, sys.argv[i+1])
### MAIN
if __name__=='__main__':
verbose = 0
debug = 0
infile = "infile"
outfile = "outfile"
# Parse command line
skip = 0
for i in range(1, len(sys.argv)):
if not skip:
if sys.argv[i][:2] == "-d": debug ^= 1
elif sys.argv[i][:2] == "-v": verbose ^= 1
elif sys.argv[i][:2] == "-i": (skip,infile) = NextArg(i)
elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
elif sys.argv[i][:2] == "-h": HelpAndExit()
elif sys.argv[i][:1] == "-": Fatal("'%s' unknown argument" % sys.argv[i])
else: Fatal("'%s' unexpected" % sys.argv[i])
else: skip = 0
print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))
The goal of NextArg() is to return the next argument while checking for missing data, and 'skip' skips the loop when NextArg() is used, keeping the flag parsing down to one liners.
I extended Erco's approach to allow for required positional arguments and for optional arguments. These should precede the -d, -v etc. arguments.
Positional and optional arguments can be retrieved with PosArg(i) and OptArg(i, default) respectively.
When an optional argument is found the start position of searching for options (e.g. -i) is moved 1 ahead to avoid causing an 'unexpected' fatal.
import os,sys
def HelpAndExit():
print("<<your help output goes here>>")
sys.exit(1)
def Fatal(msg):
sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
sys.exit(1)
def NextArg(i):
'''Return the next command line argument (if there is one)'''
if ((i+1) >= len(sys.argv)):
Fatal("'%s' expected an argument" % sys.argv[i])
return(1, sys.argv[i+1])
def PosArg(i):
'''Return positional argument'''
if i >= len(sys.argv):
Fatal("'%s' expected an argument" % sys.argv[i])
return sys.argv[i]
def OptArg(i, default):
'''Return optional argument (if there is one)'''
if i >= len(sys.argv):
Fatal("'%s' expected an argument" % sys.argv[i])
if sys.argv[i][:1] != '-':
return True, sys.argv[i]
else:
return False, default
### MAIN
if __name__=='__main__':
verbose = 0
debug = 0
infile = "infile"
outfile = "outfile"
options_start = 3
# --- Parse two positional parameters ---
n1 = int(PosArg(1))
n2 = int(PosArg(2))
# --- Parse an optional parameters ---
present, a3 = OptArg(3,50)
n3 = int(a3)
options_start += int(present)
# --- Parse rest of command line ---
skip = 0
for i in range(options_start, len(sys.argv)):
if not skip:
if sys.argv[i][:2] == "-d": debug ^= 1
elif sys.argv[i][:2] == "-v": verbose ^= 1
elif sys.argv[i][:2] == "-i": (skip,infile) = NextArg(i)
elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
elif sys.argv[i][:2] == "-h": HelpAndExit()
elif sys.argv[i][:1] == "-": Fatal("'%s' unknown argument" % sys.argv[i])
else: Fatal("'%s' unexpected" % sys.argv[i])
else: skip = 0
print("Number 1 = %d" % n1)
print("Number 2 = %d" % n2)
print("Number 3 = %d" % n3)
print("Debug = %d" % debug)
print("verbose = %d" % verbose)
print("infile = %s" % infile)
print("outfile = %s" % outfile)

Categories

Resources