Plac mirror update to Plac 0.7.4.

This commit is contained in:
Kenneth Reitz
2010-09-06 01:32:36 -04:00
commit ce84e509c0
81 changed files with 22446 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
HISTORY
----------
0.7.4 Fixed the plac_runner switches -i and -s; fixed a bug with multiline output
and issue with nosetest (2010-09-04)
0.7.3 Put the documentation in a single document; added runp (2010-08-31)
0.7.2 Interpreter.call does not start an interpreter automagically anymore;
better documented and added tests for the metavar concept (2010-08-31)
0.7.1 A few bug fixes (2010-08-11)
0.7.0 Improved and documented the support for parallel programming;
added an asynchronous server; added plac.Interpreter.call (2010-08-07)
0.6.1 Fixed the history file location; added the ability to pass a split
function; added two forgotten files; added a reference to cmd2 by
Catherine Devlin (2010-07-12)
0.6.0 Improved the interactive experience with full readline support and
custom help. Added support for long running command, via threads and
processes (2010-07-11)
0.5.0 Gigantic release. Introduced smart options, added an Interpreter class
and the command container concept. Made the split plac/plac_core/plac_ext
and added a plac runner, able to run scripts, batch files and doctests.
Removed the default formatter class (2010-06-20)
0.4.3 Fixed the installation procedure to automatically download argparse
if needed (2010-06-11)
0.4.2 Added missing .help files, made the tests generative and added a
note about Clap in the documentation (2010-06-04)
0.4.1 Changed the default formatter class and fixed a bug in the
display of the default arguments. Added more stringent tests. (2010-06-03)
0.4.0 abbrev is now optional. Added a note about CLIArgs and opterate.
Added keyword arguments recognition. ``plac.call`` now returns the
the output of the main function. (2010-06-03)
0.3.0 Initial version. (2010-06-02)
+1
View File
@@ -0,0 +1 @@
include *.txt doc/*.py doc/*.help doc/*.txt doc/*.html doc/*.pdf
+77
View File
@@ -0,0 +1,77 @@
Metadata-Version: 1.0
Name: plac
Version: 0.7.4
Summary: The smartest command line arguments parser in the world
Home-page: http://pypi.python.org/pypi/plac
Author: Michele Simionato
Author-email: michele.simionato@gmail.com
License: BSD License
Description: Installation
-------------
If you are lazy, just perform
::
$ easy_install -U plac
which will install the module on your system (and possibly argparse
too, if it is not already installed). Notice that Python 3 requires
the easy_install version of the distribute_ project.
If you prefer to install the full distribution from source, including
the documentation, download the tarball_, unpack it and run
::
$ python setup.py install
in the main directory, possibly as superuser.
.. _tarball: http://pypi.python.org/pypi/plac
.. _distribute: http://packages.python.org/distribute/
Testing
--------
Run
::
$ python doc/test_plac.py
or
::
$ nosetests doc
or
::
$ py.test doc
Some tests will fail if sqlalchemy is not installed.
Run an ``easy_install -U sqlalchemy`` or just ignore them.
Documentation
--------------
The source code and the documentation are hosted on Google code.
Here is the full documentation in HTML and PDF form:
http://micheles.googlecode.com/hg/plac/doc/plac.html
http://micheles.googlecode.com/hg/plac/doc/plac.pdf
Keywords: command line arguments parser
Platform: All
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
+58
View File
@@ -0,0 +1,58 @@
Installation
-------------
If you are lazy, just perform
::
$ easy_install -U plac
which will install the module on your system (and possibly argparse
too, if it is not already installed). Notice that Python 3 requires
the easy_install version of the distribute_ project.
If you prefer to install the full distribution from source, including
the documentation, download the tarball_, unpack it and run
::
$ python setup.py install
in the main directory, possibly as superuser.
.. _tarball: http://pypi.python.org/pypi/plac
.. _distribute: http://packages.python.org/distribute/
Testing
--------
Run
::
$ python doc/test_plac.py
or
::
$ nosetests doc
or
::
$ py.test doc
Some tests will fail if sqlalchemy is not installed.
Run an ``easy_install -U sqlalchemy`` or just ignore them.
Documentation
--------------
The source code and the documentation are hosted on Google code.
Here is the full documentation in HTML and PDF form:
http://micheles.googlecode.com/hg/plac/doc/plac.html
http://micheles.googlecode.com/hg/plac/doc/plac.pdf
+9
View File
@@ -0,0 +1,9 @@
# annotations.py
class Positional(object):
def __init__(self, help='', type=None, choices=None, metavar=None):
self.help = help
self.kind = 'positional'
self.abbrev = None
self.type = type
self.choices = choices
self.metavar = metavar
+6
View File
@@ -0,0 +1,6 @@
# cmd_ext.py
from plac_ext import cmd_interface
import ishelve2
if __name__ == '__main__':
cmd_interface(ishelve2.main()).cmdloop()
+13
View File
@@ -0,0 +1,13 @@
usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]]
A script to run queries and SQL scripts on a database
positional arguments:
db Connection string
scripts SQL scripts
optional arguments:
-h, --help show this help message and exit
-H, --header Header
-c SQL, --sqlcmd SQL SQL command
-d |, --delimiter | Column separator
+29
View File
@@ -0,0 +1,29 @@
# dbcli.py
import plac
from sqlalchemy.ext.sqlsoup import SqlSoup
@plac.annotations(
db=("Connection string", 'positional', None, SqlSoup),
header=("Header", 'flag', 'H'),
sqlcmd=("SQL command", 'option', 'c', str, None, "SQL"),
delimiter=("Column separator", 'option', 'd'),
scripts="SQL scripts",
)
def main(db, header, sqlcmd, delimiter="|", *scripts):
"A script to run queries and SQL scripts on a database"
yield 'Working on %s' % db.bind.url
if sqlcmd:
result = db.bind.execute(sqlcmd)
if header: # print the header
yield delimiter.join(result.keys())
for row in result: # print the rows
yield delimiter.join(map(str, row))
for script in scripts:
db.bind.execute(file(script).read())
yield 'executed %s' % script
if __name__ == '__main__':
for output in plac.call(main):
print(output)
+47
View File
@@ -0,0 +1,47 @@
..
Multiline support and Emacs integration
plac_ is optimized for the simplest use case and by default it provide
support for simple command-line languages where a command take
a single line. This is the simplest case: it is easy to keep
track of the line number and to print it in the error message, if
there is some error in a plac_ script. Starting from release 0.7
plac_ is beginning to support multiline input: it is now possible
to define command-line languages with commands spanning multiple
lines. The topical use case is the implementation of a tool
to interact with a relational database: the tool must be able to send
complex SQL queries spanning multiple lines to the backend.
To support multiline input the ``Interpreter`` class provides
a method ``multiline(stdin=sys.stdin, terminator=';', verbose=False)``
which reads input from ``stdin`` until the terminator character
(by default a semicolon) is reached.
Since the Python readline module does not expose the
multiline functionality of the underlying C library (which is there),
plac_ multiline mode does not have readline functionality. This is
not a big deal really, because if you are writing multiple line
commands you don't really want to type them at the command-line. It is
much better to use a real editor to type them, and to call plac_ from
the editor. Since I use Emacs I will give the recipe to integrate
Emacs with plac_: something equivalent can be done for vi and for
other editors/IDEs.
The multiline mode can be enabled by invoking the plac_ runner with
the ``-m`` option. Since the multiline mode is intended for use with
Emacs in inferior mode, it does not print any prompt. Here is an example
of usage::
$ plac -m ishelve2.py
set a 1;
setting a=1
show a;
a = 1
To integrate plac_ with Emacs, enters the following lines in your
.emacs:
.. include:: plac.el
:literal:
+10
View File
@@ -0,0 +1,10 @@
import os, plac, ishelve2
try:
fifo = os.mkfifo('/tmp/x')
except OSError: # fifo exists
pass
if __name__ == '__main__':
i = plac.Interpreter(ishelve2.main())
i.execute(iter(open('/tmp/x').readline, ''), verbose=True)
+6
View File
@@ -0,0 +1,6 @@
def main(arg: "required argument"):
"do something with arg"
print('Got %s' % arg)
if __name__ == '__main__':
import plac; plac.call(main) # passes sys.argv[1:] to main
+15
View File
@@ -0,0 +1,15 @@
# example1.py
def main(dsn):
"Do something with the database"
print(dsn)
# ...
if __name__ == '__main__':
import sys
n = len(sys.argv[1:])
if n == 0:
sys.exit('usage: python %s dsn' % sys.argv[0])
elif n == 1:
main(sys.argv[1])
else:
sys.exit('Unrecognized arguments: %s' % ' '.join(sys.argv[2:]))
+10
View File
@@ -0,0 +1,10 @@
usage: example10.py [-h] {add,mul} [n [n ...]]
A script to add and multiply numbers
positional arguments:
{add,mul} The name of an operator
n A number
optional arguments:
-h, --help show this help message and exit
+20
View File
@@ -0,0 +1,20 @@
# example10.py
import plac
@plac.annotations(
operator=("The name of an operator", 'positional', None, str, ['add', 'mul']),
numbers=("A number", 'positional', None, float, None, "n"))
def main(operator, *numbers):
"A script to add and multiply numbers"
if operator == 'mul':
op = float.__mul__
result = 1.0
else: # operator == 'add'
op = float.__add__
result = 0.0
for n in numbers:
result = op(result, n)
return result
if __name__ == '__main__':
print(plac.call(main))
+9
View File
@@ -0,0 +1,9 @@
usage: example11.py [-h] i n [rest [rest ...]]
positional arguments:
i This is an int
n This is a float
rest Other arguments
optional arguments:
-h, --help show this help message and exit
+13
View File
@@ -0,0 +1,13 @@
# example11.py
import plac
from annotations import Positional
@plac.annotations(
i=Positional("This is an int", int),
n=Positional("This is a float", float),
rest=Positional("Other arguments"))
def main(i, n, *rest):
print(i, n, rest)
if __name__ == '__main__':
import plac; plac.call(main)
+9
View File
@@ -0,0 +1,9 @@
usage: example12.py [-h] [-opt OPT] [args [args ...]] [kw [kw ...]]
positional arguments:
args default arguments
kw keyword arguments
optional arguments:
-h, --help show this help message and exit
-opt OPT some option
+18
View File
@@ -0,0 +1,18 @@
# example12.py
import plac
@plac.annotations(
opt=('some option', 'option'),
args='default arguments',
kw='keyword arguments')
def main(opt, *args, **kw):
if opt:
yield 'opt=%s' % opt
if args:
yield 'args=%s' % str(args)
if kw:
yield 'kw=%s' % kw
if __name__ == '__main__':
for output in plac.call(main):
print(output)
+9
View File
@@ -0,0 +1,9 @@
usage: example13.py [-h] {status,commit,checkout,help} ...
A Fake Version Control System
optional arguments:
-h, --help show this help message and exit
subcommands:
{status,commit,checkout,help}
+27
View File
@@ -0,0 +1,27 @@
import plac
class FVCS(object):
"A Fake Version Control System"
commands = 'checkout', 'commit', 'status', 'help'
@plac.annotations(
name=('a recognized command', 'positional', None, str, commands))
def help(self, name):
print(plac.parser_from(self).help_cmd(name))
@plac.annotations(
url=('url of the source code', 'positional'))
def checkout(self, url):
print('checkout', url)
def commit(self):
print('commit')
@plac.annotations(quiet=('summary information', 'flag'))
def status(self, quiet):
print('status', quiet)
main = FVCS()
if __name__ == '__main__':
plac.call(main)
+12
View File
@@ -0,0 +1,12 @@
# example2.py
def main(dsn):
"Do something on the database"
print(dsn)
# ...
if __name__ == '__main__':
import argparse
p = argparse.ArgumentParser()
p.add_argument('dsn')
arg = p.parse_args()
main(arg.dsn)
+9
View File
@@ -0,0 +1,9 @@
usage: example3.py [-h] dsn
Do something with the database
positional arguments:
dsn
optional arguments:
-h, --help show this help message and exit
+8
View File
@@ -0,0 +1,8 @@
# example3.py
def main(dsn):
"Do something with the database"
print(dsn)
# ...
if __name__ == '__main__':
import plac; plac.call(main)
+15
View File
@@ -0,0 +1,15 @@
# example4.py
from datetime import datetime
def main(dsn, table='product', today=datetime.today()):
"Do something on the database"
print(dsn, table, today)
if __name__ == '__main__':
import sys
args = sys.argv[1:]
if not args:
sys.exit('usage: python %s dsn' % sys.argv[0])
elif len(args) > 2:
sys.exit('Unrecognized arguments: %s' % ' '.join(argv[2:]))
main(*args)
+11
View File
@@ -0,0 +1,11 @@
usage: example5.py [-h] dsn [table] [today]
Do something on the database
positional arguments:
dsn
table
today
optional arguments:
-h, --help show this help message and exit
+9
View File
@@ -0,0 +1,9 @@
# example5.py
from datetime import datetime
def main(dsn, table='product', today=datetime.today()):
"Do something on the database"
print(dsn, table, today)
if __name__ == '__main__':
import plac; plac.call(main)
+8
View File
@@ -0,0 +1,8 @@
usage: example6.py [-h] [-command COMMAND] dsn
positional arguments:
dsn
optional arguments:
-h, --help show this help message and exit
-command COMMAND SQL query
+6
View File
@@ -0,0 +1,6 @@
# example6.py
def main(dsn, command: ("SQL query", 'option')):
print('executing %r on %s' % (command, dsn))
if __name__ == '__main__':
import plac; plac.call(main)
+10
View File
@@ -0,0 +1,10 @@
usage: example7.py [-h] dsn [scripts [scripts ...]]
Run the given scripts on the database
positional arguments:
dsn
scripts
optional arguments:
-h, --help show this help message and exit
+11
View File
@@ -0,0 +1,11 @@
# example7.py
from datetime import datetime
def main(dsn, *scripts):
"Run the given scripts on the database"
for script in scripts:
print('executing %s' % script)
# ...
if __name__ == '__main__':
import plac; plac.call(main)
+10
View File
@@ -0,0 +1,10 @@
usage: example7_.py [-h] dsn [scripts [scripts ...]]
Run the given scripts on the database
positional arguments:
dsn Database dsn
scripts SQL scripts
optional arguments:
-h, --help show this help message and exit
+11
View File
@@ -0,0 +1,11 @@
# example7_.py
from datetime import datetime
def main(dsn: "Database dsn", *scripts: "SQL scripts"):
"Run the given scripts on the database"
for script in scripts:
print('executing %s' % script)
# ...
if __name__ == '__main__':
import plac; plac.call(main)
+9
View File
@@ -0,0 +1,9 @@
usage: example8.py [-h] [-c COMMAND] dsn
positional arguments:
dsn
optional arguments:
-h, --help show this help message and exit
-c COMMAND, --command COMMAND
SQL query
+8
View File
@@ -0,0 +1,8 @@
# example8.py
def main(command: ("SQL query", 'option', 'c'), dsn):
if command:
print('executing %s on %s' % (command, dsn))
# ...
if __name__ == '__main__':
import plac; plac.call(main)
+9
View File
@@ -0,0 +1,9 @@
usage: example8_.py [-h] [-command select * from table] dsn
positional arguments:
dsn
optional arguments:
-h, --help show this help message and exit
-command select * from table
SQL query
+6
View File
@@ -0,0 +1,6 @@
# example8_.py
def main(dsn, command: ("SQL query", 'option')='select * from table'):
print('executing %r on %s' % (command, dsn))
if __name__ == '__main__':
import plac; plac.call(main)
+8
View File
@@ -0,0 +1,8 @@
usage: example9.py [-h] [-v] dsn
positional arguments:
dsn connection string
optional arguments:
-h, --help show this help message and exit
-v, --verbose prints more info
+9
View File
@@ -0,0 +1,9 @@
# example9.py
def main(verbose: ('prints more info', 'flag', 'v'), dsn: 'connection string'):
if verbose:
print('connecting to %s' % dsn)
# ...
if __name__ == '__main__':
import plac; plac.call(main)
+5
View File
@@ -0,0 +1,5 @@
import plac
from ishelve import ishelve
if __name__ == '__main__':
plac.Interpreter(ishelve).execute(file('ishelve2.bat'))
+20
View File
@@ -0,0 +1,20 @@
import time
import plac
class FakeImporter(object):
"A fake importer with an import_file command"
commands = ['import_file']
def __init__(self, dsn):
self.dsn = dsn
def import_file(self, fname):
"Import a file into the database"
try:
for n in range(10000):
time.sleep(.01)
if n % 100 == 99:
yield 'Imported %d lines' % (n+1)
finally:
print('closing the file')
if __name__ == '__main__':
plac.Interpreter.call(FakeImporter)
+22
View File
@@ -0,0 +1,22 @@
import time
import plac
class FakeImporter(object):
"A fake importer with an import_file command"
thcommands = ['import_file']
def __init__(self, dsn):
self.dsn = dsn
def import_file(self, fname):
"Import a file into the database"
try:
for n in range(10000):
time.sleep(.02)
if n % 100 == 99: # every two seconds
yield 'Imported %d lines' % (n+1)
if n % 10 == 9: # every 0.2 seconds
yield # go back and check the TOBEKILLED status
finally:
print('closing the file')
if __name__ == '__main__':
plac.Interpreter.call(FakeImporter)
+20
View File
@@ -0,0 +1,20 @@
import time
import plac
class FakeImporter(object):
"A fake importer with an import_file command"
mpcommands = ['import_file']
def __init__(self, dsn):
self.dsn = dsn
def import_file(self, fname):
"Import a file into the database"
try:
for n in range(10000):
time.sleep(.02)
if n % 100 == 99:
yield 'Imported %d lines' % (n+1)
finally:
print('closing the file')
if __name__ == '__main__':
plac.Interpreter.call(FakeImporter)
+18
View File
@@ -0,0 +1,18 @@
import time
import plac
class Importer(object):
"A fake importer with an import_file command"
thcommands = ['import_file']
def __init__(self, dsn):
self.dsn = dsn
def import_file(self, fname):
"Import a file into the database"
for n in range(10000):
time.sleep(.01)
if n % 100 == 99:
yield 'Imported %d lines' % (n+1)
yield
if __name__ == '__main__':
plac.Interpreter.plac.call(Importer)
+29
View File
@@ -0,0 +1,29 @@
from Tkinter import *
from importer3 import FakeImporter
def taskwidget(root, task, tick=500):
"A Label widget showing the output of a task every 500 ms"
sv = StringVar(root)
lb = Label(root, textvariable=sv)
def show_outlist():
try:
out = task.outlist[-1]
except IndexError: # no output yet
out = ''
sv.set('%s %s' % (task, out))
root.after(tick, show_outlist)
root.after(0, show_outlist)
return lb
def monitor(tasks):
root = Tk()
for task in tasks:
task.run()
taskwidget(root, task).pack()
root.mainloop()
if __name__ == '__main__':
import plac
with plac.Interpreter(plac.call(FakeImporter)) as i:
tasks = [i.submit('import_file f1'), i.submit('import_file f2')]
monitor(tasks)
+18
View File
@@ -0,0 +1,18 @@
usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]
[.filename /home/micheles/conf.shelve]
[params [params ...]] [setters [setters ...]]
Simple interface to a shelve
positional arguments:
params names of the parameters in the shelve
setters setters param=value
optional arguments:
.help show help
.showall show all parameters in the shelve
.clear clear the shelve
.delete DELETE delete an element
.filename /home/micheles/conf.shelve
filename of the shelve
+54
View File
@@ -0,0 +1,54 @@
# ishelve.py
import os, shelve, plac
DEFAULT_SHELVE = os.path.expanduser('~/conf.shelve')
@plac.annotations(
help=('show help', 'flag'),
showall=('show all parameters in the shelve', 'flag'),
clear=('clear the shelve', 'flag'),
delete=('delete an element', 'option'),
filename=('filename of the shelve', 'option'),
params='names of the parameters in the shelve',
setters='setters param=value')
def main(help, showall, clear, delete, filename=DEFAULT_SHELVE,
*params, **setters):
"A simple interface to a shelve. Use .help to see the available commands."
sh = shelve.open(filename)
try:
if not any([help, showall, clear, delete, params, setters]):
yield 'no arguments passed, use .help to see the available commands'
elif help: # custom help
yield 'Commands: .help, .showall, .clear, .delete'
yield '<param> ...'
yield '<param=value> ...'
elif showall:
for param, name in sh.items():
yield '%s=%s' % (param, name)
elif clear:
sh.clear()
yield 'cleared the shelve'
elif delete:
try:
del sh[delete]
except KeyError:
yield '%s: not found' % delete
else:
yield 'deleted %s' % delete
for param in params:
try:
yield sh[param]
except KeyError:
yield '%s: not found' % param
for param, value in setters.items():
sh[param] = value
yield 'setting %s=%s' % (param, value)
finally:
sh.close()
main.add_help = False # there is a custom help, remove the default one
main.prefix_chars = '.' # use dot-prefixed commands
if __name__ == '__main__':
for output in plac.call(main):
print(output)
+8
View File
@@ -0,0 +1,8 @@
usage: ishelve2.py [-h] [-configfile CONFIGFILE]
A minimal interface over a shelve object.
optional arguments:
-h, --help show this help message and exit
-configfile CONFIGFILE
path name of the shelve
+43
View File
@@ -0,0 +1,43 @@
# ishelve2.py
import shelve, os, sys, plac
class ShelveInterface(object):
"A minimal interface over a shelve object."
commands = 'set', 'show', 'showall', 'delete'
@plac.annotations(
configfile=('path name of the shelve', 'option'))
def __init__(self, configfile):
self.configfile = configfile or '~/conf.shelve'
self.fname = os.path.expanduser(self.configfile)
self.__doc__ += '\nOperating on %s.\n.help to see '\
'the available commands.\n' % self.fname
def __enter__(self):
self.sh = shelve.open(self.fname)
return self
def __exit__(self, etype, exc, tb):
self.sh.close()
def set(self, name, value):
"set name value"
yield 'setting %s=%s' % (name, value)
self.sh[name] = value
def show(self, *names):
"show given parameters"
for name in names:
yield '%s = %s' % (name, self.sh[name]) # no error checking
def showall(self):
"show all parameters"
for name in self.sh:
yield '%s = %s' % (name, self.sh[name])
def delete(self, name=None):
"delete given parameter (or everything)"
if name is None:
yield 'deleting everything'
self.sh.clear()
else:
yield 'deleting %s' % name
del self.sh[name] # no error checking
main = ShelveInterface # useful for the tests
if __name__ == '__main__':
plac.Interpreter.call(ShelveInterface)
+5
View File
@@ -0,0 +1,5 @@
# ishelve3.py
from ishelve2 import ShelveInterface as main
if __name__ == '__main__':
import plac; plac.Interpreter.call(main)
+10
View File
@@ -0,0 +1,10 @@
import sys
from plac_ext import Process
if __name__ == '__main__':
proc = Process(['ishelve2.py', '-c', '~/conf.shelve'])
while True:
multiline = ' '.join(iter(sys.stdin.readline, 'END\n'))
if not multiline:
break
print proc.send(multiline)
+32
View File
@@ -0,0 +1,32 @@
from Tkinter import *
from picalculator import PiCalculator
def taskwidget(root, task, tick=500):
"A Label widget showing the output of a task every 500 ms"
sv = StringVar(root)
lb = Label(root, textvariable=sv)
def show_outlist():
try:
out = task.outlist[-1]
except IndexError: # no output yet
out = ''
sv.set('%s %s' % (task, out))
root.after(tick, show_outlist)
root.after(0, show_outlist)
return lb
def monitor(calculator):
tasks = calculator.submit_tasks()
root = Tk()
try:
for task in tasks:
task.run()
taskwidget(root, task).pack()
root.mainloop()
except KeyboardInterrupt:
root.quit()
finally:
calculator.close()
if __name__ == '__main__':
import plac; monitor(plac.call(PiCalculator))
+36
View File
@@ -0,0 +1,36 @@
import time
from Tkinter import *
from threading import Thread
from picalculator import PiCalculator
def taskwidget(root, task, tick=500):
"A Label widget showing the output of a task every 500 ms"
sv = StringVar(root)
lb = Label(root, textvariable=sv)
def show_outlist():
try:
out = task.outlist[-1]
except IndexError: # no output yet
out = ''
sv.set('%s %s' % (task, out))
root.after(tick, show_outlist)
root.after(0, show_outlist)
return lb
def monitor(calculator):
calculator.submit_tasks()
th = Thread(target=calculator.run)
th.start()
root = Tk()
try:
for task in calculator.i.tasks():
taskwidget(root, task).pack()
root.mainloop()
except KeyboardInterrupt:
root.quit()
finally:
calculator.close()
th.join()
if __name__ == '__main__':
import plac; monitor(plac.call(PiCalculator))
+63
View File
@@ -0,0 +1,63 @@
from __future__ import with_statement
from random import random
import multiprocessing
import plac
class PiCalculator(object):
"""Compute pi in parallel with threads or processes"""
@plac.annotations(
npoints=('number of integration points', 'positional', None, int),
mode=('sequential|parallel|threaded', 'option', 'm', str, 'SPT'))
def __init__(self, npoints, mode='S'):
self.npoints = npoints
if mode == 'P':
self.mpcommands = ['calc_pi']
elif mode == 'T':
self.thcommands = ['calc_pi']
elif mode == 'S':
self.commands = ['calc_pi']
self.n_cpu = multiprocessing.cpu_count()
def submit_tasks(self):
self.i = plac.Interpreter(self).__enter__()
return [self.i.submit('calc_pi %d' % (self.npoints / self.n_cpu))
for _ in range(self.n_cpu)]
def close(self):
self.i.close()
@plac.annotations(
npoints=('npoints', 'positional', None, int))
def calc_pi(self, npoints):
counts = 0
for j in xrange(npoints):
n, r = divmod(j, 1000000)
if r == 0:
yield '%dM iterations' % n
x, y = random(), random()
if x*x + y*y < 1:
counts += 1
yield (4.0 * counts)/npoints
def run(self):
tasks = self.i.tasks()
for t in tasks:
t.run()
try:
total = 0
for task in tasks:
total += task.result
except: # the task was killed
print tasks
return
return total / self.n_cpu
if __name__ == '__main__':
pc = plac.call(PiCalculator)
pc.submit_tasks()
try:
import time; t0 = time.time()
print '%f in %f seconds ' % (pc.run(), time.time() - t0)
finally:
pc.close()
+2967
View File
File diff suppressed because it is too large Load Diff
+12911
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
.. include:: plac_core.txt
.. include:: plac_adv.txt
+1221
View File
File diff suppressed because it is too large Load Diff
+1354
View File
File diff suppressed because it is too large Load Diff
+782
View File
@@ -0,0 +1,782 @@
Plac: Parsing the Command Line the Easy Way
=====================================================================
:Author: Michele Simionato
:E-mail: michele.simionato@gmail.com
:Date: August 2010
:Download page: http://pypi.python.org/pypi/plac
:Project page: http://micheles.googlecode.com/hg/plac/doc/plac.html
:Requires: Python 2.3+
:Installation: ``easy_install -U plac``
:License: BSD license
.. contents::
The importance of scaling down
------------------------------------------------
There is no want of command line arguments parsers in the Python
world. The standard library alone contains three different modules:
getopt_ (from the stone age),
optparse_ (from Python 2.3) and argparse_ (from Python 2.7). All of
them are quite powerful and especially argparse_ is an industrial
strength solution; unfortunately, all of them feature a non-zero learning
curve and a certain verbosity. They do not scale down well, at
least in my opinion.
It should not be necessary to stress the importance `scaling down`_;
nevertheless, a lot of people are obsessed with features and concerned with
the possibility of scaling up, forgetting the equally important
issue of scaling down. This is an old meme in
the computing world: programs should address the common cases simply and
simple things should be kept simple, while at the same keeping
difficult things possible. plac_ adhere as much as possible to this
philosophy and it is designed to handle well the simple cases, while
retaining the ability to handle complex cases by relying on the
underlying power of argparse_.
Technically plac_ is just a simple wrapper over argparse_ which hides
most of its complexity by using a declarative interface: the argument
parser is inferred rather than written down by imperatively. Still, plac_ is
surprisingly scalable upwards, even without using the underlying
argparse_. I have been using Python for 8 years and in my experience
it is extremely unlikely that you will ever need to go beyond the
features provided by the declarative interface of plac_: they should
be more than enough for 99.9% of the use cases.
plac_ is targetting especially unsophisticated users,
programmers, sys-admins, scientists and in general people writing
throw-away scripts for themselves, choosing the command line
interface because it is the quick and simple. Such users are not
interested in features, they are interested in a small learning curve:
they just want to be able to write a simple command line tool from a
simple specification, not to build a command-line parser by
hand. Unfortunately, the modules in the standard library forces them
to go the hard way. They are designed to implement power user tools
and they have a non-trivial learning curve. On the contrary, plac_
is designed to be simple to use and extremely concise, as the examples
below will show.
Scripts with required arguments
---------------------------------------------
Let me start with the simplest possible thing: a script that takes a
single argument and does something to it. It cannot get simpler
than that, unless you consider a script without command-line
arguments, where there is nothing to parse. Still, it is a use
case *extremely common*: I need to write scripts like that nearly
every day, I wrote hundreds of them in the last few years and I have
never been happy. Here is a typical example of code I have been
writing by hand for years:
.. include:: example1.py
:literal:
As you see the whole ``if __name__ == '__main__'`` block (nine lines)
is essentially boilerplate that should not exist. Actually I think
the language should recognize the main function and pass to it the
command-line arguments automatically; unfortunaly this is unlikely to
happen. I have been writing boilerplate like this in hundreds of
scripts for years, and every time I *hate* it. The purpose of using a
scripting language is convenience and trivial things should be
trivial. Unfortunately the standard library does not help for this
incredibly common use case. Using getopt_ and optparse_ does not help,
since they are intended to manage options and not positional
arguments; the argparse_ module helps a bit and it is able to reduce
the boilerplate from nine lines to six lines:
.. include:: example2.py
:literal:
However saving three lines does not justify introducing the external
dependency: most people will not switch to Python 2.7, which at the time of
this writing is just about to be released, for many years.
Moreover, it just feels too complex to instantiate a class and to
define a parser by hand for such a trivial task.
The plac_ module is designed to manage well such use cases, and it is able
to reduce the original nine lines of boiler plate to two lines. With the
plac_ module all you need to write is
.. include:: example3.py
:literal:
The plac_ module provides for free (actually the work is done by the
underlying argparse_ module) a nice usage message::
$ python example3.py -h
.. include:: example3.help
:literal:
Moreover plac_ manages the case of missing arguments and of too many arguments.
This is only the tip of the iceberg: plac_ is able to do much more than that.
Scripts with default arguments
--------------------------------------------------
The need to have suitable defaults for command-line scripts is quite
common. For instance I have encountered this use case at work hundreds
of times:
.. include:: example4.py
:literal:
Here I want to perform a query on a database table, by extracting the
most recent data: it makes sense for ``today`` to be a default argument.
If there is a most used table (in this example a table called ``'product'``)
it also makes sense to make it a default argument. Performing the parsing
of the command-line arguments by hand takes 8 ugly lines of boilerplate
(using argparse_ would require about the same number of lines).
With plac_ the entire ``__main__`` block reduces to the usual two lines::
if __name__ == '__main__':
import plac; plac.call(main)
In other words, six lines of boilerplate have been removed, and we get
the usage message for free:
.. include:: example5.help
:literal:
plac_ manages transparently even the case when you want to pass a
variable number of arguments. Here is an example, a script running
on a database a series of SQL scripts:
.. include:: example7.py
:literal:
Here is the usage message:
.. include:: example7.help
:literal:
The examples here should have made clear that *plac is able to figure out
the command-line arguments parser to use from the signature of the main
function*. This is the whole idea behind plac_: if the intent is clear,
let's the machine take care of the details.
plac_ is inspired to an old Python Cookbook recipe (optionparse_), in
the sense that it delivers the programmer from the burden of writing
the parser, but is less of a hack: instead of extracting the parser
from the docstring of the module, it extracts it from the signature of
the ``main`` function.
The idea comes from the `function annotations` concept, a new
feature of Python 3. An example is worth a thousand words, so here
it is:
.. include:: example7_.py
:literal:
Here the arguments of the ``main`` function have been annotated with
strings which are intented to be used in the help message:
.. include:: example7_.help
:literal:
plac_ is able to recognize much more complex annotations, as
I will show in the next paragraphs.
Scripts with options (and smart options)
-----------------------------------------
It is surprising how few command-line scripts with options I have
written over the years (probably less than a hundred), compared to the
number of scripts with positional arguments I wrote (certainly more
than a thousand of them). Still, this use case cannot be neglected.
The standard library modules (all of them) are quite verbose when it
comes to specifying the options and frankly I have never used them
directly. Instead, I have always relied on the
optionparse_ recipe, which provides a convenient wrapper over
optionparse_. Alternatively, in the simplest cases, I have just
performed the parsing by hand. In plac_ the parser is inferred by the
function annotations. Here is an example:
.. include:: example8.py
:literal:
Here the argument ``command`` has been annotated with the tuple
``("SQL query", 'option', 'c')``: the first string is the help string
which will appear in the usage message, the second string tells plac_
that ``command`` is an option and the third string that there is also
a short form of the option ``-c``, the long form being ``--command``.
The usage message is the following:
.. include:: example8.help
:literal:
Here are two examples of usage::
$ python3 example8.py -c"select * from table" dsn
executing select * from table on dsn
$ python3 example8.py --command="select * from table" dsn
executing select * from table on dsn
The third argument in the function annotation can be omitted: in such
case it will be assumed to be ``None``. The consequence is that
the usual dichotomy between long and short options (GNU-style options)
disappears: we get *smart options*, which have the single character prefix
of short options and behave like both long and short options, since
they can be abbreviated. Here is an example featuring smart options:
.. include:: example6.py
:literal:
.. include:: example6.help
:literal:
The following are all valid invocations ot the script::
$ python3 example6.py -c "select" dsn
executing 'select' on dsn
$ python3 example6.py -com "select" dsn
executing 'select' on dsn
$ python3 example6.py -command="select" dsn
executing 'select' on dsn
Notice that the form ``-command=SQL`` is recognized only for the full
option, not for its abbreviations::
$ python3 example6.py -com="select" dsn
usage: example6.py [-h] [-command COMMAND] dsn
example6.py: error: unrecognized arguments: -com=select
If the option is not passed, the variable ``command``
will get the value ``None``. However, it is possible to specify a non-trivial
default. Here is an example:
.. include:: example8_.py
:literal:
Notice that the default value appears in the help message:
.. include:: example8_.help
:literal:
When you run the script and you do not pass the ``-command`` option, the
default query will be executed::
$ python3 example8_.py dsn
executing 'select * from table' on dsn
Scripts with flags
--------------------
plac_ is able to recognize flags, i.e. boolean options which are
``True`` if they are passed to the command line and ``False``
if they are absent. Here is an example:
.. include:: example9.py
:literal:
.. include:: example9.help
:literal:
::
$ python3 example9.py -v dsn
connecting to dsn
Notice that it is an error trying to specify a default for flags: the
default value for a flag is always ``False``. If you feel the need to
implement non-boolean flags, you should use an option with two
choices, as explained in the "more features" section.
For consistency with the way the usage message is printed, I suggest
you to follow the Flag-Option-Required-Default (FORD) convention: in
the ``main`` function write first the flag arguments, then the option
arguments, then the required arguments and finally the default
arguments. This is just a convention and you are not forced to use it,
except for the default arguments (including the varargs) which must
stay at the end as it is required by the Python syntax.
I also suggests to specify a one-character abbreviation for flags: in
this way you can use the GNU-style composition of flags (i.e. ``-zxvf``
is an abbreviation of ``-z -x -v -f``). I usually do not provide
the one-character abbreviation for options, since it does not make sense
to compose them.
plac for Python 2.X users
--------------------------------------------------
I do not use Python 3. At work we are just starting to think about
migrating to Python 2.6. It will take years before we
think to migrate to Python 3. I am pretty much sure most Pythonistas
are in the same situation. Therefore plac_ provides a way to work
with function annotations even in Python 2.X (including Python 2.3).
There is no magic involved; you just need to add the annotations
by hand. For instance the annotated function declaration
::
def main(dsn: "Database dsn", *scripts: "SQL scripts"):
...
is equivalent to the following code::
def main(dsn, *scripts):
...
main.__annotations__ = dict(
dsn="Database dsn",
scripts="SQL scripts")
One should be careful to match the keys of the annotation dictionary
with the names of the arguments in the annotated function; for lazy
people with Python 2.4 available the simplest way is to use the
``plac.annotations`` decorator that performs the check for you::
@plac.annotations(
dsn="Database dsn",
scripts="SQL scripts")
def main(dsn, *scripts):
...
In the rest of this article I will assume that you are using Python 2.X with
X >= 4 and I will use the ``plac.annotations`` decorator. Notice however
that the core features of plac_ run even on Python 2.3.
More features
--------------------------------------------------
One of the goals of plac_ is to have a learning curve of *minutes* for
its core features, compared to the learning curve of *hours* of
argparse_. In order to reach this goal, I have *not* sacrificed all
the features of argparse_. Actually a lot of argparse_ power persists
in plac_. Until now, I have only showed simple annotations, but in
general an annotation is a 6-tuple of the form
``(help, kind, abbrev, type, choices, metavar)``
where ``help`` is the help message, ``kind`` is a string in the set {
``"flag"``, ``"option"``, ``"positional"``}, ``abbrev`` is a
one-character string or ``None``, ``type`` is a callable taking a
string in input,
``choices`` is a discrete sequence of values and ``metavar`` is a string.
``type`` is used to automagically convert the command line arguments
from the string type to any Python type; by default there is no
conversion and ``type=None``.
``choices`` is used to restrict the number of the valid
options; by default there is no restriction i.e. ``choices=None``.
``metavar`` has two meanings. For a positional argument it is used to
change the argument name in the usage message (and only there). By
default the metavar is ``None`` and the name in the usage message is
the same as the argument name. For an option
the ``metavar`` is used differently in the usage message, which has
now the form ``[--option-name METAVAR]``. If the ``metavar`` is ``None``,
then it is equal to the uppercased name of the argument, unless the
argument has a default and in such a case is equal to the stringified
form of the default.
Here is an example showing many of the features (copied from the
argparse_ documentation):
.. include:: example10.py
:literal:
Here is the usage:
.. include:: example10.help
:literal:
Notice that the docstring of the ``main`` function has been automatically added
to the usage message. Here are a couple of examples of use::
$ python example10.py add 1 2 3 4
10.0
$ python example10.py mul 1 2 3 4
24.0
$ python example10.py ad 1 2 3 4 # a mispelling error
usage: example10.py [-h] {add,mul} [n [n ...]]
example10.py: error: argument operator: invalid choice: 'ad' (choose from 'add', 'mul')
``plac.call`` can also be used in doctests like this:
>>> import plac, example10
>>> plac.call(example10.main, ['add', '1', '2'])
3.0
``plac.call`` works for generators too:
>>> def main(n):
... for i in range(int(n)):
... yield i
>>> plac.call(main, ['3'])
[0, 1, 2]
Internally ``plac.call`` tries to convert the output of the main function
into a list, if possible. If the output is not iterable or it is a
string, it is left unchanged, but if it is iterable it is converted.
In particular, generator objects are exhausted by ``plac.call``.
This behavior avoids mistakes like forgetting of applying
``list(result)`` to the result of ``plac.call``; moreover it makes
errors visible early, and avoids mistakes in code like the following::
try:
result = plac.call(main, args)
except:
# do something
Without the "listify" functionality, a main function returning a
generator object would not raise any exception until the generator
is iterated over.
If you are a fan of lazyness, you can still have it by setting the ``eager``
flag to ``False``, as in the following example::
for line in plac.call(main, args, eager=False):
print(line)
If ``main`` returns a generator object this example will print each
line as soon as available, whereas the default behaviour is to print
all the lines together and the end of the computation.
A realistic example
---------------------------------------
Here is a more realistic script using most of the features of plac_ to
run SQL queries on a database by relying on SQLAlchemy_. Notice the usage
of the ``type`` feature to automagically convert a SQLAlchemy connection
string into a SqlSoup_ object:
.. include:: dbcli.py
:literal:
You can see the *yield-is-print* pattern here: instead of using
``print`` in the main function, I use ``yield``, and I perform the
print in the ``__main__`` block. The advantage of the pattern is that
tests invoking ``plac.call`` and checking the result become trivial:
had I performed the printing in the main function, the test would have
involved an ugly hack like redirecting ``sys.stdout`` to a
``StringIO`` object.
Here is the usage message:
.. include:: dbcli.help
:literal:
You can check for yourself that the script works.
Keyword arguments
---------------------------------------
Starting from release 0.4, plac_ supports keyword arguments. In
practice that means that if your main function has keyword arguments,
plac_ treats specially arguments of the form ``"name=value"`` in the
command line. Here is an example:
.. include:: example12.py
:literal:
Here is the generated usage message:
.. include:: example12.help
:literal:
Here is how you call the script::
$ python example12.py -o X a1 a2 name=value
opt=X
args=('a1', 'a2')
kw={'name': 'value'}
When using keyword arguments, one must be careful to use names which
are not alreay taken; for instance in this examples the name ``opt``
is taken::
$ python example12.py 1 2 kw1=1 kw2=2 opt=0
usage: example12.py [-h] [-o OPT] [args [args ...]] [kw [kw ...]]
example12.py: error: colliding keyword arguments: opt
The names taken are the names of the flags, of the options, and of the
positional arguments, excepted varargs and keywords. This limitation
is a consequence of the way the argument names are managed in function calls
by the Python language.
Final example: a shelve interface
----------------------------------------------------------
Here is a less trivial example for the keyword arguments feature.
The use case is the following: suppose we have stored the
configuration parameters of a given application into a Python shelve
and we need a command-line tool to edit the shelve.
A possible implementation using plac_ could be the following:
.. include:: ishelve.py
:literal:
A few notes are in order:
1. I have disabled the ordinary help provided by argparse_ and I have
implemented a custom help command.
2. I have changed the prefix character used to recognize the options
to a dot.
3. Keyword arguments recognition (in the ``**setters``) is used to make it
possible to store a value in the shelve with the syntax
``param_name=param_value``.
4. ``*params`` are used to retrieve parameters from the shelve and some
error checking is performed in the case of missing parameters
5. A command to clear the shelve is implemented as a flag (``.clear``).
6. A command to delete a given parameter is implemented as an option
(``.delete``).
7. There is an option with default (``.filename=conf.shelve``) to store
the filename of the shelve.
8. All things considered, the code looks like a poor man object oriented
interface implemented with a chain of elifs instead of methods. Of course,
plac_ can do better than that, but let me start from a low-level approach
first.
If you run ``ishelve.py`` without arguments you get the following
message::
$ python ishelve.py
no arguments passed, use .help to see the available commands
If you run ``ishelve.py`` with the option ``.h`` (or any abbreviation
of ``.help``) you get::
$ python ishelve.py .h
Commands: .help, .showall, .clear, .delete
<param> ...
<param=value> ...
You can check by hand that the tool work::
$ python ishelve.py .clear # start from an empty shelve
cleared the shelve
$ python ishelve.py a=1 b=2
setting a=1
setting b=2
$ python ishelve.py .showall
b=2
a=1
$ python ishelve.py .del b # abbreviation for .delete
deleted b
$ python ishelve.py a
1
$ python ishelve.py b
b: not found
$ python ishelve.py .cler # mispelled command
usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]
[.filename /home/micheles/conf.shelve]
[params [params ...]] [setters [setters ...]]
ishelve.py: error: unrecognized arguments: .cler
plac vs argparse
---------------------------------------------
plac_ is opinionated and by design it does not try to make available
all of the features of argparse_ in an easy way. In particular you
should be aware of the following limitations/differences (the
following assumes knowledge of argparse_):
- plac does not support the destination concept: the destination
coincides with the name of the argument, always. This restriction
has some drawbacks. For instance, suppose you want to define a long
option called ``--yield``. In this case the destination would be ``yield``,
which is a Python keyword, and since you cannot introduce an
argument with that name in a function definition, it is impossible
to implement it. Your choices are to change the name of the long
option, or to use argparse_ with a suitable destination.
- plac_ does not support "required options". As the argparse_
documentation puts it: *Required options are generally considered bad
form - normal users expect options to be optional. You should avoid
the use of required options whenever possible.* Notice that since
argparse_ supports them, plac_ can manage them too, but not directly.
- plac_ supports only regular boolean flags. argparse_ has the ability to
define generalized two-value flags with values different from ``True``
and ``False``. An earlier version of plac_ had this feature too, but
since you can use options with two choices instead, and in any case
the conversion from ``{True, False}`` to any couple of values
can be trivially implemented with a ternary operator
(``value1 if flag else value2``), I have removed it (KISS rules!).
- plac_ does not support ``nargs`` options directly (it uses them internally,
though, to implement flag recognition). The reason it that all the use
cases of interest to me are covered by plac_ and did not feel the need
to increase the learning curve by adding direct support for ``nargs``.
- plac_ does support subparsers, but you must read the `advanced usage
document`_ to see how it works.
- plac_ does not support actions directly. This also
looks like a feature too advanced for the goals of plac_. Notice however
that the ability to define your own annotation objects (again, see
the `advanced usage document`_) may mitigate the need for custom actions.
plac_ can leverage directly on many argparse_ features.
For instance, you can make invisible an argument in the usage message
simply by using ``'==SUPPRESS=='`` as help string (or
``argparse.SUPPRESS``). Similarly, you can use argparse.FileType_
directly.
It is also possible to pass options to the underlying
``argparse.ArgumentParser`` object (currently it accepts the default
arguments ``description``, ``epilog``, ``prog``, ``usage``,
``add_help``, ``argument_default``, ``parents``, ``prefix_chars``,
``fromfile_prefix_chars``, ``conflict_handler``, ``formatter_class``).
It is enough to set such attributes on the ``main`` function. For
instance
::
def main(...):
pass
main.add_help = False
disables the recognition of the help flag ``-h, --help``. This
mechanism does not look particularly elegant, but it works well
enough. I assume that the typical user of plac_ will be happy with
the defaults and would not want to change them; still it is possible
if she wants to.
For instance, by setting the ``description`` attribute, it is possible
to add a comment to the usage message (by default the docstring of the
``main`` function is used as description).
It is also possible to change the option prefix; for
instance if your script must run under Windows and you want to use "/"
as option prefix you can add the line::
main.prefix_chars='/-'
The first prefix char (``/``) is used
as the default for the recognition of options and flags;
the second prefix char (``-``) is kept to keep the ``-h/--help`` option
working: however you can disable it and reimplement it, if you like,
as seen in the ``ishelve`` example.
It is possible to access directly the underlying ArgumentParser_ object, by
invoking the ``plac.parser_from`` utility function:
>>> import plac
>>> def main(arg):
... pass
...
>>> print(plac.parser_from(main)) #doctest: +ELLIPSIS
ArgumentParser(prog=...)
Internally ``plac.call`` uses ``plac.parser_from`` and adds the parser
to the main function as an attribute. When ``plac.call(func)`` is
invoked multiple time, the parser is re-used and not rebuilt from scratch again.
I use ``plac.parser_from`` in the unit tests of the module, but regular
users should not need to use it, unless they want to access *all*
of the features of argparse_ directly without calling the main function.
Interested readers should read the documentation of argparse_ to
understand the meaning of the other options. If there is a set of
options that you use very often, you may consider writing a decorator
adding such options to the ``main`` function for you. For simplicity,
plac_ does not perform any magic except the addition of the ``.p``
attribute.
plac vs the rest of the world
------------------------------------------
Originally plac_ boasted about being "the easiest command-line
arguments parser in the world". Since then, people started pointing
out to me various projects which are based on the same idea
(extracting the parser from the main function signature) and are
arguably even easier than plac_:
- opterator_ by Dusty Phillips
- CLIArgs_ by Pavel Panchekha
Luckily for me none of such projects had the idea of using
function annotations and argparse_; as a consequence, they are
no match for the capabilities of plac_.
Of course, there are tons of other libraries to parse the command
line. For instance Clap_ by Matthew Frazier which appeared on PyPI
just the day before plac_; Clap_ is fine but it is certainly not
easier than plac_.
plac_ can also be used as a replacement of the cmd_ module in the standard
library and as such it shares many features with the module cmd2_ by
Catherine Devlin. However, this is completely coincidental, since I became
aware of the cmd2_ module only after writing plac_.
The future
-------------------------------
Currently the core of plac_ is around 200 lines of code, not counting blanks,
comments and docstrings. I do not plan to extend the core much in the
future. The idea is to keep the module short: it is and it should
remain a little wrapper over argparse_. Actually I have thought about
contributing the core back to argparse_ if plac_ becomes successfull
and gains a reasonable number of users. For the moment it should be
considered in alpha status.
Notice that even if plac_ has been designed to be simple to use for
simple stuff, its power should not be underestimated; it is actually a
quite advanced tool with a domain of applicability which far exceeds
the realm of command-line arguments parsers.
Version 0.5 of plac_ doubled the code base and the documentation: it is
based on the idea of using plac_ to implement command-line interpreters,
i.e. something akin to the ``cmd`` module in the standard library, only better.
The new features of plac_ are described in the `advanced usage document`_ .
They are implemented in a separated module (``plac_ext.py``), since
they require Python 2.5 to work, whereas ``plac_core.py`` only requires
Python 2.3.
Trivia: the story behind the name
-----------------------------------------
The plac_ project started very humbly: I just wanted to make
easy_installable my old optionparse_ recipe, and to publish it on PyPI.
The original name of plac_ was optionparser and the idea behind it was
to build an OptionParser_ object from the docstring of the module.
However, before doing that, I decided to check out the argparse_ module,
since I knew it was going into Python 2.7 and Python 2.7 was coming out.
Soon enough I realized two things:
1. the single greatest idea of argparse_ was unifying the positional arguments
and the options in a single namespace object;
2. parsing the docstring was so old-fashioned, considering the existence
of functions annotations in Python 3.
Putting together these two observations with the original idea of inferring the
parser I decided to build an ArgumentParser_ object from function
annotations. The ``optionparser`` name was ruled out, since I was
now using argparse_; a name like ``argparse_plus`` was also ruled out,
since the typical usage was completely different from the argparse_ usage.
I made a research on PyPI and the name *clap* (Command Line Arguments Parser)
was not taken, so I renamed everything to clap. After two days
a Clap_ module appeared on PyPI <expletives deleted>!
Having little imagination, I decided to rename everything again to plac,
an anagram of clap: since it is a non-existing English name, I hope nobody
will steal it from me!
That's all, I hope you will enjoy working with plac_!
.. _argparse: http://argparse.googlecode.com
.. _optparse: http://docs.python.org/library/optparse.html
.. _getopt: http://docs.python.org/library/getopt.html
.. _optionparse: http://code.activestate.com/recipes/278844-parsing-the-command-line/
.. _plac: http://pypi.python.org/pypi/plac
.. _scaling down: http://www.welton.it/articles/scalable_systems
.. _ArgumentParser: http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html
.. _argparse.FileType: http://argparse.googlecode.com/svn/tags/r11/doc/other-utilities.html?highlight=filetype#FileType
.. _Clap: http://pypi.python.org/pypi/Clap/0.7
.. _OptionParser: http://docs.python.org/library/optparse.html?highlight=optionparser#optparse.OptionParser
.. _SQLAlchemy: http://www.sqlalchemy.org/
.. _SqlSoup: http://www.sqlalchemy.org/docs/reference/ext/sqlsoup.html
.. _CLIArgs: http://pypi.python.org/pypi/CLIArgs
.. _opterator: http://pypi.python.org/pypi/opterator
.. _advanced usage document: in-writing
.. _cmd2: http://packages.python.org/cmd2/
.. _cmd: http://docs.python.org/library/cmd.html
+24
View File
@@ -0,0 +1,24 @@
# literal translation from http://groups.google.com/group/comp.lang.python/msg/de7c188e705f8eb2?hl=en
# parser = optparse.OptionParser("usage: %lines [options] arg1")
# parser.add_option("-l", "--lines", dest="lines",
# default=10, type="int",
# help="number of lines")
# parser.add_option("-t", "--topbottom", dest="topbottom",
# default="T", type="str",
# help="T(op) or B(ottom)")
# (options, args) = parser.parse_args()
# if len(args) != 1:
# parser.error("incorrect number of arguments")
# lines=options.lines
# tb=options.topbottom
import plac
@plac.annotations(
lines=('number of lines', 'option', 'l', int),
topbottom=('T(op) or B(ottom)', 'option', 't', str, 'TB'))
def main(arg, lines=10, topbottom='T'):
print arg, lines, topbottom
if __name__ == '__main__':
plac.call(main)
+13
View File
@@ -0,0 +1,13 @@
"""
You can run this script as
$ python read_stdin.py < ishelve.bat
"""
from __future__ import with_statement
import sys
from ishelve import ishelve
import plac
if __name__ == '__main__':
with plac.Interpreter(ishelve) as i:
for line in sys.stdin:
print(i.send(line))
+9
View File
@@ -0,0 +1,9 @@
import plac
from importer2 import FakeImporter
def main(port=2199):
main = FakeImporter('dsn')
plac.Interpreter(main).start_server(port)
if __name__ == '__main__':
plac.call(main)
+13
View File
@@ -0,0 +1,13 @@
usage: shelve_interpreter.py [-h] [-interactive]
[subcommands [subcommands ...]]
This script works both interactively and non-interactively.
Use .help to see the internal commands.
positional arguments:
subcommands the commands of the underlying ishelve interpreter
optional arguments:
-h, --help show this help message and exit
-interactive start interactive interface
+19
View File
@@ -0,0 +1,19 @@
# shelve_interpreter.py
import plac, ishelve
@plac.annotations(
interactive=('start interactive interface', 'flag'),
subcommands='the commands of the underlying ishelve interpreter')
def main(interactive, *subcommands):
"""
This script works both interactively and non-interactively.
Use .help to see the internal commands.
"""
if interactive:
plac.Interpreter(ishelve.main).interact()
else:
for out in plac.call(ishelve.main, subcommands):
print(out)
if __name__ == '__main__':
plac.call(main)
+28
View File
@@ -0,0 +1,28 @@
import os, plac
from sqlalchemy.ext.sqlsoup import SqlSoup
SQLKEYWORDS = set(['select', 'from', 'inner', 'join', 'outer', 'left', 'right']
) # and many others
DBTABLES = set(['table1', 'table2']) # you can read them from the db schema
COMPLETIONS = SQLKEYWORDS | DBTABLES
class SqlInterface(object):
commands = ['SELECT']
def __init__(self, dsn):
self.soup = SqlSoup(dsn)
def SELECT(self, argstring):
sql = 'SELECT ' + argstring
for row in self.soup.bind.execute(sql):
yield str(row) # the formatting can be much improved
rl_input = plac.ReadlineInput(
COMPLETIONS, histfile=os.path.expanduser('~/.sql_interface.history'),
case_sensitive=False)
def split_on_first_space(line, commentchar):
return line.strip().split(' ', 1) # ignoring comments
if __name__ == '__main__':
plac.Interpreter.call(SqlInterface, split=split_on_first_space,
stdin=rl_input, prompt='sql> ')
+11
View File
@@ -0,0 +1,11 @@
from __future__ import with_statement
import plac, plac_ext
def main(*args):
yield args
if __name__ == '__main__':
proc = plac_ext.SyncProcess(['syncproc.py'])
while True:
inp = raw_input(proc.recv())
proc.send(inp)
+12
View File
@@ -0,0 +1,12 @@
# test_ishelve.py
import plac, ishelve
def test():
assert plac.call(ishelve.main, ['.clear']) == ['cleared the shelve']
assert plac.call(ishelve.main, ['a=1']) == ['setting a=1']
assert plac.call(ishelve.main, ['a']) == ['1']
assert plac.call(ishelve.main, ['.delete=a']) == ['deleted a']
assert plac.call(ishelve.main, ['a']) == ['a: not found']
if __name__ == '__main__':
test()
+11
View File
@@ -0,0 +1,11 @@
# test_ishelve_more.py
from __future__ import with_statement
import plac, ishelve
def test():
with plac.Interpreter(ishelve.main) as i:
i.check('.clear', 'cleared the shelve')
i.check('a=1', 'setting a=1')
i.check('a', '1')
i.check('.delete=a', 'deleted a')
i.check('a', 'a: not found')
+223
View File
@@ -0,0 +1,223 @@
"""
The tests are runnable with nose, with py.test, or even as standalone script
"""
import os, sys, doctest, subprocess
import plac
sys_argv0 = sys.argv[0]
os.chdir(os.path.dirname(__file__) or '.') # work in the current directory
######################## helpers #######################
def expect(errclass, func, *args, **kw):
try:
func(*args, **kw)
except errclass:
pass
else:
raise RuntimeError('%s expected, got none!', errclass.__name__)
def parser_from(f, **kw):
f.__annotations__ = kw
return plac.parser_from(f)
def check_help(name):
sys.argv[0] = name + '.py' # avoid issue with nosetests
plac.parser_registry.clear() # makes different imports independent
try:
try:
main = plac.import_main(name + '.py')
except SyntaxError:
if sys.version < '3': # expected for Python 2.X
return
else: # not expected for Python 3.X
raise
p = plac.parser_from(main)
expected = open(name + '.help').read().strip()
got = p.format_help().strip()
assert got == expected, got
finally:
sys.argv[0] = sys_argv0
####################### tests ############################
def test_expected_help():
for fname in os.listdir('.'):
if fname.endswith('.help'):
name = fname[:-5]
if name not in ('vcs', 'ishelve'):
yield check_help, fname[:-5]
p1 = parser_from(lambda delete, *args: None,
delete=('delete a file', 'option'))
def test_p1():
arg = p1.parse_args(['-d', 'foo', 'arg1', 'arg2'])
assert arg.delete == 'foo'
assert arg.args == ['arg1', 'arg2']
arg = p1.parse_args([])
assert arg.delete is None, arg.delete
assert arg.args == [], arg.args
p2 = parser_from(lambda arg1, delete, *args: None,
delete=('delete a file', 'option', 'd'))
def test_p2():
arg = p2.parse_args(['-d', 'foo', 'arg1', 'arg2'])
assert arg.delete == 'foo', arg.delete
assert arg.arg1 == 'arg1', arg.arg1
assert arg.args == ['arg2'], arg.args
arg = p2.parse_args(['arg1'])
assert arg.delete is None, arg.delete
assert arg.args == [], arg.args
assert arg, arg
expect(SystemExit, p2.parse_args, [])
p3 = parser_from(lambda arg1, delete: None,
delete=('delete a file', 'option', 'd'))
def test_p3():
arg = p3.parse_args(['arg1'])
assert arg.delete is None, arg.delete
assert arg.arg1 == 'arg1', arg.args
expect(SystemExit, p3.parse_args, ['arg1', 'arg2'])
expect(SystemExit, p3.parse_args, [])
p4 = parser_from(lambda delete, delete_all, color="black": None,
delete=('delete a file', 'option', 'd'),
delete_all=('delete all files', 'flag', 'a'),
color=('color', 'option', 'c'))
def test_p4():
arg = p4.parse_args(['-a'])
assert arg.delete_all is True, arg.delete_all
arg = p4.parse_args([])
arg = p4.parse_args(['--color=black'])
assert arg.color == 'black'
arg = p4.parse_args(['--color=red'])
assert arg.color == 'red'
def test_flag_with_default():
expect(TypeError, parser_from, lambda yes_or_no='no': None,
yes_or_no=('A yes/no flag', 'flag', 'f'))
def assert_usage(parser, expected):
usage = parser.format_usage()
assert usage == expected, usage
def test_metavar_no_defaults():
sys.argv[0] = 'test_plac.py'
# positional
p = parser_from(lambda x: None,
x=('first argument', 'positional', None, str, [], 'METAVAR'))
assert_usage(p, 'usage: test_plac.py [-h] METAVAR\n')
# option
p = parser_from(lambda x: None,
x=('first argument', 'option', None, str, [], 'METAVAR'))
assert_usage(p, 'usage: test_plac.py [-h] [-x METAVAR]\n')
sys.argv[0] = sys_argv0
def test_metavar_with_defaults():
sys.argv[0] = 'test_plac.py'
# positional
p = parser_from(lambda x='a': None,
x=('first argument', 'positional', None, str, [], 'METAVAR'))
assert_usage(p, 'usage: test_plac.py [-h] [METAVAR]\n')
# option
p = parser_from(lambda x='a': None,
x=('first argument', 'option', None, str, [], 'METAVAR'))
assert_usage(p, 'usage: test_plac.py [-h] [-x METAVAR]\n')
p = parser_from(lambda x='a': None,
x=('first argument', 'option', None, str, []))
assert_usage(p, 'usage: test_plac.py [-h] [-x a]\n')
sys.argv[0] = sys_argv0
def test_kwargs():
def main(opt, arg1, *args, **kw):
print(opt, arg1)
return args, kw
main.__annotations__ = dict(opt=('Option', 'option'))
argskw = plac.call(main, ['arg1', 'arg2', 'a=1', 'b=2'])
assert argskw == [('arg2',), {'a': '1', 'b': '2'}], argskw
argskw = plac.call(main, ['arg1', 'arg2', 'a=1', '-o', '2'])
assert argskw == [('arg2',), {'a': '1'}], argskw
expect(SystemExit, plac.call, main, ['arg1', 'arg2', 'a=1', 'opt=2'])
class Cmds(object):
add_help = False
commands = 'help', 'commit'
def help(self, name):
return 'help', name
def commit(self):
return 'commit'
cmds = Cmds()
def test_cmds():
assert 'commit' == plac.call(cmds, ['commit'])
assert ['help', 'foo'] == plac.call(cmds, ['help', 'foo'])
expect(SystemExit, plac.call, cmds, [])
def test_cmd_abbrevs():
assert 'commit' == plac.call(cmds, ['comm'])
assert ['help', 'foo'] == plac.call(cmds, ['h', 'foo'])
expect(SystemExit, plac.call, cmds, ['foo'])
def test_yield():
def main():
for i in (1, 2, 3):
yield i
assert plac.call(main, []) == [1, 2, 3]
def test_doctest():
failure, tot= doctest.testfile('plac.txt', module_relative=False)
assert not failure, failure
failing_scripts = set(['ishelve2.plac'])
def check_script(args):
if failing_scripts.intersection(args):
assert subprocess.call(args) > 0, ( # expected failure
'Unexpected success for %s' % ' '.join(args))
else:
assert subprocess.call(args) == 0 , 'Failed %s' % ' '.join(args)
def test_batch():
for batch in os.listdir('.'):
if batch.endswith('.plac'):
yield check_script, ['plac_runner.py', '-b', batch]
def test_placet():
for placet in os.listdir('.'):
if placet.endswith('.placet'):
yield check_script, ['plac_runner.py', '-t', placet]
if __name__ == '__main__':
n = 0
for name, test in sorted(globals().items()):
if name.startswith('test_'):
print('Running ' + name)
maybegen = test()
if hasattr(maybegen, '__iter__'):
for func, arg in maybegen:
func(arg)
n += 1
else:
n +=1
print('Executed %d tests OK' % n)
+35
View File
@@ -0,0 +1,35 @@
import multiprocessing, subprocess, time, random
import plac
from ishelve2 import ShelveInterface
i = plac.Interpreter(ShelveInterface(configfile=None))
COMMANDS = ['''\
.help
set a 1
''',
'''\
set b 1
wrong command
showall
''']
def client_send(commands, port):
time.sleep(.5) # wait a bit for the server to start
po = subprocess.Popen(['telnet', 'localhost', str(port)],
stdin=subprocess.PIPE)
for cmd in commands.splitlines():
po.stdin.write(cmd + '\n')
time.sleep(.1) # wait a bit for the server to answer
def test():
port = random.choice(range(2000, 20000))
clients = []
for cmds in COMMANDS:
cl = multiprocessing.Process(target=client_send, args=(cmds, port))
clients.append(cl)
cl.start()
i.stop_server(wait=1)
i.start_server(port, timeout=.1)
for cl in clients:
cl.join()
+40
View File
@@ -0,0 +1,40 @@
import threading, Queue, time, random, Tkinter
import plac
queue = Queue.Queue()
def make_target(i):
def thunk():
time.sleep(random.random())
queue.put(str(i))
return thunk
def launch_threads(n):
for i in range(n):
th = threading.Thread(target=make_target(i))
th.start()
yield th
class ThreadContext(object):
def __init__(self, func, *args, **kw):
self.thread = threading.Thread(None, func, args=args, kwargs=kw)
def __enter__(self):
self.thread.start()
return self
def __exit__(self, etype, exc, tb):
self.thread.join()
def interpret_queue(interpreter, queue):
with interpreter:
for value in iter(queue.get, 'exit'):
print(interpreter.send(value))
if __name__ == '__main__':
thlist = list(launch_threads(10))
i = plac.Interpreter(lambda x: x)
root = Tkinter.Tk()
threading.Timer(3, lambda : [queue.put('exit'), root.quit()]).start()
with ThreadContext(interpret_queue, i, queue):
root.mainloop()
for th in thlist:
th.join()
+10
View File
@@ -0,0 +1,10 @@
usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...
A Fake Version Control System
optional arguments:
-h, --help show this help message and exit
subcommands:
{status,commit,checkout}
-h to get additional help
+30
View File
@@ -0,0 +1,30 @@
"A Fake Version Control System"
import plac
commands = 'checkout', 'commit', 'status'
@plac.annotations(url='url of the source code')
def checkout(url):
"A fake checkout command"
return ('checkout ', url)
@plac.annotations(message=('commit message', 'option'))
def commit(message):
"A fake commit command"
return ('commit ', message)
@plac.annotations(quiet=('summary information', 'flag', 'q'))
def status(quiet):
"A fake status command"
return ('status ', quiet)
def __missing__(name):
return 'Command %r does not exist' % name
def __exit__(etype, exc, tb):
"Will be called automatically at the end of the call/cmdloop"
if etype in (None, GeneratorExit): # success
print('ok')
main = __import__(__name__) # the module imports itself!
+7
View File
@@ -0,0 +1,7 @@
class Main(object):
commands = ['do']
def do(self, x):
pass
if __name__ == '__main__':
import plac; plac.Interpreter.call(Main)
+406
View File
@@ -0,0 +1,406 @@
Traceback (most recent call last):
File "importer3.py", line 20, in <module>
plac.Interpreter(plac.call(FakeImporter)).interact()
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 775, in interact
self._manage_input()
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 785, in _manage_input
write(str(task) + '\n')
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
return '\n'.join(map(str, self.outlist))
RuntimeError: maximum recursion depth exceeded while calling a Python object
+35
View File
@@ -0,0 +1,35 @@
########################## LICENCE ###############################
##
## Copyright (c) 2010, Michele Simionato
## All rights reserved.
##
## Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## Redistributions in bytecode form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in
## the documentation and/or other materials provided with the
## distribution.
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
## DAMAGE.
"""
See doc/plac.pdf, doc/plac_adv.pdf for the documentation.
"""
__version__ = '0.7.4'
from plac_core import *
if sys.version >= '2.5':
from plac_ext import Interpreter, import_main, ReadlineInput, stdout, runp
+303
View File
@@ -0,0 +1,303 @@
# this module should be kept Python 2.3 compatible
import re, sys, inspect, argparse
if sys.version >= '3':
from inspect import getfullargspec
else:
class getfullargspec(object):
"A quick and dirty replacement for getfullargspec for Python 2.X"
def __init__(self, f):
self.args, self.varargs, self.varkw, self.defaults = \
inspect.getargspec(f)
self.annotations = getattr(f, '__annotations__', {})
try:
set
except NameError: # Python 2.3
from sets import Set as set
from gettext import gettext as _
def annotations(**ann):
"""
Returns a decorator annotating a function with the given annotations.
This is a trick to support function annotations in Python 2.X.
"""
def annotate(f):
fas = getfullargspec(f)
args = fas.args
if fas.varargs:
args.append(fas.varargs)
if fas.varkw:
args.append(fas.varkw)
for argname in ann:
if argname not in args:
raise NameError(
_('Annotating non-existing argument: %s') % argname)
f.__annotations__ = ann
return f
return annotate
def is_annotation(obj):
"""
An object is an annotation object if it has the attributes
help, kind, abbrev, type, choices, metavar.
"""
return (hasattr(obj, 'help') and hasattr(obj, 'kind') and
hasattr(obj, 'abbrev') and hasattr(obj, 'type')
and hasattr(obj, 'choices') and hasattr(obj, 'metavar'))
class Annotation(object):
def __init__(self, help="", kind="positional", abbrev=None, type=None,
choices=None, metavar=None):
assert kind in ('positional', 'option', 'flag'), kind
if kind == "positional":
assert abbrev is None, abbrev
self.help = help
self.kind = kind
self.abbrev = abbrev
self.type = type
self.choices = choices
self.metavar = metavar
def from_(cls, obj):
"Helper to convert an object into an annotation, if needed"
if is_annotation(obj):
return obj # do nothing
elif hasattr(obj, '__iter__') and not isinstance(obj, basestring):
return cls(*obj)
return cls(obj)
from_ = classmethod(from_)
NONE = object() # sentinel use to signal the absence of a default
PARSER_CFG = getfullargspec(argparse.ArgumentParser.__init__).args[1:]
# the default arguments accepted by an ArgumentParser object
def pconf(obj):
"Extracts the configuration of the underlying ArgumentParser from obj"
cfg = dict(description=obj.__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
for name in dir(obj):
if name in PARSER_CFG: # argument of ArgumentParser
cfg[name] = getattr(obj, name)
return cfg
parser_registry = {}
def parser_from(obj, **confparams):
"""
obj can be a callable or an object with a .commands attribute.
Returns an ArgumentParser.
"""
try: # the underlying parser has been generated already
return parser_registry[obj]
except KeyError: # generate a new parser
pass
conf = pconf(obj).copy()
conf.update(confparams)
parser_registry[obj] = parser = ArgumentParser(**conf)
parser.obj = obj
parser.case_sensitive = confparams.get(
'case_sensitive', getattr(obj, 'case_sensitive', True))
if hasattr(obj, 'commands') and obj.commands and not inspect.isclass(obj):
# a command container instance
parser.addsubcommands(obj.commands, obj, 'subcommands')
else:
parser.populate_from(obj)
return parser
def _extract_kwargs(args):
"Returns two lists: regular args and name=value args"
arglist = []
kwargs = {}
for arg in args:
match = re.match(r'([a-zA-Z_]\w*)=', arg)
if match:
name = match.group(1)
kwargs[name] = arg[len(name)+1:]
else:
arglist.append(arg)
return arglist, kwargs
def _match_cmd(abbrev, commands, case_sensitive=True):
"Extract the command name from an abbreviation or raise a NameError"
if not case_sensitive:
abbrev = abbrev.upper(); commands = [c.upper() for c in commands]
perfect_matches = [name for name in commands if name == abbrev]
if len(perfect_matches) == 1:
return perfect_matches[0]
matches = [name for name in commands if name.startswith(abbrev)]
n = len(matches)
if n == 1:
return matches[0]
elif n > 1:
raise NameError(
_('Ambiguous command %r: matching %s' % (abbrev, matches)))
class ArgumentParser(argparse.ArgumentParser):
"""
An ArgumentParser with .func and .argspec attributes, and possibly
.commands and .subparsers.
"""
case_sensitive = True
def consume(self, args):
"""Call the underlying function with the args. Works also for
command containers, by dispatching to the right subparser."""
arglist = list(args)
cmd = None
if hasattr(self, 'subparsers'):
subp, cmd = self._extract_subparser_cmd(arglist)
if subp is None and cmd is not None:
return cmd, self.missing(cmd)
elif subp is not None: # use the subparser
self = subp
if hasattr(self, 'argspec') and self.argspec.varkw:
arglist, kwargs = _extract_kwargs(arglist) # modify arglist!
else:
kwargs = {}
if hasattr(self, 'argspec') and self.argspec.varargs:
# ignore unrecognized arguments
ns, extraopts = self.parse_known_args(arglist)
else:
ns, extraopts = self.parse_args(arglist), [] # may raise an exit
args = [getattr(ns, a) for a in self.argspec.args]
varargs = getattr(ns, self.argspec.varargs or '', [])
collision = set(self.argspec.args) & set(kwargs)
if collision:
self.error(
_('colliding keyword arguments: %s') % ' '.join(collision))
return cmd, self.func(*(args + varargs + extraopts), **kwargs)
def _extract_subparser_cmd(self, arglist):
"Extract the right subparser from the first recognized argument"
optprefix = self.prefix_chars[0]
name_parser_map = self.subparsers._name_parser_map
for i, arg in enumerate(arglist):
if not arg.startswith(optprefix):
cmd = _match_cmd(arg, name_parser_map, self.case_sensitive)
del arglist[i]
return name_parser_map.get(cmd), cmd or arg
return None, None
def addsubcommands(self, commands, obj, title=None, cmdprefix=''):
"Extract a list of subcommands from obj and add them to the parser"
if hasattr(obj, cmdprefix) and obj.cmdprefix in self.prefix_chars:
raise ValueError(_('The prefix %r is already taken!' % cmdprefix))
if not hasattr(self, 'subparsers'):
self.subparsers = self.add_subparsers(title=title)
elif title:
self.add_argument_group(title=title) # populate ._action_groups
prefixlen = len(getattr(obj, 'cmdprefix', ''))
for cmd in commands:
func = getattr(obj, cmd[prefixlen:]) # strip the prefix
self.subparsers.add_parser(
cmd, add_help=False, **pconf(func)).populate_from(func)
def _set_func_argspec(self, obj):
"""Extracts the signature from a callable object and adds an .argspec
attribute to the parser. Also adds a .func reference to the object."""
if inspect.isfunction(obj):
self.argspec = getfullargspec(obj)
elif inspect.ismethod(obj):
self.argspec = getfullargspec(obj)
del self.argspec.args[0] # remove first argument
elif inspect.isclass(obj):
if obj.__init__ is object.__init__: # to avoid an error
self.argspec = getfullargspec(lambda self: None)
else:
self.argspec = getfullargspec(obj.__init__)
del self.argspec.args[0] # remove first argument
elif hasattr(obj, '__call__'):
self.argspec = getfullargspec(obj.__call__)
del self.argspec.args[0] # remove first argument
else:
raise TypeError(_('Could not determine the signature of %r') % obj)
self.func = obj
parser_registry[obj] = self
def populate_from(self, func):
"""
Extract the arguments from the attributes of the passed function
and return a populated ArgumentParser instance.
"""
self._set_func_argspec(func)
f = self.argspec
defaults = f.defaults or ()
n_args = len(f.args)
n_defaults = len(defaults)
alldefaults = (NONE,) * (n_args - n_defaults) + defaults
prefix = self.prefix = getattr(func, 'prefix_chars', '-')[0]
for name, default in zip(f.args, alldefaults):
ann = f.annotations.get(name, ())
a = Annotation.from_(ann)
metavar = a.metavar
if default is NONE:
dflt = None
else:
dflt = default
if a.kind in ('option', 'flag'):
if a.abbrev:
shortlong = (prefix + a.abbrev, prefix*2 + name)
else:
shortlong = (prefix + name,)
elif default is NONE: # required argument
self.add_argument(name, help=a.help, type=a.type,
choices=a.choices, metavar=metavar)
else: # default argument
self.add_argument(
name, nargs='?', help=a.help, default=dflt,
type=a.type, choices=a.choices, metavar=metavar)
if a.kind == 'option':
if default is not NONE:
metavar = metavar or str(default)
self.add_argument(
help=a.help, default=dflt, type=a.type,
choices=a.choices, metavar=metavar, *shortlong)
elif a.kind == 'flag':
if default is not NONE and default is not False:
raise TypeError(_('Flag %r wants default False, got %r') %
(name, default))
self.add_argument(action='store_true', help=a.help, *shortlong)
if f.varargs:
a = Annotation.from_(f.annotations.get(f.varargs, ()))
self.add_argument(f.varargs, nargs='*', help=a.help, default=[],
type=a.type, metavar=a.metavar)
if f.varkw:
a = Annotation.from_(f.annotations.get(f.varkw, ()))
self.add_argument(f.varkw, nargs='*', help=a.help, default={},
type=a.type, metavar=a.metavar)
def missing(self, name):
miss = getattr(self.obj, '__missing__', lambda name:
self.error('No command %r' % name))
return miss(name)
def help_cmd(self, cmd):
"Return the help message for a subcommand"
p = self.subparsers._name_parser_map.get(cmd)
if p is None:
return _('Unknown command %s' % cmd)
else:
return p.format_help()
def print_actions(self):
"Useful for debugging"
print(self)
for a in self._actions:
print(a)
def iterable(obj):
"Any object with an __iter__ method which is not a string"
return hasattr(obj, '__iter__') and not isinstance(obj, basestring)
def call(obj, arglist=sys.argv[1:], eager=True):
"""
If obj is a function or a bound method, parse the given arglist
by using the parser inferred from the annotations of obj
and call obj with the parsed arguments.
If obj is an object with attribute .commands, dispatch to the
associated subparser.
"""
cmd, result = parser_from(obj).consume(arglist)
if iterable(result) and eager: # listify the result
return list(result)
return result
+892
View File
@@ -0,0 +1,892 @@
# this module requires Python 2.5+
from __future__ import with_statement
from contextlib import contextmanager
from operator import attrgetter
from gettext import gettext as _
import imp, inspect, os, sys, cmd, shlex, subprocess
import itertools, traceback, time, select, multiprocessing, signal, threading
import plac_core
try:
import readline
except ImportError:
readline = False
############################# generic utils ################################
@contextmanager
def stdout(fileobj):
"usage: with stdout(file('out.txt', 'a')): do_something()"
orig_stdout = sys.stdout
sys.stdout = fileobj
try:
yield
finally:
sys.stdout = orig_stdout
def write(x):
"Write str(x) on stdout and flush, no newline added"
sys.stdout.write(str(x))
sys.stdout.flush()
def gen_val(value):
"Return a generator object with a single element"
yield value
def gen_exc(etype, exc, tb):
"Return a generator object raising an exception"
raise etype, exc, tb
yield
def less(text):
"Send a text to less via a pipe"
# -c clear the screen before starting less
po = subprocess.Popen(['less', '-c'], stdin=subprocess.PIPE)
try:
po.stdin.write(text)
except IOError:
pass
po.stdin.close()
po.wait()
use_less = (sys.platform != 'win32') # unices
class TerminatedProcess(Exception):
pass
def terminatedProcess(signum, frame):
raise TerminatedProcess
########################### readline support #############################
def read_line(stdin, prompt=''):
"Read a line from stdin, using readline when possible"
if isinstance(stdin, ReadlineInput):
return stdin.readline(prompt)
else:
write(prompt)
return stdin.readline()
def read_long_line(stdin, terminator):
"""
Read multiple lines from stdin until the terminator character is found, then
yield a single space-separated long line.
"""
while True:
lines = []
while True:
line = stdin.readline() # ends with \n
if not line: # EOF
return
line = line.strip()
if not line:
continue
elif line[-1] == terminator:
lines.append(line[:-1])
break
else:
lines.append(line)
yield ' '.join(lines)
class ReadlineInput(object):
"""
An iterable with a .readline method reading from stdin.
"""
def __init__(self, completions, case_sensitive=True, histfile=None):
self.completions = completions
self.case_sensitive = case_sensitive
self.histfile = histfile
if not case_sensitive:
self.completions = map(str.upper, completions)
readline.parse_and_bind("tab: complete")
readline.set_completer(self.complete)
def __enter__(self):
self.old_completer = readline.get_completer()
try:
if self.histfile:
readline.read_history_file(self.histfile)
except IOError: # the first time
pass
return self
def __exit__(self, etype, exc, tb):
readline.set_completer(self.old_completer)
if self.histfile:
readline.write_history_file(self.histfile)
def complete(self, kw, state):
# state is 0, 1, 2, ... and increases by hitting TAB
if not self.case_sensitive:
kw = kw.upper()
try:
return [k for k in self.completions if k.startswith(kw)][state]
except IndexError: # no completions
return # exit
def readline(self, prompt=''):
try:
return raw_input(prompt) + '\n'
except EOFError:
return ''
def __iter__(self):
return iter(self.readline, '')
########################### import management ################################
try:
PLACDIRS = os.environ.get('PLACPATH', '.').split(':')
except:
raise ValueError(_('Ill-formed PLACPATH: got %PLACPATHs') % os.environ)
def partial_call(factory, arglist):
"Call a container factory with the arglist and return a plac object"
a = plac_core.parser_from(factory).argspec
if a.defaults or a.varargs or a.varkw:
raise TypeError('Interpreter.call must be invoked on '
'factories with required arguments only')
required_args = ', '.join(a.args)
if required_args:
required_args += ',' # trailing comma
code = '''def makeobj(interact, %s *args):
obj = factory(%s)
obj._interact_ = interact
obj._args_ = args
return obj\n'''% (required_args, required_args)
dic = dict(factory=factory)
exec code in dic
makeobj = dic['makeobj']
if inspect.isclass(factory):
makeobj.__annotations__ = getattr(
factory.__init__, '__annotations__', {})
else:
makeobj.__annotations__ = getattr(
factory, '__annotations__', {})
makeobj.__annotations__['interact'] = (
'start interactive interpreter', 'flag', 'i')
return plac_core.call(makeobj, arglist)
def import_main(path, *args, **pconf):
"""
An utility to import the main function of a plac tool. It also
works with command container factories.
"""
if ':' in path: # importing a factory
path, factory_name = path.split(':')
else: # importing the main function
factory_name = None
if not os.path.isabs(path): # relative path, look at PLACDIRS
for placdir in PLACDIRS:
fullpath = os.path.join(placdir, path)
if os.path.exists(fullpath):
break
else: # no break
raise ImportError(_('Cannot find %s' % path))
else:
fullpath = path
name, ext = os.path.splitext(os.path.basename(fullpath))
module = imp.load_module(name, open(fullpath), fullpath, (ext, 'U', 1))
if factory_name:
tool = partial_call(getattr(module, factory_name), args)
else:
tool = module.main
# set the parser configuration
plac_core.parser_from(tool, **pconf)
return tool
############################## Task classes ##############################
# base class not instantiated directly
class BaseTask(object):
"""
A task is a wrapper over a generator object with signature
Task(no, arglist, genobj), attributes
.no
.arglist
.outlist
.str
.etype
.exc
.tb
.status
and methods .run and .kill.
"""
STATES = ('SUBMITTED', 'RUNNING', 'TOBEKILLED', 'KILLED', 'FINISHED',
'ABORTED')
def __init__(self, no, arglist, genobj):
self.no = no
self.arglist = arglist
self._genobj = self._wrap(genobj)
self.str, self.etype, self.exc, self.tb = '', None, None, None
self.status = 'SUBMITTED'
self.outlist = []
def _wrap(self, genobj, stringify_tb=False):
"""
Wrap the genobj into a generator managing the exceptions,
populating the .outlist, setting the .status and yielding None.
stringify_tb must be True if the traceback must be sent to a process.
"""
self.status = 'RUNNING'
try:
for value in genobj:
if self.status == 'TOBEKILLED': # exit from the loop
raise GeneratorExit
if value is not None: # add output
self.outlist.append(value)
yield
except (GeneratorExit, TerminatedProcess, KeyboardInterrupt):
# soft termination
self.status = 'KILLED'
except: # unexpected exception
self.etype, self.exc, tb = sys.exc_info()
self.tb = self.traceback if stringify_tb else tb
self.status = 'ABORTED'
else: # regular exit
self.status = 'FINISHED'
try:
self.str = '\n'.join(map(str, self.outlist))
except IndexError:
self.str = 'no result'
def run(self):
"Run the inner generator"
for none in self._genobj:
pass
def kill(self):
"Set a TOBEKILLED status"
self.status = 'TOBEKILLED'
def wait(self):
"Wait for the task to finish: to be overridden"
@property
def traceback(self):
"Return the traceback as a (possibly empty) string"
if self.tb is None:
return ''
elif isinstance(self.tb, basestring):
return self.tb
else:
return ''.join(traceback.format_tb(self.tb))
@property
def result(self):
self.wait()
if self.exc:
raise self.etype, self.exc, self.tb or None
if not self.outlist:
return None
return self.outlist[-1]
def __repr__(self):
"String representation containing class name, number, arglist, status"
return '<%s %d [%s] %s>' % (
self.__class__.__name__, self.no,
' '.join(self.arglist), self.status)
nulltask = BaseTask(0, [], ('skip' for dummy in (1,)))
########################## synchronous tasks ###############################
class SynTask(BaseTask):
"""
Synchronous task running in the interpreter loop and displaying its
output as soon as available.
"""
def __str__(self):
"Return the output string or the error message"
if self.etype: # there was an error
return '%s: %s' % (self.etype.__name__, self.exc)
else:
return '\n'.join(map(str, self.outlist))
class ThreadedTask(BaseTask):
"""
A task running in a separated thread.
"""
def __init__(self, no, arglist, genobj):
BaseTask.__init__(self, no, arglist, genobj)
self.thread = threading.Thread(target=super(ThreadedTask, self).run)
def run(self):
"Run the task into a thread"
self.thread.start()
def wait(self):
"Block until the thread ends"
self.thread.join()
######################### multiprocessing tasks ##########################
def sharedattr(name, on_error):
"Return a property to be attached to an MPTask"
def get(self):
try:
return getattr(self.ns, name)
except: # the process was killed or died hard
return on_error
def set(self, value):
try:
setattr(self.ns, name, value)
except: # the process was killed or died hard
pass
return property(get, set)
class MPTask(BaseTask):
"""
A task running as an external process. The current implementation
only works on Unix-like systems, where multiprocessing use forks.
"""
str = sharedattr('str', '')
etype = sharedattr('etype', None)
exc = sharedattr('exc', None)
tb = sharedattr('tb', None)
status = sharedattr('status', 'ABORTED')
@property
def outlist(self):
try:
return self._outlist
except: # the process died hard
return []
def __init__(self, no, arglist, genobj, mp_manager):
self.no = no
self.arglist = arglist
self._genobj = self._wrap(genobj, stringify_tb=True)
self.mp_manager = mp_manager
self._outlist = self.mp_manager.list()
self.ns = self.mp_manager.Namespace()
self.status = 'SUBMITTED'
self.etype, self.exc, self.tb = None, None, None
self.str = repr(self)
self.proc = multiprocessing.Process(target=super(MPTask, self).run)
def run(self):
"Run the task into an external process"
self.proc.start()
def wait(self):
"Block until the external process ends or is killed"
self.proc.join()
def kill(self):
"""Kill the process with a SIGTERM inducing a TerminatedProcess
exception in the children"""
self.proc.terminate()
######################### Task Manager #######################
class HelpSummary(object):
"Build the help summary consistently with the cmd module"
@classmethod
def make(cls, obj, specialcommands):
c = cmd.Cmd(stdout=cls())
c.stdout.write('\n')
c.print_topics('special commands',
sorted(specialcommands), 15, 80)
c.print_topics('custom commands',
sorted(obj.syncommands), 15, 80)
c.print_topics('commands run in external processes',
sorted(obj.mpcommands), 15, 80)
c.print_topics('threaded commands',
sorted(obj.thcommands), 15, 80)
return c.stdout
def __init__(self):
self._ls = []
def write(self, s):
self._ls.append(s)
def __str__(self):
return ''.join(self._ls)
class TaskManager(object):
"""
Store the given commands into a task registry. Provides methods to
manage the submitted tasks.
"""
cmdprefix = '.'
specialcommands = set(['.help', '.last_tb'])
def __init__(self, obj):
self.obj = obj
self.registry = {} # {taskno : task}
if obj.mpcommands or obj.thcommands:
self.specialcommands.update(['.kill', '.list', '.output'])
self.helpsummary = HelpSummary.make(obj, self.specialcommands)
self.mp_manager = multiprocessing.Manager() if obj.mpcommands else None
signal.signal(signal.SIGTERM, terminatedProcess)
def close(self):
"Kill all the running tasks"
for task in self.registry.itervalues():
try:
if task.status == 'RUNNING':
task.kill()
task.wait()
except: # task killed, nothing to wait
pass
if self.mp_manager:
self.mp_manager.shutdown()
def _get_latest(self, taskno=-1, status=None):
"Get the latest submitted task from the registry"
assert taskno < 0, 'You must pass a negative number'
if status:
tasks = [t for t in self.registry.itervalues()
if t.status == status]
else:
tasks = [t for t in self.registry.itervalues()]
tasks.sort(key=attrgetter('no'))
if len(tasks) >= abs(taskno):
return tasks[taskno]
########################### special commands #########################
@plac_core.annotations(
taskno=('task to kill', 'positional', None, int))
def kill(self, taskno=-1):
'kill the given task (-1 to kill the latest running task)'
if taskno < 0:
task = self._get_latest(taskno, status='RUNNING')
if task is None:
yield 'Nothing to kill'
return
elif not taskno in self.registry:
yield 'Unknown task %d' % taskno
return
else:
task = self.registry[taskno]
if task.status in ('ABORTED', 'KILLED', 'FINISHED'):
yield 'Already finished %s' % task
return
task.kill()
yield task
@plac_core.annotations(
status=('', 'positional', None, str, BaseTask.STATES))
def list(self, status='RUNNING'):
'list tasks with a given status'
for task in self.registry.values():
if task.status == status:
yield task
@plac_core.annotations(
taskno=('task number', 'positional', None, int))
def output(self, taskno=-1):
'show the output of a given task'
if taskno < 0:
task = self._get_latest(taskno)
if task is None:
yield 'Nothing to show'
return
elif taskno not in self.registry:
yield 'Unknown task %d' % taskno
return
else:
task = self.registry[taskno]
outstr = '\n'.join(map(str, task.outlist))
yield task
if len(task.outlist) > 20 and use_less:
less(outstr)
else:
yield outstr
@plac_core.annotations(
taskno=('task number', 'positional', None, int))
def last_tb(self, taskno=-1):
"show the traceback of a given task, if any"
task = self._get_latest(taskno)
if task:
yield task.traceback
else:
yield 'Nothing to show'
def help(self, cmd=None):
"show help about a given command"
if cmd is None:
yield str(self.helpsummary)
else:
yield plac_core.parser_from(self.obj).help_cmd(cmd)
########################### SyncProcess ##############################
class Process(subprocess.Popen):
"Start the interpreter specified by the params in a subprocess"
def __init__(self, params):
code = '''import plac
plac.Interpreter(plac.import_main(*%s)).interact(prompt='i>\\n')
''' % params
subprocess.Popen.__init__(
self, [sys.executable, '-u', '-c', code],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def close(self):
"Close stdin and stdout"
self.stdin.close()
self.stdout.close()
def recv(self): # char-by-char cannot work
"Return the output of the subprocess, line-by-line until the prompt"
lines = []
while True:
lines.append(self.stdout.readline())
if lines[-1] == 'i>\n':
out = ''.join(lines)
return out[:-1] + ' ' # remove last newline
def send(self, line):
"""Send a line (adding a newline) to the underlying subprocess
and wait for the answer"""
self.stdin.write(line + os.linesep)
return self.recv()
########################## plac server ##############################
import asyncore, asynchat, socket
class _AsynHandler(asynchat.async_chat):
"asynchat handler starting a new interpreter loop for each connection"
terminator = '\r\n' # the standard one for telnet
prompt = 'i> '
def __init__(self, socket, interpreter):
asynchat.async_chat.__init__(self, socket)
self.set_terminator(self.terminator)
self.i = interpreter
self.i.__enter__()
self.data = []
self.write(self.prompt)
def write(self, data, *args):
"Push a string back to the client"
if args:
data %= args
if data.endswith('\n') and not data.endswith(self.terminator):
data = data[:-1] + self.terminator # fix newlines
self.push(data)
def collect_incoming_data(self, data):
"Collect one character at the time"
self.data.append(data)
def found_terminator(self):
"Put in the queue the line received from the client"
line = ''.join(self.data)
self.log('Received line %r from %s' % (line, self.addr))
if line == 'EOF':
self.i.__exit__()
self.handle_close()
else:
task = self.i.submit(line)
task.run() # synchronous or not
if task.etype: # manage exception
error = '%s: %s\nReceived: %s' % (
task.etype.__name__, task.exc, ' '.join(task.arglist))
self.log_info(task.traceback + error) # on the server
self.write(error + self.terminator) # back to the client
else: # no exception
self.write(task.str + self.terminator)
self.data = []
self.write(self.prompt)
class _AsynServer(asyncore.dispatcher):
"asyncore-based server spawning AsynHandlers"
def __init__(self, interpreter, newhandler, port, listen=5):
self.interpreter = interpreter
self.newhandler = newhandler
self.port = port
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(('', port))
self.listen(listen)
def handle_accept(self):
clientsock, clientaddr = self.accept()
self.log('Connected from %s' % str(clientaddr))
i = self.interpreter.__class__(self.interpreter.obj) # new interpreter
self.newhandler(clientsock, i) # spawn a new handler
########################### the Interpreter #############################
class Interpreter(object):
"""
A context manager with a .send method and a few utility methods:
execute, test and doctest.
"""
@classmethod
def call(cls, factory, arglist=sys.argv[1:],
commentchar='#', split=shlex.split,
stdin=sys.stdin, prompt='i> ', verbose=False):
"""
Call a container factory with the arglist and instantiate an
interpreter object. If there are remaining arguments, send them to the
interpreter, else start an interactive session.
"""
obj = partial_call(factory, arglist)
i = cls(obj, commentchar, split)
if i.obj._args_:
with i:
task = i.send(i.obj._args_) # synchronous
if task.exc:
raise task.etype, task.exc, task.tb
print(task)
elif i.obj._interact_:
i.interact(stdin, prompt, verbose)
else:
i.parser.print_usage()
def __init__(self, obj, commentchar='#', split=shlex.split):
self.obj = obj
try:
self.name = obj.__module__
except AttributeError:
self.name = 'plac'
self.commentchar = commentchar
self.split = split
self._set_commands(obj)
self.tm = TaskManager(obj)
self.parser = plac_core.parser_from(obj, prog='', add_help=False)
if self.commands:
self.commands.update(self.tm.specialcommands)
self.parser.addsubcommands(self.tm.specialcommands, self.tm,
title='special commands')
if obj.mpcommands:
self.parser.addsubcommands(obj.mpcommands, obj,
title='commands run in external processes')
if obj.thcommands:
self.parser.addsubcommands(obj.thcommands, obj,
title='threaded commands')
self.parser.error = lambda msg: sys.exit(msg) # patch the parser
self._interpreter = None
def _set_commands(self, obj):
"Make sure obj has the right command attributes as Python sets"
for attrname in ('commands', 'syncommands', 'mpcommands', 'thcommands'):
try:
sequence = getattr(obj, attrname)
except AttributeError:
sequence = []
if not isinstance(sequence, set):
sequence = set(sequence)
setattr(obj, attrname, sequence)
obj.syncommands.update(obj.commands)
self.commands = obj.commands
self.commands.update(obj.syncommands)
self.commands.update(obj.mpcommands)
self.commands.update(obj.thcommands)
def __enter__(self):
"Start the inner interpreter loop"
self._interpreter = self._make_interpreter()
self._interpreter.send(None)
return self
def __exit__(self, *exc):
"Close the inner interpreter and the task manager"
self.close()
def submit(self, line):
"Send a line to the underlying interpreter and return a task object"
if self._interpreter is None:
raise RuntimeError(_('%r not initialized: probably you forgot to '
'use the with statement') % self)
if isinstance(line, basestring):
arglist = self.split(line, self.commentchar)
else: # expects a list of strings
arglist = line
if not arglist:
return nulltask
task = self._interpreter.send(arglist) # nonblocking
if not plac_core._match_cmd(arglist[0], self.tm.specialcommands):
self.tm.registry[task.no] = task
return task
def send(self, line):
"Send a line to the underlying interpreter and return the finished task"
task = self.submit(line)
BaseTask.run(task) # blocking
return task
def tasks(self):
"The full lists of the submitted tasks"
return self.tm.registry.values()
def close(self):
"Can be called to close the interpreter prematurely"
self.tm.close()
self._interpreter.close()
def _make_interpreter(self):
"The interpreter main loop, from lists of arguments to task objects"
enter = getattr(self.obj, '__enter__', lambda : None)
exit = getattr(self.obj, '__exit__', lambda et, ex, tb: None)
enter()
task = None
try:
for no in itertools.count(1):
arglist = yield task
try:
cmd, result = self.parser.consume(arglist)
except: # i.e. SystemExit for invalid command
task = SynTask(no, arglist, gen_exc(*sys.exc_info()))
continue
if not plac_core.iterable(result): # atomic result
task = SynTask(no, arglist, gen_val(result))
elif cmd in self.obj.mpcommands:
task = MPTask(no, arglist, result, self.tm.mp_manager)
elif cmd in self.obj.thcommands:
task = ThreadedTask(no, arglist, result)
else: # blocking task
task = SynTask(no, arglist, result)
except GeneratorExit: # regular exit
exit(None, None, None)
except: # exceptional exit
exit(*sys.exc_info())
raise
def check(self, given_input, expected_output):
"Make sure you get the expected_output from the given_input"
output = self.send(given_input).str # blocking
ok = (output == expected_output)
if not ok:
# the message here is not internationalized on purpose
msg = 'input: %s\noutput: %s\nexpected: %s' % (
given_input, output, expected_output)
raise AssertionError(msg)
def _parse_doctest(self, lineiter):
"Returns the lines of input, the lines of output, and the line number"
lines = [line.strip() for line in lineiter]
inputs = []
positions = []
for i, line in enumerate(lines):
if line.startswith('i> '):
inputs.append(line[3:])
positions.append(i)
positions.append(len(lines) + 1) # last position
outputs = []
for i, start in enumerate(positions[:-1]):
end = positions[i + 1]
outputs.append('\n'.join(lines[start+1:end]))
return zip(inputs, outputs, positions)
def doctest(self, lineiter, verbose=False):
"""
Parse a text containing doctests in a context and tests of all them.
Raise an error even if a single doctest if broken. Use this for
sequential tests which are logically grouped.
"""
with self:
for input, output, no in self._parse_doctest(lineiter):
if verbose:
write('i> %s\n' % input)
write('-> %s\n' % output)
task = self.send(input) # blocking
if not str(task) == output:
msg = 'line %d: input: %s\noutput: %s\nexpected: %s\n' % (
no + 1, input, task, output)
write(msg)
raise task.etype, task.exc, task.tb
def execute(self, lineiter, verbose=False):
"Execute a lineiter of commands in a context and print the output"
with self:
for line in lineiter:
if verbose:
write('i> ' + line)
task = self.send(line) # finished task
if task.etype: # there was an error
raise task.etype, task.exc, task.tb
write('%s\n' % task.str)
def multiline(self, stdin=sys.stdin, terminator=';', verbose=False):
"The multiline mode is especially suited for usage with emacs"
with self:
for line in read_long_line(stdin, terminator):
task = self.submit(line)
task.run()
write('%s\n' % task.str)
if verbose and task.traceback:
write(task.traceback)
def interact(self, stdin=sys.stdin, prompt='i> ', verbose=False):
"Starts an interactive command loop reading commands from the consolle"
if stdin is sys.stdin and readline: # use readline
histfile = os.path.expanduser('~/.%s.history' % self.name)
self.stdin = ReadlineInput(self.commands, histfile=histfile)
else:
self.stdin = stdin
self.prompt = prompt
self.verbose = verbose
intro = self.obj.__doc__ or ''
write(intro + '\n')
with self:
if self.stdin is sys.stdin: # do not close stdin automatically
self._manage_input()
else:
with self.stdin: # close stdin automatically
self._manage_input()
def _manage_input(self):
"Convert input lines into task which are then executed"
for line in iter(lambda : read_line(self.stdin, self.prompt), ''):
line = line.strip()
if not line:
continue
task = self.submit(line)
task.run() # synchronous or not
write(str(task) + '\n')
if self.verbose and task.etype:
write(task.traceback)
def start_server(self, port=2199, **kw):
"""Starts an asyncore server reading commands for clients and opening
a new interpreter for each connection."""
_AsynServer(self, _AsynHandler, port) # register the server
try:
asyncore.loop(**kw)
except KeyboardInterrupt:
pass
finally:
asyncore.close_all()
def stop_server(self, wait=0.0):
"Stops the asyncore server, possibly after a given number of seconds"
threading.Timer(wait, asyncore.socket_map.clear).start()
#################################### runp #####################################
class _TaskLauncher(object):
"Helper for runp"
def __init__(self, genseq, mode):
if mode == 'p':
self.mpcommands = ['rungen']
else:
self.thcommands = ['rungen']
self.genlist = list(genseq)
def rungen(self, i):
for out in self.genlist[int(i) - 1]:
yield out
def runp(genseq, mode='p', start=True):
"""Run a sequence of generators in parallel. Mode can be 'p' (use processes)
or 't' (use threads). Return a list of running task objects. If start is
False, the tasks are only submitted and not automatically started.
"""
assert mode in 'pt', mode
launcher = _TaskLauncher(genseq, mode)
inter = Interpreter(launcher).__enter__()
for i in range(len(launcher.genlist)):
inter.submit('rungen %d' % (i + 1))
if start:
for task in inter.tasks():
task.run()
return inter.tasks()
+65
View File
@@ -0,0 +1,65 @@
#!python
from __future__ import with_statement
import os, sys, shlex, inspect
import plac
def run(fnames, cmd, verbose):
"Run batch scripts and tests"
for fname in fnames:
with open(fname) as f:
lines = list(f)
if not lines[0].startswith('#!'):
sys.exit('Missing or incorrect shebang line!')
firstline = lines[0][2:] # strip the shebang
init_args = shlex.split(firstline)
tool = plac.import_main(*init_args)
command = getattr(plac.Interpreter(tool), cmd) # doctest or execute
if verbose:
sys.stdout.write('Running %s with %s' % (fname, firstline))
command(lines[1:], verbose=verbose)
@plac.annotations(
verbose=('verbose mode', 'flag', 'v'),
interactive=('run plac tool in interactive mode', 'flag', 'i'),
multiline=('run plac tool in multiline mode', 'flag', 'm'),
serve=('run plac server', 'option', 's', int),
batch=('run plac batch files', 'flag', 'b'),
test=('run plac test files', 'flag', 't'),
fname='script to run (.py or .plac or .placet)',
extra='additional arguments',
)
def main(verbose, interactive, multiline, serve, batch, test, fname=None,
*extra):
"Runner for plac tools, plac batch files and plac tests"
baseparser = plac.parser_from(main)
if fname is None:
baseparser.print_help()
elif sys.argv[1] == fname: # script mode
plactool = plac.import_main(
fname, prog=os.path.basename(sys.argv[0]) + ' ' + fname)
out = plac.call(plactool, sys.argv[2:], eager=False)
if plac.iterable(out):
for output in out:
print(output)
else:
print(out)
elif interactive or multiline or serve:
plactool = plac.import_main(fname, *extra, **{'prog': ''})
i = plac.Interpreter(plactool)
if interactive:
i.interact(verbose=verbose)
elif multiline:
i.multiline(verbose=verbose)
elif serve:
i.start_server(serve)
elif batch:
run((fname,) + extra, 'execute', verbose)
elif test:
run((fname,) + extra, 'doctest', verbose)
print('run %s plac test(s)' % (len(extra) + 1))
else:
baseparser.print_usage()
main.add_help = False
if __name__ == '__main__':
plac.call(main)
+5
View File
@@ -0,0 +1,5 @@
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
+49
View File
@@ -0,0 +1,49 @@
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
import os.path
def require(*modules):
"""Check if the given modules are already available; if not add them to
the dependency list."""
deplist = []
for module in modules:
try:
__import__(module)
except ImportError:
deplist.append(module)
return deplist
def getversion(fname):
"Get the __version__ without importing plac"
for line in open(fname):
if line.startswith('__version__'):
return eval(line[13:])
if __name__ == '__main__':
setup(name='plac',
version=getversion(
os.path.join(os.path.dirname(__file__), 'plac.py')),
description=('The smartest command line arguments parser '
'in the world'),
long_description=open('README.txt').read(),
author='Michele Simionato',
author_email='michele.simionato@gmail.com',
url='http://pypi.python.org/pypi/plac',
license="BSD License",
py_modules = ['plac_core', 'plac_ext', 'plac'],
scripts = ['plac_runner.py'],
install_requires=require('argparse', 'multiprocessing'),
use_2to3=True,
keywords="command line arguments parser",
platforms=["All"],
classifiers=['Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries',
'Topic :: Utilities'],
zip_safe=False)