plac-0.9.6/0000775000175000017500000000000012740075376013520 5ustar michelemichele00000000000000plac-0.9.6/MANIFEST.in0000664000175000017500000000010612740075216015244 0ustar michelemichele00000000000000include *.md *.rst doc/*.py doc/*.help doc/*.txt doc/*.html doc/*.pdf plac-0.9.6/PKG-INFO0000664000175000017500000000370012740075376014615 0ustar michelemichele00000000000000Metadata-Version: 1.1 Name: plac Version: 0.9.6 Summary: The smartest command line arguments parser in the world Home-page: https://github.com/micheles/plac Author: Michele Simionato Author-email: michele.simionato@gmail.com License: BSD License Description: Installation ------------- If you are lazy, just perform :: $ pip install plac which will install the module on your system (and possibly argparse too, if it is not already installed). 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 Testing -------- Run :: $ python doc/test_plac.py or :: $ nosetests doc or :: $ py.test doc Some tests will fail if sqlsoup is not installed. Run a ``pip install sqlsoup`` or just ignore them. Documentation -------------- The source code and the documentation are hosted on GitHub. Here is the full documentation: https://github.com/micheles/plac/blob/0.9.6/doc/plac.pdf Keywords: command line arguments parser Platform: All Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities plac-0.9.6/doc/0000775000175000017500000000000012740075376014265 5ustar michelemichele00000000000000plac-0.9.6/doc/example6.help0000664000175000017500000000034612740075202016647 0ustar michelemichele00000000000000usage: example6.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 plac-0.9.6/doc/example2.py0000664000175000017500000000036012740075202016337 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/example12.py0000664000175000017500000000056012740075202016422 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/example7_.py0000664000175000017500000000043712740075202016510 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/importer1.py0000664000175000017500000000105112740075202016542 0ustar michelemichele00000000000000import 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) plac-0.9.6/doc/example8_.py0000664000175000017500000000031112740075202016500 0ustar michelemichele00000000000000# example8_.py def main(dsn, command: ("SQL query", 'option', 'c')='select * from table'): print('executing %r on %s' % (command, dsn)) if __name__ == '__main__': import plac; plac.call(main) plac-0.9.6/doc/vcs.help0000664000175000017500000000054012740075202015715 0ustar michelemichele00000000000000usage: 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} checkout A fake checkout command commit A fake commit command status A fake status command plac-0.9.6/doc/example11.py0000664000175000017500000000047112740075202016422 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/example7.help0000664000175000017500000000030512740075202016643 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/annotations.py0000664000175000017500000000043712740075202017164 0ustar michelemichele00000000000000# 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 plac-0.9.6/doc/server_ex.py0000664000175000017500000000030512740075202016623 0ustar michelemichele00000000000000import plac from importer2 import FakeImporter def main(port=2199): main = FakeImporter('dsn') plac.Interpreter(main).start_server(port) if __name__ == '__main__': plac.call(main) plac-0.9.6/doc/example4.py0000664000175000017500000000070012740075202016337 0ustar michelemichele00000000000000# 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__': # manual management before argparse 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) plac-0.9.6/doc/shelve_interpreter.py0000664000175000017500000000106112740075202020532 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/ishelve.py0000664000175000017500000000357112740075202016270 0ustar michelemichele00000000000000# ishelve.py import os import shelve import 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 ' ...' yield ' ...' 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) plac-0.9.6/doc/ishelve2.py0000664000175000017500000000266312740075202016353 0ustar michelemichele00000000000000# ishelve2.py import shelve, os, 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.\nUse 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 if __name__ == '__main__': plac.Interpreter(plac.call(ShelveInterface)).interact() plac-0.9.6/doc/vcs.py0000664000175000017500000000170312740075216015424 0ustar michelemichele00000000000000"A Fake Version Control System" import plac # this implementation also works with Python 2.4 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 intepreter loop" if etype in (None, GeneratorExit): # success print('ok') main = __import__(__name__) # the module imports itself! if __name__ == '__main__': import plac for out in plac.call(main, version='0.1.0'): print(out) plac-0.9.6/doc/example8.help0000664000175000017500000000032512740075202016646 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/plac.html0000664000175000017500000043215612740075216016076 0ustar michelemichele00000000000000

Plac: Parsing the Command Line the Easy Way

Author:Michele Simionato
E-mail:michele.simionato@gmail.com
Date:June 2016
Download page:http://pypi.python.org/pypi/plac
Project page:https://github.com/micheles/plac
Requires:Python 2.6+
Installation:pip install plac
License:BSD license

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-negligible learning curve and a certain verbosity. They do not scale down well enough, at least in my opinion.

It should not be necessary to stress the importance of 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 time 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 9 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 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 the case of 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:

# example1.py
def main(dsn):
    "Do something with the database"
    print("ok")

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:]))

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 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:

# 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)

However, 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

# example3.py
def main(dsn):
    "Do something with the database"
    print(dsn)
    # ...
 
if __name__ == '__main__':
    import plac; plac.call(main)

The plac module provides for free (actually the work is done by the underlying argparse module) a nice usage message:

$ python example3.py -h
usage: example3.py [-h] dsn

Do something with the database

positional arguments:
  dsn

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

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:

# 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__': # manual management before argparse
    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)

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:

usage: example5.py [-h] dsn [table] [today]

Do something on the database

positional arguments:
  dsn
  table       [product]
  today       [YYYY-MM-DD]

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

Notice that by default plac prints the string representation of the default values (with square brackets) in the usage message. 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:

# 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)

Here is the usage message:

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

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 of mine (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:

# 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)

Here the arguments of the main function have been annotated with strings which are intented to be used in the help message:

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

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 argparse. 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:

# 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)

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:

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

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:

# example6.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)

usage: example6.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

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:

# example8_.py
def main(dsn, command: ("SQL query", 'option', 'c')='select * from table'):
    print('executing %r on %s' % (command, dsn))

if __name__ == '__main__':
    import plac; plac.call(main)

Notice that the default value appears in the help message:

usage: example8_.py [-h] [-c select * from table] dsn

positional arguments:
  dsn

optional arguments:
  -h, --help            show this help message and exit
  -c select * from table, --command select * from table
                        SQL query

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:

# 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)

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

$ 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.

Starting from plac 0.9.1 underscores in options and flags are automatically turned into dashes. This feature was implemented at user request, to make it possible to use a more traditional naming. For instance now you can have a --dry-run flag, whereas before you had to use --dry_run.

def main(dry_run: ('Dry run', 'flag', 'd')):
    if dry_run:
        print('Doing nothing')
    else:
        print('Doing something')

if __name__ == '__main__':
    import plac; plac.call(main)

Here is an example of usage:

$ python3.2 dry_run.py -h
usage: dry_run.py [-h] [-d]

optional arguments:
  -h, --help     show this help message and exit
  -d, --dry-run  Dry run

plac for Python 2.X users

While plac runs great on Python 3, I do not personally use it. At work we migrated to Python 2.7 in 2011. It will take a few more years before we consider migrating to Python 3. I am pretty much sure many 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 the 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: then it is equal to the stringified form of the default.

Here is an example showing many of the features (copied from the argparse documentation):

# 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))

Here is the usage:

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

Notice that the docstring of the main function has been automatically added to the usage message. Here are a couple of examples of usage:

$ 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 eagerness, 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:

# dbcli.py
import plac
from sqlsoup import SQLSoup


@plac.annotations(
    db=plac.Annotation("Connection string", type=SQLSoup),
    header=plac.Annotation("Header", 'flag', 'H'),
    sqlcmd=plac.Annotation("SQL command", 'option', 'c', str, metavar="SQL"),
    delimiter=plac.Annotation("Column separator", 'option', 'd'),
    scripts=plac.Annotation("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(open(script).read())
        yield 'executed %s' % script

if __name__ == '__main__':
    for output in plac.call(main):
        print(output)

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:

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

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:

# 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)

Here is the generated usage message:

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

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.

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 I 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.

On the plus side, plac can leverage directly on a number of argparse features.

For instance, you can use argparse.FileType directly. Moreover, it is 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 writing

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.

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. Notice that 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.

Final example: a shelve interface

Here is a nontrivial example showing off many plac feature, including keyword arguments recognition. 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:

# ishelve.py
import os
import shelve
import 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)

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 set the filename of the shelve.
  8. All things considered, the code looks like a poor man's 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 works:

$ 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 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:

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.

Command-line argument parsers keep coming out; between the newcomers I will notice marrow.script by Alice Bevan-McGregor, which is quite similar to plac in spirit, but does not rely on argparse at all. Argh by Andrey Mikhaylenko is also worth mentioning: it is based on argparse, it came after plac and I must give credit to the author for the choice of the name, much funnier than 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 successful and gains a reasonable number of users. For the moment it should be considered in a frozen 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 my old optionparse recipe easy_installable, 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 concludes the section about the basic usage of plac. You are now ready to read about the advanced usage.

Advanced usages of plac

Introduction

One of the design goals of plac is to make it dead easy to write a scriptable and testable interface for an application. You can use plac whenever you have an API with strings in input and strings in output, and that includes a huge domain of applications.

A string-oriented interface is a scriptable interface by construction. That means that you can define a command language for your application and that it is possible to write scripts which are interpretable by plac and can be run as batch scripts.

Actually, at the most general level, you can see plac as a generic tool to write domain specific languages (DSL). With plac you can test your application interactively as well as with batch scripts, and even with the analogous of Python doctests for your defined language.

You can easily replace the cmd module of the standard library and you could easily write an application like twill with plac. Or you could use it to script your building procedure. plac also supports parallel execution of multiple commands and can be used as task manager. It is also quite easy to build a GUI or a Web application on top of plac. When speaking of things you can do with plac, your imagination is the only limit!

From scripts to interactive applications

Command-line scripts have many advantages, but they are no substitute for interactive applications.

In particular, if you have a script with a large startup time which must be run multiple times, it is best to turn it into an interactive application, so that the startup is performed only once. plac provides an Interpreter class just for this purpose.

The Interpreter class wraps the main function of a script and provides an .interact method to start an interactive interpreter reading commands from the console.

For instance, you can define an interactive interpreter on top of the ishelve script introduced in the basic documentation as follows:

# 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)

A trick has been used here: the ishelve command-line interface has been hidden inside an external interface. They are distinct: for instance the external interface recognizes the -h/--help flag whereas the internal interface only recognizes the .help command:

$ python shelve_interpreter.py -h
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

Thanks to this ingenuous trick, the script can be run both interactively and non-interactively:

$ python shelve_interpreter.py .clear # non-interactive use
cleared the shelve

Here is an usage session:

$ python shelve_interpreter.py -i # interactive use
A simple interface to a shelve. Use .help to see the available commands.
i> .help
Commands: .help, .showall, .clear, .delete
<param> ...
<param=value> ...
i> a=1
setting a=1
i> a
1
i> b=2
setting b=2
i> a b
1
2
i> .del a
deleted a
i> a
a: not found
i> .show
b=2
i> [CTRL-D]

The .interact method reads commands from the console and send them to the underlying interpreter, until the user send a CTRL-D command (CTRL-Z in Windows). There is a default argument prompt='i> ' which can be used to change the prompt. The text displayed at the beginning of the interactive session is the docstring of the main function. plac also understands command abbreviations: in this example del is an abbreviation for delete. In case of ambiguous abbreviations plac raises a NameError.

Finally I must notice that plac.Interpreter is available only if you are using a recent version of Python (>= 2.5), because it is a context manager object which uses extended generators internally.

Testing a plac application

You can conveniently test your application in interactive mode. However manual testing is a poor substitute for automatic testing.

In principle, one could write automatic tests for the ishelve application by using plac.call directly:

# 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()

However, using plac.call is not especially nice. The big issue is that plac.call responds to invalid input by printing an error message on stderr and by raising a SystemExit: this is certainly not a nice thing to do in a test.

As a consequence of this behavior it is impossible to test for invalid commands, unless you wrap the SystemExit exception by hand each time (and possibly you do something with the error message in stderr too). Luckily, plac offers a better testing support through the check method of Interpreter objects:

# test_ishelve_more.py
from __future__ import with_statement
import ishelve
import plac


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')

The method .check(given_input, expected_output) works on strings and raises an AssertionError if the output produced by the interpreter is different from the expected output for the given input. Notice that AssertionError is catched by tools like py.test and nosetests and actually plac tests are intended to be run with such tools.

Interpreters offer a minor syntactic advantage with respect to calling plac.call directly, but they offer a major semantic advantage when things go wrong (read exceptions): an Interpreter object internally invokes something like plac.call, but it wraps all exceptions, so that i.check is guaranteed not to raise any exception except AssertionError.

Even the SystemExit exception is captured and you can write your test as

i.check('-cler', 'SystemExit: unrecognized arguments: -cler')

without risk of exiting from the Python interpreter.

There is a second advantage of interpreters: if the main function contains some initialization code and finalization code (__enter__ and __exit__ functions) they will be run at the beginning and at the end of the interpreter loop, whereas plac.call ignores the initialization/finalization code.

Plac easy tests

Writing your tests in terms of Interpreter.check is certainly an improvement over writing them in terms of plac.call, but they are still too low-level for my taste. The Interpreter class provides support for doctest-style tests, a.k.a. plac easy tests.

By using plac easy tests you can cut and paste your interactive session and turn it into a runnable automatics test. Consider for instance the following file ishelve.placet (the .placet extension is a mnemonic for "plac easy tests"):

#!ishelve.py
i> .clear # start from a clean state
cleared the shelve
i> a=1
setting a=1
i> a
1
i> .del a
deleted a
i> a
a: not found
i> .cler # spelling error
.cler: not found

Notice the presence of the shebang line containing the name of the plac tool to test (a plac tool is just a Python module with a function called main). The shebang is ignored by the interpreter (it looks like a comment to it) but it is there so that external tools (say a test runner) can infer the plac interpreter to use to test the file.

You can run the ishelve.placet file by calling the .doctest method of the interpreter, as in this example:

$ python -c "import plac, ishelve
plac.Interpreter(ishelve.main).doctest(open('ishelve.placet'), verbose=True)"

Internally Interpreter.doctests invokes something like Interpreter.check multiple times inside the same context and compares the output with the expected output: if even one check fails, the whole test fail.

You should realize that the easy tests supported by plac are not unittests: they are functional tests. They model the user interaction and the order of the operations generally matters. The single subtests in a .placet file are not independent and it makes sense to exit immediately at the first failure.

The support for doctests in plac comes nearly for free, thanks to the shlex module in the standard library, which is able to parse simple languages as the ones you can implement with plac. In particular, thanks to shlex, plac is able to recognize comments (the default comment character is #), escape sequences and more. Look at the shlex documentation if you need to customize how the language is interpreted. For more flexibility, it is even possible to pass the interpreter a custom split function with signature split(line, commentchar).

In addition, I have implemented some support for line number recognition, so that if a test fails you get the line number of the failing command. This is especially useful if your tests are stored in external files, though they do not need to be in a file: you can just pass to the .doctest method a list of strings corresponding to the lines of the file.

At the present plac does not use any code from the doctest module, but the situation may change in the future (it would be nice if plac could reuse doctests directives like ELLIPSIS).

It is straighforward to integrate your .placet tests with standard testing tools. For instance, you can integrate your doctests with nose or py.test as follow:

import os, shlex, plac

def test_doct():
   """
   Find all the doctests in the current directory and run them with the
   corresponding plac interpreter (the shebang rules!)
   """
   placets = [f for f in os.listdir('.') if f.endswith('.placet')]
   for placet in placets:
       lines = list(open(placet))
       assert lines[0].startswith('#!'), 'Missing or incorrect shebang line!'
       firstline = lines[0][2:] # strip the shebang
       main = plac.import_main(*shlex.split(firstline))
       yield plac.Interpreter(main).doctest, lines[1:]

Here you should notice that usage of plac.import_main, an utility which is able to import the main function of the script specified in the shebang line. You can use both the full path name of the tool, or a relative path name. In this case the runner looks at the environment variable PLACPATH and it searches the plac tool in the directories specified there (PLACPATH is just a string containing directory names separated by colons). If the variable PLACPATH is not defined, it just looks in the current directory. If the plac tool is not found, an ImportError is raised.

Plac batch scripts

It is pretty easy to realize that an interactive interpreter can also be used to run batch scripts: instead of reading the commands from the console, it is enough to read the commands from a file. plac interpreters provide an .execute method to perform just that.

There is just a subtle point to notice: whereas in an interactive loop one wants to manage all exceptions, a batch script should not continue in the background in case of unexpected errors. The implementation of Interpreter.execute makes sure that any error raised by plac.call internally is re-raised. In other words, plac interpreters wrap the errors, but does not eat them: the errors are always accessible and can be re-raised on demand.

The exception is the case of invalid commands, which are skipped. Consider for instance the following batch file, which contains a mispelled command (.dl instead of .del):

#!ishelve.py
.clear 
a=1 b=2
.show
.del a
.dl b
.show

If you execute the batch file, the interpreter will print a .dl: not found at the .dl line and will continue:

$ python -c "import plac, ishelve
plac.Interpreter(ishelve.main).execute(open('ishelve.plac'), verbose=True)"
i> .clear
cleared the shelve
i> a=1 b=2
setting a=1
setting b=2
i> .show
b=2
a=1
i> .del a
deleted a
i> .dl b
2
.dl: not found
i> .show
b=2

The verbose flag is there to show the lines which are being interpreted (prefixed by i>). This is done on purpose, so that you can cut and paste the output of the batch script and turn it into a .placet test (cool, isn't it?).

Implementing subcommands

When I discussed the ishelve implementation in the basic documentation, I said that it looked like the poor man implementation of an object system as a chain of elifs; I also said that plac was able to do much better than that. Here I will substantiate my claim.

plac is actually able to infer a set of subparsers from a generic container of commands. This is useful if you want to implement subcommands (a familiar example of a command-line application featuring subcommands is version control system). Technically a container of commands is any object with a .commands attribute listing a set of functions or methods which are valid commands. A command container may have initialization/finalization hooks (__enter__/__exit__) and dispatch hooks (__missing__, invoked for invalid command names). Moreover, only when using command containers plac is able to provide automatic autocompletion of commands.

The shelve interface can be rewritten in an object-oriented way as follows:

# ishelve2.py
import shelve, os, 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.\nUse 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

if __name__ == '__main__':
    plac.Interpreter(plac.call(ShelveInterface)).interact()

plac.Interpreter objects wrap context manager objects consistently. In other words, if you wrap an object with __enter__ and __exit__ methods, they are invoked in the right order (__enter__ before the interpreter loop starts and __exit__ after the interpreter loop ends, both in the regular and in the exceptional case). In our example, the methods __enter__ and __exit__ make sure the the shelve is opened and closed correctly even in the case of exceptions. Notice that I have not implemented any error checking in the show and delete methods on purpose, to verify that plac works correctly in the presence of exceptions.

When working with command containers, plac automatically adds two special commands to the set of provided commands: help and .last_tb. The help command is the easier to understand: when invoked without arguments it displays the list of available commands with the same formatting of the cmd module; when invoked with the name of a command it displays the usage message for that command. The .last_tb command is useful when debugging: in case of errors, it allows you to display the traceback of the last executed command.

Here is the usage message:

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

Here is a session of usage on an Unix-like operating system:

$ python ishelve2.py -c ~/test.shelve
A minimal interface over a shelve object.
Operating on /home/micheles/test.shelve.
Use help to see the available commands.
i> help

special commands
================
.last_tb

custom commands
===============
delete  set  show  showall

i> delete
deleting everything
i> set a pippo
setting a=pippo
i> set b lippo
setting b=lippo
i> showall
b = lippo
a = pippo
i> show a b
a = pippo
b = lippo
i> del a
deleting a
i> showall
b = lippo
i> delete a
deleting a
KeyError: 'a'
i> .last_tb
 File "/usr/local/lib/python2.6/dist-packages/plac-0.6.0-py2.6.egg/plac_ext.py", line 190, in _wrap
    for value in genobj:
  File "./ishelve2.py", line 37, in delete
    del self.sh[name] # no error checking
  File "/usr/lib/python2.6/shelve.py", line 136, in __delitem__
    del self.dict[key]
i>

Notice that in interactive mode the traceback is hidden, unless you pass the verbose flag to the Interpreter.interact method.

CHANGED IN VERSION 0.9: if you have an old version of plac the help command must be prefixed with a dot, i.e. you must write .help. The old behavior was more consistent in my opinion, since it made it clear that the help command was special and threated differently from the regular commands. Notice that if you implement a custom help command in the commander class the default help will not be added, as you would expect.

In version 0.9 an exception `plac.Interpreter.Exit was added. Its purpose is to make it easy to define commands to exit from the command loop. Just define something like:

def quit(self):
   raise plac.Interpreter.Exit

and the interpreter will be closed properly when the quit command is entered.

plac.Interpreter.call

At the core of plac there is the call function which invokes a callable with the list of arguments passed at the command-line (sys.argv[1:]). Thanks to plac.call you can launch your module by simply adding the lines:

if __name__ == '__main__':
    plac.call(main)

Everything works fine if main is a simple callable performing some action; however, in many cases, one has a main "function" which is a actually a factory returning a command container object. For instance, in my second shelve example the main function is the class ShelveInterface, and the two lines needed to run the module are a bit ugly:

if __name__ == '__main__':
   plac.Interpreter(plac.call(ShelveInterface)).interact()

Moreover, now the program runs, but only in interactive mode, i.e. it is not possible to run it as a script. Instead, it would be nice to be able to specify the command to execute on the command-line and have the interpreter start, execute the command and finish properly (I mean by calling __enter__ and __exit__) without needing user input. Then the script could be called from a batch shell script working in the background. In order to provide such functionality plac.Interpreter provides a classmethod named .call which takes the factory, instantiates it with the arguments read from the command line, wraps the resulting container object as an interpreter and runs it with the remaining arguments found in the command line. Here is the code to turn the ShelveInterface into a script

# ishelve3.py
from ishelve2 import ShelveInterface

if __name__ == '__main__':
    import plac; plac.Interpreter.call(ShelveInterface)

## try the following:
# $ python ishelve3.py delete
# $ python ishelve3.py set a 1
# $ python ishelve3.py showall

and here are a few examples of usage:

$ python ishelve3.py help

special commands
================
.last_tb

custom commands
===============
delete  set  show  showall

$ python ishelve3.py set a 1
setting a=1
$ python ishelve3.py show a
a = 1

If you pass the -i flag in the command line, then the script will enter in interactive mode and ask the user for the commands to execute:

$ python ishelve3.py -i
A minimal interface over a shelve object.
Operating on /home/micheles/conf.shelve.
Use help to see the available commands.

i>

In a sense, I have closed the circle: at the beginning of this document I discussed how to turn a script into an interactive application (the shelve_interpreter.py example), whereas here I have show how to turn an interactive application into a script.

The complete signature of plac.Interpreter.call is the following:

call(factory, arglist=sys.argv[1:],
     commentchar='#', split=shlex.split,
     stdin=sys.stdin, prompt='i> ', verbose=False)

The factory must have a fixed number of positional arguments (no default arguments, no varargs, no kwargs), otherwise a TypeError is raised: the reason is that we want to be able to distinguish the command-line arguments needed to instantiate the factory from the remaining arguments that must be sent to the corresponding interpreter object. It is also possible to specify a list of arguments different from sys.argv[1:] (useful in tests), the character to be recognized as a comment, the splitting function, the input source, the prompt to use while in interactive mode, and a verbose flag.

Readline support

Starting from release 0.6 plac offers full readline support. That means that if your Python was compiled with readline support you get autocompletion and persistent command history for free. By default all commands autocomplete in a case sensitive way. If you want to add new words to the autocompletion set, or you want to change the location of the .history file, or to change the case sensitivity, the way to go is to pass a plac.ReadlineInput object to the interpreter. Here is an example, assuming you want to build a database interface understanding SQL commands:

import os
import plac
from sqlsoup import SQLSoup

SQLKEYWORDS = set(['help', '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> ')

Here is an example of usage:

$ python sql_interface.py <some dsn>
sql> SELECT a.* FROM TABLE1 AS a INNER JOIN TABLE2 AS b ON a.id = b.id
...

You can check that entering just sel and pressing TAB the readline library completes the SELECT keyword for you and makes it upper case; idem for FROM, INNER, JOIN and even for the names of the tables. An obvious improvement is to read the names of the tables by introspecting the database: actually you can even read the names of the views and the columns, and get full autocompletion. All the entered commands are recorded and saved in the file ~/.sql_interface.history when exiting from the command-line interface.

If the readline library is not available, my suggestion is to use the rlwrap tool which provides similar features, at least on Unix-like platforms. plac should also work fine on Windows with the pyreadline library (I do not use Windows, so this part is very little tested: I tried it only once and it worked, but your mileage may vary). For people worried about licenses, I will notice that plac uses the readline library only if available, it does not include it and it does not rely on it in any fundamental way, so that the plac licence does not need to be the GPL (actually it is a BSD do-whatever-you-want-with-it licence).

The interactive mode of plac can be used as a replacement of the cmd module in the standard library. It is actually better than cmd: for instance, the help command is more powerful, since it provides information about the arguments accepted by the given command:

i> help set
usage:  set name value

set name value

positional arguments:
  name
  value

i> help delete
usage:  delete [name]

delete given parameter (or everything)

positional arguments:
  name        [None]

i> help show
usage:  show [names [names ...]]

show given parameters

positional arguments:
  names

As you can imagine, the help message is provided by the underlying argparse subparser: there is a subparser for each command. plac commands accept options, flags, varargs, keyword arguments, arguments with defaults, arguments with a fixed number of choices, type conversion and all the features provided of argparse .

Moreover at the moment plac also understands command abbreviations. However, this feature may disappear in future releases. It was meaningful in the past, when plac did not support readline.

Notice that if an abbreviation is ambiguous, plac warns you:

i> sh
NameError: Ambiguous command 'sh': matching ['showall', 'show']

The plac runner

The distribution of plac includes a runner script named plac_runner.py, which will be installed in a suitable directory in your system by distutils (say in /usr/local/bin/plac_runner.py in a Unix-like operative system). The runner provides many facilities to run .plac scripts and .placet files, as well as Python modules containg a main object, which can be a function, a command container object or even a command container class.

For instance, suppose you want to execute a script containing commands defined in the ishelve2 module like the following one:

#!ishelve2.py:ShelveInterface -c ~/conf.shelve
set a 1
del a
del a # intentional error

The first line of the .plac script contains the name of the python module containing the plac interpreter and the arguments which must be passed to its main function in order to be able to instantiate an interpreter object. In this case I appended :ShelveInterface to the name of the module to specify the object that must be imported: if not specified, by default the object named 'main' is imported. The other lines contains commands. You can run the script as follows:

$ plac_runner.py --batch ishelve2.plac
setting a=1
deleting a
Traceback (most recent call last):
  ...
_bsddb.DBNotFoundError: (-30988, 'DB_NOTFOUND: No matching key/data pair found')

The last command intentionally contained an error, to show that the plac runner does not eat the traceback.

The runner can also be used to run Python modules in interactive mode and non-interactive mode. If you put this alias in your bashrc

alias plac="plac_runner.py"

(or you define a suitable plac.bat script in Windows) you can run the ishelve2.py script in interactive mode as follows:

$ plac -i ishelve2.py:ShelveInterface
A minimal interface over a shelve object.
Operating on /home/micheles/conf.shelve.
.help to see the available commands.

i> del
deleting everything
i> set a 1
setting a=1
i> set b 2
setting b=2
i> show b
b = 2

Now you can cut and paste the interactive session and turn it into a .placet file like the following:

#!ishelve2.py:ShelveInterface -configfile=~/test.shelve
# an example of a .placet file for the ShelveInterface
i> del
deleting everything
i> set a 1
setting a=1
i> set b 2
setting b=2
i> show a
a = 1

Notice that the first line specifies a test database ~/test.shelve, to avoid clobbering your default shelve. If you mispell the arguments in the first line plac will give you an argparse error message (just try).

You can run placets following the shebang convention directly with the plac runner:

$ plac --test ishelve2.placet
run 1 plac test(s)

If you want to see the output of the tests, pass the -v/--verbose flag. Notice that he runner ignores the extension, so you can actually use any extension your like, but it relies on the first line of the file to invoke the corresponding plac tool with the given arguments.

The plac runner does not provide any test discovery facility, but you can use standard Unix tools to help. For instance, you can run all the .placet files into a directory and its subdirectories as follows:

$ find . -name \*.placet | xargs plac_runner.py -t

The plac runner expects the main function of your script to return a plac tool, i.e. a function or an object with a .commands attribute. If this is not the case the runner exits gracefully.

It also works in non-interactive mode, if you call it as

$ plac module.py args ...

Here is an example:

$ plac ishelve.py a=1
setting a=1
$ plac ishelve.py .show
a=1

Notice that in non-interactive mode the runner just invokes plac.call on the main object of the Python module.

A non class-based example

plac does not force you to use classes to define command containers. Even a simple function can be a valid command container, it is enough to add a .commands attribute to it, and possibly __enter__ and/or __exit__ attributes too.

In particular, a Python module is a perfect container of commands. As an example, consider the following module implementing a fake Version Control System:

"A Fake Version Control System"

import plac # this implementation also works with Python 2.4

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 intepreter loop"
    if etype in (None, GeneratorExit): # success
        print('ok')

main = __import__(__name__) # the module imports itself!

if __name__ == '__main__':
    import plac
    for out in plac.call(main): print(out)

Notice that I have defined both an __exit__ hook and a __missing__ hook, invoked for non-existing commands. The real trick here is the line main = __import__(__name__), which define main to be an alias for the current module.

The vcs module can be run through the plac runner (try plac vcs.py -h):

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}
    checkout            A fake checkout command
    commit              A fake commit command
    status              A fake status command

You can get help for the subcommands by inserting an -h after the name of the command:

$ plac vcs.py status -h
usage: plac_runner.py vcs.py status [-h] [-q]

A fake status command

optional arguments:
  -h, --help   show this help message and exit
  -q, --quiet  summary information

Notice how the docstring of the command is automatically shown in the usage message, as well as the documentation for the sub flag -q.

Here is an example of a non-interactive session:

$ plac vcs.py check url
checkout
url
$ plac vcs.py st -q
status
True
$ plac vcs.py co
commit
None

and here is an interactive session:

$ plac -i vcs.py
usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...
i> check url
checkout
url
i> st -q
status
True
i> co
commit
None
i> sto
Command 'sto' does not exist
i> [CTRL-D]
ok

Notice the invocation of the __missing__ hook for non-existing commands. Notice also that the __exit__ hook gets called only in interactive mode.

If the commands are completely independent, a module is a good fit for a method container. In other situations, it is best to use a custom class.

Writing your own plac runner

The runner included in the plac distribution is intentionally kept small (around 50 lines of code) so that you can study it and write your own runner if you want to. If you need to go to such level of detail, you should know that the most important method of the Interpreter class is the .send method, which takes strings as input and returns a four elements tuple with attributes .str, .etype, .exc and .tb:

  • .str is the output of the command, if successful (a string);
  • .etype is the class of the exception, if the command fails;
  • .exc is the exception instance;
  • .tb is the traceback.

Moreover, the __str__ representation of the output object is redefined to return the output string if the command was successful, or the error message (preceded by the name of the exception class) if the command failed.

For instance, if you send a mispelled option to the interpreter a SystemExit will be trapped:

>>> import plac
>>> from ishelve import ishelve
>>> with plac.Interpreter(ishelve) as i:
...     print(i.send('.cler'))
...
SystemExit: unrecognized arguments: .cler

It is important to invoke the .send method inside the context manager, otherwise you will get a RuntimeError.

For instance, suppose you want to implement a graphical runner for a plac-based interpreter with two text widgets: one to enter the commands and one to display the results. Suppose you want to display the errors with tracebacks in red. You will need to code something like that (pseudocode follows):

input_widget = WidgetReadingInput()
output_widget = WidgetDisplayingOutput()

def send(interpreter, line):
    out = interpreter.send(line)
    if out.tb: # there was an error
        output_widget.display(out.tb, color='red')
    else:
        output_widget.display(out.str)

main = plac.import_main(tool_path) # get the main object

with plac.Interpreter(main) as i:
   def callback(event):
      if event.user_pressed_ENTER():
           send(i, input_widget.last_line)
   input_widget.addcallback(callback)
   gui_mainloop.start()

You can adapt the pseudocode to your GUI toolkit of choice and you can also change the file associations in such a way that the graphical user interface starts when clicking on a plac tool file.

An example of a GUI program built on top of plac is given later on, in the paragraph Managing the output of concurrent commands (using Tkinter for simplicity and portability).

There is a final caveat: since the plac interpreter loop is implemented via extended generators, plac interpreters are single threaded: you will get an error if you .send commands from separated threads. You can circumvent the problem by using a queue. If EXIT is a sentinel value to signal exiting from the interpreter loop, you can write code like this:

with interpreter:
    for input_value in iter(input_queue.get, EXIT):
        output_queue.put(interpreter.send(input_value))

The same trick also works for processes; you could run the interpreter loop in a separate process and send commands to it via the Queue class provided by the multiprocessing module.

Long running commands

As we saw, by default a plac interpreter blocks until the command terminates. This is an issue, in the sense that it makes the interactive experience quite painful for long running commands. An example is better than a thousand words, so consider the following fake importer:

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)

If you run the import_file command, you will have to wait for 200 seconds before entering a new command:

$ python importer1.py dsn -i
A fake importer with an import_file command
i> import_file file1
... <wait 3+ minutes>
Imported 100 lines
Imported 200 lines
Imported 300 lines
...
Imported 10000 lines
closing the file

Being unable to enter any other command is quite annoying: in such situation one would like to run the long running commands in the background, to keep the interface responsive. plac provides two ways to reach this goal: threads and processes.

Threaded commands

The most familiar way to execute a task in the background (even if not necessarily the best way) is to run it into a separate thread. In our example it is sufficient to replace the line

commands = ['import_file']

with

thcommands = ['import_file']

to tell to the plac interpreter that the command import_file should be run into a separated thread. Here is an example session:

i> import_file file1
<ThreadedTask 1 [import_file file1] RUNNING>

The import task started in a separated thread. You can see the progress of the task by using the special command .output:

i> .output 1
<ThreadedTask 1 [import_file file1] RUNNING>
Imported 100 lines
Imported 200 lines

If you look after a while, you will get more lines of output:

i> .output 1
<ThreadedTask 1 [import_file file1] RUNNING>
Imported 100 lines
Imported 200 lines
Imported 300 lines
Imported 400 lines

If you look after a time long enough, the task will be finished:

i> .output 1
<ThreadedTask 1 [import_file file1] FINISHED>

It is possible to store the output of a task into a file, to be read later (this is useful for tasks with a large output):

i> .output 1 /tmp/out.txt
saved output of 1 into /tmp/out.txt

You can even skip the number argument: then .output will the return the output of the last launched command (the special commands like .output do not count).

You can launch many tasks one after the other:

i> import_file file2
<ThreadedTask 5 [import_file file2] RUNNING>
i> import_file file3
<ThreadedTask 6 [import_file file3] RUNNING>

The .list command displays all the running tasks:

i> .list
<ThreadedTask 5 [import_file file2] RUNNING>
<ThreadedTask 6 [import_file file3] RUNNING>

It is even possible to kill a task:

i> .kill 5
<ThreadedTask 5 [import_file file2] TOBEKILLED>
# wait a bit ...
closing the file
i> .output 5
<ThreadedTask 5 [import_file file2] KILLED>

Note that since at the Python level it is impossible to kill a thread, the .kill command works by setting the status of the task to TOBEKILLED. Internally the generator corresponding to the command is executed in the thread and the status is checked at each iteration: when the status becomes TOBEKILLED, a GeneratorExit exception is raised and the thread terminates (softly, so that the finally clause is honored). In our example the generator is yielding back control once every 100 iterations, i.e. every two seconds (not much). In order to get a responsive interface it is a good idea to yield more often, for instance every 10 iterations (i.e. 5 times per second), as in the following code:

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)

Running commands as external processes

Threads are not loved much in the Python world and actually most people prefer to use processes instead. For this reason plac provides the option to execute long running commands as external processes. Unfortunately the current implementation only works on Unix-like operating systems (including Mac OS/X) because it relies on fork via the multiprocessing module.

In our example, to enable the feature it is sufficient to replace the line

thcommands = ['import_file']

with

mpcommands = ['import_file'].

The user experience is exactly the same as with threads and you will not see any difference at the user interface level:

i> import_file file3
<MPTask 1 [import_file file3] SUBMITTED>
i> .kill 1
<MPTask 1 [import_file file3] RUNNING>
closing the file
i> .output 1
<MPTask 1 [import_file file3] KILLED>
Imported 100 lines
Imported 200 lines
i>

Still, using processes is quite different than using threads: in particular, when using processes you can only yield pickleable values and you cannot re-raise an exception first raised in a different process, because traceback objects are not pickleable. Moreover, you cannot rely on automatic sharing of your objects.

On the plus side, when using processes you do not need to worry about killing a command: they are killed immediately using a SIGTERM signal, and there is no TOBEKILLED mechanism. Moreover, the killing is guaranteed to be soft: internally a command receiving a SIGTERM raises a TerminatedProcess exception which is trapped in the generator loop, so that the command is closed properly.

Using processes allows one to take full advantage of multicore machines and it is safer than using threads, so it is the recommended approach unless you are working on Windows.

Managing the output of concurrent commands

plac acts as a command-line task launcher and can be used as the base to build a GUI-based task launcher and task monitor. To this aim the interpreter class provides a .submit method which returns a task object and a .tasks method returning the list of all the tasks submitted to the interpreter. The submit method does not start the task and thus it is nonblocking. Each task has an .outlist attribute which is a list storing the value yielded by the generator underlying the task (the None values are skipped though): the .outlist grows as the task runs and more values are yielded. Accessing the .outlist is nonblocking and can be done freely. Finally there is a .result property which waits for the task to finish and returns the last yielded value or raises an exception. The code below provides an example of how you could implement a GUI over the importer example:

from __future__ import with_statement
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)

Experimental features

The distribution of plac includes a few experimental features which I am not committed to fully support and that may go away in future versions. They are included as examples of things that you may build on top of plac: the aim is to give you ideas. Some of the experimental features might grow to become external projects built on plac.

Parallel computing with plac

plac is certainly not intended as a tool for parallel computing, but still you can use it to launch a set of commands and collect the results, similarly to the MapReduce pattern popularized by Google. In order to give an example, I will consider the "Hello World" of parallel computing, i.e. the computation of pi with independent processes. There is a huge number of algorithms to compute pi; here I will describe a trivial one chosen for simplicity, not for efficiency. The trick is to consider the first quadrant of a circle with radius 1 and to extract a number of points (x, y) with x and y random variables in the interval [0,1]. The probability of extracting a number inside the quadrant (i.e. with x^2 + y^2 < 1) is proportional to the area of the quadrant (i.e. pi/4). The value of pi therefore can be extracted by multiplying by 4 the ratio between the number of points in the quadrant versus the total number of points N, for N large:

def calc_pi(N):
    inside = 0
    for j in xrange(N):
        x, y = random(), random()
        if x*x + y*y < 1:
            inside += 1
    return (4.0 * inside) / N

The algorithm is trivially parallelizable: if you have n CPUs, you can compute pi n times with N/n iterations, sum the results and divide the total by n. I have a Macbook with two cores, therefore I would expect a speedup factor of 2 with respect to a sequential computation. Moreover, I would expect a threaded computation to be even slower than a sequential computation, due to the GIL and the scheduling overhead.

Here is a script implementing the algorithm and working in three different modes (parallel mode, threaded mode and sequential mode) depending on a mode option:

#  -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import with_statement
from __future__ import division
import math
from random import random
import multiprocessing
import plac


class PiCalculator(object):
    """Compute \u03C0 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):
        npoints = math.ceil(self.npoints / self.n_cpu)
        self.i = plac.Interpreter(self).__enter__()
        return [self.i.submit('calc_pi %d' % npoints)
                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 range(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()

Notice the submit_tasks method, which instantiates and initializes a plac.Interpreter object and submits a number of commands corresponding to the number of available CPUs. The calc_pi command yields a log message for each million interactions, in order to monitor the progress of the computation. The run method starts all the submitted commands in parallel and sums the results. It returns the average value of pi after the slowest CPU has finished its job (if the CPUs are equal and equally busy they should finish more or less at the same time).

Here are the results on my old Macbook with Ubuntu 10.04 and Python 2.6, for 10 million of iterations:

$ python picalculator.py -mP 10000000 # two processes
3.141904 in 5.744545 seconds
$ python picalculator.py -mT 10000000 # two threads
3.141272 in 13.875645 seconds
$ python picalculator.py -mS 10000000 # sequential
3.141586 in 11.353841 seconds

As you see using processes one gets a 2x speedup indeed, where the threaded mode is some 20% slower than the sequential mode.

Since the pattern "submit a bunch of tasks, start them and collect the results" is so common, plac provides an utility function runp(genseq, mode='p') to start a bunch of generators and return a list of results. By default runp use processes, but you can use threads by passing mode='t'. With runp the parallel pi calculation becomes a one-liner:

sum(task.result for task in plac.runp(calc_pi(N) for i in range(ncpus)))/ncpus

The file test_runp in the doc directory of the plac distribution shows another usage example. Note that if one of the tasks fails for some reason, you will get the exception object instead of the result.

Monitor support

plac provides experimental support for monitoring the output of concurrent commands, at least for platforms where multiprocessing is fully supported. You can define your own monitor class, simply by inheriting from plac.Monitor and overriding the methods add_listener(self, taskno), del_listener(self, taskno), notify_listener(self, taskno, msg), read_queue(self), start(self) and stop(self). Then you can add a monitor object to any plac.Interpreter object by calling the add_monitor method. For convenience, plac comes with a very simple TkMonitor based on Tkinter (I chose Tkinter because it is easy to use and in the standard library, but you can use any GUI): you can look at how the TkMonitor is implemented in plac_tk.py and adapt it. Here is an usage example of the TkMonitor:

from __future__ import with_statement
import plac

class Hello(object):
    mpcommands = ['hello', 'quit']
    def hello(self):
        yield 'hello'
    def quit(self):
        raise plac.Interpreter.Exit

if __name__ == '__main__':
    i = plac.Interpreter(Hello())
    i.add_monitor(plac.TkMonitor('tkmon'))
    i.interact()

    

Try to run the hello command in the interactive interpreter: each time, a new text widget will be added displaying the output of the command. Note that if Tkinter is not installed correctly on your system, the TkMonitor class will not be available.

The plac server

A command-line oriented interface can be easily converted into a socket-based interface. Starting from release 0.7 plac features a built-in server which is able to accept commands from multiple clients and execute them. The server works by instantiating a separate interpreter for each client, so that if a client interpreter dies for any reason, the other interpreters keep working. To avoid external dependencies the server is based on the asynchat module in the standard library, but it would not be difficult to replace the server with a different one (for instance, a Twisted server). Notice that at the moment the plac server does not work with to Python 3.2+ due to changes to asynchat. In time I will fix this and other known issues. You should consider the server functionality still experimental and subject to changes. Also, notice that since asynchat-based servers are asynchronous, any blocking command in the interpreter should be run in a separated process or thread. The default port for the plac server is 2199, and the command to signal end-of-connection is EOF. For instance, here is how you could manage remote import on a database (say a SQLite db):

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)

You can connect to the server with telnet on port 2199, as follows:

$ telnet localhost 2199
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
i> import_file f1
i> .list
<ThreadedTask 1 [import_file f1] RUNNING>
i> .out
Imported 100 lines
Imported 200 lines
i> EOF
Connection closed by foreign host.

Summary

Once plac claimed to be the easiest command-line arguments parser in the world. Having read this document you may think that it is not so easy after all. But it is a false impression. Actually the rules are quite simple:

  1. if you want to implement a command-line script, use plac.call;
  2. if you want to implement a command interpreter, use plac.Interpreter:
    • for an interactive interpreter, call the .interact method;
    • for a batch interpreter, call the .execute method;
  3. for testing call the Interpreter.check method in the appropriate context or use the Interpreter.doctest feature;
  4. if you need to go to a lower level, you may need to call the Interpreter.send method which returns a (finished) Task object;
  5. long running commands can be executed in the background as threads or processes: just declare them in the lists thcommands and mpcommands respectively;
  6. the .start_server method starts an asynchronous server on the given port number (default 2199).

Moreover, remember that plac_runner.py is your friend.


Appendix: custom annotation objects

Internally plac uses an Annotation class to convert the tuples in the function signature to annotation objects, i.e. objects with six attributes: help, kind, short, type, choices, metavar.

Advanced users can implement their own annotation objects. For instance, here is an example of how you could implement annotations for positional arguments:

# 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

You can use such annotation objects as follows:

# 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)

Here is the usage message you get:

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

You can go on and define Option and Flag classes, if you like. Using custom annotation objects you could do advanced things like extracting the annotations from a configuration file or from a database, but I expect such use cases to be quite rare: the default mechanism should work pretty well for most users.

plac-0.9.6/doc/test_ishelve.py0000664000175000017500000000062712740075202017326 0ustar michelemichele00000000000000# 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() plac-0.9.6/doc/importer_ui.py0000664000175000017500000000152512740075202017164 0ustar michelemichele00000000000000from __future__ import with_statement 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) plac-0.9.6/doc/example3.py0000664000175000017500000000023112740075202016335 0ustar michelemichele00000000000000# example3.py def main(dsn): "Do something with the database" print(dsn) # ... if __name__ == '__main__': import plac; plac.call(main) plac-0.9.6/doc/ishelve.help0000664000175000017500000000116212740075202016562 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/example11.help0000664000175000017500000000034212740075202016717 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/example1.py0000664000175000017500000000053212740075202016337 0ustar michelemichele00000000000000# example1.py def main(dsn): "Do something with the database" print("ok") 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:])) plac-0.9.6/doc/plac.pdf0000664000175000017500000114625612740075216015707 0ustar michelemichele00000000000000%PDF-1.4 %“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com 1 0 obj << /F1 2 0 R /F2 3 0 R /F3 7 0 R /F4 98 0 R >> endobj 2 0 obj << /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> endobj 3 0 obj << /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font >> endobj 4 0 obj << /A << /S /URI /Type /Action /URI (mailto:michele.simionato@gmail.com) >> /Border [ 0 0 0 ] /Rect [ 153.7323 705.7736 289.4623 717.7736 ] /Subtype /Link /Type /Annot >> endobj 5 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 153.7323 675.7736 289.3623 687.7736 ] /Subtype /Link /Type /Annot >> endobj 6 0 obj << /A << /S /URI /Type /Action /URI (https://github.com/micheles/plac) >> /Border [ 0 0 0 ] /Rect [ 153.7323 648.7736 296.0123 660.7736 ] /Subtype /Link /Type /Annot >> endobj 7 0 obj << /BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font >> endobj 8 0 obj << /Annots [ 4 0 R 5 0 R 6 0 R ] /Contents 362 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 9 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 8 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 62.69291 723.0236 286.0929 735.0236 ] /Subtype /Link /Type /Annot >> endobj 10 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 8 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 527.0227 723.7736 532.5827 735.7736 ] /Subtype /Link /Type /Annot >> endobj 11 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 99 0 R /XYZ 62.69291 735.0236 0 ] /Rect [ 82.69291 705.0236 223.8629 717.0236 ] /Subtype /Link /Type /Annot >> endobj 12 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 99 0 R /XYZ 62.69291 735.0236 0 ] /Rect [ 527.0227 705.7736 532.5827 717.7736 ] /Subtype /Link /Type /Annot >> endobj 13 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 99 0 R /XYZ 62.69291 381.0236 0 ] /Rect [ 82.69291 687.0236 223.2929 699.0236 ] /Subtype /Link /Type /Annot >> endobj 14 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 99 0 R /XYZ 62.69291 381.0236 0 ] /Rect [ 527.0227 687.7736 532.5827 699.7736 ] /Subtype /Link /Type /Annot >> endobj 15 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 114 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 82.69291 669.0236 216.6329 681.0236 ] /Subtype /Link /Type /Annot >> endobj 16 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 114 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 527.0227 669.7736 532.5827 681.7736 ] /Subtype /Link /Type /Annot >> endobj 17 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 124 0 R /XYZ 62.69291 695.8236 0 ] /Rect [ 82.69291 651.0236 257.7529 663.0236 ] /Subtype /Link /Type /Annot >> endobj 18 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 124 0 R /XYZ 62.69291 695.8236 0 ] /Rect [ 527.0227 651.7736 532.5827 663.7736 ] /Subtype /Link /Type /Annot >> endobj 19 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 128 0 R /XYZ 62.69291 650.6236 0 ] /Rect [ 82.69291 633.0236 157.7129 645.0236 ] /Subtype /Link /Type /Annot >> endobj 20 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 128 0 R /XYZ 62.69291 650.6236 0 ] /Rect [ 527.0227 633.7736 532.5827 645.7736 ] /Subtype /Link /Type /Annot >> endobj 21 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 131 0 R /XYZ 62.69291 530.6236 0 ] /Rect [ 82.69291 615.0236 194.4129 627.0236 ] /Subtype /Link /Type /Annot >> endobj 22 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 131 0 R /XYZ 62.69291 530.6236 0 ] /Rect [ 521.4627 615.7736 532.5827 627.7736 ] /Subtype /Link /Type /Annot >> endobj 23 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 138 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 82.69291 597.0236 144.3829 609.0236 ] /Subtype /Link /Type /Annot >> endobj 24 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 138 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 521.4627 597.7736 532.5827 609.7736 ] /Subtype /Link /Type /Annot >> endobj 25 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 143 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 82.69291 579.0236 166.6029 591.0236 ] /Subtype /Link /Type /Annot >> endobj 26 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 143 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 521.4627 579.7736 532.5827 591.7736 ] /Subtype /Link /Type /Annot >> endobj 27 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 146 0 R /XYZ 62.69291 695.8236 0 ] /Rect [ 82.69291 561.0236 171.6129 573.0236 ] /Subtype /Link /Type /Annot >> endobj 28 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 146 0 R /XYZ 62.69291 695.8236 0 ] /Rect [ 521.4627 561.7736 532.5827 573.7736 ] /Subtype /Link /Type /Annot >> endobj 29 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 170 0 R /XYZ 62.69291 659.8236 0 ] /Rect [ 82.69291 543.0236 156.0529 555.0236 ] /Subtype /Link /Type /Annot >> endobj 30 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 170 0 R /XYZ 62.69291 659.8236 0 ] /Rect [ 521.4627 543.7736 532.5827 555.7736 ] /Subtype /Link /Type /Annot >> endobj 31 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 177 0 R /XYZ 62.69291 394.6236 0 ] /Rect [ 82.69291 525.0236 228.8629 537.0236 ] /Subtype /Link /Type /Annot >> endobj 32 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 177 0 R /XYZ 62.69291 394.6236 0 ] /Rect [ 521.4627 525.7736 532.5827 537.7736 ] /Subtype /Link /Type /Annot >> endobj 33 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 192 0 R /XYZ 62.69291 277.4236 0 ] /Rect [ 82.69291 507.0236 204.4129 519.0236 ] /Subtype /Link /Type /Annot >> endobj 34 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 192 0 R /XYZ 62.69291 277.4236 0 ] /Rect [ 521.4627 507.7736 532.5827 519.7736 ] /Subtype /Link /Type /Annot >> endobj 35 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 224 0 R /XYZ 62.69291 663.0236 0 ] /Rect [ 82.69291 489.0236 128.2729 501.0236 ] /Subtype /Link /Type /Annot >> endobj 36 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 224 0 R /XYZ 62.69291 663.0236 0 ] /Rect [ 521.4627 489.7736 532.5827 501.7736 ] /Subtype /Link /Type /Annot >> endobj 37 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 224 0 R /XYZ 62.69291 459.0236 0 ] /Rect [ 82.69291 471.0236 228.3129 483.0236 ] /Subtype /Link /Type /Annot >> endobj 38 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 224 0 R /XYZ 62.69291 459.0236 0 ] /Rect [ 521.4627 471.7736 532.5827 483.7736 ] /Subtype /Link /Type /Annot >> endobj 39 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 236 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 62.69291 453.0236 182.7329 465.0236 ] /Subtype /Link /Type /Annot >> endobj 40 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 236 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 521.4627 453.7736 532.5827 465.7736 ] /Subtype /Link /Type /Annot >> endobj 41 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 236 0 R /XYZ 62.69291 732.0236 0 ] /Rect [ 82.69291 435.0236 134.9429 447.0236 ] /Subtype /Link /Type /Annot >> endobj 42 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 236 0 R /XYZ 62.69291 732.0236 0 ] /Rect [ 521.4627 435.7736 532.5827 447.7736 ] /Subtype /Link /Type /Annot >> endobj 43 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 236 0 R /XYZ 62.69291 510.0236 0 ] /Rect [ 82.69291 417.0236 252.7429 429.0236 ] /Subtype /Link /Type /Annot >> endobj 44 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 236 0 R /XYZ 62.69291 510.0236 0 ] /Rect [ 521.4627 417.7736 532.5827 429.7736 ] /Subtype /Link /Type /Annot >> endobj 45 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 62.69291 699.0236 0 ] /Rect [ 82.69291 399.0236 195.5229 411.0236 ] /Subtype /Link /Type /Annot >> endobj 46 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 62.69291 699.0236 0 ] /Rect [ 521.4627 399.7736 532.5827 411.7736 ] /Subtype /Link /Type /Annot >> endobj 47 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 247 0 R /XYZ 62.69291 633.0236 0 ] /Rect [ 82.69291 381.0236 149.9429 393.0236 ] /Subtype /Link /Type /Annot >> endobj 48 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 247 0 R /XYZ 62.69291 633.0236 0 ] /Rect [ 521.4627 381.7736 532.5827 393.7736 ] /Subtype /Link /Type /Annot >> endobj 49 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 62.69291 333.8236 0 ] /Rect [ 82.69291 363.0236 161.0529 375.0236 ] /Subtype /Link /Type /Annot >> endobj 50 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 62.69291 333.8236 0 ] /Rect [ 521.4627 363.7736 532.5827 375.7736 ] /Subtype /Link /Type /Annot >> endobj 51 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 258 0 R /XYZ 62.69291 414.6236 0 ] /Rect [ 82.69291 345.0236 210.5129 357.0236 ] /Subtype /Link /Type /Annot >> endobj 52 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 258 0 R /XYZ 62.69291 414.6236 0 ] /Rect [ 521.4627 345.7736 532.5827 357.7736 ] /Subtype /Link /Type /Annot >> endobj 53 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 265 0 R /XYZ 62.69291 663.8236 0 ] /Rect [ 82.69291 327.0236 167.7229 339.0236 ] /Subtype /Link /Type /Annot >> endobj 54 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 265 0 R /XYZ 62.69291 663.8236 0 ] /Rect [ 521.4627 327.7736 532.5827 339.7736 ] /Subtype /Link /Type /Annot >> endobj 55 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 62.69291 319.4236 0 ] /Rect [ 82.69291 309.0236 158.2829 321.0236 ] /Subtype /Link /Type /Annot >> endobj 56 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 62.69291 319.4236 0 ] /Rect [ 521.4627 309.7736 532.5827 321.7736 ] /Subtype /Link /Type /Annot >> endobj 57 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 62.69291 350.6236 0 ] /Rect [ 82.69291 291.0236 152.7229 303.0236 ] /Subtype /Link /Type /Annot >> endobj 58 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 62.69291 350.6236 0 ] /Rect [ 521.4627 291.7736 532.5827 303.7736 ] /Subtype /Link /Type /Annot >> endobj 59 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 287 0 R /XYZ 62.69291 508.6236 0 ] /Rect [ 82.69291 273.0236 205.5229 285.0236 ] /Subtype /Link /Type /Annot >> endobj 60 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 287 0 R /XYZ 62.69291 508.6236 0 ] /Rect [ 521.4627 273.7736 532.5827 285.7736 ] /Subtype /Link /Type /Annot >> endobj 61 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 290 0 R /XYZ 62.69291 477.8236 0 ] /Rect [ 82.69291 255.0236 209.9529 267.0236 ] /Subtype /Link /Type /Annot >> endobj 62 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 290 0 R /XYZ 62.69291 477.8236 0 ] /Rect [ 521.4627 255.7736 532.5827 267.7736 ] /Subtype /Link /Type /Annot >> endobj 63 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 294 0 R /XYZ 62.69291 318.6236 0 ] /Rect [ 82.69291 237.0236 192.7429 249.0236 ] /Subtype /Link /Type /Annot >> endobj 64 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 294 0 R /XYZ 62.69291 318.6236 0 ] /Rect [ 521.4627 237.7736 532.5827 249.7736 ] /Subtype /Link /Type /Annot >> endobj 65 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 297 0 R /XYZ 62.69291 426.6236 0 ] /Rect [ 82.69291 219.0236 177.1729 231.0236 ] /Subtype /Link /Type /Annot >> endobj 66 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 297 0 R /XYZ 62.69291 426.6236 0 ] /Rect [ 521.4627 219.7736 532.5827 231.7736 ] /Subtype /Link /Type /Annot >> endobj 67 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 301 0 R /XYZ 62.69291 499.8236 0 ] /Rect [ 82.69291 201.0236 271.6529 213.0236 ] /Subtype /Link /Type /Annot >> endobj 68 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 301 0 R /XYZ 62.69291 499.8236 0 ] /Rect [ 521.4627 201.7736 532.5827 213.7736 ] /Subtype /Link /Type /Annot >> endobj 69 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 306 0 R /XYZ 62.69291 699.0236 0 ] /Rect [ 82.69291 183.0236 286.6829 195.0236 ] /Subtype /Link /Type /Annot >> endobj 70 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 306 0 R /XYZ 62.69291 699.0236 0 ] /Rect [ 521.4627 183.7736 532.5827 195.7736 ] /Subtype /Link /Type /Annot >> endobj 71 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 306 0 R /XYZ 62.69291 173.8236 0 ] /Rect [ 62.69291 165.0236 167.1729 177.0236 ] /Subtype /Link /Type /Annot >> endobj 72 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 306 0 R /XYZ 62.69291 173.8236 0 ] /Rect [ 521.4627 165.7736 532.5827 177.7736 ] /Subtype /Link /Type /Annot >> endobj 73 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 308 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 82.69291 147.0236 206.6229 159.0236 ] /Subtype /Link /Type /Annot >> endobj 74 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 308 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 521.4627 147.7736 532.5827 159.7736 ] /Subtype /Link /Type /Annot >> endobj 75 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 312 0 R /XYZ 62.69291 540.6236 0 ] /Rect [ 82.69291 129.0236 152.1629 141.0236 ] /Subtype /Link /Type /Annot >> endobj 76 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 312 0 R /XYZ 62.69291 540.6236 0 ] /Rect [ 521.4627 129.7736 532.5827 141.7736 ] /Subtype /Link /Type /Annot >> endobj 77 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 317 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 82.69291 111.0236 151.6029 123.0236 ] /Subtype /Link /Type /Annot >> endobj 78 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 317 0 R /XYZ 62.69291 765.0236 0 ] /Rect [ 521.4627 111.7736 532.5827 123.7736 ] /Subtype /Link /Type /Annot >> endobj 79 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 317 0 R /XYZ 62.69291 258.6236 0 ] /Rect [ 82.69291 93.02362 125.4729 105.0236 ] /Subtype /Link /Type /Annot >> endobj 80 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 317 0 R /XYZ 62.69291 258.6236 0 ] /Rect [ 521.4627 93.77362 532.5827 105.7736 ] /Subtype /Link /Type /Annot >> endobj 81 0 obj << /Annots [ 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R 35 0 R 36 0 R 37 0 R 38 0 R 39 0 R 40 0 R 41 0 R 42 0 R 43 0 R 44 0 R 45 0 R 46 0 R 47 0 R 48 0 R 49 0 R 50 0 R 51 0 R 52 0 R 53 0 R 54 0 R 55 0 R 56 0 R 57 0 R 58 0 R 59 0 R 60 0 R 61 0 R 62 0 R 63 0 R 64 0 R 65 0 R 66 0 R 67 0 R 68 0 R 69 0 R 70 0 R 71 0 R 72 0 R 73 0 R 74 0 R 75 0 R 76 0 R 77 0 R 78 0 R 79 0 R 80 0 R ] /Contents 363 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 82 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 319 0 R /XYZ 62.69291 622.6772 0 ] /Rect [ 82.69291 750.0236 246.1129 762.0236 ] /Subtype /Link /Type /Annot >> endobj 83 0 obj << /Border [ 0 0 0 ] /Contents () /Dest [ 319 0 R /XYZ 62.69291 622.6772 0 ] /Rect [ 521.4627 750.7736 532.5827 762.7736 ] /Subtype /Link /Type /Annot >> endobj 84 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/getopt.html) >> /Border [ 0 0 0 ] /Rect [ 214.8914 687.0236 243.7785 699.0236 ] /Subtype /Link /Type /Annot >> endobj 85 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/optparse.html) >> /Border [ 0 0 0 ] /Rect [ 346.507 687.0236 386.5042 699.0236 ] /Subtype /Link /Type /Annot >> endobj 86 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 493.1227 687.0236 531.4956 699.0236 ] /Subtype /Link /Type /Annot >> endobj 87 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 346.384 675.0236 386.0677 687.0236 ] /Subtype /Link /Type /Annot >> endobj 88 0 obj << /A << /S /URI /Type /Action /URI (http://www.welton.it/articles/scalable_systems) >> /Border [ 0 0 0 ] /Rect [ 316.9951 633.0236 376.2133 645.0236 ] /Subtype /Link /Type /Annot >> endobj 89 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 105.4396 585.0236 124.2862 597.0236 ] /Subtype /Link /Type /Annot >> endobj 90 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 485.1029 573.0236 524.5629 585.0236 ] /Subtype /Link /Type /Annot >> endobj 91 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 116.9711 555.0236 136.7994 567.0236 ] /Subtype /Link /Type /Annot >> endobj 92 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 277.9887 555.0236 318.9369 567.0236 ] /Subtype /Link /Type /Annot >> endobj 93 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 504.0394 543.0236 522.5827 555.0236 ] /Subtype /Link /Type /Annot >> endobj 94 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 351.0408 531.0236 390.5008 543.0236 ] /Subtype /Link /Type /Annot >> endobj 95 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 247.8817 507.0236 266.2217 519.0236 ] /Subtype /Link /Type /Annot >> endobj 96 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 477.0236 82.5738 489.0236 ] /Subtype /Link /Type /Annot >> endobj 97 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 124.2211 405.0236 144.1452 417.0236 ] /Subtype /Link /Type /Annot >> endobj 98 0 obj << /BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font >> endobj 99 0 obj << /Annots [ 82 0 R 83 0 R 84 0 R 85 0 R 86 0 R 87 0 R 88 0 R 89 0 R 90 0 R 91 0 R 92 0 R 93 0 R 94 0 R 95 0 R 96 0 R 97 0 R ] /Contents 364 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 100 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/getopt.html) >> /Border [ 0 0 0 ] /Rect [ 325.341 693.0236 353.8398 705.0236 ] /Subtype /Link /Type /Annot >> endobj 101 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/optparse.html) >> /Border [ 0 0 0 ] /Rect [ 376.7786 693.0236 416.3874 705.0236 ] /Subtype /Link /Type /Annot >> endobj 102 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 365.694 681.0236 406.0481 693.0236 ] /Subtype /Link /Type /Annot >> endobj 103 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 83.82606 453.8236 103.2892 465.8236 ] /Subtype /Link /Type /Annot >> endobj 104 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 243.8829 441.8236 262.2229 453.8236 ] /Subtype /Link /Type /Annot >> endobj 105 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 83.6329 304.6236 102.9029 316.6236 ] /Subtype /Link /Type /Annot >> endobj 106 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 421.9727 304.6236 462.3627 316.6236 ] /Subtype /Link /Type /Annot >> endobj 107 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 107.8707 110.2236 126.3784 122.2236 ] /Subtype /Link /Type /Annot >> endobj 108 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 117.7229 98.22362 136.0629 110.2236 ] /Subtype /Link /Type /Annot >> endobj 109 0 obj << /Annots [ 100 0 R 101 0 R 102 0 R 103 0 R 104 0 R 105 0 R 106 0 R 107 0 R 108 0 R ] /Contents 365 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 110 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 321.4303 459.8236 360.974 471.8236 ] /Subtype /Link /Type /Annot >> endobj 111 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 126.0429 447.8236 144.3829 459.8236 ] /Subtype /Link /Type /Annot >> endobj 112 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 162.6654 209.4236 181.8185 221.4236 ] /Subtype /Link /Type /Annot >> endobj 113 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 155.8754 197.4236 174.3329 209.4236 ] /Subtype /Link /Type /Annot >> endobj 114 0 obj << /Annots [ 110 0 R 111 0 R 112 0 R 113 0 R ] /Contents 366 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 115 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 446.1627 498.6236 464.5027 510.6236 ] /Subtype /Link /Type /Annot >> endobj 116 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 468.6236 81.75568 480.6236 ] /Subtype /Link /Type /Annot >> endobj 117 0 obj << /A << /S /URI /Type /Action /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >> /Border [ 0 0 0 ] /Rect [ 331.3133 468.6236 383.5633 480.6236 ] /Subtype /Link /Type /Annot >> endobj 118 0 obj << /Annots [ 115 0 R 116 0 R 117 0 R ] /Contents 367 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 119 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 707.8236 81.03291 719.8236 ] /Subtype /Link /Type /Annot >> endobj 120 0 obj << /A << /S /URI /Type /Action /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >> /Border [ 0 0 0 ] /Rect [ 240.1228 611.8236 294.9299 623.8236 ] /Subtype /Link /Type /Annot >> endobj 121 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 62.69291 599.8236 102.1529 611.8236 ] /Subtype /Link /Type /Annot >> endobj 122 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 495.6727 599.8236 515.9027 611.8236 ] /Subtype /Link /Type /Annot >> endobj 123 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 494.1558 438.6236 513.1227 450.6236 ] /Subtype /Link /Type /Annot >> endobj 124 0 obj << /Annots [ 119 0 R 120 0 R 121 0 R 122 0 R 123 0 R ] /Contents 368 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 125 0 obj << /Contents 369 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 126 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 614.6236 81.84846 626.6236 ] /Subtype /Link /Type /Annot >> endobj 127 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 127.2715 141.0236 147.8907 153.0236 ] /Subtype /Link /Type /Annot >> endobj 128 0 obj << /Annots [ 126 0 R 127 0 R ] /Contents 370 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 129 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 286.7201 470.6236 306.3355 482.6236 ] /Subtype /Link /Type /Annot >> endobj 130 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 392.5829 143.0236 410.9229 155.0236 ] /Subtype /Link /Type /Annot >> endobj 131 0 obj << /Annots [ 129 0 R 130 0 R ] /Contents 371 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 132 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 157.3904 729.0236 177.2138 741.0236 ] /Subtype /Link /Type /Annot >> endobj 133 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 184.0634 717.0236 223.5234 729.0236 ] /Subtype /Link /Type /Annot >> endobj 134 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 62.69291 705.0236 102.1529 717.0236 ] /Subtype /Link /Type /Annot >> endobj 135 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 205.4687 705.0236 246.6763 717.0236 ] /Subtype /Link /Type /Annot >> endobj 136 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 332.4992 705.0236 350.8392 717.0236 ] /Subtype /Link /Type /Annot >> endobj 137 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 360.0429 477.0236 399.5029 489.0236 ] /Subtype /Link /Type /Annot >> endobj 138 0 obj << /Annots [ 132 0 R 133 0 R 134 0 R 135 0 R 136 0 R 137 0 R ] /Contents 372 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 139 0 obj << /Contents 373 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 140 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 338.1568 729.0236 357.7313 741.0236 ] /Subtype /Link /Type /Annot >> endobj 141 0 obj << /A << /S /URI /Type /Action /URI (http://www.sqlalchemy.org/) >> /Border [ 0 0 0 ] /Rect [ 110.6843 717.0236 169.0343 729.0236 ] /Subtype /Link /Type /Annot >> endobj 142 0 obj << /A << /S /URI /Type /Action /URI (http://www.sqlalchemy.org/docs/reference/ext/sqlsoup.html) >> /Border [ 0 0 0 ] /Rect [ 168.3029 705.0236 206.1029 717.0236 ] /Subtype /Link /Type /Annot >> endobj 143 0 obj << /Annots [ 140 0 R 141 0 R 142 0 R ] /Contents 374 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 144 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 185.0709 659.8236 205.2428 671.8236 ] /Subtype /Link /Type /Annot >> endobj 145 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 220.5998 647.8236 241.039 659.8236 ] /Subtype /Link /Type /Annot >> endobj 146 0 obj << /Annots [ 144 0 R 145 0 R ] /Contents 375 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 147 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 623.8236 82.0989 635.8236 ] /Subtype /Link /Type /Annot >> endobj 148 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 466.5307 623.8236 507.0567 635.8236 ] /Subtype /Link /Type /Annot >> endobj 149 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 124.3929 599.8236 163.8529 611.8236 ] /Subtype /Link /Type /Annot >> endobj 150 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 85.69291 518.8236 125.1529 530.8236 ] /Subtype /Link /Type /Annot >> endobj 151 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 85.69291 500.8236 105.1537 512.8236 ] /Subtype /Link /Type /Annot >> endobj 152 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 308.5389 500.8236 349.1197 512.8236 ] /Subtype /Link /Type /Annot >> endobj 153 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 380.6856 476.8236 421.0199 488.8236 ] /Subtype /Link /Type /Annot >> endobj 154 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 494.4684 476.8236 513.6827 488.8236 ] /Subtype /Link /Type /Annot >> endobj 155 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 85.69291 446.8236 105.5729 458.8236 ] /Subtype /Link /Type /Annot >> endobj 156 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 277.2428 446.8236 318.2428 458.8236 ] /Subtype /Link /Type /Annot >> endobj 157 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 404.5839 434.8236 423.2857 446.8236 ] /Subtype /Link /Type /Annot >> endobj 158 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 85.69291 380.8236 105.83 392.8236 ] /Subtype /Link /Type /Annot >> endobj 159 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 454.0887 368.8236 473.0122 380.8236 ] /Subtype /Link /Type /Annot >> endobj 160 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 85.69291 338.8236 106.1442 350.8236 ] /Subtype /Link /Type /Annot >> endobj 161 0 obj << /A << /S /URI /Type /Action /URI (file:///home/michele/plac/doc/in-writing) >> /Border [ 0 0 0 ] /Rect [ 340.9248 338.8236 467.3287 350.8236 ] /Subtype /Link /Type /Annot >> endobj 162 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 85.69291 308.8236 105.1447 320.8236 ] /Subtype /Link /Type /Annot >> endobj 163 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 85.69291 296.8236 104.0329 308.8236 ] /Subtype /Link /Type /Annot >> endobj 164 0 obj << /A << /S /URI /Type /Action /URI (file:///home/michele/plac/doc/in-writing) >> /Border [ 0 0 0 ] /Rect [ 489.2227 296.8236 532.176 308.8236 ] /Subtype /Link /Type /Annot >> endobj 165 0 obj << /A << /S /URI /Type /Action /URI (file:///home/michele/plac/doc/in-writing) >> /Border [ 0 0 0 ] /Rect [ 85.69291 284.8236 159.6229 296.8236 ] /Subtype /Link /Type /Annot >> endobj 166 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 140.5129 269.8236 158.8529 281.8236 ] /Subtype /Link /Type /Annot >> endobj 167 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 326.1529 269.8236 365.6129 281.8236 ] /Subtype /Link /Type /Annot >> endobj 168 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com/svn/tags/r11/doc/other-utilities.html?highlight=filetype#FileType) >> /Border [ 0 0 0 ] /Rect [ 190.6162 251.8236 273.3248 263.8236 ] /Subtype /Link /Type /Annot >> endobj 169 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 325.7268 102.6236 344.6338 114.6236 ] /Subtype /Link /Type /Annot >> endobj 170 0 obj << /Annots [ 147 0 R 148 0 R 149 0 R 150 0 R 151 0 R 152 0 R 153 0 R 154 0 R 155 0 R 156 0 R 157 0 R 158 0 R 159 0 R 160 0 R 161 0 R 162 0 R 163 0 R 164 0 R 165 0 R 166 0 R 167 0 R 168 0 R 169 0 R ] /Contents 376 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 171 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html) >> /Border [ 0 0 0 ] /Rect [ 327.2261 615.8236 407.7352 627.8236 ] /Subtype /Link /Type /Annot >> endobj 172 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 275.5829 448.6236 315.0429 460.6236 ] /Subtype /Link /Type /Annot >> endobj 173 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 307.9178 430.6236 348.8199 442.6236 ] /Subtype /Link /Type /Annot >> endobj 174 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 317.2329 406.6236 335.5729 418.6236 ] /Subtype /Link /Type /Annot >> endobj 175 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 270.2156 358.6236 288.6535 370.6236 ] /Subtype /Link /Type /Annot >> endobj 176 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 322.6236 81.03291 334.6236 ] /Subtype /Link /Type /Annot >> endobj 177 0 obj << /Annots [ 171 0 R 172 0 R 173 0 R 174 0 R 175 0 R 176 0 R ] /Contents 377 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 178 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 304.0655 218.8236 345.6008 230.8236 ] /Subtype /Link /Type /Annot >> endobj 179 0 obj << /Annots [ 178 0 R ] /Contents 378 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 180 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 293.7749 720.0236 313.4602 732.0236 ] /Subtype /Link /Type /Annot >> endobj 181 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 109.0098 241.4236 129.2167 253.4236 ] /Subtype /Link /Type /Annot >> endobj 182 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 397.2929 217.4236 415.6329 229.4236 ] /Subtype /Link /Type /Annot >> endobj 183 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/opterator) >> /Border [ 0 0 0 ] /Rect [ 85.69291 196.4236 125.7129 208.4236 ] /Subtype /Link /Type /Annot >> endobj 184 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/CLIArgs) >> /Border [ 0 0 0 ] /Rect [ 85.69291 178.4236 121.8129 190.4236 ] /Subtype /Link /Type /Annot >> endobj 185 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/commandline) >> /Border [ 0 0 0 ] /Rect [ 85.69291 160.4236 145.1529 172.4236 ] /Subtype /Link /Type /Annot >> endobj 186 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 464.3898 145.4236 503.8498 157.4236 ] /Subtype /Link /Type /Annot >> endobj 187 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 305.0429 133.4236 323.3829 145.4236 ] /Subtype /Link /Type /Annot >> endobj 188 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/Clap) >> /Border [ 0 0 0 ] /Rect [ 455.0104 115.4236 477.1215 127.4236 ] /Subtype /Link /Type /Annot >> endobj 189 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 303.707 103.4236 322.047 115.4236 ] /Subtype /Link /Type /Annot >> endobj 190 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/Clap) >> /Border [ 0 0 0 ] /Rect [ 328.8186 103.4236 350.5901 115.4236 ] /Subtype /Link /Type /Annot >> endobj 191 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 91.42362 81.03291 103.4236 ] /Subtype /Link /Type /Annot >> endobj 192 0 obj << /Annots [ 180 0 R 181 0 R 182 0 R 183 0 R 184 0 R 185 0 R 186 0 R 187 0 R 188 0 R 189 0 R 190 0 R 191 0 R ] /Contents 379 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 193 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 753.0236 81.6554 765.0236 ] /Subtype /Link /Type /Annot >> endobj 194 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/cmd.html) >> /Border [ 0 0 0 ] /Rect [ 275.6978 753.0236 295.2103 765.0236 ] /Subtype /Link /Type /Annot >> endobj 195 0 obj << /A << /S /URI /Type /Action /URI (http://packages.python.org/cmd2/) >> /Border [ 0 0 0 ] /Rect [ 203.5285 741.0236 228.3557 753.0236 ] /Subtype /Link /Type /Annot >> endobj 196 0 obj << /A << /S /URI /Type /Action /URI (http://packages.python.org/cmd2/) >> /Border [ 0 0 0 ] /Rect [ 164.4129 729.0236 188.8629 741.0236 ] /Subtype /Link /Type /Annot >> endobj 197 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 302.7929 729.0236 321.1329 741.0236 ] /Subtype /Link /Type /Annot >> endobj 198 0 obj << /A << /S /URI /Type /Action /URI (https://github.com/pulp/marrow.script) >> /Border [ 0 0 0 ] /Rect [ 458.7927 711.0236 519.2427 723.0236 ] /Subtype /Link /Type /Annot >> endobj 199 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 276.5607 699.0236 295.2076 711.0236 ] /Subtype /Link /Type /Annot >> endobj 200 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 427.9754 699.0236 467.7422 711.0236 ] /Subtype /Link /Type /Annot >> endobj 201 0 obj << /A << /S /URI /Type /Action /URI (http://packages.python.org/argh) >> /Border [ 0 0 0 ] /Rect [ 497.8158 699.0236 519.2427 711.0236 ] /Subtype /Link /Type /Annot >> endobj 202 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 336.0283 687.0236 375.4883 699.0236 ] /Subtype /Link /Type /Annot >> endobj 203 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 441.2505 687.0236 460.191 699.0236 ] /Subtype /Link /Type /Annot >> endobj 204 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 156.6051 627.0236 175.0806 639.0236 ] /Subtype /Link /Type /Annot >> endobj 205 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 186.6535 603.0236 226.1135 615.0236 ] /Subtype /Link /Type /Annot >> endobj 206 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 493.1227 603.0236 532.4646 615.0236 ] /Subtype /Link /Type /Annot >> endobj 207 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 72.91915 591.0236 93.70538 603.0236 ] /Subtype /Link /Type /Annot >> endobj 208 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 149.2229 561.0236 168.4904 573.0236 ] /Subtype /Link /Type /Annot >> endobj 209 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 128.0309 519.0236 146.6569 531.0236 ] /Subtype /Link /Type /Annot >> endobj 210 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 502.8367 519.0236 521.4627 531.0236 ] /Subtype /Link /Type /Annot >> endobj 211 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 187.4797 495.0236 206.3191 507.0236 ] /Subtype /Link /Type /Annot >> endobj 212 0 obj << /A << /S /URI /Type /Action /URI (file:///home/michele/plac/doc/in-writing) >> /Border [ 0 0 0 ] /Rect [ 301.6965 495.0236 423.2646 507.0236 ] /Subtype /Link /Type /Annot >> endobj 213 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 83.6829 423.0236 103.0029 435.0236 ] /Subtype /Link /Type /Annot >> endobj 214 0 obj << /A << /S /URI /Type /Action /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >> /Border [ 0 0 0 ] /Rect [ 371.6627 423.0236 424.8927 435.0236 ] /Subtype /Link /Type /Annot >> endobj 215 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 275.6828 411.0236 294.5888 423.0236 ] /Subtype /Link /Type /Annot >> endobj 216 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/optparse.html?highlight=optionparser#optparse.OptionParser) >> /Border [ 0 0 0 ] /Rect [ 77.19665 399.0236 136.7104 411.0236 ] /Subtype /Link /Type /Annot >> endobj 217 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 96.54131 387.0236 136.2455 399.0236 ] /Subtype /Link /Type /Annot >> endobj 218 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 203.5016 354.0236 243.0653 366.0236 ] /Subtype /Link /Type /Annot >> endobj 219 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html) >> /Border [ 0 0 0 ] /Rect [ 62.69291 285.0236 136.0098 297.0236 ] /Subtype /Link /Type /Annot >> endobj 220 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 114.6649 273.0236 154.1249 285.0236 ] /Subtype /Link /Type /Annot >> endobj 221 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 191.6329 261.0236 231.0929 273.0236 ] /Subtype /Link /Type /Annot >> endobj 222 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/Clap) >> /Border [ 0 0 0 ] /Rect [ 263.3429 231.0236 283.9029 243.0236 ] /Subtype /Link /Type /Annot >> endobj 223 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 298.1928 183.0236 316.5328 195.0236 ] /Subtype /Link /Type /Annot >> endobj 224 0 obj << /Annots [ 193 0 R 194 0 R 195 0 R 196 0 R 197 0 R 198 0 R 199 0 R 200 0 R 201 0 R 202 0 R 203 0 R 204 0 R 205 0 R 206 0 R 207 0 R 208 0 R 209 0 R 210 0 R 211 0 R 212 0 R 213 0 R 214 0 R 215 0 R 216 0 R 217 0 R 218 0 R 219 0 R 220 0 R 221 0 R 222 0 R 223 0 R ] /Contents 380 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 225 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 185.4471 696.0236 204.3262 708.0236 ] /Subtype /Link /Type /Annot >> endobj 226 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 177.6784 684.0236 196.8323 696.0236 ] /Subtype /Link /Type /Annot >> endobj 227 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 630.0236 81.03291 642.0236 ] /Subtype /Link /Type /Annot >> endobj 228 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 278.4678 612.0236 297.2528 624.0236 ] /Subtype /Link /Type /Annot >> endobj 229 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 117.3573 600.0236 135.8045 612.0236 ] /Subtype /Link /Type /Annot >> endobj 230 0 obj << /A << /S /URI /Type /Action /URI (http://twill.idyll.org/) >> /Border [ 0 0 0 ] /Rect [ 82.74466 558.0236 101.6764 570.0236 ] /Subtype /Link /Type /Annot >> endobj 231 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 127.2882 558.0236 145.6282 570.0236 ] /Subtype /Link /Type /Annot >> endobj 232 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 410.1674 558.0236 430.7792 570.0236 ] /Subtype /Link /Type /Annot >> endobj 233 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 190.9318 534.0236 209.2718 546.0236 ] /Subtype /Link /Type /Annot >> endobj 234 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 405.7901 534.0236 424.1301 546.0236 ] /Subtype /Link /Type /Annot >> endobj 235 0 obj << /A << /S /URI /Type /Action /URI (http://plac.googlecode.com/hg/doc/plac.html) >> /Border [ 0 0 0 ] /Rect [ 62.69291 372.0236 154.4029 384.0236 ] /Subtype /Link /Type /Annot >> endobj 236 0 obj << /Annots [ 225 0 R 226 0 R 227 0 R 228 0 R 229 0 R 230 0 R 231 0 R 232 0 R 233 0 R 234 0 R 235 0 R ] /Contents 381 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 237 0 obj << /Contents 382 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 238 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 473.5715 753.0236 493.3521 765.0236 ] /Subtype /Link /Type /Annot >> endobj 239 0 obj << /Annots [ 238 0 R ] /Contents 383 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 240 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 370.6785 333.8236 389.7156 345.8236 ] /Subtype /Link /Type /Annot >> endobj 241 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 455.8742 333.8236 474.9113 345.8236 ] /Subtype /Link /Type /Annot >> endobj 242 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 185.9351 118.6236 204.6895 130.6236 ] /Subtype /Link /Type /Annot >> endobj 243 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/shlex.html) >> /Border [ 0 0 0 ] /Rect [ 369.8905 118.6236 393.645 130.6236 ] /Subtype /Link /Type /Annot >> endobj 244 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 408.8916 106.6236 427.2316 118.6236 ] /Subtype /Link /Type /Annot >> endobj 245 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/shlex.html) >> /Border [ 0 0 0 ] /Rect [ 62.69291 94.62362 86.03291 106.6236 ] /Subtype /Link /Type /Annot >> endobj 246 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 92.4689 94.62362 111.6849 106.6236 ] /Subtype /Link /Type /Annot >> endobj 247 0 obj << /Annots [ 240 0 R 241 0 R 242 0 R 243 0 R 244 0 R 245 0 R 246 0 R ] /Contents 384 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 248 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/shlex.html) >> /Border [ 0 0 0 ] /Rect [ 149.3204 753.0236 174.1672 765.0236 ] /Subtype /Link /Type /Annot >> endobj 249 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 129.6923 657.0236 148.6855 669.0236 ] /Subtype /Link /Type /Annot >> endobj 250 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 173.8529 645.0236 192.1929 657.0236 ] /Subtype /Link /Type /Annot >> endobj 251 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 460.388 285.8236 479.2327 297.8236 ] /Subtype /Link /Type /Annot >> endobj 252 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 95.32996 219.8236 114.077 231.8236 ] /Subtype /Link /Type /Annot >> endobj 253 0 obj << /Annots [ 248 0 R 249 0 R 250 0 R 251 0 R 252 0 R ] /Contents 385 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 254 0 obj << /A << /S /URI /Type /Action /URI (http://plac.googlecode.com/hg/doc/plac.html) >> /Border [ 0 0 0 ] /Rect [ 312.1828 378.6236 404.554 390.6236 ] /Subtype /Link /Type /Annot >> endobj 255 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 419.1694 366.6236 437.6261 378.6236 ] /Subtype /Link /Type /Annot >> endobj 256 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 336.6236 81.92395 348.6236 ] /Subtype /Link /Type /Annot >> endobj 257 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 264.6236 81.03291 276.6236 ] /Subtype /Link /Type /Annot >> endobj 258 0 obj << /Annots [ 254 0 R 255 0 R 256 0 R 257 0 R ] /Contents 386 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 259 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 431.7904 311.8236 450.9445 323.8236 ] /Subtype /Link /Type /Annot >> endobj 260 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 255.5885 281.8236 275.4957 293.8236 ] /Subtype /Link /Type /Annot >> endobj 261 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/cmd.html) >> /Border [ 0 0 0 ] /Rect [ 513.6927 257.8236 532.1846 269.8236 ] /Subtype /Link /Type /Annot >> endobj 262 0 obj << /Annots [ 259 0 R 260 0 R 261 0 R ] /Contents 387 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 263 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 323.3 144.6236 341.6861 156.6236 ] /Subtype /Link /Type /Annot >> endobj 264 0 obj << /Annots [ 263 0 R ] /Contents 388 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 265 0 obj << /Contents 389 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 266 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 179.0529 283.4236 198.4153 295.4236 ] /Subtype /Link /Type /Annot >> endobj 267 0 obj << /Annots [ 266 0 R ] /Contents 390 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 268 0 obj << /A << /S /URI /Type /Action /URI (http://freshmeat.net/projects/rlwrap/) >> /Border [ 0 0 0 ] /Rect [ 377.8504 264.6236 407.081 276.6236 ] /Subtype /Link /Type /Annot >> endobj 269 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 242.4466 252.6236 261.0122 264.6236 ] /Subtype /Link /Type /Annot >> endobj 270 0 obj << /A << /S /URI /Type /Action /URI (http://ipython.scipy.org/moin/PyReadline/Intro) >> /Border [ 0 0 0 ] /Rect [ 456.2271 252.6236 502.5827 264.6236 ] /Subtype /Link /Type /Annot >> endobj 271 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 363.1739 228.6236 383.7204 240.6236 ] /Subtype /Link /Type /Annot >> endobj 272 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 479.7508 216.6236 498.6827 228.6236 ] /Subtype /Link /Type /Annot >> endobj 273 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/cmd.html) >> /Border [ 0 0 0 ] /Rect [ 366.9454 186.6236 386.0233 198.6236 ] /Subtype /Link /Type /Annot >> endobj 274 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/cmd.html) >> /Border [ 0 0 0 ] /Rect [ 172.4855 174.6236 191.3755 186.6236 ] /Subtype /Link /Type /Annot >> endobj 275 0 obj << /Annots [ 268 0 R 269 0 R 270 0 R 271 0 R 272 0 R 273 0 R 274 0 R ] /Contents 391 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 276 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 391.2152 491.8236 432.6696 503.8236 ] /Subtype /Link /Type /Annot >> endobj 277 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 211.3319 479.8236 232.9292 491.8236 ] /Subtype /Link /Type /Annot >> endobj 278 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 114.9429 455.8236 154.4029 467.8236 ] /Subtype /Link /Type /Annot >> endobj 279 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 350.6129 425.8236 368.9529 437.8236 ] /Subtype /Link /Type /Annot >> endobj 280 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 256.6729 407.8236 275.0129 419.8236 ] /Subtype /Link /Type /Annot >> endobj 281 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 149.5469 314.6236 169.4182 326.6236 ] /Subtype /Link /Type /Annot >> endobj 282 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/distutils/) >> /Border [ 0 0 0 ] /Rect [ 224.3178 302.6236 258.1053 314.6236 ] /Subtype /Link /Type /Annot >> endobj 283 0 obj << /Annots [ 276 0 R 277 0 R 278 0 R 279 0 R 280 0 R 281 0 R 282 0 R ] /Contents 392 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 284 0 obj << /A << /S /URI /Type /Action /URI (http://argparse.googlecode.com) >> /Border [ 0 0 0 ] /Rect [ 381.1529 199.4236 420.6129 211.4236 ] /Subtype /Link /Type /Annot >> endobj 285 0 obj << /Annots [ 284 0 R ] /Contents 393 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 286 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 472.6236 81.94012 484.6236 ] /Subtype /Link /Type /Annot >> endobj 287 0 obj << /Annots [ 286 0 R ] /Contents 394 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 288 0 obj << /Contents 395 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 289 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 182.479 441.8236 200.9862 453.8236 ] /Subtype /Link /Type /Annot >> endobj 290 0 obj << /Annots [ 289 0 R ] /Contents 396 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 291 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 265.1128 485.8236 283.8518 497.8236 ] /Subtype /Link /Type /Annot >> endobj 292 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/multiprocessing.html) >> /Border [ 0 0 0 ] /Rect [ 319.4829 330.6236 388.9429 342.6236 ] /Subtype /Link /Type /Annot >> endobj 293 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 179.1295 282.6236 198.9039 294.6236 ] /Subtype /Link /Type /Annot >> endobj 294 0 obj << /Annots [ 291 0 R 292 0 R 293 0 R ] /Contents 397 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 295 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 414.8874 450.6236 434.1687 462.6236 ] /Subtype /Link /Type /Annot >> endobj 296 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 122.7054 306.6236 142.4285 318.6236 ] /Subtype /Link /Type /Annot >> endobj 297 0 obj << /Annots [ 295 0 R 296 0 R ] /Contents 398 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 298 0 obj << /Contents 399 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 299 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 183.3657 451.8236 205.0564 463.8236 ] /Subtype /Link /Type /Annot >> endobj 300 0 obj << /A << /S /URI /Type /Action /URI (http://docs.python.org/library/multiprocessing.html) >> /Border [ 0 0 0 ] /Rect [ 254.9929 427.8236 324.4529 439.8236 ] /Subtype /Link /Type /Annot >> endobj 301 0 obj << /Annots [ 299 0 R 300 0 R ] /Contents 400 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 302 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 663.0236 82.92846 675.0236 ] /Subtype /Link /Type /Annot >> endobj 303 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 145.094 134.8236 163.4811 146.8236 ] /Subtype /Link /Type /Annot >> endobj 304 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 110.8236 81.03291 122.8236 ] /Subtype /Link /Type /Annot >> endobj 305 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 135.5029 98.82362 153.8429 110.8236 ] /Subtype /Link /Type /Annot >> endobj 306 0 obj << /Annots [ 302 0 R 303 0 R 304 0 R 305 0 R ] /Contents 401 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 307 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 729.0236 82.20766 741.0236 ] /Subtype /Link /Type /Annot >> endobj 308 0 obj << /Annots [ 307 0 R ] /Contents 402 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 309 0 obj << /Contents 403 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 310 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 473.6216 653.8236 492.0127 665.8236 ] /Subtype /Link /Type /Annot >> endobj 311 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 62.69291 504.6236 84.00136 516.6236 ] /Subtype /Link /Type /Annot >> endobj 312 0 obj << /Annots [ 310 0 R 311 0 R ] /Contents 404 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 313 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 117.8941 717.0236 137.7097 729.0236 ] /Subtype /Link /Type /Annot >> endobj 314 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 483.1438 669.0236 502.0227 681.0236 ] /Subtype /Link /Type /Annot >> endobj 315 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 350.3153 621.0236 368.8877 633.0236 ] /Subtype /Link /Type /Annot >> endobj 316 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 91.57623 222.6236 112.1195 234.6236 ] /Subtype /Link /Type /Annot >> endobj 317 0 obj << /Annots [ 313 0 R 314 0 R 315 0 R 316 0 R ] /Contents 405 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 318 0 obj << /A << /S /URI /Type /Action /URI (http://pypi.python.org/pypi/plac) >> /Border [ 0 0 0 ] /Rect [ 107.1402 586.6772 126.5775 598.6772 ] /Subtype /Link /Type /Annot >> endobj 319 0 obj << /Annots [ 318 0 R ] /Contents 406 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 320 0 obj << /Contents 407 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 361 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 321 0 obj << /Outlines 323 0 R /PageLabels 408 0 R /PageMode /UseNone /Pages 361 0 R /Type /Catalog >> endobj 322 0 obj << /Author () /CreationDate (D:20160708151322-01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20160708151322-01'00') /Producer (ReportLab PDF Library - www.reportlab.com) /Subject (\(unspecified\)) /Title () /Trapped /False >> endobj 323 0 obj << /Count 40 /First 324 0 R /Last 355 0 R /Type /Outlines >> endobj 324 0 obj << /Count 14 /Dest [ 8 0 R /XYZ 62.69291 765.0236 0 ] /First 325 0 R /Last 338 0 R /Next 339 0 R /Parent 323 0 R /Title (Plac: Parsing the Command Line the Easy Way) >> endobj 325 0 obj << /Dest [ 99 0 R /XYZ 62.69291 735.0236 0 ] /Next 326 0 R /Parent 324 0 R /Title (The importance of scaling down) >> endobj 326 0 obj << /Dest [ 99 0 R /XYZ 62.69291 381.0236 0 ] /Next 327 0 R /Parent 324 0 R /Prev 325 0 R /Title (Scripts with required arguments) >> endobj 327 0 obj << /Dest [ 114 0 R /XYZ 62.69291 765.0236 0 ] /Next 328 0 R /Parent 324 0 R /Prev 326 0 R /Title (Scripts with default arguments) >> endobj 328 0 obj << /Dest [ 124 0 R /XYZ 62.69291 695.8236 0 ] /Next 329 0 R /Parent 324 0 R /Prev 327 0 R /Title (Scripts with options \(and smart options\)) >> endobj 329 0 obj << /Dest [ 128 0 R /XYZ 62.69291 650.6236 0 ] /Next 330 0 R /Parent 324 0 R /Prev 328 0 R /Title (Scripts with flags) >> endobj 330 0 obj << /Dest [ 131 0 R /XYZ 62.69291 530.6236 0 ] /Next 331 0 R /Parent 324 0 R /Prev 329 0 R /Title (plac for Python 2.X users) >> endobj 331 0 obj << /Dest [ 138 0 R /XYZ 62.69291 765.0236 0 ] /Next 332 0 R /Parent 324 0 R /Prev 330 0 R /Title (More features) >> endobj 332 0 obj << /Dest [ 143 0 R /XYZ 62.69291 765.0236 0 ] /Next 333 0 R /Parent 324 0 R /Prev 331 0 R /Title (A realistic example) >> endobj 333 0 obj << /Dest [ 146 0 R /XYZ 62.69291 695.8236 0 ] /Next 334 0 R /Parent 324 0 R /Prev 332 0 R /Title (Keyword arguments) >> endobj 334 0 obj << /Dest [ 170 0 R /XYZ 62.69291 659.8236 0 ] /Next 335 0 R /Parent 324 0 R /Prev 333 0 R /Title (plac vs argparse) >> endobj 335 0 obj << /Dest [ 177 0 R /XYZ 62.69291 394.6236 0 ] /Next 336 0 R /Parent 324 0 R /Prev 334 0 R /Title (Final example: a shelve interface) >> endobj 336 0 obj << /Dest [ 192 0 R /XYZ 62.69291 277.4236 0 ] /Next 337 0 R /Parent 324 0 R /Prev 335 0 R /Title (plac vs the rest of the world) >> endobj 337 0 obj << /Dest [ 224 0 R /XYZ 62.69291 663.0236 0 ] /Next 338 0 R /Parent 324 0 R /Prev 336 0 R /Title (The future) >> endobj 338 0 obj << /Dest [ 224 0 R /XYZ 62.69291 459.0236 0 ] /Parent 324 0 R /Prev 337 0 R /Title (Trivia: the story behind the name) >> endobj 339 0 obj << /Count 15 /Dest [ 236 0 R /XYZ 62.69291 765.0236 0 ] /First 340 0 R /Last 354 0 R /Next 355 0 R /Parent 323 0 R /Prev 324 0 R /Title (Advanced usages of plac) >> endobj 340 0 obj << /Dest [ 236 0 R /XYZ 62.69291 732.0236 0 ] /Next 341 0 R /Parent 339 0 R /Title (Introduction) >> endobj 341 0 obj << /Dest [ 236 0 R /XYZ 62.69291 510.0236 0 ] /Next 342 0 R /Parent 339 0 R /Prev 340 0 R /Title (From scripts to interactive applications) >> endobj 342 0 obj << /Dest [ 239 0 R /XYZ 62.69291 699.0236 0 ] /Next 343 0 R /Parent 339 0 R /Prev 341 0 R /Title (Testing a plac application) >> endobj 343 0 obj << /Dest [ 247 0 R /XYZ 62.69291 633.0236 0 ] /Next 344 0 R /Parent 339 0 R /Prev 342 0 R /Title (Plac easy tests) >> endobj 344 0 obj << /Dest [ 253 0 R /XYZ 62.69291 333.8236 0 ] /Next 345 0 R /Parent 339 0 R /Prev 343 0 R /Title (Plac batch scripts) >> endobj 345 0 obj << /Dest [ 258 0 R /XYZ 62.69291 414.6236 0 ] /Next 346 0 R /Parent 339 0 R /Prev 344 0 R /Title (Implementing subcommands) >> endobj 346 0 obj << /Dest [ 265 0 R /XYZ 62.69291 663.8236 0 ] /Next 347 0 R /Parent 339 0 R /Prev 345 0 R /Title (plac.Interpreter.call) >> endobj 347 0 obj << /Dest [ 267 0 R /XYZ 62.69291 319.4236 0 ] /Next 348 0 R /Parent 339 0 R /Prev 346 0 R /Title (Readline support) >> endobj 348 0 obj << /Dest [ 283 0 R /XYZ 62.69291 350.6236 0 ] /Next 349 0 R /Parent 339 0 R /Prev 347 0 R /Title (The plac runner) >> endobj 349 0 obj << /Dest [ 287 0 R /XYZ 62.69291 508.6236 0 ] /Next 350 0 R /Parent 339 0 R /Prev 348 0 R /Title (A non class-based example) >> endobj 350 0 obj << /Dest [ 290 0 R /XYZ 62.69291 477.8236 0 ] /Next 351 0 R /Parent 339 0 R /Prev 349 0 R /Title (Writing your own plac runner) >> endobj 351 0 obj << /Dest [ 294 0 R /XYZ 62.69291 318.6236 0 ] /Next 352 0 R /Parent 339 0 R /Prev 350 0 R /Title (Long running commands) >> endobj 352 0 obj << /Dest [ 297 0 R /XYZ 62.69291 426.6236 0 ] /Next 353 0 R /Parent 339 0 R /Prev 351 0 R /Title (Threaded commands) >> endobj 353 0 obj << /Dest [ 301 0 R /XYZ 62.69291 499.8236 0 ] /Next 354 0 R /Parent 339 0 R /Prev 352 0 R /Title (Running commands as external processes) >> endobj 354 0 obj << /Dest [ 306 0 R /XYZ 62.69291 699.0236 0 ] /Parent 339 0 R /Prev 353 0 R /Title (Managing the output of concurrent commands) >> endobj 355 0 obj << /Count 5 /Dest [ 306 0 R /XYZ 62.69291 173.8236 0 ] /First 356 0 R /Last 360 0 R /Parent 323 0 R /Prev 339 0 R /Title (Experimental features) >> endobj 356 0 obj << /Dest [ 308 0 R /XYZ 62.69291 765.0236 0 ] /Next 357 0 R /Parent 355 0 R /Title (Parallel computing with plac) >> endobj 357 0 obj << /Dest [ 312 0 R /XYZ 62.69291 540.6236 0 ] /Next 358 0 R /Parent 355 0 R /Prev 356 0 R /Title (Monitor support) >> endobj 358 0 obj << /Dest [ 317 0 R /XYZ 62.69291 765.0236 0 ] /Next 359 0 R /Parent 355 0 R /Prev 357 0 R /Title (The plac server) >> endobj 359 0 obj << /Dest [ 317 0 R /XYZ 62.69291 258.6236 0 ] /Next 360 0 R /Parent 355 0 R /Prev 358 0 R /Title (Summary) >> endobj 360 0 obj << /Dest [ 319 0 R /XYZ 62.69291 622.6772 0 ] /Parent 355 0 R /Prev 359 0 R /Title (Appendix: custom annotation objects) >> endobj 361 0 obj << /Count 46 /Kids [ 8 0 R 81 0 R 99 0 R 109 0 R 114 0 R 118 0 R 124 0 R 125 0 R 128 0 R 131 0 R 138 0 R 139 0 R 143 0 R 146 0 R 170 0 R 177 0 R 179 0 R 192 0 R 224 0 R 236 0 R 237 0 R 239 0 R 247 0 R 253 0 R 258 0 R 262 0 R 264 0 R 265 0 R 267 0 R 275 0 R 283 0 R 285 0 R 287 0 R 288 0 R 290 0 R 294 0 R 297 0 R 298 0 R 301 0 R 306 0 R 308 0 R 309 0 R 312 0 R 317 0 R 319 0 R 320 0 R ] /Type /Pages >> endobj 362 0 obj << /Length 2941 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 744.0236 cm q BT 1 0 0 1 0 3.5 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Plac: Parsing the Command Line the Easy Way) Tj T* ET Q Q q 1 0 0 1 62.69291 732.0236 cm Q q 1 0 0 1 62.69291 717.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 36.93937 0 Td (Author:) Tj T* -36.93937 0 Td ET Q Q q 1 0 0 1 91.03937 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Michele Simionato) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 702.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 39.69937 0 Td (E-mail:) Tj T* -39.69937 0 Td ET Q Q q 1 0 0 1 91.03937 3 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (michele.simionato@gmail.com) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 687.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 48.03937 0 Td (Date:) Tj T* -48.03937 0 Td ET Q Q q 1 0 0 1 91.03937 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (June 2016) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 660.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F2 10 Tf 12 TL 25.25937 0 Td (Download) Tj T* 21.11 0 Td (page:) Tj T* -46.36937 0 Td ET Q Q q 1 0 0 1 91.03937 15 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (http://pypi.python.org/pypi/plac) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 645.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 9.68937 0 Td (Project page:) Tj T* -9.68937 0 Td ET Q Q q 1 0 0 1 91.03937 3 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (https://github.com/micheles/plac) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 630.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 26.91937 0 Td (Requires:) Tj T* -26.91937 0 Td ET Q Q q 1 0 0 1 91.03937 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Python 2.6+) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 615.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 16.91937 0 Td (Installation:) Tj T* -16.91937 0 Td ET Q Q q 1 0 0 1 91.03937 3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (pip) Tj ( ) Tj (install) Tj ( ) Tj (plac) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 600.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 32.46937 0 Td (License:) Tj T* -32.46937 0 Td ET Q Q q 1 0 0 1 91.03937 3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (BSD license) Tj T* ET Q Q q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (1) Tj T* -238.1649 0 Td ET Q Q endstream endobj 363 0 obj << /Length 9615 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 744.0236 cm q BT 1 0 0 1 0 3.5 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Contents) Tj T* ET Q Q q 1 0 0 1 62.69291 90.02362 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 0 633 cm q BT 1 0 0 1 0 2 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Plac: Parsing the Command Line the Easy Way) Tj T* ET Q Q q 1 0 0 1 397.8898 633 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 66.44 0 Td (1) Tj T* -66.44 0 Td ET Q Q q 1 0 0 1 0 615 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The importance of scaling down) Tj T* ET Q Q q 1 0 0 1 397.8898 615 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (3) Tj T* -66.44 0 Td ET Q Q q 1 0 0 1 0 597 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with required arguments) Tj T* ET Q Q q 1 0 0 1 397.8898 597 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (3) Tj T* -66.44 0 Td ET Q Q q 1 0 0 1 0 579 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with default arguments) Tj T* ET Q Q q 1 0 0 1 397.8898 579 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (5) Tj T* -66.44 0 Td ET Q Q q 1 0 0 1 0 561 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with options \(and smart options\)) Tj T* ET Q Q q 1 0 0 1 397.8898 561 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (7) Tj T* -66.44 0 Td ET Q Q q 1 0 0 1 0 543 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with flags) Tj T* ET Q Q q 1 0 0 1 397.8898 543 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (9) Tj T* -66.44 0 Td ET Q Q q 1 0 0 1 0 525 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac for Python 2.X users) Tj T* ET Q Q q 1 0 0 1 397.8898 525 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (10) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 507 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (More features) Tj T* ET Q Q q 1 0 0 1 397.8898 507 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (11) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 489 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (A realistic example) Tj T* ET Q Q q 1 0 0 1 397.8898 489 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (13) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 471 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Keyword arguments) Tj T* ET Q Q q 1 0 0 1 397.8898 471 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (14) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 453 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac vs argparse) Tj T* ET Q Q q 1 0 0 1 397.8898 453 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (15) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 435 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Final example: a shelve interface) Tj T* ET Q Q q 1 0 0 1 397.8898 435 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (16) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 417 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac vs the rest of the world) Tj T* ET Q Q q 1 0 0 1 397.8898 417 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (18) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 399 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The future) Tj T* ET Q Q q 1 0 0 1 397.8898 399 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (19) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 381 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Trivia: the story behind the name) Tj T* ET Q Q q 1 0 0 1 397.8898 381 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (19) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 363 cm q BT 1 0 0 1 0 2 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Advanced usages of plac) Tj T* ET Q Q q 1 0 0 1 397.8898 363 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 60.88 0 Td (20) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 345 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Introduction) Tj T* ET Q Q q 1 0 0 1 397.8898 345 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (20) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 327 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (From scripts to interactive applications) Tj T* ET Q Q q 1 0 0 1 397.8898 327 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (20) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 309 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Testing a plac application) Tj T* ET Q Q q 1 0 0 1 397.8898 309 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (22) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 291 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Plac easy tests) Tj T* ET Q Q q 1 0 0 1 397.8898 291 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (23) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 273 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Plac batch scripts) Tj T* ET Q Q q 1 0 0 1 397.8898 273 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (24) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 255 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Implementing subcommands) Tj T* ET Q Q q 1 0 0 1 397.8898 255 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (25) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 237 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac.Interpreter.call) Tj T* ET Q Q q 1 0 0 1 397.8898 237 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (28) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 219 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Readline support) Tj T* ET Q Q q 1 0 0 1 397.8898 219 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (29) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 201 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The plac runner) Tj T* ET Q Q q 1 0 0 1 397.8898 201 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (31) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 183 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (A non class-based example) Tj T* ET Q Q q 1 0 0 1 397.8898 183 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (33) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 165 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Writing your own plac runner) Tj T* ET Q Q q 1 0 0 1 397.8898 165 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (35) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 147 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Long running commands) Tj T* ET Q Q q 1 0 0 1 397.8898 147 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (36) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 129 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Threaded commands) Tj T* ET Q Q q 1 0 0 1 397.8898 129 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (37) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 111 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Running commands as external processes) Tj T* ET Q Q q 1 0 0 1 397.8898 111 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (39) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 93 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Managing the output of concurrent commands) Tj T* ET Q Q q 1 0 0 1 397.8898 93 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (40) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 75 cm q BT 1 0 0 1 0 2 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Experimental features) Tj T* ET Q Q q 1 0 0 1 397.8898 75 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 60.88 0 Td (40) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 57 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Parallel computing with plac) Tj T* ET Q Q q 1 0 0 1 397.8898 57 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (41) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 39 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Monitor support) Tj T* ET Q Q q 1 0 0 1 397.8898 39 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (43) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 21 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The plac server) Tj T* ET Q Q q 1 0 0 1 397.8898 21 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (44) Tj T* -60.88 0 Td ET Q Q q 1 0 0 1 0 3 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Summary) Tj T* ET Q Q q 1 0 0 1 397.8898 3 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (44) Tj T* -60.88 0 Td ET Q Q q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (2) Tj T* -238.1649 0 Td ET Q Q endstream endobj 364 0 obj << /Length 5948 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 747.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 0 3 cm q BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Appendix: custom annotation objects) Tj T* ET Q Q q 1 0 0 1 397.8898 3 cm q 0 0 .501961 rg 0 0 .501961 RG BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (45) Tj T* -60.88 0 Td ET Q Q q Q Q q 1 0 0 1 62.69291 717.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The importance of scaling down) Tj T* ET Q Q q 1 0 0 1 62.69291 651.0236 cm q BT 1 0 0 1 0 50 Tm 1.573318 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is no want of command-line arguments parsers in the Python world. The standard library alone) Tj T* 0 Tw 1.087126 Tw (contains three different modules: ) Tj 0 0 .501961 rg (getopt ) Tj 0 0 0 rg (\(from the stone age\), ) Tj 0 0 .501961 rg (optparse ) Tj 0 0 0 rg (\(from Python 2.3\) and ) Tj 0 0 .501961 rg (argparse) Tj T* 0 Tw .223735 Tw 0 0 0 rg (\(from Python 2.7\). All of them are quite powerful and especially ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (is an industrial strength solution;) Tj T* 0 Tw 1.718651 Tw (unfortunately, all of them feature a non-negligible learning curve and a certain verbosity. They do not) Tj T* 0 Tw (scale down well enough, at least in my opinion.) Tj T* ET Q Q q 1 0 0 1 62.69291 573.0236 cm q BT 1 0 0 1 0 62 Tm 1.418221 Tw 12 TL /F1 10 Tf 0 0 0 rg (It should not be necessary to stress the importance of ) Tj 0 0 .501961 rg (scaling down) Tj 0 0 0 rg (; nevertheless, a lot of people are) Tj T* 0 Tw .968555 Tw (obsessed with features and concerned with the possibility of scaling up, forgetting the equally important) Tj T* 0 Tw .048221 Tw (issue of scaling down. This is an old meme in the computing world: programs should address the common) Tj T* 0 Tw 1.648735 Tw (cases simply and simple things should be kept simple, while at the same time keeping difficult things) Tj T* 0 Tw .506654 Tw (possible. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (adhere as much as possible to this philosophy and it is designed to handle well the simple) Tj T* 0 Tw (cases, while retaining the ability to handle complex cases by relying on the underlying power of ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 495.0236 cm q BT 1 0 0 1 0 62 Tm 1.488221 Tw 12 TL /F1 10 Tf 0 0 0 rg (Technically ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is just a simple wrapper over ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (which hides most of its complexity by using a) Tj T* 0 Tw .203318 Tw (declarative interface: the argument parser is inferred rather than written down by imperatively. Still, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is) Tj T* 0 Tw .125984 Tw (surprisingly scalable upwards, even without using the underlying ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. I have been using Python for 9) Tj T* 0 Tw 1.618876 Tw (years and in my experience it is extremely unlikely that you will ever need to go beyond the features) Tj T* 0 Tw 1.776457 Tw (provided by the declarative interface of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (: they should be more than enough for 99.9% of the use) Tj T* 0 Tw (cases.) Tj T* ET Q Q q 1 0 0 1 62.69291 393.0236 cm q BT 1 0 0 1 0 86 Tm 1.540888 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is targetting especially unsophisticated users, programmers, sys-admins, scientists and in general) Tj T* 0 Tw .106905 Tw (people writing throw-away scripts for themselves, choosing the command-line interface because it is quick) Tj T* 0 Tw .732927 Tw (and simple. Such users are not interested in features, they are interested in a small learning curve: they) Tj T* 0 Tw 2.177882 Tw (just want to be able to write a simple command line tool from a simple specification, not to build a) Tj T* 0 Tw 1.127318 Tw (command-line parser by hand. Unfortunately, the modules in the standard library forces them to go the) Tj T* 0 Tw .014104 Tw (hard way. They are designed to implement power user tools and they have a non-trivial learning curve. On) Tj T* 0 Tw 1.584104 Tw (the contrary, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is designed to be simple to use and extremely concise, as the examples below will) Tj T* 0 Tw (show.) Tj T* ET Q Q q 1 0 0 1 62.69291 363.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with required arguments) Tj T* ET Q Q q 1 0 0 1 62.69291 297.0236 cm q BT 1 0 0 1 0 50 Tm .352209 Tw 12 TL /F1 10 Tf 0 0 0 rg (Let me start with the simplest possible thing: a script that takes a single argument and does something to) Tj T* 0 Tw 2.932485 Tw (it. It cannot get simpler than that, unless you consider the case of a script without command-line) Tj T* 0 Tw 1.712209 Tw (arguments, where there is nothing to parse. Still, it is a use case ) Tj /F4 10 Tf (extremely common) Tj /F1 10 Tf (: I need to write) Tj T* 0 Tw .900488 Tw (scripts like that nearly every day, I wrote hundreds of them in the last few years and I have never been) Tj T* 0 Tw (happy. Here is a typical example of code I have been writing by hand for years:) Tj T* ET Q Q q 1 0 0 1 62.69291 107.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 180 re B* Q q 0 0 0 rg BT 1 0 0 1 0 158 Tm /F3 10 Tf 12 TL (# example1.py) Tj T* (def main\(dsn\):) Tj T* ( "Do something with the database") Tj T* ( print\("ok"\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import sys) Tj T* ( n = len\(sys.argv[1:]\)) Tj T* ( if n == 0:) Tj T* ( sys.exit\('usage: python %s dsn' % sys.argv[0]\)) Tj T* ( elif n == 1:) Tj T* ( main\(sys.argv[1]\)) Tj T* ( else:) Tj T* ( sys.exit\('Unrecognized arguments: %s' % ' '.join\(sys.argv[2:]\)\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (3) Tj T* -238.1649 0 Td ET Q Q endstream endobj 365 0 obj << /Length 4494 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 669.0236 cm q BT 1 0 0 1 0 86 Tm .880651 Tw 12 TL /F1 10 Tf 0 0 0 rg (As you see the whole ) Tj /F3 10 Tf 0 0 0 rg (if) Tj ( ) Tj (__name__) Tj ( ) Tj (==) Tj ( ) Tj ('__main__' ) Tj /F1 10 Tf 0 0 0 rg (block \(nine lines\) is essentially boilerplate that) Tj T* 0 Tw 3.495984 Tw (should not exist. Actually I think the language should recognize the main function and pass the) Tj T* 0 Tw 3.309147 Tw (command-line arguments automatically; unfortunaly this is unlikely to happen. I have been writing) Tj T* 0 Tw 1.767356 Tw (boilerplate like this in hundreds of scripts for years, and every time I ) Tj /F4 10 Tf (hate ) Tj /F1 10 Tf (it. The purpose of using a) Tj T* 0 Tw 1.47229 Tw (scripting language is convenience and trivial things should be trivial. Unfortunately the standard library) Tj T* 0 Tw .69881 Tw (does not help for this incredibly common use case. Using ) Tj 0 0 .501961 rg (getopt ) Tj 0 0 0 rg (and ) Tj 0 0 .501961 rg (optparse ) Tj 0 0 0 rg (does not help, since they) Tj T* 0 Tw .894104 Tw (are intended to manage options and not positional arguments; the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (module helps a bit and it is) Tj T* 0 Tw (able to reduce the boilerplate from nine lines to six lines:) Tj T* ET Q Q q 1 0 0 1 62.69291 503.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 156 re B* Q q 0 0 0 rg BT 1 0 0 1 0 134 Tm /F3 10 Tf 12 TL (# example2.py) Tj T* (def main\(dsn\):) Tj T* ( "Do something on the database") Tj T* ( print\(dsn\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import argparse) Tj T* ( p = argparse.ArgumentParser\(\)) Tj T* ( p.add_argument\('dsn'\)) Tj T* ( arg = p.parse_args\(\)) Tj T* ( main\(arg.dsn\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 471.8236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .817488 Tw (However, it just feels too complex to instantiate a class and to define a parser by hand for such a trivial) Tj T* 0 Tw (task.) Tj T* ET Q Q q 1 0 0 1 62.69291 441.8236 cm q BT 1 0 0 1 0 14 Tm 1.123145 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (module is designed to manage well such use cases, and it is able to reduce the original nine) Tj T* 0 Tw (lines of boiler plate to two lines. With the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (module all you need to write is) Tj T* ET Q Q q 1 0 0 1 62.69291 324.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (# example3.py) Tj T* (def main\(dsn\):) Tj T* ( "Do something with the database") Tj T* ( print\(dsn\)) Tj T* ( # ...) Tj T* ( ) Tj T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 292.6236 cm q BT 1 0 0 1 0 14 Tm .929986 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (module provides for free \(actually the work is done by the underlying ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (module\) a nice) Tj T* 0 Tw (usage message:) Tj T* ET Q Q q 1 0 0 1 62.69291 259.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 24 re B* Q q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL ($ python example3.py -h) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 130.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (usage: example3.py [-h] dsn) Tj T* T* (Do something with the database) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 98.22362 cm q BT 1 0 0 1 0 14 Tm .167765 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (manages the case of missing arguments and of too many arguments. This is only the tip of) Tj T* 0 Tw (the iceberg: ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to do much more than that.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (4) Tj T* -238.1649 0 Td ET Q Q endstream endobj 366 0 obj << /Length 4301 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 747.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with default arguments) Tj T* ET Q Q q 1 0 0 1 62.69291 717.0236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 2.609984 Tw (The need to have suitable defaults for command-line scripts is quite common. For instance I have) Tj T* 0 Tw (encountered this use case at work hundreds of times:) Tj T* ET Q Q q 1 0 0 1 62.69291 515.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 192 re B* Q q BT 1 0 0 1 0 170 Tm 12 TL /F3 10 Tf 0 0 0 rg (# example4.py) Tj T* (from datetime import datetime) Tj T* T* (def main\(dsn, table='product', today=datetime.today\(\)\):) Tj T* ( "Do something on the database") Tj T* ( print\(dsn, table, today\)) Tj T* T* (if __name__ == '__main__': # manual management before argparse) Tj T* ( import sys) Tj T* ( args = sys.argv[1:]) Tj T* ( if not args:) Tj T* ( sys.exit\('usage: python %s dsn' % sys.argv[0]\)) Tj T* ( elif len\(args\) ) Tj (>) Tj ( 2:) Tj T* ( sys.exit\('Unrecognized arguments: %s' % ' '.join\(argv[2:]\)\)) Tj T* ( main\(*args\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 447.8236 cm q BT 1 0 0 1 0 50 Tm .038488 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here I want to perform a query on a database table, by extracting the most recent data: it makes sense for) Tj T* 0 Tw .299988 Tw /F3 10 Tf 0 0 0 rg (today ) Tj /F1 10 Tf 0 0 0 rg (to be a default argument. If there is a most used table \(in this example a table called ) Tj /F3 10 Tf 0 0 0 rg ('product') Tj /F1 10 Tf 0 0 0 rg (\)) Tj T* 0 Tw 3.313984 Tw (it also makes sense to make it a default argument. Performing the parsing of the command-line) Tj T* 0 Tw .083735 Tw (arguments by hand takes 8 ugly lines of boilerplate \(using ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (would require about the same number) Tj T* 0 Tw (of lines\). With ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (the entire ) Tj /F3 10 Tf 0 0 0 rg (__main__ ) Tj /F1 10 Tf 0 0 0 rg (block reduces to the usual two lines:) Tj T* ET Q Q q 1 0 0 1 62.69291 402.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 382.6236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (In other words, six lines of boilerplate have been removed, and we get the usage message for free:) Tj T* ET Q Q q 1 0 0 1 62.69291 229.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 144 re B* Q q 0 0 0 rg BT 1 0 0 1 0 122 Tm /F3 10 Tf 12 TL (usage: example5.py [-h] dsn [table] [today]) Tj T* T* (Do something on the database) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* ( table [product]) Tj T* ( today [YYYY-MM-DD]) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 185.4236 cm q BT 1 0 0 1 0 26 Tm .81311 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that by default ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (prints the string representation of the default values \(with square brackets\) in) Tj T* 0 Tw .117485 Tw (the usage message. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (manages transparently even the case when you want to pass a variable number) Tj T* 0 Tw (of arguments. Here is an example, a script running on a database a series of SQL scripts:) Tj T* ET Q Q q 1 0 0 1 62.69291 92.22362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL (# example7.py) Tj T* (from datetime import datetime) Tj T* T* (def main\(dsn, *scripts\):) Tj T* ( "Run the given scripts on the database") Tj T* ( for script in scripts:) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (5) Tj T* -238.1649 0 Td ET Q Q endstream endobj 367 0 obj << /Length 3899 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 691.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 72 re B* Q q 0 0 0 rg BT 1 0 0 1 0 50 Tm /F3 10 Tf 12 TL ( print\('executing %s' % script\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 671.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage message:) Tj T* ET Q Q q 1 0 0 1 62.69291 530.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 132 re B* Q q 0 0 0 rg BT 1 0 0 1 0 110 Tm /F3 10 Tf 12 TL (usage: example7.py [-h] dsn [scripts [scripts ...]]) Tj T* T* (Run the given scripts on the database) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* ( scripts) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 486.6236 cm q BT 1 0 0 1 0 26 Tm .952485 Tw 12 TL /F1 10 Tf 0 0 0 rg (The examples here should have made clear that ) Tj /F4 10 Tf (plac is able to figure out the command-line arguments) Tj T* 0 Tw .899988 Tw (parser to use from the signature of the main function) Tj /F1 10 Tf (. This is the whole idea behind ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (: if the intent is) Tj T* 0 Tw (clear, let's the machine take care of the details.) Tj T* ET Q Q q 1 0 0 1 62.69291 444.6236 cm q BT 1 0 0 1 0 26 Tm .722765 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is inspired to an old Python Cookbook recipe of mine \() Tj 0 0 .501961 rg (optionparse) Tj 0 0 0 rg (\), in the sense that it delivers the) Tj T* 0 Tw .847209 Tw (programmer from the burden of writing the parser, but is less of a hack: instead of extracting the parser) Tj T* 0 Tw (from the docstring of the module, it extracts it from the signature of the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (function.) Tj T* ET Q Q q 1 0 0 1 62.69291 414.6236 cm q BT 1 0 0 1 0 14 Tm .319987 Tw 12 TL /F1 10 Tf 0 0 0 rg (The idea comes from the ) Tj /F4 10 Tf 0 0 0 rg (function annotations ) Tj /F1 10 Tf 0 0 0 rg (concept, a new feature of Python 3. An example is worth a) Tj T* 0 Tw (thousand words, so here it is:) Tj T* ET Q Q q 1 0 0 1 62.69291 261.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 144 re B* Q q 0 0 0 rg BT 1 0 0 1 0 122 Tm /F3 10 Tf 12 TL (# example7_.py) Tj T* (from datetime import datetime) Tj T* T* (def main\(dsn: "Database dsn", *scripts: "SQL scripts"\):) Tj T* ( "Run the given scripts on the database") Tj T* ( for script in scripts:) Tj T* ( print\('executing %s' % script\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 229.4236 cm q BT 1 0 0 1 0 14 Tm .17528 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here the arguments of the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (function have been annotated with strings which are intented to be used) Tj T* 0 Tw (in the help message:) Tj T* ET Q Q q 1 0 0 1 62.69291 112.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (usage: example7_.py [-h] dsn [scripts [scripts ...]]) Tj T* T* (Run the given scripts on the database) Tj T* T* (positional arguments:) Tj T* ( dsn Database dsn) Tj T* ( scripts SQL scripts) Tj T* T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (6) Tj T* -238.1649 0 Td ET Q Q endstream endobj 368 0 obj << /Length 5028 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 727.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 707.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to recognize much more complex annotations, as I will show in the next paragraphs.) Tj T* ET Q Q q 1 0 0 1 62.69291 677.8236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with options \(and smart options\)) Tj T* ET Q Q q 1 0 0 1 62.69291 587.8236 cm q BT 1 0 0 1 0 74 Tm .016457 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is surprising how few command-line scripts with options I have written over the years \(probably less than) Tj T* 0 Tw 1.02311 Tw (a hundred\), compared to the number of scripts with positional arguments I wrote \(certainly more than a) Tj T* 0 Tw .177045 Tw (thousand of them\). Still, this use case cannot be neglected. The standard library modules \(all of them\) are) Tj T* 0 Tw 2.30686 Tw (quite verbose when it comes to specifying the options and frankly I have never used them directly.) Tj T* 0 Tw 2.557126 Tw (Instead, I have always relied on the ) Tj 0 0 .501961 rg (optionparse ) Tj 0 0 0 rg (recipe, which provides a convenient wrapper over) Tj T* 0 Tw 1.889985 Tw 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. Alternatively, in the simplest cases, I have just performed the parsing by hand. In ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (the) Tj T* 0 Tw (parser is inferred by the function annotations. Here is an example:) Tj T* ET Q Q q 1 0 0 1 62.69291 470.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (# example8.py) Tj T* (def main\(command: \("SQL query", 'option', 'c'\), dsn\):) Tj T* ( if command:) Tj T* ( print\('executing %s on %s' % \(command, dsn\)\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 414.6236 cm q BT 1 0 0 1 0 38 Tm .929213 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here the argument ) Tj /F3 10 Tf 0 0 0 rg (command ) Tj /F1 10 Tf 0 0 0 rg (has been annotated with the tuple ) Tj /F3 10 Tf 0 0 0 rg (\("SQL) Tj ( ) Tj (query",) Tj ( ) Tj ('option',) Tj ( ) Tj ('c'\)) Tj /F1 10 Tf 0 0 0 rg (:) Tj T* 0 Tw .62683 Tw (the first string is the help string which will appear in the usage message, the second string tells ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (that) Tj T* 0 Tw .931894 Tw /F3 10 Tf 0 0 0 rg (command ) Tj /F1 10 Tf 0 0 0 rg (is an option and the third string that there is also a short form of the option ) Tj /F3 10 Tf 0 0 0 rg (-c) Tj /F1 10 Tf 0 0 0 rg (, the long form) Tj T* 0 Tw (being ) Tj /F3 10 Tf 0 0 0 rg (--command) Tj /F1 10 Tf 0 0 0 rg (. The usage message is the following:) Tj T* ET Q Q q 1 0 0 1 62.69291 285.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (usage: example8.py [-h] [-c COMMAND] dsn) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -c COMMAND, --command COMMAND) Tj T* ( SQL query) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 265.4236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here are two examples of usage:) Tj T* ET Q Q q 1 0 0 1 62.69291 184.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 72 re B* Q q 0 0 0 rg BT 1 0 0 1 0 50 Tm /F3 10 Tf 12 TL ($ python3 example8.py -c "select * from table" dsn) Tj T* (executing select * from table on dsn) Tj T* T* ($ python3 example8.py --command="select * from table" dsn) Tj T* (executing select * from table on dsn) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 128.2236 cm q BT 1 0 0 1 0 38 Tm .268935 Tw 12 TL /F1 10 Tf 0 0 0 rg (The third argument in the function annotation can be omitted: in such case it will be assumed to be ) Tj /F3 10 Tf 0 0 0 rg (None) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* 0 Tw 2.839213 Tw (The consequence is that the usual dichotomy between long and short options \(GNU-style options\)) Tj T* 0 Tw .396235 Tw (disappears: we get ) Tj /F4 10 Tf (smart options) Tj /F1 10 Tf (, which have the single character prefix of short options and behave like) Tj T* 0 Tw (both long and short options, since they can be abbreviated. Here is an example featuring smart options:) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (7) Tj T* -238.1649 0 Td ET Q Q endstream endobj 369 0 obj << /Length 3963 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 679.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL (# example6.py) Tj T* (def main\(dsn, command: \("SQL query", 'option'\)='select * from table'\):) Tj T* ( print\('executing %r on %s' % \(command, dsn\)\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 550.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (usage: example6.py [-h] [-command select * from table] dsn) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -command select * from table) Tj T* ( SQL query) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 530.6236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (The following are all valid invocations ot the script:) Tj T* ET Q Q q 1 0 0 1 62.69291 437.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL ($ python3 example6.py -c "select" dsn) Tj T* (executing 'select' on dsn) Tj T* ($ python3 example6.py -com "select" dsn) Tj T* (executing 'select' on dsn) Tj T* ($ python3 example6.py -command="select" dsn) Tj T* (executing 'select' on dsn) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 417.4236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Notice that the form ) Tj /F3 10 Tf 0 0 0 rg (-command=SQL ) Tj /F1 10 Tf 0 0 0 rg (is recognized only for the full option, not for its abbreviations:) Tj T* ET Q Q q 1 0 0 1 62.69291 360.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q 0 0 0 rg BT 1 0 0 1 0 26 Tm /F3 10 Tf 12 TL ($ python3 example6.py -com="select" dsn) Tj T* (usage: example6.py [-h] [-command COMMAND] dsn) Tj T* (example6.py: error: unrecognized arguments: -com=select) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 328.2236 cm q BT 1 0 0 1 0 14 Tm 1.724987 Tw 12 TL /F1 10 Tf 0 0 0 rg (If the option is not passed, the variable ) Tj /F3 10 Tf 0 0 0 rg (command ) Tj /F1 10 Tf 0 0 0 rg (will get the value ) Tj /F3 10 Tf 0 0 0 rg (None) Tj /F1 10 Tf 0 0 0 rg (. However, it is possible to) Tj T* 0 Tw (specify a non-trivial default. Here is an example:) Tj T* ET Q Q q 1 0 0 1 62.69291 235.0236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL (# example8_.py) Tj T* (def main\(dsn, command: \("SQL query", 'option', 'c'\)='select * from table'\):) Tj T* ( print\('executing %r on %s' % \(command, dsn\)\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 215.0236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Notice that the default value appears in the help message:) Tj T* ET Q Q q 1 0 0 1 62.69291 109.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 96 re B* Q q 0 0 0 rg BT 1 0 0 1 0 74 Tm /F3 10 Tf 12 TL (usage: example8_.py [-h] [-c select * from table] dsn) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (8) Tj T* -238.1649 0 Td ET Q Q endstream endobj 370 0 obj << /Length 5230 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 727.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ( -c select * from table, --command select * from table) Tj T* ( SQL query) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 707.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (When you run the script and you do not pass the ) Tj /F3 10 Tf 0 0 0 rg (-command ) Tj /F1 10 Tf 0 0 0 rg (option, the default query will be executed:) Tj T* ET Q Q q 1 0 0 1 62.69291 662.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python3 example8_.py dsn) Tj T* (executing 'select * from table' on dsn) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 632.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with flags) Tj T* ET Q Q q 1 0 0 1 62.69291 602.6236 cm q BT 1 0 0 1 0 14 Tm .815542 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to recognize flags, i.e. boolean options which are ) Tj /F3 10 Tf 0 0 0 rg (True ) Tj /F1 10 Tf 0 0 0 rg (if they are passed to the command) Tj T* 0 Tw (line and ) Tj /F3 10 Tf 0 0 0 rg (False ) Tj /F1 10 Tf 0 0 0 rg (if they are absent. Here is an example:) Tj T* ET Q Q q 1 0 0 1 62.69291 473.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 486 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (# example9.py) Tj T* T* (def main\(verbose: \('prints more info', 'flag', 'v'\), dsn: 'connection string'\):) Tj T* ( if verbose:) Tj T* ( print\('connecting to %s' % dsn\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 356.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (usage: example9.py [-h] [-v] dsn) Tj T* T* (positional arguments:) Tj T* ( dsn connection string) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -v, --verbose prints more info) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 311.0236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python3 example9.py -v dsn) Tj T* (connecting to dsn) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 267.0236 cm q BT 1 0 0 1 0 26 Tm .31408 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that it is an error trying to specify a default for flags: the default value for a flag is always ) Tj /F3 10 Tf 0 0 0 rg (False) Tj /F1 10 Tf 0 0 0 rg (. If) Tj T* 0 Tw 2.652485 Tw (you feel the need to implement non-boolean flags, you should use an option with two choices, as) Tj T* 0 Tw (explained in the "more features" section.) Tj T* ET Q Q q 1 0 0 1 62.69291 201.0236 cm q BT 1 0 0 1 0 50 Tm 5.832651 Tw 12 TL /F1 10 Tf 0 0 0 rg (For consistency with the way the usage message is printed, I suggest you to follow the) Tj T* 0 Tw 1.895433 Tw (Flag-Option-Required-Default \(FORD\) convention: in the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (function write first the flag arguments,) Tj T* 0 Tw .881235 Tw (then the option arguments, then the required arguments and finally the default arguments. This is just a) Tj T* 0 Tw .110574 Tw (convention and you are not forced to use it, except for the default arguments \(including the varargs\) which) Tj T* 0 Tw (must stay at the end as it is required by the Python syntax.) Tj T* ET Q Q q 1 0 0 1 62.69291 159.0236 cm q BT 1 0 0 1 0 26 Tm .897045 Tw 12 TL /F1 10 Tf 0 0 0 rg (I also suggests to specify a one-character abbreviation for flags: in this way you can use the GNU-style) Tj T* 0 Tw 2.034431 Tw (composition of flags \(i.e. ) Tj /F3 10 Tf 0 0 0 rg (-zxvf ) Tj /F1 10 Tf 0 0 0 rg (is an abbreviation of ) Tj /F3 10 Tf 0 0 0 rg (-z) Tj ( ) Tj (-x) Tj ( ) Tj (-v) Tj ( ) Tj (-f) Tj /F1 10 Tf 0 0 0 rg (\). I usually do not provide the) Tj T* 0 Tw (one-character abbreviation for options, since it does not make sense to compose them.) Tj T* ET Q Q q 1 0 0 1 62.69291 117.0236 cm q BT 1 0 0 1 0 26 Tm 2.279269 Tw 12 TL /F1 10 Tf 0 0 0 rg (Starting from ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (0.9.1 underscores in options and flags are automatically turned into dashes. This) Tj T* 0 Tw 2.13436 Tw (feature was implemented at user request, to make it possible to use a more traditional naming. For) Tj T* 0 Tw (instance now you can have a ) Tj /F3 10 Tf 0 0 0 rg (--dry-run ) Tj /F1 10 Tf 0 0 0 rg (flag, whereas before you had to use ) Tj /F3 10 Tf 0 0 0 rg (--dry_run) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (9) Tj T* -238.1649 0 Td ET Q Q endstream endobj 371 0 obj << /Length 4173 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 655.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (def main\(dry_run: \('Dry run', 'flag', 'd'\)\):) Tj T* ( if dry_run:) Tj T* ( print\('Doing nothing'\)) Tj T* ( else:) Tj T* ( print\('Doing something'\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 635.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an example of usage:) Tj T* ET Q Q q 1 0 0 1 62.69291 542.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL ($ python3.2 dry_run.py -h) Tj T* (usage: dry_run.py [-h] [-d]) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -d, --dry-run Dry run) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 512.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac for Python 2.X users) Tj T* ET Q Q q 1 0 0 1 62.69291 446.6236 cm q BT 1 0 0 1 0 50 Tm .059989 Tw 12 TL /F1 10 Tf 0 0 0 rg (While plac runs great on Python 3, I do not personally use it. At work we migrated to Python 2.7 in 2011. It) Tj T* 0 Tw 2.268876 Tw (will take a few more years before we consider migrating to Python 3. I am pretty much sure many) Tj T* 0 Tw 1.275318 Tw (Pythonistas are in the same situation. Therefore ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides a way to work with function annotations) Tj T* 0 Tw 2.921751 Tw (even in Python 2.X \(including Python 2.3\). There is no magic involved; you just need to add the) Tj T* 0 Tw (annotations by hand. For instance the annotated function declaration) Tj T* ET Q Q q 1 0 0 1 62.69291 401.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (def main\(dsn: "Database dsn", *scripts: "SQL scripts"\):) Tj T* ( ...) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 381.4236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (is equivalent to the following code:) Tj T* ET Q Q q 1 0 0 1 62.69291 300.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 72 re B* Q q 0 0 0 rg BT 1 0 0 1 0 50 Tm /F3 10 Tf 12 TL (def main\(dsn, *scripts\):) Tj T* ( ...) Tj T* (main.__annotations__ = dict\() Tj T* ( dsn="Database dsn",) Tj T* ( scripts="SQL scripts"\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 256.2236 cm q BT 1 0 0 1 0 26 Tm .536098 Tw 12 TL /F1 10 Tf 0 0 0 rg (One should be careful to match the keys of the annotation dictionary with the names of the arguments in) Tj T* 0 Tw 3.347485 Tw (the annotated function; for lazy people with Python 2.4 available the simplest way is to use the) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (plac.annotations ) Tj /F1 10 Tf 0 0 0 rg (decorator that performs the check for you:) Tj T* ET Q Q q 1 0 0 1 62.69291 175.0236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 72 re B* Q q 0 0 0 rg BT 1 0 0 1 0 50 Tm /F3 10 Tf 12 TL (@plac.annotations\() Tj T* ( dsn="Database dsn",) Tj T* ( scripts="SQL scripts"\)) Tj T* (def main\(dsn, *scripts\):) Tj T* ( ...) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 143.0236 cm q BT 1 0 0 1 0 14 Tm 1.846077 Tw 12 TL /F1 10 Tf 0 0 0 rg (In the rest of this article I will assume that you are using Python 2.X with X >) Tj (= 4 and I will use the) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (plac.annotations ) Tj /F1 10 Tf 0 0 0 rg (decorator. Notice however that the core features of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (run even on Python 2.3.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (10) Tj T* -235.3849 0 Td ET Q Q endstream endobj 372 0 obj << /Length 5791 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 747.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (More features) Tj T* ET Q Q q 1 0 0 1 62.69291 693.0236 cm q BT 1 0 0 1 0 38 Tm 1.483488 Tw 12 TL /F1 10 Tf 0 0 0 rg (One of the goals of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is to have a learning curve of ) Tj /F4 10 Tf (minutes ) Tj /F1 10 Tf (for its core features, compared to the) Tj T* 0 Tw 1.152093 Tw (learning curve of ) Tj /F4 10 Tf (hours ) Tj /F1 10 Tf (of ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. In order to reach this goal, I have ) Tj /F4 10 Tf (not ) Tj /F1 10 Tf (sacrificed all the features of) Tj T* 0 Tw 1.747633 Tw 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. Actually a lot of the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (power persists in ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. Until now, I have only showed simple) Tj T* 0 Tw (annotations, but in general an annotation is a 6-tuple of the form) Tj T* ET Q Q q 1 0 0 1 62.69291 687.0236 cm Q q 1 0 0 1 62.69291 675.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (\(help,) Tj ( ) Tj (kind,) Tj ( ) Tj (abbrev,) Tj ( ) Tj (type,) Tj ( ) Tj (choices,) Tj ( ) Tj (metavar\)) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 675.0236 cm Q q 1 0 0 1 62.69291 633.0236 cm q BT 1 0 0 1 0 26 Tm 1.068735 Tw 12 TL /F1 10 Tf 0 0 0 rg (where ) Tj /F3 10 Tf 0 0 0 rg (help ) Tj /F1 10 Tf 0 0 0 rg (is the help message, ) Tj /F3 10 Tf 0 0 0 rg (kind ) Tj /F1 10 Tf 0 0 0 rg (is a string in the set { ) Tj /F3 10 Tf 0 0 0 rg ("flag") Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg ("option") Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg ("positional") Tj /F1 10 Tf 0 0 0 rg (},) Tj T* 0 Tw 1.579431 Tw /F3 10 Tf 0 0 0 rg (abbrev ) Tj /F1 10 Tf 0 0 0 rg (is a one-character string or ) Tj /F3 10 Tf 0 0 0 rg (None) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (type ) Tj /F1 10 Tf 0 0 0 rg (is a callable taking a string in input, ) Tj /F3 10 Tf 0 0 0 rg (choices ) Tj /F1 10 Tf 0 0 0 rg (is a) Tj T* 0 Tw (discrete sequence of values and ) Tj /F3 10 Tf 0 0 0 rg (metavar ) Tj /F1 10 Tf 0 0 0 rg (is a string.) Tj T* ET Q Q q 1 0 0 1 62.69291 603.0236 cm q BT 1 0 0 1 0 14 Tm 1.05061 Tw 12 TL /F3 10 Tf 0 0 0 rg (type ) Tj /F1 10 Tf 0 0 0 rg (is used to automagically convert the command line arguments from the string type to any Python) Tj T* 0 Tw (type; by default there is no conversion and ) Tj /F3 10 Tf 0 0 0 rg (type=None) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 573.0236 cm q BT 1 0 0 1 0 14 Tm 2.904692 Tw 12 TL /F3 10 Tf 0 0 0 rg (choices ) Tj /F1 10 Tf 0 0 0 rg (is used to restrict the number of the valid options; by default there is no restriction i.e.) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (choices=None) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 495.0236 cm q BT 1 0 0 1 0 62 Tm 1.171163 Tw 12 TL /F3 10 Tf 0 0 0 rg (metavar ) Tj /F1 10 Tf 0 0 0 rg (has two meanings. For a positional argument it is used to change the argument name in the) Tj T* 0 Tw .352209 Tw (usage message \(and only there\). By default the metavar is ) Tj /F3 10 Tf 0 0 0 rg (None ) Tj /F1 10 Tf 0 0 0 rg (and the name in the usage message is) Tj T* 0 Tw .752339 Tw (the same as the argument name. For an option the ) Tj /F3 10 Tf 0 0 0 rg (metavar ) Tj /F1 10 Tf 0 0 0 rg (is used differently in the usage message,) Tj T* 0 Tw .802927 Tw (which has now the form ) Tj /F3 10 Tf 0 0 0 rg ([--option-name) Tj ( ) Tj (METAVAR]) Tj /F1 10 Tf 0 0 0 rg (. If the ) Tj /F3 10 Tf 0 0 0 rg (metavar ) Tj /F1 10 Tf 0 0 0 rg (is ) Tj /F3 10 Tf 0 0 0 rg (None) Tj /F1 10 Tf 0 0 0 rg (, then it is equal to the) Tj T* 0 Tw 1.024692 Tw (uppercased name of the argument, unless the argument has a default: then it is equal to the stringified) Tj T* 0 Tw (form of the default.) Tj T* ET Q Q q 1 0 0 1 62.69291 477.0236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Here is an example showing many of the features \(copied from the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (documentation\):) Tj T* ET Q Q q 1 0 0 1 62.69291 215.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 504 252 re B* Q q 0 0 0 rg BT 1 0 0 1 0 230 Tm /F3 10 Tf 12 TL (# example10.py) Tj T* (import plac) Tj T* T* (@plac.annotations\() Tj T* ( operator=\("The name of an operator", 'positional', None, str, ['add', 'mul']\),) Tj T* ( numbers=\("A number", 'positional', None, float, None, "n"\)\)) Tj T* (def main\(operator, *numbers\):) Tj T* ( "A script to add and multiply numbers") Tj T* ( if operator == 'mul':) Tj T* ( op = float.__mul__) Tj T* ( result = 1.0) Tj T* ( else: # operator == 'add') Tj T* ( op = float.__add__) Tj T* ( result = 0.0) Tj T* ( for n in numbers:) Tj T* ( result = op\(result, n\)) Tj T* ( return result) Tj T* T* (if __name__ == '__main__':) Tj T* ( print\(plac.call\(main\)\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 195.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage:) Tj T* ET Q Q q 1 0 0 1 62.69291 90.62362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 96 re B* Q q 0 0 0 rg BT 1 0 0 1 0 74 Tm /F3 10 Tf 12 TL (usage: example10.py [-h] {add,mul} [n [n ...]]) Tj T* T* (A script to add and multiply numbers) Tj T* T* (positional arguments:) Tj T* ( {add,mul} The name of an operator) Tj T* ( n A number) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (11) Tj T* -235.3849 0 Td ET Q Q endstream endobj 373 0 obj << /Length 4948 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 715.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q 0 0 0 rg BT 1 0 0 1 0 26 Tm /F3 10 Tf 12 TL T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 683.8236 cm q BT 1 0 0 1 0 14 Tm .15186 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that the docstring of the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (function has been automatically added to the usage message. Here) Tj T* 0 Tw (are a couple of examples of usage:) Tj T* ET Q Q q 1 0 0 1 62.69291 578.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 534 96 re B* Q q 0 0 0 rg BT 1 0 0 1 0 74 Tm /F3 10 Tf 12 TL ($ python example10.py add 1 2 3 4) Tj T* (10.0) Tj T* ($ python example10.py mul 1 2 3 4) Tj T* (24.0) Tj T* ($ python example10.py ad 1 2 3 4 # a mispelling error) Tj T* (usage: example10.py [-h] {add,mul} [n [n ...]]) Tj T* (example10.py: error: argument operator: invalid choice: 'ad' \(choose from 'add', 'mul'\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 558.6236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (can also be used in doctests like this:) Tj T* ET Q Q q 1 0 0 1 62.69291 501.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( import plac, example10) Tj T* (>) Tj (>) Tj (>) Tj ( plac.call\(example10.main, ['add', '1', '2']\)) Tj T* (3.0) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 481.4236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (works for generators too:) Tj T* ET Q Q q 1 0 0 1 62.69291 400.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 72 re B* Q q BT 1 0 0 1 0 50 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( def main\(n\):) Tj T* (... for i in range\(int\(n\)\):) Tj T* (... yield i) Tj T* (>) Tj (>) Tj (>) Tj ( plac.call\(main, ['3']\)) Tj T* ([0, 1, 2]) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 356.2236 cm q BT 1 0 0 1 0 26 Tm .158409 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (tries to convert the output of the main function into a list, if possible. If the output is) Tj T* 0 Tw .725703 Tw (not iterable or it is a string, it is left unchanged, but if it is iterable it is converted. In particular, generator) Tj T* 0 Tw (objects are exhausted by ) Tj /F3 10 Tf 0 0 0 rg (plac.call) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 326.2236 cm q BT 1 0 0 1 0 14 Tm 1.450751 Tw 12 TL /F1 10 Tf 0 0 0 rg (This behavior avoids mistakes like forgetting of applying ) Tj /F3 10 Tf 0 0 0 rg (list\(result\) ) Tj /F1 10 Tf 0 0 0 rg (to the result of ) Tj /F3 10 Tf 0 0 0 rg (plac.call) Tj /F1 10 Tf 0 0 0 rg (;) Tj T* 0 Tw (moreover it makes errors visible early, and avoids mistakes in code like the following:) Tj T* ET Q Q q 1 0 0 1 62.69291 257.0236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q 0 0 0 rg BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL (try:) Tj T* ( result = plac.call\(main, args\)) Tj T* (except:) Tj T* ( # do something) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 213.0236 cm q BT 1 0 0 1 0 26 Tm 1.014651 Tw 12 TL /F1 10 Tf 0 0 0 rg (Without eagerness, a main function returning a generator object would not raise any exception until the) Tj T* 0 Tw .730465 Tw (generator is iterated over. If you are a fan of lazyness, you can still have it by setting the ) Tj /F3 10 Tf 0 0 0 rg (eager ) Tj /F1 10 Tf 0 0 0 rg (flag to) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (False) Tj /F1 10 Tf 0 0 0 rg (, as in the following example:) Tj T* ET Q Q q 1 0 0 1 62.69291 167.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (for line in plac.call\(main, args, eager=False\):) Tj T* ( print\(line\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 135.8236 cm q BT 1 0 0 1 0 14 Tm 1.35528 Tw 12 TL /F1 10 Tf 0 0 0 rg (If ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (returns a generator object this example will print each line as soon as available, whereas the) Tj T* 0 Tw (default behaviour is to print all the lines together and the end of the computation.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (12) Tj T* -235.3849 0 Td ET Q Q endstream endobj 374 0 obj << /Length 3936 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 747.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (A realistic example) Tj T* ET Q Q q 1 0 0 1 62.69291 705.0236 cm q BT 1 0 0 1 0 26 Tm 1.234488 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here is a more realistic script using most of the features of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (to run SQL queries on a database by) Tj T* 0 Tw .930697 Tw (relying on ) Tj 0 0 .501961 rg (SQLAlchemy) Tj 0 0 0 rg (. Notice the usage of the ) Tj /F3 10 Tf 0 0 0 rg (type ) Tj /F1 10 Tf 0 0 0 rg (feature to automagically convert a SQLAlchemy) Tj T* 0 Tw (connection string into a ) Tj 0 0 .501961 rg (SqlSoup ) Tj 0 0 0 rg (object:) Tj T* ET Q Q q 1 0 0 1 62.69291 335.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 474 360 re B* Q q 0 0 0 rg BT 1 0 0 1 0 338 Tm /F3 10 Tf 12 TL (# dbcli.py) Tj T* (import plac) Tj T* (from sqlsoup import SQLSoup) Tj T* T* T* (@plac.annotations\() Tj T* ( db=plac.Annotation\("Connection string", type=SQLSoup\),) Tj T* ( header=plac.Annotation\("Header", 'flag', 'H'\),) Tj T* ( sqlcmd=plac.Annotation\("SQL command", 'option', 'c', str, metavar="SQL"\),) Tj T* ( delimiter=plac.Annotation\("Column separator", 'option', 'd'\),) Tj T* ( scripts=plac.Annotation\("SQL scripts"\)\)) Tj T* (def main\(db, header, sqlcmd, delimiter="|", *scripts\):) Tj T* ( "A script to run queries and SQL scripts on a database") Tj T* ( yield 'Working on %s' % db.bind.url) Tj T* T* ( if sqlcmd:) Tj T* ( result = db.bind.execute\(sqlcmd\)) Tj T* ( if header: # print the header) Tj T* ( yield delimiter.join\(result.keys\(\)\)) Tj T* ( for row in result: # print the rows) Tj T* ( yield delimiter.join\(map\(str, row\)\)) Tj T* T* ( for script in scripts:) Tj T* ( db.bind.execute\(open\(script\).read\(\)\)) Tj T* ( yield 'executed %s' % script) Tj T* T* (if __name__ == '__main__':) Tj T* ( for output in plac.call\(main\):) Tj T* ( print\(output\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 279.8236 cm q BT 1 0 0 1 0 38 Tm .049987 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can see the ) Tj /F4 10 Tf (yield-is-print ) Tj /F1 10 Tf (pattern here: instead of using ) Tj /F3 10 Tf 0 0 0 rg (print ) Tj /F1 10 Tf 0 0 0 rg (in the main function, I use ) Tj /F3 10 Tf 0 0 0 rg (yield) Tj /F1 10 Tf 0 0 0 rg (, and) Tj T* 0 Tw 3.55061 Tw (I perform the print in the ) Tj /F3 10 Tf 0 0 0 rg (__main__ ) Tj /F1 10 Tf 0 0 0 rg (block. The advantage of the pattern is that tests invoking) Tj T* 0 Tw .52936 Tw /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (and checking the result become trivial: had I performed the printing in the main function, the) Tj T* 0 Tw (test would have involved an ugly hack like redirecting ) Tj /F3 10 Tf 0 0 0 rg (sys.stdout ) Tj /F1 10 Tf 0 0 0 rg (to a ) Tj /F3 10 Tf 0 0 0 rg (StringIO ) Tj /F1 10 Tf 0 0 0 rg (object.) Tj T* ET Q Q q 1 0 0 1 62.69291 261.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage message:) Tj T* ET Q Q q 1 0 0 1 62.69291 108.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 144 re B* Q q 0 0 0 rg BT 1 0 0 1 0 122 Tm /F3 10 Tf 12 TL (usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]]) Tj T* T* (A script to run queries and SQL scripts on a database) Tj T* T* (positional arguments:) Tj T* ( db Connection string) Tj T* ( scripts SQL scripts) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -H, --header Header) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (13) Tj T* -235.3849 0 Td ET Q Q endstream endobj 375 0 obj << /Length 3524 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 727.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ( -c SQL, --sqlcmd SQL SQL command) Tj T* ( -d |, --delimiter | Column separator) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 707.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can check for yourself that the script works.) Tj T* ET Q Q q 1 0 0 1 62.69291 677.8236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Keyword arguments) Tj T* ET Q Q q 1 0 0 1 62.69291 635.8236 cm q BT 1 0 0 1 0 26 Tm 1.831984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Starting from release 0.4, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (supports keyword arguments. In practice that means that if your main) Tj T* 0 Tw 2.099213 Tw (function has keyword arguments, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (treats specially arguments of the form ) Tj /F3 10 Tf 0 0 0 rg ("name=value" ) Tj /F1 10 Tf 0 0 0 rg (in the) Tj T* 0 Tw (command line. Here is an example:) Tj T* ET Q Q q 1 0 0 1 62.69291 398.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 228 re B* Q q 0 0 0 rg BT 1 0 0 1 0 206 Tm /F3 10 Tf 12 TL (# example12.py) Tj T* (import plac) Tj T* T* (@plac.annotations\() Tj T* ( opt=\('some option', 'option'\),) Tj T* ( args='default arguments',) Tj T* ( kw='keyword arguments'\)) Tj T* (def main\(opt, *args, **kw\):) Tj T* ( if opt:) Tj T* ( yield 'opt=%s' % opt) Tj T* ( if args:) Tj T* ( yield 'args=%s' % str\(args\)) Tj T* ( if kw:) Tj T* ( yield 'kw=%s' % kw) Tj T* T* (if __name__ == '__main__':) Tj T* ( for output in plac.call\(main\):) Tj T* ( print\(output\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 378.6236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the generated usage message:) Tj T* ET Q Q q 1 0 0 1 62.69291 249.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (usage: example12.py [-h] [-opt OPT] [args [args ...]] [kw [kw ...]]) Tj T* T* (positional arguments:) Tj T* ( args default arguments) Tj T* ( kw keyword arguments) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -opt OPT some option) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 229.4236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is how you call the script:) Tj T* ET Q Q q 1 0 0 1 62.69291 160.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q 0 0 0 rg BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL ($ python example12.py -o X a1 a2 name=value) Tj T* (opt=X) Tj T* (args=\('a1', 'a2'\)) Tj T* (kw={'name': 'value'}) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 128.2236 cm q BT 1 0 0 1 0 14 Tm 2.133735 Tw 12 TL /F1 10 Tf 0 0 0 rg (When using keyword arguments, one must be careful to use names which are not alreay taken; for) Tj T* 0 Tw (instance in this examples the name ) Tj /F3 10 Tf 0 0 0 rg (opt ) Tj /F1 10 Tf 0 0 0 rg (is taken:) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (14) Tj T* -235.3849 0 Td ET Q Q endstream endobj 376 0 obj << /Length 9157 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 715.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q 0 0 0 rg BT 1 0 0 1 0 26 Tm /F3 10 Tf 12 TL ($ python example12.py 1 2 kw1=1 kw2=2 opt=0) Tj T* (usage: example12.py [-h] [-o OPT] [args [args ...]] [kw [kw ...]]) Tj T* (example12.py: error: colliding keyword arguments: opt) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 671.8236 cm q 0 0 0 rg BT 1 0 0 1 0 26 Tm /F1 10 Tf 12 TL 1.024104 Tw (The names taken are the names of the flags, of the options, and of the positional arguments, excepted) Tj T* 0 Tw .60561 Tw (varargs and keywords. This limitation is a consequence of the way the argument names are managed in) Tj T* 0 Tw (function calls by the Python language.) Tj T* ET Q Q q 1 0 0 1 62.69291 641.8236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac vs argparse) Tj T* ET Q Q q 1 0 0 1 62.69291 599.8236 cm q BT 1 0 0 1 0 26 Tm 1.065988 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is opinionated and by design it does not try to make available all of the features of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (in an) Tj T* 0 Tw .177126 Tw (easy way. In particular you should be aware of the following limitations/differences \(the following assumes) Tj T* 0 Tw (knowledge of ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (\):) Tj T* ET Q Q q 1 0 0 1 62.69291 593.8236 cm Q q 1 0 0 1 62.69291 593.8236 cm Q q 1 0 0 1 62.69291 521.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 57 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 62 Tm 2.69784 Tw 12 TL /F1 10 Tf 0 0 0 rg (plac does not support the destination concept: the destination coincides with the name of the) Tj T* 0 Tw .359983 Tw (argument, always. This restriction has some drawbacks. For instance, suppose you want to define a) Tj T* 0 Tw 2.758651 Tw (long option called ) Tj /F3 10 Tf 0 0 0 rg (--yield) Tj /F1 10 Tf 0 0 0 rg (. In this case the destination would be ) Tj /F3 10 Tf 0 0 0 rg (yield) Tj /F1 10 Tf 0 0 0 rg (, which is a Python) Tj T* 0 Tw 1.181235 Tw (keyword, and since you cannot introduce an argument with that name in a function definition, it is) Tj T* 0 Tw 2.12528 Tw (impossible to implement it. Your choices are to change the name of the long option, or to use) Tj T* 0 Tw 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (with a suitable destination.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 515.8236 cm Q q 1 0 0 1 62.69291 467.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 33 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 38 Tm 1.120751 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not support "required options". As the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (documentation puts it: ) Tj /F4 10 Tf (Required options) Tj T* 0 Tw 1.075318 Tw (are generally considered bad form - normal users expect options to be optional. You should avoid) Tj T* 0 Tw .874269 Tw (the use of required options whenever possible. ) Tj /F1 10 Tf (Notice that since ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (supports them, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can) Tj T* 0 Tw (manage them too, but not directly.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 461.8236 cm Q q 1 0 0 1 62.69291 401.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 45 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 50 Tm 1.539982 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (supports only regular boolean flags. ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (has the ability to define generalized two-value) Tj T* 0 Tw .361751 Tw (flags with values different from ) Tj /F3 10 Tf 0 0 0 rg (True ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (False) Tj /F1 10 Tf 0 0 0 rg (. An earlier version of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (had this feature too, but) Tj T* 0 Tw 3.669318 Tw (since you can use options with two choices instead, and in any case the conversion from) Tj T* 0 Tw 2.489983 Tw /F3 10 Tf 0 0 0 rg ({True,) Tj ( ) Tj (False} ) Tj /F1 10 Tf 0 0 0 rg (to any couple of values can be trivially implemented with a ternary operator) Tj T* 0 Tw (\() Tj /F3 10 Tf 0 0 0 rg (value1) Tj ( ) Tj (if) Tj ( ) Tj (flag) Tj ( ) Tj (else) Tj ( ) Tj (value2) Tj /F1 10 Tf 0 0 0 rg (\), I have removed it \(KISS rules!\).) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 395.8236 cm Q q 1 0 0 1 62.69291 359.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 21 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 26 Tm 1.797126 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not support ) Tj /F3 10 Tf 0 0 0 rg (nargs ) Tj /F1 10 Tf 0 0 0 rg (options directly \(it uses them internally, though, to implement flag) Tj T* 0 Tw .583488 Tw (recognition\). The reason it that all the use cases of interest to me are covered by ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (and I did not) Tj T* 0 Tw (feel the need to increase the learning curve by adding direct support for ) Tj /F3 10 Tf 0 0 0 rg (nargs) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 353.8236 cm Q q 1 0 0 1 62.69291 329.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm 2.111318 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does support subparsers, but you must read the ) Tj 0 0 .501961 rg (advanced usage document ) Tj 0 0 0 rg (to see how it) Tj T* 0 Tw (works.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 323.8236 cm Q q 1 0 0 1 62.69291 287.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 21 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 26 Tm 1.111751 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not support actions directly. This also looks like a feature too advanced for the goals of) Tj T* 0 Tw .406651 Tw 0 0 .501961 rg (plac) Tj 0 0 0 rg (. Notice however that the ability to define your own annotation objects \(again, see the ) Tj 0 0 .501961 rg (advanced) Tj T* 0 Tw (usage document) Tj 0 0 0 rg (\) may mitigate the need for custom actions.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 287.8236 cm Q q 1 0 0 1 62.69291 269.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (On the plus side, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can leverage directly on a number of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (features.) Tj T* ET Q Q q 1 0 0 1 62.69291 203.8236 cm q BT 1 0 0 1 0 50 Tm 2.128651 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, you can use ) Tj 0 0 .501961 rg (argparse.FileType ) Tj 0 0 0 rg (directly. Moreover, it is possible to pass options to the) Tj T* 0 Tw 7.66622 Tw (underlying ) Tj /F3 10 Tf 0 0 0 rg (argparse.ArgumentParser ) Tj /F1 10 Tf 0 0 0 rg (object \(currently it accepts the default arguments) Tj T* 0 Tw 2.027109 Tw /F3 10 Tf 0 0 0 rg (description) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (epilog) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (prog) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (usage) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (add_help) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (argument_default) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (parents) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (prefix_chars) Tj /F1 10 Tf 0 0 0 rg (,) Tj T* 0 Tw 4.37997 Tw /F3 10 Tf 0 0 0 rg (fromfile_prefix_chars) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (conflict_handler) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (formatter_class) Tj /F1 10 Tf 0 0 0 rg (\). It is enough to set such) Tj T* 0 Tw (attributes on the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (function. For instance writing) Tj T* ET Q Q q 1 0 0 1 62.69291 134.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q 0 0 0 rg BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL (def main\(...\):) Tj T* ( pass) Tj T* T* (main.add_help = False) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 90.62362 cm q BT 1 0 0 1 0 26 Tm .239318 Tw 12 TL /F1 10 Tf 0 0 0 rg (disables the recognition of the help flag ) Tj /F3 10 Tf 0 0 0 rg (-h,) Tj ( ) Tj (--help) Tj /F1 10 Tf 0 0 0 rg (. This mechanism does not look particularly elegant,) Tj T* 0 Tw .566988 Tw (but it works well enough. I assume that the typical user of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (will be happy with the defaults and would) Tj T* 0 Tw (not want to change them; still it is possible if she wants to.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (15) Tj T* -235.3849 0 Td ET Q Q endstream endobj 377 0 obj << /Length 5495 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm q BT 1 0 0 1 0 14 Tm 2.391235 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, by setting the ) Tj /F3 10 Tf 0 0 0 rg (description ) Tj /F1 10 Tf 0 0 0 rg (attribute, it is possible to add a comment to the usage) Tj T* 0 Tw (message \(by default the docstring of the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (function is used as description\).) Tj T* ET Q Q q 1 0 0 1 62.69291 711.0236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .392619 Tw (It is also possible to change the option prefix; for instance if your script must run under Windows and you) Tj T* 0 Tw (want to use "/" as option prefix you can add the line:) Tj T* ET Q Q q 1 0 0 1 62.69291 677.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 24 re B* Q q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL (main.prefix_chars='/-') Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 633.8236 cm q BT 1 0 0 1 0 26 Tm .924198 Tw 12 TL /F1 10 Tf 0 0 0 rg (The first prefix char \() Tj /F3 10 Tf 0 0 0 rg (/) Tj /F1 10 Tf 0 0 0 rg (\) is used as the default for the recognition of options and flags; the second prefix) Tj T* 0 Tw .26832 Tw (char \() Tj /F3 10 Tf 0 0 0 rg (-) Tj /F1 10 Tf 0 0 0 rg (\) is kept to keep the ) Tj /F3 10 Tf 0 0 0 rg (-h/--help ) Tj /F1 10 Tf 0 0 0 rg (option working: however you can disable it and reimplement it, if) Tj T* 0 Tw (you like.) Tj T* ET Q Q q 1 0 0 1 62.69291 603.8236 cm q BT 1 0 0 1 0 14 Tm 7.709147 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is possible to access directly the underlying ) Tj 0 0 .501961 rg (ArgumentParser ) Tj 0 0 0 rg (object, by invoking the) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (plac.parser_from ) Tj /F1 10 Tf 0 0 0 rg (utility function:) Tj T* ET Q Q q 1 0 0 1 62.69291 510.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( import plac) Tj T* (>) Tj (>) Tj (>) Tj ( def main\(arg\):) Tj T* (... pass) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( print\(plac.parser_from\(main\)\) #doctest: +ELLIPSIS) Tj T* (ArgumentParser\(prog=...\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 478.6236 cm q BT 1 0 0 1 0 14 Tm 2.595529 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (uses ) Tj /F3 10 Tf 0 0 0 rg (plac.parser_from) Tj /F1 10 Tf 0 0 0 rg (. Notice that when ) Tj /F3 10 Tf 0 0 0 rg (plac.call\(func\) ) Tj /F1 10 Tf 0 0 0 rg (is invoked) Tj T* 0 Tw (multiple time, the parser is re-used and not rebuilt from scratch again.) Tj T* ET Q Q q 1 0 0 1 62.69291 448.6236 cm q BT 1 0 0 1 0 14 Tm .982765 Tw 12 TL /F1 10 Tf 0 0 0 rg (I use ) Tj /F3 10 Tf 0 0 0 rg (plac.parser_from ) Tj /F1 10 Tf 0 0 0 rg (in the unit tests of the module, but regular users should not need to use it,) Tj T* 0 Tw (unless they want to access ) Tj /F4 10 Tf (all ) Tj /F1 10 Tf (of the features of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (directly without calling the main function.) Tj T* ET Q Q q 1 0 0 1 62.69291 406.6236 cm q BT 1 0 0 1 0 26 Tm 1.442126 Tw 12 TL /F1 10 Tf 0 0 0 rg (Interested readers should read the documentation of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (to understand the meaning of the other) Tj T* 0 Tw .771567 Tw (options. If there is a set of options that you use very often, you may consider writing a decorator adding) Tj T* 0 Tw (such options to the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (function for you. For simplicity, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not perform any magic.) Tj T* ET Q Q q 1 0 0 1 62.69291 376.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Final example: a shelve interface) Tj T* ET Q Q q 1 0 0 1 62.69291 322.6236 cm q BT 1 0 0 1 0 38 Tm .09784 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here is a nontrivial example showing off many ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (feature, including keyword arguments recognition. The) Tj T* 0 Tw .117485 Tw (use case is the following: suppose we have stored the configuration parameters of a given application into) Tj T* 0 Tw 1.159985 Tw (a Python shelve and we need a command-line tool to edit the shelve. A possible implementation using) Tj T* 0 Tw 0 0 .501961 rg (plac ) Tj 0 0 0 rg (could be the following:) Tj T* ET Q Q q 1 0 0 1 62.69291 97.42362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 216 re B* Q q BT 1 0 0 1 0 194 Tm 12 TL /F3 10 Tf 0 0 0 rg (# ishelve.py) Tj T* (import os) Tj T* (import shelve) Tj T* (import plac) Tj T* T* (DEFAULT_SHELVE = os.path.expanduser\('~/conf.shelve'\)) Tj T* T* T* (@plac.annotations\() Tj T* ( help=\('show help', 'flag'\),) Tj T* ( showall=\('show all parameters in the shelve', 'flag'\),) Tj T* ( clear=\('clear the shelve', 'flag'\),) Tj T* ( delete=\('delete an element', 'option'\),) Tj T* ( filename=\('filename of the shelve', 'option'\),) Tj T* ( params='names of the parameters in the shelve',) Tj T* ( setters='setters param=value'\)) Tj T* (def main\(help, showall, clear, delete, filename=DEFAULT_SHELVE,) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (16) Tj T* -235.3849 0 Td ET Q Q endstream endobj 378 0 obj << /Length 5172 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 259.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 480 504 re B* Q q BT 1 0 0 1 0 482 Tm 12 TL /F3 10 Tf 0 0 0 rg ( *params, **setters\):) Tj T* ( "A simple interface to a shelve. Use .help to see the available commands.") Tj T* ( sh = shelve.open\(filename\)) Tj T* ( try:) Tj T* ( if not any\([help, showall, clear, delete, params, setters]\):) Tj T* ( yield \('no arguments passed, use .help to see the ') Tj T* ( 'available commands'\)) Tj T* ( elif help: # custom help) Tj T* ( yield 'Commands: .help, .showall, .clear, .delete') Tj T* ( yield ') Tj (<) Tj (param) Tj (>) Tj ( ...') Tj T* ( yield ') Tj (<) Tj (param=value) Tj (>) Tj ( ...') Tj T* ( elif showall:) Tj T* ( for param, name in sh.items\(\):) Tj T* ( yield '%s=%s' % \(param, name\)) Tj T* ( elif clear:) Tj T* ( sh.clear\(\)) Tj T* ( yield 'cleared the shelve') Tj T* ( elif delete:) Tj T* ( try:) Tj T* ( del sh[delete]) Tj T* ( except KeyError:) Tj T* ( yield '%s: not found' % delete) Tj T* ( else:) Tj T* ( yield 'deleted %s' % delete) Tj T* ( for param in params:) Tj T* ( try:) Tj T* ( yield sh[param]) Tj T* ( except KeyError:) Tj T* ( yield '%s: not found' % param) Tj T* ( for param, value in setters.items\(\):) Tj T* ( sh[param] = value) Tj T* ( yield 'setting %s=%s' % \(param, value\)) Tj T* ( finally:) Tj T* ( sh.close\(\)) Tj T* T* (main.add_help = False # there is a custom help, remove the default one) Tj T* (main.prefix_chars = '.' # use dot-prefixed commands) Tj T* T* (if __name__ == '__main__':) Tj T* ( for output in plac.call\(main\):) Tj T* ( print\(output\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 239.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (A few notes are in order:) Tj T* ET Q Q q 1 0 0 1 62.69291 233.8236 cm Q q 1 0 0 1 62.69291 233.8236 cm Q q 1 0 0 1 62.69291 209.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (1.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm 2.075318 Tw 12 TL /F1 10 Tf 0 0 0 rg (I have disabled the ordinary help provided by ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (and I have implemented a custom help) Tj T* 0 Tw (command.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 203.8236 cm Q q 1 0 0 1 62.69291 191.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (2.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (I have changed the prefix character used to recognize the options to a dot.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 185.8236 cm Q q 1 0 0 1 62.69291 161.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (3.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm .864985 Tw 12 TL /F1 10 Tf 0 0 0 rg (Keyword arguments recognition \(in the ) Tj /F3 10 Tf 0 0 0 rg (**setters) Tj /F1 10 Tf 0 0 0 rg (\) is used to make it possible to store a value in) Tj T* 0 Tw (the shelve with the syntax ) Tj /F3 10 Tf 0 0 0 rg (param_name=param_value) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 155.8236 cm Q q 1 0 0 1 62.69291 131.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (4.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm .649318 Tw 12 TL /F3 10 Tf 0 0 0 rg (*params ) Tj /F1 10 Tf 0 0 0 rg (are used to retrieve parameters from the shelve and some error checking is performed in) Tj T* 0 Tw (the case of missing parameters) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 125.8236 cm Q q 1 0 0 1 62.69291 113.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (5.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (A command to clear the shelve is implemented as a flag \() Tj /F3 10 Tf 0 0 0 rg (.clear) Tj /F1 10 Tf 0 0 0 rg (\).) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 107.8236 cm Q q 1 0 0 1 62.69291 95.82362 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (6.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (A command to delete a given parameter is implemented as an option \() Tj /F3 10 Tf 0 0 0 rg (.delete) Tj /F1 10 Tf 0 0 0 rg (\).) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 89.82362 cm Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (17) Tj T* -235.3849 0 Td ET Q Q endstream endobj 379 0 obj << /Length 6256 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 753.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (7.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (There is an option with default \() Tj /F3 10 Tf 0 0 0 rg (.filename=conf.shelve) Tj /F1 10 Tf 0 0 0 rg (\) to set the filename of the shelve.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 747.0236 cm Q q 1 0 0 1 62.69291 711.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 21 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (8.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 26 Tm .541318 Tw 12 TL /F1 10 Tf 0 0 0 rg (All things considered, the code looks like a poor man's object oriented interface implemented with a) Tj T* 0 Tw 1.345251 Tw (chain of elifs instead of methods. Of course, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can do better than that, but let me start from a) Tj T* 0 Tw (low-level approach first.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 711.0236 cm Q q 1 0 0 1 62.69291 693.0236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (If you run ) Tj /F3 10 Tf 0 0 0 rg (ishelve.py ) Tj /F1 10 Tf 0 0 0 rg (without arguments you get the following message:) Tj T* ET Q Q q 1 0 0 1 62.69291 647.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python ishelve.py) Tj T* (no arguments passed, use .help to see the available commands) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 627.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (If you run ) Tj /F3 10 Tf 0 0 0 rg (ishelve.py ) Tj /F1 10 Tf 0 0 0 rg (with the option ) Tj /F3 10 Tf 0 0 0 rg (.h ) Tj /F1 10 Tf 0 0 0 rg (\(or any abbreviation of ) Tj /F3 10 Tf 0 0 0 rg (.help) Tj /F1 10 Tf 0 0 0 rg (\) you get:) Tj T* ET Q Q q 1 0 0 1 62.69291 558.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q BT 1 0 0 1 0 38 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python ishelve.py .h) Tj T* (Commands: .help, .showall, .clear, .delete) Tj T* (<) Tj (param) Tj (>) Tj ( ...) Tj T* (<) Tj (param=value) Tj (>) Tj ( ...) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 538.6236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can check by hand that the tool works:) Tj T* ET Q Q q 1 0 0 1 62.69291 289.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 240 re B* Q q 0 0 0 rg BT 1 0 0 1 0 218 Tm /F3 10 Tf 12 TL ($ python ishelve.py .clear # start from an empty shelve) Tj T* (cleared the shelve) Tj T* ($ python ishelve.py a=1 b=2) Tj T* (setting a=1) Tj T* (setting b=2) Tj T* ($ python ishelve.py .showall) Tj T* (b=2) Tj T* (a=1) Tj T* ($ python ishelve.py .del b # abbreviation for .delete) Tj T* (deleted b) Tj T* ($ python ishelve.py a) Tj T* (1) Tj T* ($ python ishelve.py b) Tj T* (b: not found) Tj T* ($ python ishelve.py .cler # mispelled command) Tj T* (usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]) Tj T* ( [.filename /home/micheles/conf.shelve]) Tj T* ( [params [params ...]] [setters [setters ...]]) Tj T* (ishelve.py: error: unrecognized arguments: .cler) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 259.4236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac vs the rest of the world) Tj T* ET Q Q q 1 0 0 1 62.69291 217.4236 cm q BT 1 0 0 1 0 26 Tm 1.866905 Tw 12 TL /F1 10 Tf 0 0 0 rg (Originally ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (boasted about being "the easiest command-line arguments parser in the world". Since) Tj T* 0 Tw .306457 Tw (then, people started pointing out to me various projects which are based on the same idea \(extracting the) Tj T* 0 Tw (parser from the main function signature\) and are arguably even easier than ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (:) Tj T* ET Q Q q 1 0 0 1 62.69291 211.4236 cm Q q 1 0 0 1 62.69291 211.4236 cm Q q 1 0 0 1 62.69291 199.4236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (opterator ) Tj 0 0 0 rg (by Dusty Phillips) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 193.4236 cm Q q 1 0 0 1 62.69291 181.4236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (CLIArgs ) Tj 0 0 0 rg (by Pavel Panchekha) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 175.4236 cm Q q 1 0 0 1 62.69291 163.4236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (commandline ) Tj 0 0 0 rg (by David Laban) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 163.4236 cm Q q 1 0 0 1 62.69291 133.4236 cm q BT 1 0 0 1 0 14 Tm 2.136457 Tw 12 TL /F1 10 Tf 0 0 0 rg (Luckily for me none of such projects had the idea of using function annotations and ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (; as a) Tj T* 0 Tw (consequence, they are no match for the capabilities of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 91.42362 cm q BT 1 0 0 1 0 26 Tm 1.551163 Tw 12 TL /F1 10 Tf 0 0 0 rg (Of course, there are tons of other libraries to parse the command line. For instance ) Tj 0 0 .501961 rg (Clap ) Tj 0 0 0 rg (by Matthew) Tj T* 0 Tw 1.211567 Tw (Frazier which appeared on PyPI just the day before ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (; ) Tj 0 0 .501961 rg (Clap ) Tj 0 0 0 rg (is fine but it is certainly not easier than) Tj T* 0 Tw 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (18) Tj T* -235.3849 0 Td ET Q Q endstream endobj 380 0 obj << /Length 7396 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 729.0236 cm q BT 1 0 0 1 0 26 Tm .622488 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can also be used as a replacement of the ) Tj 0 0 .501961 rg (cmd ) Tj 0 0 0 rg (module in the standard library and as such it shares) Tj T* 0 Tw .377126 Tw (many features with the module ) Tj 0 0 .501961 rg (cmd2 ) Tj 0 0 0 rg (by Catherine Devlin. However, this is completely coincidental, since) Tj T* 0 Tw (I became aware of the ) Tj 0 0 .501961 rg (cmd2 ) Tj 0 0 0 rg (module only after writing ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 675.0236 cm q BT 1 0 0 1 0 38 Tm .449982 Tw 12 TL /F1 10 Tf 0 0 0 rg (Command-line argument parsers keep coming out; between the newcomers I will notice ) Tj 0 0 .501961 rg (marrow.script ) Tj 0 0 0 rg (by) Tj T* 0 Tw .30683 Tw (Alice Bevan-McGregor, which is quite similar to ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (in spirit, but does not rely on ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (at all. ) Tj 0 0 .501961 rg (Argh ) Tj 0 0 0 rg (by) Tj T* 0 Tw .600542 Tw (Andrey Mikhaylenko is also worth mentioning: it is based on ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (, it came after ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (and I must give) Tj T* 0 Tw (credit to the author for the choice of the name, much funnier than plac!) Tj T* ET Q Q q 1 0 0 1 62.69291 645.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The future) Tj T* ET Q Q q 1 0 0 1 62.69291 579.0236 cm q BT 1 0 0 1 0 50 Tm .135542 Tw 12 TL /F1 10 Tf 0 0 0 rg (Currently the core of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is around 200 lines of code, not counting blanks, comments and docstrings. I do) Tj T* 0 Tw .968626 Tw (not plan to extend the core much in the future. The idea is to keep the module short: it is and it should) Tj T* 0 Tw .11811 Tw (remain a little wrapper over ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. Actually I have thought about contributing the core back to ) Tj 0 0 .501961 rg (argparse) Tj T* 0 Tw 2.446235 Tw 0 0 0 rg (if ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (becomes successful and gains a reasonable number of users. For the moment it should be) Tj T* 0 Tw (considered in a frozen status.) Tj T* ET Q Q q 1 0 0 1 62.69291 537.0236 cm q BT 1 0 0 1 0 26 Tm .927488 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that even if ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (has been designed to be simple to use for simple stuff, its power should not be) Tj T* 0 Tw 1.02186 Tw (underestimated; it is actually a quite advanced tool with a domain of applicability which far exceeds the) Tj T* 0 Tw (realm of command-line arguments parsers.) Tj T* ET Q Q q 1 0 0 1 62.69291 471.0236 cm q BT 1 0 0 1 0 50 Tm .285988 Tw 12 TL /F1 10 Tf 0 0 0 rg (Version 0.5 of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (doubled the code base and the documentation: it is based on the idea of using ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (to) Tj T* 0 Tw .408555 Tw (implement command-line interpreters, i.e. something akin to the ) Tj /F3 10 Tf 0 0 0 rg (cmd ) Tj /F1 10 Tf 0 0 0 rg (module in the standard library, only) Tj T* 0 Tw .49936 Tw (better. The new features of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (are described in the ) Tj 0 0 .501961 rg (advanced usage document ) Tj 0 0 0 rg (. They are implemented) Tj T* 0 Tw .313828 Tw (in a separated module \() Tj /F3 10 Tf 0 0 0 rg (plac_ext.py) Tj /F1 10 Tf 0 0 0 rg (\), since they require Python 2.5 to work, whereas ) Tj /F3 10 Tf 0 0 0 rg (plac_core.py) Tj T* 0 Tw /F1 10 Tf 0 0 0 rg (only requires Python 2.3.) Tj T* ET Q Q q 1 0 0 1 62.69291 441.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Trivia: the story behind the name) Tj T* ET Q Q q 1 0 0 1 62.69291 375.0236 cm q BT 1 0 0 1 0 50 Tm .979984 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (project started very humbly: I just wanted to make my old ) Tj 0 0 .501961 rg (optionparse ) Tj 0 0 0 rg (recipe easy_installable,) Tj T* 0 Tw .565988 Tw (and to publish it on PyPI. The original name of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (was optionparser and the idea behind it was to build) Tj T* 0 Tw .603735 Tw (an ) Tj 0 0 .501961 rg (OptionParser ) Tj 0 0 0 rg (object from the docstring of the module. However, before doing that, I decided to check) Tj T* 0 Tw .244198 Tw (out the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (module, since I knew it was going into Python 2.7 and Python 2.7 was coming out. Soon) Tj T* 0 Tw (enough I realized two things:) Tj T* ET Q Q q 1 0 0 1 62.69291 369.0236 cm Q q 1 0 0 1 62.69291 369.0236 cm Q q 1 0 0 1 62.69291 345.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (1.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm .103735 Tw 12 TL /F1 10 Tf 0 0 0 rg (the single greatest idea of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (was unifying the positional arguments and the options in a single) Tj T* 0 Tw (namespace object;) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 339.0236 cm Q q 1 0 0 1 62.69291 315.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (2.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 1.66748 Tw (parsing the docstring was so old-fashioned, considering the existence of functions annotations in) Tj T* 0 Tw (Python 3.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 315.0236 cm Q q 1 0 0 1 62.69291 261.0236 cm q BT 1 0 0 1 0 38 Tm .600574 Tw 12 TL /F1 10 Tf 0 0 0 rg (Putting together these two observations with the original idea of inferring the parser I decided to build an) Tj T* 0 Tw .516905 Tw 0 0 .501961 rg (ArgumentParser ) Tj 0 0 0 rg (object from function annotations. The ) Tj /F3 10 Tf 0 0 0 rg (optionparser ) Tj /F1 10 Tf 0 0 0 rg (name was ruled out, since I was) Tj T* 0 Tw 2.085984 Tw (now using ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (; a name like ) Tj /F3 10 Tf 0 0 0 rg (argparse_plus ) Tj /F1 10 Tf 0 0 0 rg (was also ruled out, since the typical usage was) Tj T* 0 Tw (completely different from the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (usage.) Tj T* ET Q Q q 1 0 0 1 62.69291 231.0236 cm q BT 1 0 0 1 0 14 Tm 1.093876 Tw 12 TL /F1 10 Tf 0 0 0 rg (I made a research on PyPI and the name ) Tj /F4 10 Tf (clap ) Tj /F1 10 Tf (\(Command Line Arguments Parser\) was not taken, so I) Tj T* 0 Tw (renamed everything to clap. After two days a ) Tj 0 0 .501961 rg (Clap ) Tj 0 0 0 rg (module appeared on PyPI <) Tj (expletives deleted) Tj (>) Tj (!) Tj T* ET Q Q q 1 0 0 1 62.69291 201.0236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .877209 Tw (Having little imagination, I decided to rename everything again to plac, an anagram of clap: since it is a) Tj T* 0 Tw (non-existing English name, I hope nobody will steal it from me!) Tj T* ET Q Q q 1 0 0 1 62.69291 171.0236 cm q BT 1 0 0 1 0 14 Tm .225542 Tw 12 TL /F1 10 Tf 0 0 0 rg (That concludes the section about the basic usage of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. You are now ready to read about the advanced) Tj T* 0 Tw (usage.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (19) Tj T* -235.3849 0 Td ET Q Q endstream endobj 381 0 obj << /Length 5201 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 744.0236 cm q BT 1 0 0 1 0 3.5 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Advanced usages of plac) Tj T* ET Q Q q 1 0 0 1 62.69291 714.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Introduction) Tj T* ET Q Q q 1 0 0 1 62.69291 672.0236 cm q BT 1 0 0 1 0 26 Tm .539036 Tw 12 TL /F1 10 Tf 0 0 0 rg (One of the design goals of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is to make it dead easy to write a scriptable and testable interface for an) Tj T* 0 Tw .813876 Tw (application. You can use ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (whenever you have an API with strings in input and strings in output, and) Tj T* 0 Tw (that includes a ) Tj /F4 10 Tf (huge ) Tj /F1 10 Tf (domain of applications.) Tj T* ET Q Q q 1 0 0 1 62.69291 630.0236 cm q BT 1 0 0 1 0 26 Tm 1.756651 Tw 12 TL /F1 10 Tf 0 0 0 rg (A string-oriented interface is a scriptable interface by construction. That means that you can define a) Tj T* 0 Tw .918735 Tw (command language for your application and that it is possible to write scripts which are interpretable by) Tj T* 0 Tw 0 0 .501961 rg (plac ) Tj 0 0 0 rg (and can be run as batch scripts.) Tj T* ET Q Q q 1 0 0 1 62.69291 588.0236 cm q BT 1 0 0 1 0 26 Tm .444987 Tw 12 TL /F1 10 Tf 0 0 0 rg (Actually, at the most general level, you can see ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (as a generic tool to write domain specific languages) Tj T* 0 Tw .107209 Tw (\(DSL\). With ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (you can test your application interactively as well as with batch scripts, and even with the) Tj T* 0 Tw (analogous of Python doctests for your defined language.) Tj T* ET Q Q q 1 0 0 1 62.69291 522.0236 cm q BT 1 0 0 1 0 50 Tm .694104 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can easily replace the ) Tj /F3 10 Tf 0 0 0 rg (cmd ) Tj /F1 10 Tf 0 0 0 rg (module of the standard library and you could easily write an application) Tj T* 0 Tw 2.271751 Tw (like ) Tj 0 0 .501961 rg (twill ) Tj 0 0 0 rg (with ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. Or you could use it to script your building procedure. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (also supports parallel) Tj T* 0 Tw .372488 Tw (execution of multiple commands and can be used as task manager. It is also quite easy to build a GUI or) Tj T* 0 Tw .713145 Tw (a Web application on top of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. When speaking of things you can do with ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (, your imagination is the) Tj T* 0 Tw (only limit!) Tj T* ET Q Q q 1 0 0 1 62.69291 492.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (From scripts to interactive applications) Tj T* ET Q Q q 1 0 0 1 62.69291 474.0236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Command-line scripts have many advantages, but they are no substitute for interactive applications.) Tj T* ET Q Q q 1 0 0 1 62.69291 432.0236 cm q BT 1 0 0 1 0 26 Tm .886179 Tw 12 TL /F1 10 Tf 0 0 0 rg (In particular, if you have a script with a large startup time which must be run multiple times, it is best to) Tj T* 0 Tw 2.272485 Tw (turn it into an interactive application, so that the startup is performed only once. ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (provides an) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (Interpreter ) Tj /F1 10 Tf 0 0 0 rg (class just for this purpose.) Tj T* ET Q Q q 1 0 0 1 62.69291 402.0236 cm q BT 1 0 0 1 0 14 Tm 1.293984 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf 0 0 0 rg (Interpreter ) Tj /F1 10 Tf 0 0 0 rg (class wraps the main function of a script and provides an ) Tj /F3 10 Tf 0 0 0 rg (.interact ) Tj /F1 10 Tf 0 0 0 rg (method to) Tj T* 0 Tw (start an interactive interpreter reading commands from the console.) Tj T* ET Q Q q 1 0 0 1 62.69291 372.0236 cm q BT 1 0 0 1 0 14 Tm 1.49436 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, you can define an interactive interpreter on top of the ) Tj /F3 10 Tf 0 0 0 rg (ishelve ) Tj /F1 10 Tf 0 0 0 rg (script introduced in the) Tj T* 0 Tw 0 0 .501961 rg (basic documentation ) Tj 0 0 0 rg (as follows:) Tj T* ET Q Q q 1 0 0 1 62.69291 122.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 240 re B* Q q 0 0 0 rg BT 1 0 0 1 0 218 Tm /F3 10 Tf 12 TL (# shelve_interpreter.py) Tj T* (import plac, ishelve) Tj T* T* (@plac.annotations\() Tj T* ( interactive=\('start interactive interface', 'flag'\),) Tj T* ( subcommands='the commands of the underlying ishelve interpreter'\)) Tj T* (def main\(interactive, *subcommands\):) Tj T* ( """) Tj T* ( This script works both interactively and non-interactively.) Tj T* ( Use .help to see the internal commands.) Tj T* ( """) Tj T* ( if interactive:) Tj T* ( plac.Interpreter\(ishelve.main\).interact\(\)) Tj T* ( else:) Tj T* ( for out in plac.call\(ishelve.main, subcommands\):) Tj T* ( print\(out\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (20) Tj T* -235.3849 0 Td ET Q Q endstream endobj 382 0 obj << /Length 3999 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 729.0236 cm q BT 1 0 0 1 0 26 Tm 2.200651 Tw 12 TL /F1 10 Tf 0 0 0 rg (A trick has been used here: the ishelve command-line interface has been hidden inside an external) Tj T* 0 Tw .917674 Tw (interface. They are distinct: for instance the external interface recognizes the ) Tj /F3 10 Tf 0 0 0 rg (-h/--help ) Tj /F1 10 Tf 0 0 0 rg (flag whereas) Tj T* 0 Tw (the internal interface only recognizes the ) Tj /F3 10 Tf 0 0 0 rg (.help ) Tj /F1 10 Tf 0 0 0 rg (command:) Tj T* ET Q Q q 1 0 0 1 62.69291 695.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 24 re B* Q q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL ($ python shelve_interpreter.py -h) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 518.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 168 re B* Q q 0 0 0 rg BT 1 0 0 1 0 146 Tm /F3 10 Tf 12 TL (usage: shelve_interpreter.py [-h] [-interactive]) Tj T* ( [subcommands [subcommands ...]]) Tj T* T* ( This script works both interactively and non-interactively.) Tj T* ( Use .help to see the internal commands.) Tj T* ( ) Tj T* T* (positional arguments:) Tj T* ( subcommands the commands of the underlying ishelve interpreter) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -interactive start interactive interface) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 498.6236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Thanks to this ingenuous trick, the script can be run both interactively and non-interactively:) Tj T* ET Q Q q 1 0 0 1 62.69291 453.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python shelve_interpreter.py .clear # non-interactive use) Tj T* (cleared the shelve) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 433.4236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an usage session:) Tj T* ET Q Q q 1 0 0 1 62.69291 148.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 276 re B* Q q BT 1 0 0 1 0 254 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python shelve_interpreter.py -i # interactive use) Tj T* (A simple interface to a shelve. Use .help to see the available commands.) Tj T* (i) Tj (>) Tj ( .help) Tj T* (Commands: .help, .showall, .clear, .delete) Tj T* (<) Tj (param) Tj (>) Tj ( ...) Tj T* (<) Tj (param=value) Tj (>) Tj ( ...) Tj T* (i) Tj (>) Tj ( a=1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( a) Tj T* (1) Tj T* (i) Tj (>) Tj ( b=2) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( a b) Tj T* (1) Tj T* (2) Tj T* (i) Tj (>) Tj ( .del a) Tj T* (deleted a) Tj T* (i) Tj (>) Tj ( a) Tj T* (a: not found) Tj T* (i) Tj (>) Tj ( .show) Tj T* (b=2) Tj T* (i) Tj (>) Tj ( [CTRL-D]) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 92.22362 cm q BT 1 0 0 1 0 38 Tm .256412 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf 0 0 0 rg (.interact ) Tj /F1 10 Tf 0 0 0 rg (method reads commands from the console and send them to the underlying interpreter, ) Tj T* 0 Tw 4.784983 Tw (until the user send a CTRL-D command \(CTRL-Z in Windows\). There is a default argument ) Tj T* 0 Tw 1.562339 Tw /F3 10 Tf 0 0 0 rg (prompt='i) Tj (>) Tj ( ) Tj (' ) Tj /F1 10 Tf 0 0 0 rg (which can be used to change the prompt. The text displayed at the beginning of the ) Tj T* 0 Tw .318443 Tw (interactive session is the docstring of the main function. ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (also understands command abbreviations:) Tj T* 0 Tw ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (21) Tj T* -235.3849 0 Td ET Q Q endstream endobj 383 0 obj << /Length 5565 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm q BT 1 0 0 1 0 14 Tm 1.44061 Tw 12 TL /F1 10 Tf 0 0 0 rg (in this example ) Tj /F3 10 Tf 0 0 0 rg (del ) Tj /F1 10 Tf 0 0 0 rg (is an abbreviation for ) Tj /F3 10 Tf 0 0 0 rg (delete) Tj /F1 10 Tf 0 0 0 rg (. In case of ambiguous abbreviations ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (raises a) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (NameError) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 711.0236 cm q BT 1 0 0 1 0 14 Tm 1.942485 Tw 12 TL /F1 10 Tf 0 0 0 rg (Finally I must notice that ) Tj /F3 10 Tf 0 0 0 rg (plac.Interpreter ) Tj /F1 10 Tf 0 0 0 rg (is available only if you are using a recent version of) Tj T* 0 Tw (Python \() Tj (>) Tj (= 2.5\), because it is a context manager object which uses extended generators internally.) Tj T* ET Q Q q 1 0 0 1 62.69291 681.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Testing a plac application) Tj T* ET Q Q q 1 0 0 1 62.69291 651.0236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 3.034269 Tw (You can conveniently test your application in interactive mode. However manual testing is a poor) Tj T* 0 Tw (substitute for automatic testing.) Tj T* ET Q Q q 1 0 0 1 62.69291 633.0236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (In principle, one could write automatic tests for the ) Tj /F3 10 Tf 0 0 0 rg (ishelve ) Tj /F1 10 Tf 0 0 0 rg (application by using ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (directly:) Tj T* ET Q Q q 1 0 0 1 62.69291 467.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 156 re B* Q q 0 0 0 rg BT 1 0 0 1 0 134 Tm /F3 10 Tf 12 TL (# test_ishelve.py) Tj T* (import plac, ishelve) Tj T* T* (def test\(\):) Tj T* ( assert plac.call\(ishelve.main, ['.clear']\) == ['cleared the shelve']) Tj T* ( assert plac.call\(ishelve.main, ['a=1']\) == ['setting a=1']) Tj T* ( assert plac.call\(ishelve.main, ['a']\) == ['1']) Tj T* ( assert plac.call\(ishelve.main, ['.delete=a']\) == ['deleted a']) Tj T* ( assert plac.call\(ishelve.main, ['a']\) == ['a: not found']) Tj T* T* (if __name__ == '__main__':) Tj T* ( test\(\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 423.8236 cm q BT 1 0 0 1 0 26 Tm .390651 Tw 12 TL /F1 10 Tf 0 0 0 rg (However, using ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (is not especially nice. The big issue is that ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (responds to invalid) Tj T* 0 Tw 1.249987 Tw (input by printing an error message on stderr and by raising a ) Tj /F3 10 Tf 0 0 0 rg (SystemExit) Tj /F1 10 Tf 0 0 0 rg (: this is certainly not a nice) Tj T* 0 Tw (thing to do in a test.) Tj T* ET Q Q q 1 0 0 1 62.69291 369.8236 cm q BT 1 0 0 1 0 38 Tm 1.616457 Tw 12 TL /F1 10 Tf 0 0 0 rg (As a consequence of this behavior it is impossible to test for invalid commands, unless you wrap the) Tj T* 0 Tw 1.425318 Tw /F3 10 Tf 0 0 0 rg (SystemExit ) Tj /F1 10 Tf 0 0 0 rg (exception by hand each time \(and possibly you do something with the error message in) Tj T* 0 Tw .996412 Tw (stderr too\). Luckily, ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (offers a better testing support through the ) Tj /F3 10 Tf 0 0 0 rg (check ) Tj /F1 10 Tf 0 0 0 rg (method of ) Tj /F3 10 Tf 0 0 0 rg (Interpreter) Tj T* 0 Tw /F1 10 Tf 0 0 0 rg (objects:) Tj T* ET Q Q q 1 0 0 1 62.69291 192.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 168 re B* Q q 0 0 0 rg BT 1 0 0 1 0 146 Tm /F3 10 Tf 12 TL (# test_ishelve_more.py) Tj T* (from __future__ import with_statement) Tj T* (import ishelve) Tj T* (import plac) Tj T* T* T* (def test\(\):) Tj T* ( with plac.Interpreter\(ishelve.main\) as i:) Tj T* ( i.check\('.clear', 'cleared the shelve'\)) Tj T* ( i.check\('a=1', 'setting a=1'\)) Tj T* ( i.check\('a', '1'\)) Tj T* ( i.check\('.delete=a', 'deleted a'\)) Tj T* ( i.check\('a', 'a: not found'\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 136.6236 cm q BT 1 0 0 1 0 38 Tm 6.299974 Tw 12 TL /F1 10 Tf 0 0 0 rg (The method ) Tj /F3 10 Tf 0 0 0 rg (.check\(given_input,) Tj ( ) Tj (expected_output\) ) Tj /F1 10 Tf 0 0 0 rg (works on strings and raises an) Tj T* 0 Tw .971318 Tw /F3 10 Tf 0 0 0 rg (AssertionError ) Tj /F1 10 Tf 0 0 0 rg (if the output produced by the interpreter is different from the expected output for the) Tj T* 0 Tw 2.186905 Tw (given input. Notice that ) Tj /F3 10 Tf 0 0 0 rg (AssertionError ) Tj /F1 10 Tf 0 0 0 rg (is catched by tools like ) Tj /F3 10 Tf 0 0 0 rg (py.test ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (nosetests ) Tj /F1 10 Tf 0 0 0 rg (and) Tj T* 0 Tw (actually ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (tests are intended to be run with such tools.) Tj T* ET Q Q q 1 0 0 1 62.69291 106.6236 cm q BT 1 0 0 1 0 14 Tm .239984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Interpreters offer a minor syntactic advantage with respect to calling ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (directly, but they offer a ) Tj T* 0 Tw .96748 Tw /F4 10 Tf (major ) Tj /F1 10 Tf (semantic advantage when things go wrong \(read exceptions\): an ) Tj /F3 10 Tf 0 0 0 rg (Interpreter ) Tj /F1 10 Tf 0 0 0 rg (object internally) Tj T* 0 Tw ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (22) Tj T* -235.3849 0 Td ET Q Q endstream endobj 384 0 obj << /Length 6771 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm q BT 1 0 0 1 0 14 Tm 1.181318 Tw 12 TL /F1 10 Tf 0 0 0 rg (invokes something like ) Tj /F3 10 Tf 0 0 0 rg (plac.call) Tj /F1 10 Tf 0 0 0 rg (, but it wraps all exceptions, so that ) Tj /F3 10 Tf 0 0 0 rg (i.check ) Tj /F1 10 Tf 0 0 0 rg (is guaranteed not to) Tj T* 0 Tw (raise any exception except ) Tj /F3 10 Tf 0 0 0 rg (AssertionError) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 723.0236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Even the ) Tj /F3 10 Tf 0 0 0 rg (SystemExit ) Tj /F1 10 Tf 0 0 0 rg (exception is captured and you can write your test as) Tj T* ET Q Q q 1 0 0 1 62.69291 717.0236 cm Q q 1 0 0 1 62.69291 705.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (i.check\('-cler',) Tj ( ) Tj ('SystemExit:) Tj ( ) Tj (unrecognized) Tj ( ) Tj (arguments:) Tj ( ) Tj (-cler'\)) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 705.0236 cm Q q 1 0 0 1 62.69291 687.0236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (without risk of exiting from the Python interpreter.) Tj T* ET Q Q q 1 0 0 1 62.69291 645.0236 cm q BT 1 0 0 1 0 26 Tm 1.422651 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is a second advantage of interpreters: if the main function contains some initialization code and) Tj T* 0 Tw .321235 Tw (finalization code \() Tj /F3 10 Tf 0 0 0 rg (__enter__ ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (__exit__ ) Tj /F1 10 Tf 0 0 0 rg (functions\) they will be run at the beginning and at the end) Tj T* 0 Tw (of the interpreter loop, whereas ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (ignores the initialization/finalization code.) Tj T* ET Q Q q 1 0 0 1 62.69291 615.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Plac easy tests) Tj T* ET Q Q q 1 0 0 1 62.69291 573.0236 cm q BT 1 0 0 1 0 26 Tm 1.517126 Tw 12 TL /F1 10 Tf 0 0 0 rg (Writing your tests in terms of ) Tj /F3 10 Tf 0 0 0 rg (Interpreter.check ) Tj /F1 10 Tf 0 0 0 rg (is certainly an improvement over writing them in) Tj T* 0 Tw 1.807318 Tw (terms of ) Tj /F3 10 Tf 0 0 0 rg (plac.call) Tj /F1 10 Tf 0 0 0 rg (, but they are still too low-level for my taste. The ) Tj /F3 10 Tf 0 0 0 rg (Interpreter ) Tj /F1 10 Tf 0 0 0 rg (class provides) Tj T* 0 Tw (support for doctest-style tests, a.k.a. ) Tj /F4 10 Tf (plac easy tests) Tj /F1 10 Tf (.) Tj T* ET Q Q q 1 0 0 1 62.69291 531.0236 cm q BT 1 0 0 1 0 26 Tm 2.142209 Tw 12 TL /F1 10 Tf 0 0 0 rg (By using plac easy tests you can cut and paste your interactive session and turn it into a runnable) Tj T* 0 Tw .519213 Tw (automatics test. Consider for instance the following file ) Tj /F3 10 Tf 0 0 0 rg (ishelve.placet ) Tj /F1 10 Tf 0 0 0 rg (\(the ) Tj /F3 10 Tf 0 0 0 rg (.placet ) Tj /F1 10 Tf 0 0 0 rg (extension is a) Tj T* 0 Tw (mnemonic for "plac easy tests"\):) Tj T* ET Q Q q 1 0 0 1 62.69291 353.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 168 re B* Q q BT 1 0 0 1 0 146 Tm 12 TL /F3 10 Tf 0 0 0 rg (#!ishelve.py) Tj T* (i) Tj (>) Tj ( .clear # start from a clean state) Tj T* (cleared the shelve) Tj T* (i) Tj (>) Tj ( a=1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( a) Tj T* (1) Tj T* (i) Tj (>) Tj ( .del a) Tj T* (deleted a) Tj T* (i) Tj (>) Tj ( a) Tj T* (a: not found) Tj T* (i) Tj (>) Tj ( .cler # spelling error) Tj T* (.cler: not found) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 297.8236 cm q BT 1 0 0 1 0 38 Tm .697132 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice the presence of the shebang line containing the name of the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (tool to test \(a ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (tool is just a) Tj T* 0 Tw 1.511751 Tw (Python module with a function called ) Tj /F3 10 Tf 0 0 0 rg (main) Tj /F1 10 Tf 0 0 0 rg (\). The shebang is ignored by the interpreter \(it looks like a) Tj T* 0 Tw .487608 Tw (comment to it\) but it is there so that external tools \(say a test runner\) can infer the plac interpreter to use) Tj T* 0 Tw (to test the file.) Tj T* ET Q Q q 1 0 0 1 62.69291 267.8236 cm q BT 1 0 0 1 0 14 Tm 1.33061 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can run the ) Tj /F3 10 Tf 0 0 0 rg (ishelve.placet ) Tj /F1 10 Tf 0 0 0 rg (file by calling the ) Tj /F3 10 Tf 0 0 0 rg (.doctest ) Tj /F1 10 Tf 0 0 0 rg (method of the interpreter, as in this) Tj T* 0 Tw (example:) Tj T* ET Q Q q 1 0 0 1 62.69291 222.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 474 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python -c "import plac, ishelve) Tj T* (plac.Interpreter\(ishelve.main\).doctest\(open\('ishelve.placet'\), verbose=True\)") Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 178.6236 cm q BT 1 0 0 1 0 26 Tm 4.007109 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F3 10 Tf 0 0 0 rg (Interpreter.doctests ) Tj /F1 10 Tf 0 0 0 rg (invokes something like ) Tj /F3 10 Tf 0 0 0 rg (Interpreter.check ) Tj /F1 10 Tf 0 0 0 rg (multiple times) Tj T* 0 Tw .991751 Tw (inside the same context and compares the output with the expected output: if even one check fails, the) Tj T* 0 Tw (whole test fail.) Tj T* ET Q Q q 1 0 0 1 62.69291 136.6236 cm q BT 1 0 0 1 0 26 Tm .012339 Tw 12 TL /F1 10 Tf 0 0 0 rg (You should realize that the easy tests supported by ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (are ) Tj /F4 10 Tf (not ) Tj /F1 10 Tf (unittests: they are functional tests. They) Tj T* 0 Tw 1.57686 Tw (model the user interaction and the order of the operations generally matters. The single subtests in a) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (.placet ) Tj /F1 10 Tf 0 0 0 rg (file are not independent and it makes sense to exit immediately at the first failure.) Tj T* ET Q Q q 1 0 0 1 62.69291 94.62362 cm q BT 1 0 0 1 0 26 Tm .414431 Tw 12 TL /F1 10 Tf 0 0 0 rg (The support for doctests in ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (comes nearly for free, thanks to the ) Tj 0 0 .501961 rg (shlex ) Tj 0 0 0 rg (module in the standard library, ) Tj T* 0 Tw .352765 Tw (which is able to parse simple languages as the ones you can implement with ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. In particular, thanks to ) Tj T* 0 Tw .875984 Tw 0 0 .501961 rg (shlex) Tj 0 0 0 rg (, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to recognize comments \(the default comment character is ) Tj /F3 10 Tf 0 0 0 rg (#) Tj /F1 10 Tf 0 0 0 rg (\), escape sequences and) Tj T* 0 Tw ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (23) Tj T* -235.3849 0 Td ET Q Q endstream endobj 385 0 obj << /Length 6106 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 729.0236 cm q BT 1 0 0 1 0 26 Tm 1.50686 Tw 12 TL /F1 10 Tf 0 0 0 rg (more. Look at the ) Tj 0 0 .501961 rg (shlex ) Tj 0 0 0 rg (documentation if you need to customize how the language is interpreted. For) Tj T* 0 Tw 3.722651 Tw (more flexibility, it is even possible to pass the interpreter a custom split function with signature) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (split\(line, commentchar\)) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 675.0236 cm q BT 1 0 0 1 0 38 Tm .157356 Tw 12 TL /F1 10 Tf 0 0 0 rg (In addition, I have implemented some support for line number recognition, so that if a test fails you get the) Tj T* 0 Tw 1.748221 Tw (line number of the failing command. This is especially useful if your tests are stored in external files,) Tj T* 0 Tw 1.671894 Tw (though they do not need to be in a file: you can just pass to the ) Tj /F3 10 Tf 0 0 0 rg (.doctest ) Tj /F1 10 Tf 0 0 0 rg (method a list of strings) Tj T* 0 Tw (corresponding to the lines of the file.) Tj T* ET Q Q q 1 0 0 1 62.69291 645.0236 cm q BT 1 0 0 1 0 14 Tm .653145 Tw 12 TL /F1 10 Tf 0 0 0 rg (At the present ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not use any code from the doctest module, but the situation may change in the) Tj T* 0 Tw (future \(it would be nice if ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (could reuse doctests directives like ELLIPSIS\).) Tj T* ET Q Q q 1 0 0 1 62.69291 615.0236 cm q BT 1 0 0 1 0 14 Tm 1.447318 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is straighforward to integrate your ) Tj /F3 10 Tf 0 0 0 rg (.placet ) Tj /F1 10 Tf 0 0 0 rg (tests with standard testing tools. For instance, you can) Tj T* 0 Tw (integrate your doctests with ) Tj /F3 10 Tf 0 0 0 rg (nose ) Tj /F1 10 Tf 0 0 0 rg (or ) Tj /F3 10 Tf 0 0 0 rg (py.test ) Tj /F1 10 Tf 0 0 0 rg (as follow:) Tj T* ET Q Q q 1 0 0 1 62.69291 425.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 474 180 re B* Q q 0 0 0 rg BT 1 0 0 1 0 158 Tm /F3 10 Tf 12 TL (import os, shlex, plac) Tj T* T* (def test_doct\(\):) Tj T* ( """) Tj T* ( Find all the doctests in the current directory and run them with the) Tj T* ( corresponding plac interpreter \(the shebang rules!\)) Tj T* ( """) Tj T* ( placets = [f for f in os.listdir\('.'\) if f.endswith\('.placet'\)]) Tj T* ( for placet in placets:) Tj T* ( lines = list\(open\(placet\)\)) Tj T* ( assert lines[0].startswith\('#!'\), 'Missing or incorrect shebang line!') Tj T* ( firstline = lines[0][2:] # strip the shebang) Tj T* ( main = plac.import_main\(*shlex.split\(firstline\)\)) Tj T* ( yield plac.Interpreter\(main\).doctest, lines[1:]) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 345.8236 cm q BT 1 0 0 1 0 62 Tm 1.44811 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here you should notice that usage of ) Tj /F3 10 Tf 0 0 0 rg (plac.import_main) Tj /F1 10 Tf 0 0 0 rg (, an utility which is able to import the main) Tj T* 0 Tw .775703 Tw (function of the script specified in the shebang line. You can use both the full path name of the tool, or a) Tj T* 0 Tw .56436 Tw (relative path name. In this case the runner looks at the environment variable ) Tj /F3 10 Tf 0 0 0 rg (PLACPATH ) Tj /F1 10 Tf 0 0 0 rg (and it searches) Tj T* 0 Tw 1.900651 Tw (the plac tool in the directories specified there \() Tj /F3 10 Tf 0 0 0 rg (PLACPATH ) Tj /F1 10 Tf 0 0 0 rg (is just a string containing directory names) Tj T* 0 Tw .56332 Tw (separated by colons\). If the variable ) Tj /F3 10 Tf 0 0 0 rg (PLACPATH ) Tj /F1 10 Tf 0 0 0 rg (is not defined, it just looks in the current directory. If the) Tj T* 0 Tw (plac tool is not found, an ) Tj /F3 10 Tf 0 0 0 rg (ImportError ) Tj /F1 10 Tf 0 0 0 rg (is raised.) Tj T* ET Q Q q 1 0 0 1 62.69291 315.8236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Plac batch scripts) Tj T* ET Q Q q 1 0 0 1 62.69291 273.8236 cm q BT 1 0 0 1 0 26 Tm .772093 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is pretty easy to realize that an interactive interpreter can also be used to run batch scripts: instead of) Tj T* 0 Tw .504692 Tw (reading the commands from the console, it is enough to read the commands from a file. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreters) Tj T* 0 Tw (provide an ) Tj /F3 10 Tf 0 0 0 rg (.execute ) Tj /F1 10 Tf 0 0 0 rg (method to perform just that.) Tj T* ET Q Q q 1 0 0 1 62.69291 207.8236 cm q BT 1 0 0 1 0 50 Tm .098935 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is just a subtle point to notice: whereas in an interactive loop one wants to manage all exceptions, a) Tj T* 0 Tw .903318 Tw (batch script should not continue in the background in case of unexpected errors. The implementation of) Tj T* 0 Tw .103059 Tw /F3 10 Tf 0 0 0 rg (Interpreter.execute ) Tj /F1 10 Tf 0 0 0 rg (makes sure that any error raised by ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (internally is re-raised. In other) Tj T* 0 Tw .407045 Tw (words, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreters ) Tj /F4 10 Tf (wrap the errors, but does not eat them) Tj /F1 10 Tf (: the errors are always accessible and can) Tj T* 0 Tw (be re-raised on demand.) Tj T* ET Q Q q 1 0 0 1 62.69291 177.8236 cm q BT 1 0 0 1 0 14 Tm 1.239318 Tw 12 TL /F1 10 Tf 0 0 0 rg (The exception is the case of invalid commands, which are skipped. Consider for instance the following) Tj T* 0 Tw (batch file, which contains a mispelled command \() Tj /F3 10 Tf 0 0 0 rg (.dl ) Tj /F1 10 Tf 0 0 0 rg (instead of ) Tj /F3 10 Tf 0 0 0 rg (.del) Tj /F1 10 Tf 0 0 0 rg (\):) Tj T* ET Q Q q 1 0 0 1 62.69291 96.62362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 72 re B* Q q 0 0 0 rg BT 1 0 0 1 0 50 Tm /F3 10 Tf 12 TL (#!ishelve.py) Tj T* (.clear ) Tj T* (a=1 b=2) Tj T* (.show) Tj T* (.del a) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (24) Tj T* -235.3849 0 Td ET Q Q endstream endobj 386 0 obj << /Length 4749 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 727.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (.dl b) Tj T* (.show) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 695.8236 cm q BT 1 0 0 1 0 14 Tm 1.939461 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you execute the batch file, the interpreter will print a ) Tj /F3 10 Tf 0 0 0 rg (.dl:) Tj ( ) Tj (not) Tj ( ) Tj (found ) Tj /F1 10 Tf 0 0 0 rg (at the ) Tj /F3 10 Tf 0 0 0 rg (.dl ) Tj /F1 10 Tf 0 0 0 rg (line and will) Tj T* 0 Tw (continue:) Tj T* ET Q Q q 1 0 0 1 62.69291 470.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 216 re B* Q q BT 1 0 0 1 0 194 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python -c "import plac, ishelve) Tj T* (plac.Interpreter\(ishelve.main\).execute\(open\('ishelve.plac'\), verbose=True\)") Tj T* (i) Tj (>) Tj ( .clear) Tj T* (cleared the shelve) Tj T* (i) Tj (>) Tj ( a=1 b=2) Tj T* (setting a=1) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( .show) Tj T* (b=2) Tj T* (a=1) Tj T* (i) Tj (>) Tj ( .del a) Tj T* (deleted a) Tj T* (i) Tj (>) Tj ( .dl b) Tj T* (2) Tj T* (.dl: not found) Tj T* (i) Tj (>) Tj ( .show) Tj T* (b=2) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 426.6236 cm q BT 1 0 0 1 0 26 Tm .159988 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf 0 0 0 rg (verbose ) Tj /F1 10 Tf 0 0 0 rg (flag is there to show the lines which are being interpreted \(prefixed by ) Tj /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj /F1 10 Tf 0 0 0 rg (\). This is done on) Tj T* 0 Tw 1.359988 Tw (purpose, so that you can cut and paste the output of the batch script and turn it into a ) Tj /F3 10 Tf 0 0 0 rg (.placet ) Tj /F1 10 Tf 0 0 0 rg (test) Tj T* 0 Tw (\(cool, isn't it?\).) Tj T* ET Q Q q 1 0 0 1 62.69291 396.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Implementing subcommands) Tj T* ET Q Q q 1 0 0 1 62.69291 354.6236 cm q BT 1 0 0 1 0 26 Tm .661235 Tw 12 TL /F1 10 Tf 0 0 0 rg (When I discussed the ) Tj /F3 10 Tf 0 0 0 rg (ishelve ) Tj /F1 10 Tf 0 0 0 rg (implementation in the ) Tj 0 0 .501961 rg (basic documentation) Tj 0 0 0 rg (, I said that it looked like the) Tj T* 0 Tw .116655 Tw (poor man implementation of an object system as a chain of elifs; I also said that ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (was able to do much) Tj T* 0 Tw (better than that. Here I will substantiate my claim.) Tj T* ET Q Q q 1 0 0 1 62.69291 264.6236 cm q BT 1 0 0 1 0 74 Tm .89104 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is actually able to infer a set of subparsers from a generic container of commands. This is useful if) Tj T* 0 Tw 3.125814 Tw (you want to implement ) Tj /F4 10 Tf (subcommands ) Tj /F1 10 Tf (\(a familiar example of a command-line application featuring) Tj T* 0 Tw 2.243555 Tw (subcommands is version control system\). Technically a container of commands is any object with a) Tj T* 0 Tw 2.711412 Tw /F3 10 Tf 0 0 0 rg (.commands ) Tj /F1 10 Tf 0 0 0 rg (attribute listing a set of functions or methods which are valid commands. A command) Tj T* 0 Tw 5.28872 Tw (container may have initialization/finalization hooks \() Tj /F3 10 Tf 0 0 0 rg (__enter__/__exit__) Tj /F1 10 Tf 0 0 0 rg (\) and dispatch hooks) Tj T* 0 Tw 1.04816 Tw (\() Tj /F3 10 Tf 0 0 0 rg (__missing__) Tj /F1 10 Tf 0 0 0 rg (, invoked for invalid command names\). Moreover, only when using command containers) Tj T* 0 Tw 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to provide automatic ) Tj /F4 10 Tf (autocompletion ) Tj /F1 10 Tf (of commands.) Tj T* ET Q Q q 1 0 0 1 62.69291 246.6236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (The shelve interface can be rewritten in an object-oriented way as follows:) Tj T* ET Q Q q 1 0 0 1 62.69291 93.42362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 144 re B* Q q 0 0 0 rg BT 1 0 0 1 0 122 Tm /F3 10 Tf 12 TL (# ishelve2.py) Tj T* (import shelve, os, plac) Tj T* T* (class ShelveInterface\(object\):) Tj T* ( "A minimal interface over a shelve object.") Tj T* ( commands = 'set', 'show', 'showall', 'delete') Tj T* ( @plac.annotations\() Tj T* ( configfile=\('path name of the shelve', 'option'\)\)) Tj T* ( def __init__\(self, configfile\):) Tj T* ( self.configfile = configfile or '~/conf.shelve') Tj T* ( self.fname = os.path.expanduser\(self.configfile\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (25) Tj T* -235.3849 0 Td ET Q Q endstream endobj 387 0 obj << /Length 4651 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 391.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 372 re B* Q q 0 0 0 rg BT 1 0 0 1 0 350 Tm /F3 10 Tf 12 TL ( self.__doc__ += '\\nOperating on %s.\\nUse help to see '\\) Tj T* ( 'the available commands.\\n' % self.fname) Tj T* ( def __enter__\(self\):) Tj T* ( self.sh = shelve.open\(self.fname\)) Tj T* ( return self) Tj T* ( def __exit__\(self, etype, exc, tb\):) Tj T* ( self.sh.close\(\)) Tj T* ( def set\(self, name, value\):) Tj T* ( "set name value") Tj T* ( yield 'setting %s=%s' % \(name, value\)) Tj T* ( self.sh[name] = value) Tj T* ( def show\(self, *names\):) Tj T* ( "show given parameters") Tj T* ( for name in names:) Tj T* ( yield '%s = %s' % \(name, self.sh[name]\) # no error checking) Tj T* ( def showall\(self\):) Tj T* ( "show all parameters") Tj T* ( for name in self.sh:) Tj T* ( yield '%s = %s' % \(name, self.sh[name]\)) Tj T* ( def delete\(self, name=None\):) Tj T* ( "delete given parameter \(or everything\)") Tj T* ( if name is None:) Tj T* ( yield 'deleting everything') Tj T* ( self.sh.clear\(\)) Tj T* ( else:) Tj T* ( yield 'deleting %s' % name) Tj T* ( del self.sh[name] # no error checking) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter\(plac.call\(ShelveInterface\)\).interact\(\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 299.8236 cm q BT 1 0 0 1 0 74 Tm .885366 Tw 12 TL /F3 10 Tf 0 0 0 rg (plac.Interpreter ) Tj /F1 10 Tf 0 0 0 rg (objects wrap context manager objects consistently. In other words, if you wrap an) Tj T* 0 Tw 2.323828 Tw (object with ) Tj /F3 10 Tf 0 0 0 rg (__enter__ ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (__exit__ ) Tj /F1 10 Tf 0 0 0 rg (methods, they are invoked in the right order \() Tj /F3 10 Tf 0 0 0 rg (__enter__) Tj T* 0 Tw .23528 Tw /F1 10 Tf 0 0 0 rg (before the interpreter loop starts and ) Tj /F3 10 Tf 0 0 0 rg (__exit__ ) Tj /F1 10 Tf 0 0 0 rg (after the interpreter loop ends, both in the regular and in) Tj T* 0 Tw 1.916412 Tw (the exceptional case\). In our example, the methods ) Tj /F3 10 Tf 0 0 0 rg (__enter__ ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (__exit__ ) Tj /F1 10 Tf 0 0 0 rg (make sure the the) Tj T* 0 Tw .339398 Tw (shelve is opened and closed correctly even in the case of exceptions. Notice that I have not implemented) Tj T* 0 Tw .814104 Tw (any error checking in the ) Tj /F3 10 Tf 0 0 0 rg (show ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (delete ) Tj /F1 10 Tf 0 0 0 rg (methods on purpose, to verify that ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (works correctly in) Tj T* 0 Tw (the presence of exceptions.) Tj T* ET Q Q q 1 0 0 1 62.69291 221.8236 cm q BT 1 0 0 1 0 62 Tm 1.567126 Tw 12 TL /F1 10 Tf 0 0 0 rg (When working with command containers, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (automatically adds two special commands to the set of) Tj T* 0 Tw 2.099213 Tw (provided commands: ) Tj /F3 10 Tf 0 0 0 rg (help ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (.last_tb) Tj /F1 10 Tf 0 0 0 rg (. The ) Tj /F3 10 Tf 0 0 0 rg (help ) Tj /F1 10 Tf 0 0 0 rg (command is the easier to understand: when) Tj T* 0 Tw .39811 Tw (invoked without arguments it displays the list of available commands with the same formatting of the ) Tj 0 0 .501961 rg (cmd) Tj T* 0 Tw 1.19561 Tw 0 0 0 rg (module; when invoked with the name of a command it displays the usage message for that command.) Tj T* 0 Tw 2.33686 Tw (The ) Tj /F3 10 Tf 0 0 0 rg (.last_tb ) Tj /F1 10 Tf 0 0 0 rg (command is useful when debugging: in case of errors, it allows you to display the) Tj T* 0 Tw (traceback of the last executed command.) Tj T* ET Q Q q 1 0 0 1 62.69291 203.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage message:) Tj T* ET Q Q q 1 0 0 1 62.69291 110.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL (usage: ishelve2.py [-h] [-configfile CONFIGFILE]) Tj T* T* (A minimal interface over a shelve object.) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (26) Tj T* -235.3849 0 Td ET Q Q endstream endobj 388 0 obj << /Length 3356 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 727.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ( -configfile CONFIGFILE) Tj T* ( path name of the shelve) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 707.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is a session of usage on an Unix-like operating system:) Tj T* ET Q Q q 1 0 0 1 62.69291 194.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 606 504 re B* Q q BT 1 0 0 1 0 482 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python ishelve2.py -c ~/test.shelve) Tj T* (A minimal interface over a shelve object.) Tj T* (Operating on /home/micheles/test.shelve.) Tj T* (Use help to see the available commands.) Tj T* (i) Tj (>) Tj ( help) Tj T* T* (special commands) Tj T* (================) Tj T* (.last_tb) Tj T* T* (custom commands) Tj T* (===============) Tj T* (delete set show showall) Tj T* T* (i) Tj (>) Tj ( delete) Tj T* (deleting everything) Tj T* (i) Tj (>) Tj ( set a pippo) Tj T* (setting a=pippo) Tj T* (i) Tj (>) Tj ( set b lippo) Tj T* (setting b=lippo) Tj T* (i) Tj (>) Tj ( showall) Tj T* (b = lippo) Tj T* (a = pippo) Tj T* (i) Tj (>) Tj ( show a b) Tj T* (a = pippo) Tj T* (b = lippo) Tj T* (i) Tj (>) Tj ( del a) Tj T* (deleting a) Tj T* (i) Tj (>) Tj ( showall) Tj T* (b = lippo) Tj T* (i) Tj (>) Tj ( delete a) Tj T* (deleting a) Tj T* (KeyError: 'a') Tj T* (i) Tj (>) Tj ( .last_tb) Tj T* ( File "/usr/local/lib/python2.6/dist-packages/plac-0.6.0-py2.6.egg/plac_ext.py", line 190, in _wrap) Tj T* ( for value in genobj:) Tj T* ( File "./ishelve2.py", line 37, in delete) Tj T* ( del self.sh[name] # no error checking) Tj T* ( File "/usr/lib/python2.6/shelve.py", line 136, in __delitem__) Tj T* ( del self.dict[key]) Tj T* (i) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 162.6236 cm q BT 1 0 0 1 0 14 Tm 2.571235 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that in interactive mode the traceback is hidden, unless you pass the ) Tj /F3 10 Tf 0 0 0 rg (verbose ) Tj /F1 10 Tf 0 0 0 rg (flag to the) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (Interpreter.interact ) Tj /F1 10 Tf 0 0 0 rg (method.) Tj T* ET Q Q q 1 0 0 1 62.69291 96.62362 cm q BT 1 0 0 1 0 50 Tm .046098 Tw 12 TL /F1 10 Tf 0 0 0 rg (CHANGED IN VERSION 0.9: if you have an old version of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (the ) Tj /F3 10 Tf 0 0 0 rg (help ) Tj /F1 10 Tf 0 0 0 rg (command must be prefixed with) Tj T* 0 Tw 1.096303 Tw (a dot, i.e. you must write ) Tj /F3 10 Tf 0 0 0 rg (.help) Tj /F1 10 Tf 0 0 0 rg (. The old behavior was more consistent in my opinion, since it made it) Tj T* 0 Tw 1.209983 Tw (clear that the ) Tj /F3 10 Tf 0 0 0 rg (help ) Tj /F1 10 Tf 0 0 0 rg (command was special and threated differently from the regular commands. Notice) Tj T* 0 Tw 1.813516 Tw (that if you implement a custom ) Tj /F3 10 Tf 0 0 0 rg (help ) Tj /F1 10 Tf 0 0 0 rg (command in the commander class the default help will not be) Tj T* 0 Tw (added, as you would expect.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (27) Tj T* -235.3849 0 Td ET Q Q endstream endobj 389 0 obj << /Length 5397 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm q BT 1 0 0 1 0 14 Tm 1.003984 Tw 12 TL /F1 10 Tf 0 0 0 rg (In version 0.9 an exception ) Tj /F3 10 Tf 0 0 0 rg (`plac.Interpreter.Exit ) Tj /F1 10 Tf 0 0 0 rg (was added. Its purpose is to make it easy to) Tj T* 0 Tw (define commands to exit from the command loop. Just define something like:) Tj T* ET Q Q q 1 0 0 1 62.69291 695.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (def quit\(self\):) Tj T* ( raise plac.Interpreter.Exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 675.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (and the interpreter will be closed properly when the ) Tj /F3 10 Tf 0 0 0 rg (quit ) Tj /F1 10 Tf 0 0 0 rg (command is entered.) Tj T* ET Q Q q 1 0 0 1 62.69291 645.8236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac.Interpreter.call) Tj T* ET Q Q q 1 0 0 1 62.69291 603.8236 cm q BT 1 0 0 1 0 26 Tm .10104 Tw 12 TL /F1 10 Tf 0 0 0 rg (At the core of ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (there is the ) Tj /F3 10 Tf 0 0 0 rg (call ) Tj /F1 10 Tf 0 0 0 rg (function which invokes a callable with the list of arguments passed) Tj T* 0 Tw 1.238443 Tw (at the command-line \() Tj /F3 10 Tf 0 0 0 rg (sys.argv[1:]) Tj /F1 10 Tf 0 0 0 rg (\). Thanks to ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (you can launch your module by simply) Tj T* 0 Tw (adding the lines:) Tj T* ET Q Q q 1 0 0 1 62.69291 558.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 502.6236 cm q BT 1 0 0 1 0 38 Tm .50436 Tw 12 TL /F1 10 Tf 0 0 0 rg (Everything works fine if ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (is a simple callable performing some action; however, in many cases, one) Tj T* 0 Tw .087633 Tw (has a ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg ("function" which is a actually a factory returning a command container object. For instance, in) Tj T* 0 Tw .573318 Tw (my second shelve example the main function is the class ) Tj /F3 10 Tf 0 0 0 rg (ShelveInterface) Tj /F1 10 Tf 0 0 0 rg (, and the two lines needed) Tj T* 0 Tw (to run the module are a bit ugly:) Tj T* ET Q Q q 1 0 0 1 62.69291 457.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( plac.Interpreter\(plac.call\(ShelveInterface\)\).interact\(\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 353.4236 cm q BT 1 0 0 1 0 86 Tm .873988 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover, now the program runs, but only in interactive mode, i.e. it is not possible to run it as a script.) Tj T* 0 Tw .097882 Tw (Instead, it would be nice to be able to specify the command to execute on the command-line and have the) Tj T* 0 Tw 4.08229 Tw (interpreter start, execute the command and finish properly \(I mean by calling ) Tj /F3 10 Tf 0 0 0 rg (__enter__ ) Tj /F1 10 Tf 0 0 0 rg (and) Tj T* 0 Tw .100574 Tw /F3 10 Tf 0 0 0 rg (__exit__) Tj /F1 10 Tf 0 0 0 rg (\) without needing user input. Then the script could be called from a batch shell script working in) Tj T* 0 Tw 2.26816 Tw (the background. In order to provide such functionality ) Tj /F3 10 Tf 0 0 0 rg (plac.Interpreter ) Tj /F1 10 Tf 0 0 0 rg (provides a classmethod) Tj T* 0 Tw 1.173318 Tw (named ) Tj /F3 10 Tf 0 0 0 rg (.call ) Tj /F1 10 Tf 0 0 0 rg (which takes the factory, instantiates it with the arguments read from the command line,) Tj T* 0 Tw .952485 Tw (wraps the resulting container object as an interpreter and runs it with the remaining arguments found in) Tj T* 0 Tw (the command line. Here is the code to turn the ) Tj /F3 10 Tf 0 0 0 rg (ShelveInterface ) Tj /F1 10 Tf 0 0 0 rg (into a script) Tj T* ET Q Q q 1 0 0 1 62.69291 212.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 132 re B* Q q 0 0 0 rg BT 1 0 0 1 0 110 Tm /F3 10 Tf 12 TL (# ishelve3.py) Tj T* (from ishelve2 import ShelveInterface) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.Interpreter.call\(ShelveInterface\)) Tj T* T* (## try the following:) Tj T* (# $ python ishelve3.py delete) Tj T* (# $ python ishelve3.py set a 1) Tj T* (# $ python ishelve3.py showall) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 192.2236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (and here are a few examples of usage:) Tj T* ET Q Q q 1 0 0 1 62.69291 99.02362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL ($ python ishelve3.py help) Tj T* T* (special commands) Tj T* (================) Tj T* (.last_tb) Tj T* T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (28) Tj T* -235.3849 0 Td ET Q Q endstream endobj 390 0 obj << /Length 4935 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 655.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (custom commands) Tj T* (===============) Tj T* (delete set show showall) Tj T* T* ($ python ishelve3.py set a 1) Tj T* (setting a=1) Tj T* ($ python ishelve3.py show a) Tj T* (a = 1) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 623.8236 cm q BT 1 0 0 1 0 14 Tm .079989 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you pass the ) Tj /F3 10 Tf 0 0 0 rg (-i ) Tj /F1 10 Tf 0 0 0 rg (flag in the command line, then the script will enter in interactive mode and ask the user) Tj T* 0 Tw (for the commands to execute:) Tj T* ET Q Q q 1 0 0 1 62.69291 530.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python ishelve3.py -i) Tj T* (A minimal interface over a shelve object.) Tj T* (Operating on /home/micheles/conf.shelve.) Tj T* (Use help to see the available commands.) Tj T* T* (i) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 486.6236 cm q BT 1 0 0 1 0 26 Tm .221417 Tw 12 TL /F1 10 Tf 0 0 0 rg (In a sense, I have closed the circle: at the beginning of this document I discussed how to turn a script into) Tj T* 0 Tw .784147 Tw (an interactive application \(the ) Tj /F3 10 Tf 0 0 0 rg (shelve_interpreter.py ) Tj /F1 10 Tf 0 0 0 rg (example\), whereas here I have show how to) Tj T* 0 Tw (turn an interactive application into a script.) Tj T* ET Q Q q 1 0 0 1 62.69291 468.6236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The complete signature of ) Tj /F3 10 Tf 0 0 0 rg (plac.Interpreter.call ) Tj /F1 10 Tf 0 0 0 rg (is the following:) Tj T* ET Q Q q 1 0 0 1 62.69291 411.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg (call\(factory, arglist=sys.argv[1:],) Tj T* ( commentchar='#', split=shlex.split,) Tj T* ( stdin=sys.stdin, prompt='i) Tj (>) Tj ( ', verbose=False\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 331.4236 cm q BT 1 0 0 1 0 62 Tm 1.756651 Tw 12 TL /F1 10 Tf 0 0 0 rg (The factory must have a fixed number of positional arguments \(no default arguments, no varargs, no) Tj T* 0 Tw 1.87881 Tw (kwargs\), otherwise a ) Tj /F3 10 Tf 0 0 0 rg (TypeError ) Tj /F1 10 Tf 0 0 0 rg (is raised: the reason is that we want to be able to distinguish the) Tj T* 0 Tw 1.386136 Tw (command-line arguments needed to instantiate the factory from the remaining arguments that must be) Tj T* 0 Tw .373516 Tw (sent to the corresponding interpreter object. It is also possible to specify a list of arguments different from) Tj T* 0 Tw .513318 Tw /F3 10 Tf 0 0 0 rg (sys.argv[1:] ) Tj /F1 10 Tf 0 0 0 rg (\(useful in tests\), the character to be recognized as a comment, the splitting function, the) Tj T* 0 Tw (input source, the prompt to use while in interactive mode, and a verbose flag.) Tj T* ET Q Q q 1 0 0 1 62.69291 301.4236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Readline support) Tj T* ET Q Q q 1 0 0 1 62.69291 223.4236 cm q BT 1 0 0 1 0 62 Tm 1.022485 Tw 12 TL /F1 10 Tf 0 0 0 rg (Starting from release 0.6 ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (offers full readline support. That means that if your Python was compiled) Tj T* 0 Tw 2.120697 Tw (with readline support you get autocompletion and persistent command history for free. By default all) Tj T* 0 Tw .045868 Tw (commands autocomplete in a case sensitive way. If you want to add new words to the autocompletion set,) Tj T* 0 Tw .366488 Tw (or you want to change the location of the ) Tj /F3 10 Tf 0 0 0 rg (.history ) Tj /F1 10 Tf 0 0 0 rg (file, or to change the case sensitivity, the way to go) Tj T* 0 Tw .35811 Tw (is to pass a ) Tj /F3 10 Tf 0 0 0 rg (plac.ReadlineInput ) Tj /F1 10 Tf 0 0 0 rg (object to the interpreter. Here is an example, assuming you want to) Tj T* 0 Tw (build a database interface understanding SQL commands:) Tj T* ET Q Q q 1 0 0 1 62.69291 94.22362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q BT 1 0 0 1 0 98 Tm 12 TL /F3 10 Tf 0 0 0 rg (import os) Tj T* (import plac) Tj T* (from sqlsoup import SQLSoup) Tj T* T* (SQLKEYWORDS = set\(['help', 'select', 'from', 'inner', 'join', 'outer',) Tj T* ( 'left', 'right']\) # and many others) Tj T* T* (DBTABLES = set\(['table1', 'table2']\) # you can read them from the db schema) Tj T* T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (29) Tj T* -235.3849 0 Td ET Q Q endstream endobj 391 0 obj << /Length 4854 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 439.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 324 re B* Q q BT 1 0 0 1 0 302 Tm 12 TL /F3 10 Tf 0 0 0 rg (COMPLETIONS = SQLKEYWORDS | DBTABLES) Tj T* T* T* (class SqlInterface\(object\):) Tj T* ( commands = ['SELECT']) Tj T* T* ( def __init__\(self, dsn\):) Tj T* ( self.soup = SQLSoup\(dsn\)) Tj T* T* ( def SELECT\(self, argstring\):) Tj T* ( sql = 'SELECT ' + argstring) Tj T* ( for row in self.soup.bind.execute\(sql\):) Tj T* ( yield str\(row\) # the formatting can be much improved) Tj T* T* T* (rl_input = plac.ReadlineInput\() Tj T* ( COMPLETIONS, histfile=os.path.expanduser\('~/.sql_interface.history'\),) Tj T* ( case_sensitive=False\)) Tj T* T* T* (def split_on_first_space\(line, commentchar\):) Tj T* ( return line.strip\(\).split\(' ', 1\) # ignoring comments) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter.call\(SqlInterface, split=split_on_first_space,) Tj T* ( stdin=rl_input, prompt='sql) Tj (>) Tj ( '\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 419.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an example of usage:) Tj T* ET Q Q q 1 0 0 1 62.69291 362.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python sql_interface.py ) Tj (<) Tj (some dsn) Tj (>) Tj T* (sql) Tj (>) Tj ( SELECT a.* FROM TABLE1 AS a INNER JOIN TABLE2 AS b ON a.id = b.id) Tj T* (...) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 282.6236 cm q BT 1 0 0 1 0 62 Tm 1.951318 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can check that entering just ) Tj /F3 10 Tf 0 0 0 rg (sel ) Tj /F1 10 Tf 0 0 0 rg (and pressing TAB the readline library completes the ) Tj /F3 10 Tf 0 0 0 rg (SELECT) Tj T* 0 Tw .797356 Tw /F1 10 Tf 0 0 0 rg (keyword for you and makes it upper case; idem for ) Tj /F3 10 Tf 0 0 0 rg (FROM) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (INNER) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (JOIN ) Tj /F1 10 Tf 0 0 0 rg (and even for the names of the) Tj T* 0 Tw .256235 Tw (tables. An obvious improvement is to read the names of the tables by introspecting the database: actually) Tj T* 0 Tw .62832 Tw (you can even read the names of the views and the columns, and get full autocompletion. All the entered) Tj T* 0 Tw 1.831647 Tw (commands are recorded and saved in the file ) Tj /F3 10 Tf 0 0 0 rg (~/.sql_interface.history ) Tj /F1 10 Tf 0 0 0 rg (when exiting from the) Tj T* 0 Tw (command-line interface.) Tj T* ET Q Q q 1 0 0 1 62.69291 204.6236 cm q BT 1 0 0 1 0 62 Tm 2.010574 Tw 12 TL /F1 10 Tf 0 0 0 rg (If the readline library is not available, my suggestion is to use the ) Tj 0 0 .501961 rg (rlwrap ) Tj 0 0 0 rg (tool which provides similar) Tj T* 0 Tw .22561 Tw (features, at least on Unix-like platforms. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (should also work fine on Windows with the ) Tj 0 0 .501961 rg (pyreadline ) Tj 0 0 0 rg (library) Tj T* 0 Tw .389989 Tw (\(I do not use Windows, so this part is very little tested: I tried it only once and it worked, but your mileage) Tj T* 0 Tw 2.206457 Tw (may vary\). For people worried about licenses, I will notice that ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (uses the readline library only if) Tj T* 0 Tw .591894 Tw (available, it does not include it and it does not rely on it in any fundamental way, so that the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (licence) Tj T* 0 Tw (does not need to be the GPL \(actually it is a BSD do-whatever-you-want-with-it licence\).) Tj T* ET Q Q q 1 0 0 1 62.69291 162.6236 cm q BT 1 0 0 1 0 26 Tm .187882 Tw 12 TL /F1 10 Tf 0 0 0 rg (The interactive mode of ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (can be used as a replacement of the ) Tj 0 0 .501961 rg (cmd ) Tj 0 0 0 rg (module in the standard library. It) Tj T* 0 Tw 3.130651 Tw (is actually better than ) Tj 0 0 .501961 rg (cmd) Tj 0 0 0 rg (: for instance, the ) Tj /F3 10 Tf 0 0 0 rg (help ) Tj /F1 10 Tf 0 0 0 rg (command is more powerful, since it provides) Tj T* 0 Tw (information about the arguments accepted by the given command:) Tj T* ET Q Q q 1 0 0 1 62.69291 93.42362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q BT 1 0 0 1 0 38 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( help set) Tj T* (usage: set name value) Tj T* T* (set name value) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (30) Tj T* -235.3849 0 Td ET Q Q endstream endobj 392 0 obj << /Length 4838 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 511.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 252 re B* Q q BT 1 0 0 1 0 230 Tm 12 TL /F3 10 Tf 0 0 0 rg T* (positional arguments:) Tj T* ( name) Tj T* ( value) Tj T* T* (i) Tj (>) Tj ( help delete) Tj T* (usage: delete [name]) Tj T* T* (delete given parameter \(or everything\)) Tj T* T* (positional arguments:) Tj T* ( name [None]) Tj T* T* (i) Tj (>) Tj ( help show) Tj T* (usage: show [names [names ...]]) Tj T* T* (show given parameters) Tj T* T* (positional arguments:) Tj T* ( names) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 455.8236 cm q BT 1 0 0 1 0 38 Tm 1.99436 Tw 12 TL /F1 10 Tf 0 0 0 rg (As you can imagine, the help message is provided by the underlying ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (subparser: there is a) Tj T* 0 Tw 3.257251 Tw (subparser for each command. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (commands accept options, flags, varargs, keyword arguments,) Tj T* 0 Tw .719318 Tw (arguments with defaults, arguments with a fixed number of choices, type conversion and all the features) Tj T* 0 Tw (provided of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 425.8236 cm q BT 1 0 0 1 0 14 Tm 1.78248 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover at the moment ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (also understands command abbreviations. However, this feature may) Tj T* 0 Tw (disappear in future releases. It was meaningful in the past, when ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (did not support readline.) Tj T* ET Q Q q 1 0 0 1 62.69291 407.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Notice that if an abbreviation is ambiguous, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (warns you:) Tj T* ET Q Q q 1 0 0 1 62.69291 362.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( sh) Tj T* (NameError: Ambiguous command 'sh': matching ['showall', 'show']) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 332.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The plac runner) Tj T* ET Q Q q 1 0 0 1 62.69291 266.6236 cm q BT 1 0 0 1 0 50 Tm 1.531318 Tw 12 TL /F1 10 Tf 0 0 0 rg (The distribution of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (includes a runner script named ) Tj /F3 10 Tf 0 0 0 rg (plac_runner.py) Tj /F1 10 Tf 0 0 0 rg (, which will be installed in a) Tj T* 0 Tw .44748 Tw (suitable directory in your system by ) Tj 0 0 .501961 rg (distutils ) Tj 0 0 0 rg (\(say in ) Tj /F3 10 Tf 0 0 0 rg (/usr/local/bin/plac_runner.py ) Tj /F1 10 Tf 0 0 0 rg (in a Unix-like) Tj T* 0 Tw .680651 Tw (operative system\). The runner provides many facilities to run ) Tj /F3 10 Tf 0 0 0 rg (.plac ) Tj /F1 10 Tf 0 0 0 rg (scripts and ) Tj /F3 10 Tf 0 0 0 rg (.placet ) Tj /F1 10 Tf 0 0 0 rg (files, as well) Tj T* 0 Tw 1.47311 Tw (as Python modules containg a ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (object, which can be a function, a command container object or) Tj T* 0 Tw (even a command container class.) Tj T* ET Q Q q 1 0 0 1 62.69291 236.6236 cm q BT 1 0 0 1 0 14 Tm 1.994269 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, suppose you want to execute a script containing commands defined in the ) Tj /F3 10 Tf 0 0 0 rg (ishelve2) Tj T* 0 Tw /F1 10 Tf 0 0 0 rg (module like the following one:) Tj T* ET Q Q q 1 0 0 1 62.69291 167.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q 0 0 0 rg BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL (#!ishelve2.py:ShelveInterface -c ~/conf.shelve) Tj T* (set a 1) Tj T* (del a) Tj T* (del a # intentional error) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 99.42362 cm q BT 1 0 0 1 0 50 Tm .575868 Tw 12 TL /F1 10 Tf 0 0 0 rg (The first line of the ) Tj /F3 10 Tf 0 0 0 rg (.plac ) Tj /F1 10 Tf 0 0 0 rg (script contains the name of the python module containing the plac interpreter) Tj T* 0 Tw 2.327209 Tw (and the arguments which must be passed to its main function in order to be able to instantiate an) Tj T* 0 Tw .202485 Tw (interpreter object. In this case I appended ) Tj /F3 10 Tf 0 0 0 rg (:ShelveInterface ) Tj /F1 10 Tf 0 0 0 rg (to the name of the module to specify the) Tj T* 0 Tw 1.030574 Tw (object that must be imported: if not specified, by default the object named 'main' is imported. The other) Tj T* 0 Tw (lines contains commands. You can run the script as follows:) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (31) Tj T* -235.3849 0 Td ET Q Q endstream endobj 393 0 obj << /Length 4643 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 679.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 492 84 re B* Q q 0 0 0 rg BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL ($ plac_runner.py --batch ishelve2.plac) Tj T* (setting a=1) Tj T* (deleting a) Tj T* (Traceback \(most recent call last\):) Tj T* ( ...) Tj T* (_bsddb.DBNotFoundError: \(-30988, 'DB_NOTFOUND: No matching key/data pair found'\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 647.8236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 2.79186 Tw (The last command intentionally contained an error, to show that the plac runner does not eat the) Tj T* 0 Tw (traceback.) Tj T* ET Q Q q 1 0 0 1 62.69291 617.8236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .437633 Tw (The runner can also be used to run Python modules in interactive mode and non-interactive mode. If you) Tj T* 0 Tw (put this alias in your bashrc) Tj T* ET Q Q q 1 0 0 1 62.69291 611.8236 cm Q q 1 0 0 1 62.69291 599.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (alias) Tj ( ) Tj (plac="plac_runner.py") Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 599.8236 cm Q q 1 0 0 1 62.69291 569.8236 cm q BT 1 0 0 1 0 14 Tm 2.955318 Tw 12 TL /F1 10 Tf 0 0 0 rg (\(or you define a suitable ) Tj /F3 10 Tf 0 0 0 rg (plac.bat ) Tj /F1 10 Tf 0 0 0 rg (script in Windows\) you can run the ) Tj /F3 10 Tf 0 0 0 rg (ishelve2.py ) Tj /F1 10 Tf 0 0 0 rg (script in) Tj T* 0 Tw (interactive mode as follows:) Tj T* ET Q Q q 1 0 0 1 62.69291 392.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 168 re B* Q q BT 1 0 0 1 0 146 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ plac -i ishelve2.py:ShelveInterface) Tj T* (A minimal interface over a shelve object.) Tj T* (Operating on /home/micheles/conf.shelve.) Tj T* (.help to see the available commands.) Tj T* T* (i) Tj (>) Tj ( del) Tj T* (deleting everything) Tj T* (i) Tj (>) Tj ( set a 1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( set b 2) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( show b) Tj T* (b = 2) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 372.6236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Now you can cut and paste the interactive session and turn it into a ) Tj /F3 10 Tf 0 0 0 rg (.placet ) Tj /F1 10 Tf 0 0 0 rg (file like the following:) Tj T* ET Q Q q 1 0 0 1 62.69291 231.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 132 re B* Q q BT 1 0 0 1 0 110 Tm 12 TL /F3 10 Tf 0 0 0 rg (#!ishelve2.py:ShelveInterface -configfile=~/test.shelve) Tj T* (# an example of a .placet file for the ShelveInterface) Tj T* (i) Tj (>) Tj ( del) Tj T* (deleting everything) Tj T* (i) Tj (>) Tj ( set a 1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( set b 2) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( show a) Tj T* (a = 1) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 199.4236 cm q BT 1 0 0 1 0 14 Tm 2.145697 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that the first line specifies a test database ) Tj /F3 10 Tf 0 0 0 rg (~/test.shelve) Tj /F1 10 Tf 0 0 0 rg (, to avoid clobbering your default) Tj T* 0 Tw (shelve. If you mispell the arguments in the first line plac will give you an ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (error message \(just try\).) Tj T* ET Q Q q 1 0 0 1 62.69291 181.4236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can run placets following the shebang convention directly with the plac runner:) Tj T* ET Q Q q 1 0 0 1 62.69291 136.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ plac --test ishelve2.placet) Tj T* (run 1 plac test\(s\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 92.22362 cm q BT 1 0 0 1 0 26 Tm .057882 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you want to see the output of the tests, pass the ) Tj /F3 10 Tf 0 0 0 rg (-v/--verbose ) Tj /F1 10 Tf 0 0 0 rg (flag. Notice that he runner ignores the) Tj T* 0 Tw .24856 Tw (extension, so you can actually use any extension your like, but ) Tj /F4 10 Tf (it relies on the first line of the file to invoke) Tj T* 0 Tw (the corresponding plac tool with the given arguments) Tj /F1 10 Tf (.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (32) Tj T* -235.3849 0 Td ET Q Q endstream endobj 394 0 obj << /Length 4510 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm q BT 1 0 0 1 0 14 Tm .537209 Tw 12 TL /F1 10 Tf 0 0 0 rg (The plac runner does not provide any test discovery facility, but you can use standard Unix tools to help.) Tj T* 0 Tw (For instance, you can run all the ) Tj /F3 10 Tf 0 0 0 rg (.placet ) Tj /F1 10 Tf 0 0 0 rg (files into a directory and its subdirectories as follows:) Tj T* ET Q Q q 1 0 0 1 62.69291 707.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 24 re B* Q q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL ($ find . -name \\*.placet | xargs plac_runner.py -t) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 675.8236 cm q BT 1 0 0 1 0 14 Tm .760988 Tw 12 TL /F1 10 Tf 0 0 0 rg (The plac runner expects the main function of your script to return a plac tool, i.e. a function or an object) Tj T* 0 Tw (with a ) Tj /F3 10 Tf 0 0 0 rg (.commands ) Tj /F1 10 Tf 0 0 0 rg (attribute. If this is not the case the runner exits gracefully.) Tj T* ET Q Q q 1 0 0 1 62.69291 657.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (It also works in non-interactive mode, if you call it as) Tj T* ET Q Q q 1 0 0 1 62.69291 651.8236 cm Q q 1 0 0 1 62.69291 639.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg ($) Tj ( ) Tj (plac) Tj ( ) Tj (module.py) Tj ( ) Tj (args) Tj ( ) Tj (...) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 639.8236 cm Q q 1 0 0 1 62.69291 621.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an example:) Tj T* ET Q Q q 1 0 0 1 62.69291 552.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q 0 0 0 rg BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL ($ plac ishelve.py a=1) Tj T* (setting a=1) Tj T* ($ plac ishelve.py .show) Tj T* (a=1) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 520.6236 cm q BT 1 0 0 1 0 14 Tm .01561 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that in non-interactive mode the runner just invokes ) Tj /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf 0 0 0 rg (on the ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (object of the Python) Tj T* 0 Tw (module.) Tj T* ET Q Q q 1 0 0 1 62.69291 490.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (A non class-based example) Tj T* ET Q Q q 1 0 0 1 62.69291 448.6236 cm q BT 1 0 0 1 0 26 Tm .907209 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not force you to use classes to define command containers. Even a simple function can be a) Tj T* 0 Tw 1.611318 Tw (valid command container, it is enough to add a ) Tj /F3 10 Tf 0 0 0 rg (.commands ) Tj /F1 10 Tf 0 0 0 rg (attribute to it, and possibly ) Tj /F3 10 Tf 0 0 0 rg (__enter__) Tj T* 0 Tw /F1 10 Tf 0 0 0 rg (and/or ) Tj /F3 10 Tf 0 0 0 rg (__exit__ ) Tj /F1 10 Tf 0 0 0 rg (attributes too.) Tj T* ET Q Q q 1 0 0 1 62.69291 418.6236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .327485 Tw (In particular, a Python module is a perfect container of commands. As an example, consider the following) Tj T* 0 Tw (module implementing a fake Version Control System:) Tj T* ET Q Q q 1 0 0 1 62.69291 97.42362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 312 re B* Q q 0 0 0 rg BT 1 0 0 1 0 290 Tm /F3 10 Tf 12 TL ("A Fake Version Control System") Tj T* T* (import plac # this implementation also works with Python 2.4) Tj T* T* (commands = 'checkout', 'commit', 'status') Tj T* T* (@plac.annotations\(url='url of the source code'\)) Tj T* (def checkout\(url\):) Tj T* ( "A fake checkout command") Tj T* ( return \('checkout ', url\)) Tj T* T* (@plac.annotations\(message=\('commit message', 'option'\)\)) Tj T* (def commit\(message\):) Tj T* ( "A fake commit command") Tj T* ( return \('commit ', message\)) Tj T* T* (@plac.annotations\(quiet=\('summary information', 'flag', 'q'\)\)) Tj T* (def status\(quiet\):) Tj T* ( "A fake status command") Tj T* ( return \('status ', quiet\)) Tj T* T* (def __missing__\(name\):) Tj T* ( return \('Command %r does not exist' % name,\)) Tj T* T* (def __exit__\(etype, exc, tb\):) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (33) Tj T* -235.3849 0 Td ET Q Q endstream endobj 395 0 obj << /Length 3928 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 643.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL ( "Will be called automatically at the end of the intepreter loop") Tj T* ( if etype in \(None, GeneratorExit\): # success) Tj T* ( print\('ok'\)) Tj T* T* (main = __import__\(__name__\) # the module imports itself!) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac) Tj T* ( for out in plac.call\(main\): print\(out\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 599.8236 cm q BT 1 0 0 1 0 26 Tm .431318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that I have defined both an ) Tj /F3 10 Tf 0 0 0 rg (__exit__ ) Tj /F1 10 Tf 0 0 0 rg (hook and a ) Tj /F3 10 Tf 0 0 0 rg (__missing__ ) Tj /F1 10 Tf 0 0 0 rg (hook, invoked for non-existing) Tj T* 0 Tw .592651 Tw (commands. The real trick here is the line ) Tj /F3 10 Tf 0 0 0 rg (main) Tj ( ) Tj (=) Tj ( ) Tj (__import__\(__name__\)) Tj /F1 10 Tf 0 0 0 rg (, which define ) Tj /F3 10 Tf 0 0 0 rg (main ) Tj /F1 10 Tf 0 0 0 rg (to be) Tj T* 0 Tw (an alias for the current module.) Tj T* ET Q Q q 1 0 0 1 62.69291 581.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf 0 0 0 rg (vcs ) Tj /F1 10 Tf 0 0 0 rg (module can be run through the plac runner \(try ) Tj /F3 10 Tf 0 0 0 rg (plac) Tj ( ) Tj (vcs.py) Tj ( ) Tj (-h) Tj /F1 10 Tf 0 0 0 rg (\):) Tj T* ET Q Q q 1 0 0 1 62.69291 416.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 156 re B* Q q 0 0 0 rg BT 1 0 0 1 0 134 Tm /F3 10 Tf 12 TL (usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...) Tj T* T* (A Fake Version Control System) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* T* (subcommands:) Tj T* ( {status,commit,checkout}) Tj T* ( checkout A fake checkout command) Tj T* ( commit A fake commit command) Tj T* ( status A fake status command) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 396.6236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (You can get help for the subcommands by inserting an ) Tj /F3 10 Tf 0 0 0 rg (-h ) Tj /F1 10 Tf 0 0 0 rg (after the name of the command:) Tj T* ET Q Q q 1 0 0 1 62.69291 279.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL ($ plac vcs.py status -h) Tj T* (usage: plac_runner.py vcs.py status [-h] [-q]) Tj T* T* (A fake status command) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -q, --quiet summary information) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 247.4236 cm q BT 1 0 0 1 0 14 Tm .962339 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice how the docstring of the command is automatically shown in the usage message, as well as the) Tj T* 0 Tw (documentation for the sub flag ) Tj /F3 10 Tf 0 0 0 rg (-q) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 229.4236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an example of a non-interactive session:) Tj T* ET Q Q q 1 0 0 1 62.69291 100.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL ($ plac vcs.py check url) Tj T* (checkout) Tj T* (url) Tj T* ($ plac vcs.py st -q) Tj T* (status) Tj T* (True) Tj T* ($ plac vcs.py co) Tj T* (commit) Tj T* (None) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (34) Tj T* -235.3849 0 Td ET Q Q endstream endobj 396 0 obj << /Length 6127 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 753.0236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (and here is an interactive session:) Tj T* ET Q Q q 1 0 0 1 62.69291 551.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 192 re B* Q q BT 1 0 0 1 0 170 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ plac -i vcs.py) Tj T* (usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...) Tj T* (i) Tj (>) Tj ( check url) Tj T* (checkout) Tj T* (url) Tj T* (i) Tj (>) Tj ( st -q) Tj T* (status) Tj T* (True) Tj T* (i) Tj (>) Tj ( co) Tj T* (commit) Tj T* (None) Tj T* (i) Tj (>) Tj ( sto) Tj T* (Command 'sto' does not exist) Tj T* (i) Tj (>) Tj ( [CTRL-D]) Tj T* (ok) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 519.8236 cm q BT 1 0 0 1 0 14 Tm 2.986905 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice the invocation of the ) Tj /F3 10 Tf 0 0 0 rg (__missing__ ) Tj /F1 10 Tf 0 0 0 rg (hook for non-existing commands. Notice also that the) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (__exit__ ) Tj /F1 10 Tf 0 0 0 rg (hook gets called only in interactive mode.) Tj T* ET Q Q q 1 0 0 1 62.69291 489.8236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 1.614104 Tw (If the commands are completely independent, a module is a good fit for a method container. In other) Tj T* 0 Tw (situations, it is best to use a custom class.) Tj T* ET Q Q q 1 0 0 1 62.69291 459.8236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Writing your own plac runner) Tj T* ET Q Q q 1 0 0 1 62.69291 405.8236 cm q BT 1 0 0 1 0 38 Tm .167209 Tw 12 TL /F1 10 Tf 0 0 0 rg (The runner included in the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (distribution is intentionally kept small \(around 50 lines of code\) so that you) Tj T* 0 Tw .395207 Tw (can study it and write your own runner if you want to. If you need to go to such level of detail, you should) Tj T* 0 Tw 1.559984 Tw (know that the most important method of the ) Tj /F3 10 Tf 0 0 0 rg (Interpreter ) Tj /F1 10 Tf 0 0 0 rg (class is the ) Tj /F3 10 Tf 0 0 0 rg (.send ) Tj /F1 10 Tf 0 0 0 rg (method, which takes) Tj T* 0 Tw (strings as input and returns a four elements tuple with attributes ) Tj /F3 10 Tf 0 0 0 rg (.str) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (.etype) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (.exc ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (.tb) Tj /F1 10 Tf 0 0 0 rg (:) Tj T* ET Q Q q 1 0 0 1 62.69291 399.8236 cm Q q 1 0 0 1 62.69291 399.8236 cm Q q 1 0 0 1 62.69291 387.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.str ) Tj /F1 10 Tf 0 0 0 rg (is the output of the command, if successful \(a string\);) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 381.8236 cm Q q 1 0 0 1 62.69291 369.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.etype ) Tj /F1 10 Tf 0 0 0 rg (is the class of the exception, if the command fails;) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 363.8236 cm Q q 1 0 0 1 62.69291 351.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.exc ) Tj /F1 10 Tf 0 0 0 rg (is the exception instance;) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 345.8236 cm Q q 1 0 0 1 62.69291 333.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.tb ) Tj /F1 10 Tf 0 0 0 rg (is the traceback.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 333.8236 cm Q q 1 0 0 1 62.69291 291.8236 cm q BT 1 0 0 1 0 26 Tm .763735 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover, the ) Tj /F3 10 Tf 0 0 0 rg (__str__ ) Tj /F1 10 Tf 0 0 0 rg (representation of the output object is redefined to return the output string if the) Tj T* 0 Tw 1.578735 Tw (command was successful, or the error message \(preceded by the name of the exception class\) if the) Tj T* 0 Tw (command failed.) Tj T* ET Q Q q 1 0 0 1 62.69291 273.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (For instance, if you send a mispelled option to the interpreter a ) Tj /F3 10 Tf 0 0 0 rg (SystemExit ) Tj /F1 10 Tf 0 0 0 rg (will be trapped:) Tj T* ET Q Q q 1 0 0 1 62.69291 180.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( import plac) Tj T* (>) Tj (>) Tj (>) Tj ( from ishelve import ishelve) Tj T* (>) Tj (>) Tj (>) Tj ( with plac.Interpreter\(ishelve\) as i:) Tj T* (... print\(i.send\('.cler'\)\)) Tj T* (...) Tj T* (SystemExit: unrecognized arguments: .cler) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 148.6236 cm q BT 1 0 0 1 0 14 Tm 2.90561 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is important to invoke the ) Tj /F3 10 Tf 0 0 0 rg (.send ) Tj /F1 10 Tf 0 0 0 rg (method inside the context manager, otherwise you will get a) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (RuntimeError) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 106.6236 cm q 0 0 0 rg BT 1 0 0 1 0 26 Tm /F1 10 Tf 12 TL .29311 Tw (For instance, suppose you want to implement a graphical runner for a plac-based interpreter with two text) Tj T* 0 Tw 1.548221 Tw (widgets: one to enter the commands and one to display the results. Suppose you want to display the) Tj T* 0 Tw (errors with tracebacks in red. You will need to code something like that \(pseudocode follows\):) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (35) Tj T* -235.3849 0 Td ET Q Q endstream endobj 397 0 obj << /Length 4368 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 535.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 228 re B* Q q 0 0 0 rg BT 1 0 0 1 0 206 Tm /F3 10 Tf 12 TL (input_widget = WidgetReadingInput\(\)) Tj T* (output_widget = WidgetDisplayingOutput\(\)) Tj T* T* (def send\(interpreter, line\):) Tj T* ( out = interpreter.send\(line\)) Tj T* ( if out.tb: # there was an error) Tj T* ( output_widget.display\(out.tb, color='red'\)) Tj T* ( else:) Tj T* ( output_widget.display\(out.str\)) Tj T* T* (main = plac.import_main\(tool_path\) # get the main object) Tj T* T* (with plac.Interpreter\(main\) as i:) Tj T* ( def callback\(event\):) Tj T* ( if event.user_pressed_ENTER\(\):) Tj T* ( send\(i, input_widget.last_line\)) Tj T* ( input_widget.addcallback\(callback\)) Tj T* ( gui_mainloop.start\(\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 503.8236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .102765 Tw (You can adapt the pseudocode to your GUI toolkit of choice and you can also change the file associations) Tj T* 0 Tw (in such a way that the graphical user interface starts when clicking on a plac tool file.) Tj T* ET Q Q q 1 0 0 1 62.69291 473.8236 cm q BT 1 0 0 1 0 14 Tm .398988 Tw 12 TL /F1 10 Tf 0 0 0 rg (An example of a GUI program built on top of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is given later on, in the paragraph ) Tj /F4 10 Tf (Managing the output) Tj T* 0 Tw (of concurrent commands ) Tj /F1 10 Tf (\(using Tkinter for simplicity and portability\).) Tj T* ET Q Q q 1 0 0 1 62.69291 419.8236 cm q BT 1 0 0 1 0 38 Tm 2.090651 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is a final ) Tj /F4 10 Tf (caveat) Tj /F1 10 Tf (: since the plac interpreter loop is implemented via extended generators, plac) Tj T* 0 Tw .988651 Tw (interpreters are single threaded: you will get an error if you ) Tj /F3 10 Tf 0 0 0 rg (.send ) Tj /F1 10 Tf 0 0 0 rg (commands from separated threads.) Tj T* 0 Tw .947882 Tw (You can circumvent the problem by using a queue. If EXIT is a sentinel value to signal exiting from the) Tj T* 0 Tw (interpreter loop, you can write code like this:) Tj T* ET Q Q q 1 0 0 1 62.69291 362.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q 0 0 0 rg BT 1 0 0 1 0 26 Tm /F3 10 Tf 12 TL (with interpreter:) Tj T* ( for input_value in iter\(input_queue.get, EXIT\):) Tj T* ( output_queue.put\(interpreter.send\(input_value\)\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 330.6236 cm q BT 1 0 0 1 0 14 Tm 1.257045 Tw 12 TL /F1 10 Tf 0 0 0 rg (The same trick also works for processes; you could run the interpreter loop in a separate process and) Tj T* 0 Tw (send commands to it via the Queue class provided by the ) Tj 0 0 .501961 rg (multiprocessing ) Tj 0 0 0 rg (module.) Tj T* ET Q Q q 1 0 0 1 62.69291 300.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Long running commands) Tj T* ET Q Q q 1 0 0 1 62.69291 258.6236 cm q BT 1 0 0 1 0 26 Tm 1.434431 Tw 12 TL /F1 10 Tf 0 0 0 rg (As we saw, by default a ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreter blocks until the command terminates. This is an issue, in the) Tj T* 0 Tw 1.201318 Tw (sense that it makes the interactive experience quite painful for long running commands. An example is) Tj T* 0 Tw (better than a thousand words, so consider the following fake importer:) Tj T* ET Q Q q 1 0 0 1 62.69291 93.42362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 156 re B* Q q 0 0 0 rg BT 1 0 0 1 0 134 Tm /F3 10 Tf 12 TL (import time) Tj T* (import plac) Tj T* T* (class FakeImporter\(object\):) Tj T* ( "A fake importer with an import_file command") Tj T* ( commands = ['import_file']) Tj T* ( def __init__\(self, dsn\):) Tj T* ( self.dsn = dsn) Tj T* ( def import_file\(self, fname\):) Tj T* ( "Import a file into the database") Tj T* ( try:) Tj T* ( for n in range\(10000\):) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (36) Tj T* -235.3849 0 Td ET Q Q endstream endobj 398 0 obj << /Length 4669 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 655.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 108 re B* Q q 0 0 0 rg BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL ( time.sleep\(.01\)) Tj T* ( if n % 100 == 99:) Tj T* ( yield 'Imported %d lines' % \(n+1\)) Tj T* ( finally:) Tj T* ( print\('closing the file'\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter.call\(FakeImporter\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 623.8236 cm q BT 1 0 0 1 0 14 Tm 1.466457 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you run the ) Tj /F3 10 Tf 0 0 0 rg (import_file ) Tj /F1 10 Tf 0 0 0 rg (command, you will have to wait for 200 seconds before entering a new) Tj T* 0 Tw (command:) Tj T* ET Q Q q 1 0 0 1 62.69291 482.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 132 re B* Q q BT 1 0 0 1 0 110 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python importer1.py dsn -i) Tj T* (A fake importer with an import_file command) Tj T* (i) Tj (>) Tj ( import_file file1) Tj T* (... ) Tj (<) Tj (wait 3+ minutes) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (Imported 300 lines) Tj T* (...) Tj T* (Imported 10000 lines) Tj T* (closing the file) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 438.6236 cm q BT 1 0 0 1 0 26 Tm .96832 Tw 12 TL /F1 10 Tf 0 0 0 rg (Being unable to enter any other command is quite annoying: in such situation one would like to run the) Tj T* 0 Tw .941318 Tw (long running commands in the background, to keep the interface responsive. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides two ways to) Tj T* 0 Tw (reach this goal: threads and processes.) Tj T* ET Q Q q 1 0 0 1 62.69291 408.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Threaded commands) Tj T* ET Q Q q 1 0 0 1 62.69291 378.6236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .317988 Tw (The most familiar way to execute a task in the background \(even if not necessarily the best way\) is to run) Tj T* 0 Tw (it into a separate thread. In our example it is sufficient to replace the line) Tj T* ET Q Q q 1 0 0 1 62.69291 372.6236 cm Q q 1 0 0 1 62.69291 360.6236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (commands) Tj ( ) Tj (=) Tj ( ) Tj (['import_file']) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 360.6236 cm Q q 1 0 0 1 62.69291 342.6236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (with) Tj T* ET Q Q q 1 0 0 1 62.69291 336.6236 cm Q q 1 0 0 1 62.69291 324.6236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (thcommands) Tj ( ) Tj (=) Tj ( ) Tj (['import_file']) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 324.6236 cm Q q 1 0 0 1 62.69291 294.6236 cm q BT 1 0 0 1 0 14 Tm 1.38311 Tw 12 TL /F1 10 Tf 0 0 0 rg (to tell to the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreter that the command ) Tj /F3 10 Tf 0 0 0 rg (import_file ) Tj /F1 10 Tf 0 0 0 rg (should be run into a separated thread.) Tj T* 0 Tw (Here is an example session:) Tj T* ET Q Q q 1 0 0 1 62.69291 249.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( import_file file1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] RUNNING) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 217.4236 cm q BT 1 0 0 1 0 14 Tm .595777 Tw 12 TL /F1 10 Tf 0 0 0 rg (The import task started in a separated thread. You can see the progress of the task by using the special) Tj T* 0 Tw (command ) Tj /F3 10 Tf 0 0 0 rg (.output) Tj /F1 10 Tf 0 0 0 rg (:) Tj T* ET Q Q q 1 0 0 1 62.69291 148.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q BT 1 0 0 1 0 38 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] RUNNING) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 128.2236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (If you look after a while, you will get more lines of output:) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (37) Tj T* -235.3849 0 Td ET Q Q endstream endobj 399 0 obj << /Length 5314 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 679.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] RUNNING) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (Imported 300 lines) Tj T* (Imported 400 lines) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 659.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (If you look after a time long enough, the task will be finished:) Tj T* ET Q Q q 1 0 0 1 62.69291 614.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] FINISHED) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 582.6236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .819573 Tw (It is possible to store the output of a task into a file, to be read later \(this is useful for tasks with a large) Tj T* 0 Tw (output\):) Tj T* ET Q Q q 1 0 0 1 62.69291 537.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1 /tmp/out.txt) Tj T* (saved output of 1 into /tmp/out.txt) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 505.4236 cm q BT 1 0 0 1 0 14 Tm 1.045868 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can even skip the number argument: then ) Tj /F3 10 Tf 0 0 0 rg (.output ) Tj /F1 10 Tf 0 0 0 rg (will the return the output of the last launched) Tj T* 0 Tw (command \(the special commands like .output do not count\).) Tj T* ET Q Q q 1 0 0 1 62.69291 487.4236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can launch many tasks one after the other:) Tj T* ET Q Q q 1 0 0 1 62.69291 418.2236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q BT 1 0 0 1 0 38 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( import_file file2) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] RUNNING) Tj (>) Tj T* (i) Tj (>) Tj ( import_file file3) Tj T* (<) Tj (ThreadedTask 6 [import_file file3] RUNNING) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 398.2236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf 0 0 0 rg (.list ) Tj /F1 10 Tf 0 0 0 rg (command displays all the running tasks:) Tj T* ET Q Q q 1 0 0 1 62.69291 341.0236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 48 re B* Q q BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .list) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] RUNNING) Tj (>) Tj T* (<) Tj (ThreadedTask 6 [import_file file3] RUNNING) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 321.0236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (It is even possible to kill a task:) Tj T* ET Q Q q 1 0 0 1 62.69291 227.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 84 re B* Q q BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .kill 5) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] TOBEKILLED) Tj (>) Tj T* (# wait a bit ...) Tj T* (closing the file) Tj T* (i) Tj (>) Tj ( .output 5) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] KILLED) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 135.8236 cm q BT 1 0 0 1 0 74 Tm .458409 Tw 12 TL /F1 10 Tf 0 0 0 rg (Note that since at the Python level it is impossible to kill a thread, the ) Tj /F3 10 Tf 0 0 0 rg (.kill ) Tj /F1 10 Tf 0 0 0 rg (command works by setting) Tj T* 0 Tw .089318 Tw (the status of the task to ) Tj /F3 10 Tf 0 0 0 rg (TOBEKILLED) Tj /F1 10 Tf 0 0 0 rg (. Internally the generator corresponding to the command is executed) Tj T* 0 Tw 1.993735 Tw (in the thread and the status is checked at each iteration: when the status becomes ) Tj /F3 10 Tf 0 0 0 rg (TOBEKILLED) Tj /F1 10 Tf 0 0 0 rg (, a) Tj T* 0 Tw 1.298555 Tw /F3 10 Tf 0 0 0 rg (GeneratorExit ) Tj /F1 10 Tf 0 0 0 rg (exception is raised and the thread terminates \(softly, so that the ) Tj /F3 10 Tf 0 0 0 rg (finally ) Tj /F1 10 Tf 0 0 0 rg (clause is) Tj T* 0 Tw .84811 Tw (honored\). In our example the generator is yielding back control once every 100 iterations, i.e. every two) Tj T* 0 Tw 2.029988 Tw (seconds \(not much\). In order to get a responsive interface it is a good idea to yield more often, for) Tj T* 0 Tw (instance every 10 iterations \(i.e. 5 times per second\), as in the following code:) Tj T* ET Q Q q 1 0 0 1 62.69291 90.62362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (import time) Tj T* (import plac) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (38) Tj T* -235.3849 0 Td ET Q Q endstream endobj 400 0 obj << /Length 4673 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 511.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 252 re B* Q q 0 0 0 rg BT 1 0 0 1 0 230 Tm /F3 10 Tf 12 TL T* (class FakeImporter\(object\):) Tj T* ( "A fake importer with an import_file command") Tj T* ( thcommands = ['import_file']) Tj T* ( def __init__\(self, dsn\):) Tj T* ( self.dsn = dsn) Tj T* ( def import_file\(self, fname\):) Tj T* ( "Import a file into the database") Tj T* ( try:) Tj T* ( for n in range\(10000\):) Tj T* ( time.sleep\(.02\)) Tj T* ( if n % 100 == 99: # every two seconds) Tj T* ( yield 'Imported %d lines' % \(n+1\)) Tj T* ( if n % 10 == 9: # every 0.2 seconds) Tj T* ( yield # go back and check the TOBEKILLED status) Tj T* ( finally:) Tj T* ( print\('closing the file'\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter.call\(FakeImporter\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 481.8236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Running commands as external processes) Tj T* ET Q Q q 1 0 0 1 62.69291 427.8236 cm q BT 1 0 0 1 0 38 Tm 2.30686 Tw 12 TL /F1 10 Tf 0 0 0 rg (Threads are not loved much in the Python world and actually most people prefer to use processes) Tj T* 0 Tw 3.350697 Tw (instead. For this reason ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides the option to execute long running commands as external) Tj T* 0 Tw .329069 Tw (processes. Unfortunately the current implementation only works on Unix-like operating systems \(including) Tj T* 0 Tw (Mac OS/X\) because it relies on fork via the ) Tj 0 0 .501961 rg (multiprocessing ) Tj 0 0 0 rg (module.) Tj T* ET Q Q q 1 0 0 1 62.69291 409.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (In our example, to enable the feature it is sufficient to replace the line) Tj T* ET Q Q q 1 0 0 1 62.69291 403.8236 cm Q q 1 0 0 1 62.69291 391.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (thcommands) Tj ( ) Tj (=) Tj ( ) Tj (['import_file']) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 391.8236 cm Q q 1 0 0 1 62.69291 373.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (with) Tj T* ET Q Q q 1 0 0 1 62.69291 367.8236 cm Q q 1 0 0 1 62.69291 355.8236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET BT 1 0 0 1 0 2 Tm T* ET q 1 0 0 1 20 0 cm q BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (mpcommands) Tj ( ) Tj (=) Tj ( ) Tj (['import_file']) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 355.8236 cm Q q 1 0 0 1 62.69291 325.8236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .772619 Tw (The user experience is exactly the same as with threads and you will not see any difference at the user) Tj T* 0 Tw (interface level:) Tj T* ET Q Q q 1 0 0 1 62.69291 184.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 132 re B* Q q BT 1 0 0 1 0 110 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( import_file file3) Tj T* (<) Tj (MPTask 1 [import_file file3] SUBMITTED) Tj (>) Tj T* (i) Tj (>) Tj ( .kill 1) Tj T* (<) Tj (MPTask 1 [import_file file3] RUNNING) Tj (>) Tj T* (closing the file) Tj T* (i) Tj (>) Tj ( .output 1) Tj T* (<) Tj (MPTask 1 [import_file file3] KILLED) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (i) Tj (>) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 128.6236 cm q 0 0 0 rg BT 1 0 0 1 0 38 Tm /F1 10 Tf 12 TL 1.201318 Tw (Still, using processes is quite different than using threads: in particular, when using processes you can) Tj T* 0 Tw 2.313318 Tw (only yield pickleable values and you cannot re-raise an exception first raised in a different process,) Tj T* 0 Tw 1.445697 Tw (because traceback objects are not pickleable. Moreover, you cannot rely on automatic sharing of your) Tj T* 0 Tw (objects.) Tj T* ET Q Q q 1 0 0 1 62.69291 98.62362 cm q BT 1 0 0 1 0 14 Tm .128935 Tw 12 TL /F1 10 Tf 0 0 0 rg (On the plus side, when using processes you do not need to worry about killing a command: they are killed ) Tj T* 0 Tw .546412 Tw (immediately using a SIGTERM signal, and there is no ) Tj /F3 10 Tf 0 0 0 rg (TOBEKILLED ) Tj /F1 10 Tf 0 0 0 rg (mechanism. Moreover, the killing is) Tj T* 0 Tw ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (39) Tj T* -235.3849 0 Td ET Q Q endstream endobj 401 0 obj << /Length 4630 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm q BT 1 0 0 1 0 14 Tm 2.460814 Tw 12 TL /F1 10 Tf 0 0 0 rg (guaranteed to be soft: internally a command receiving a SIGTERM raises a ) Tj /F3 10 Tf 0 0 0 rg (TerminatedProcess) Tj T* 0 Tw /F1 10 Tf 0 0 0 rg (exception which is trapped in the generator loop, so that the command is closed properly.) Tj T* ET Q Q q 1 0 0 1 62.69291 711.0236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 2.307485 Tw (Using processes allows one to take full advantage of multicore machines and it is safer than using) Tj T* 0 Tw (threads, so it is the recommended approach unless you are working on Windows.) Tj T* ET Q Q q 1 0 0 1 62.69291 681.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Managing the output of concurrent commands) Tj T* ET Q Q q 1 0 0 1 62.69291 567.0236 cm q BT 1 0 0 1 0 98 Tm 1.895542 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (acts as a command-line task launcher and can be used as the base to build a GUI-based task) Tj T* 0 Tw .38561 Tw (launcher and task monitor. To this aim the interpreter class provides a ) Tj /F3 10 Tf 0 0 0 rg (.submit ) Tj /F1 10 Tf 0 0 0 rg (method which returns a) Tj T* 0 Tw 1.792339 Tw (task object and a ) Tj /F3 10 Tf 0 0 0 rg (.tasks ) Tj /F1 10 Tf 0 0 0 rg (method returning the list of all the tasks submitted to the interpreter. The) Tj T* 0 Tw .373516 Tw /F3 10 Tf 0 0 0 rg (submit ) Tj /F1 10 Tf 0 0 0 rg (method does not start the task and thus it is nonblocking. Each task has an ) Tj /F3 10 Tf 0 0 0 rg (.outlist ) Tj /F1 10 Tf 0 0 0 rg (attribute) Tj T* 0 Tw .106098 Tw (which is a list storing the value yielded by the generator underlying the task \(the ) Tj /F3 10 Tf 0 0 0 rg (None ) Tj /F1 10 Tf 0 0 0 rg (values are skipped) Tj T* 0 Tw .633318 Tw (though\): the ) Tj /F3 10 Tf 0 0 0 rg (.outlist ) Tj /F1 10 Tf 0 0 0 rg (grows as the task runs and more values are yielded. Accessing the ) Tj /F3 10 Tf 0 0 0 rg (.outlist) Tj T* 0 Tw 1.051654 Tw /F1 10 Tf 0 0 0 rg (is nonblocking and can be done freely. Finally there is a ) Tj /F3 10 Tf 0 0 0 rg (.result ) Tj /F1 10 Tf 0 0 0 rg (property which waits for the task to) Tj T* 0 Tw .830574 Tw (finish and returns the last yielded value or raises an exception. The code below provides an example of) Tj T* 0 Tw (how you could implement a GUI over the importer example:) Tj T* ET Q Q q 1 0 0 1 62.69291 185.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 372 re B* Q q 0 0 0 rg BT 1 0 0 1 0 350 Tm /F3 10 Tf 12 TL (from __future__ import with_statement) Tj T* (from Tkinter import *) Tj T* (from importer3 import FakeImporter) Tj T* T* (def taskwidget\(root, task, tick=500\):) Tj T* ( "A Label widget showing the output of a task every 500 ms") Tj T* ( sv = StringVar\(root\)) Tj T* ( lb = Label\(root, textvariable=sv\)) Tj T* ( def show_outlist\(\):) Tj T* ( try:) Tj T* ( out = task.outlist[-1]) Tj T* ( except IndexError: # no output yet) Tj T* ( out = '') Tj T* ( sv.set\('%s %s' % \(task, out\)\)) Tj T* ( root.after\(tick, show_outlist\)) Tj T* ( root.after\(0, show_outlist\)) Tj T* ( return lb) Tj T* T* (def monitor\(tasks\):) Tj T* ( root = Tk\(\)) Tj T* ( for task in tasks:) Tj T* ( task.run\(\)) Tj T* ( taskwidget\(root, task\).pack\(\)) Tj T* ( root.mainloop\(\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac) Tj T* ( with plac.Interpreter\(plac.call\(FakeImporter\)\) as i:) Tj T* ( tasks = [i.submit\('import_file f1'\), i.submit\('import_file f2'\)]) Tj T* ( monitor\(tasks\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 152.8236 cm q BT 1 0 0 1 0 3.5 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Experimental features) Tj T* ET Q Q q 1 0 0 1 62.69291 98.82362 cm q BT 1 0 0 1 0 38 Tm .047045 Tw 12 TL /F1 10 Tf 0 0 0 rg (The distribution of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (includes a few experimental features which I am not committed to fully support and) Tj T* 0 Tw .121988 Tw (that may go away in future versions. They are included as examples of things that you may build on top of) Tj T* 0 Tw 1.615868 Tw 0 0 .501961 rg (plac) Tj 0 0 0 rg (: the aim is to give you ideas. Some of the experimental features might grow to become external) Tj T* 0 Tw (projects built on ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (40) Tj T* -235.3849 0 Td ET Q Q endstream endobj 402 0 obj << /Length 4749 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 747.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Parallel computing with plac) Tj T* ET Q Q q 1 0 0 1 62.69291 633.0236 cm q BT 1 0 0 1 0 98 Tm 1.174751 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is certainly not intended as a tool for parallel computing, but still you can use it to launch a set of) Tj T* 0 Tw .497984 Tw (commands and collect the results, similarly to the MapReduce pattern popularized by Google. In order to) Tj T* 0 Tw 1.362927 Tw (give an example, I will consider the "Hello World" of parallel computing, i.e. the computation of pi with) Tj T* 0 Tw .537633 Tw (independent processes. There is a huge number of algorithms to compute pi; here I will describe a trivial) Tj T* 0 Tw .13104 Tw (one chosen for simplicity, not for efficiency. The trick is to consider the first quadrant of a circle with radius) Tj T* 0 Tw .602488 Tw (1 and to extract a number of points ) Tj /F3 10 Tf 0 0 0 rg (\(x,) Tj ( ) Tj (y\) ) Tj /F1 10 Tf 0 0 0 rg (with ) Tj /F3 10 Tf 0 0 0 rg (x ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (y ) Tj /F1 10 Tf 0 0 0 rg (random variables in the interval ) Tj /F3 10 Tf 0 0 0 rg ([0,1]) Tj /F1 10 Tf 0 0 0 rg (. The) Tj T* 0 Tw .928876 Tw (probability of extracting a number inside the quadrant \(i.e. with ) Tj /F3 10 Tf 0 0 0 rg (x^2) Tj ( ) Tj (+) Tj ( ) Tj (y^2) Tj ( ) Tj (<) Tj ( ) Tj (1) Tj /F1 10 Tf 0 0 0 rg (\) is proportional to the) Tj T* 0 Tw .433145 Tw (area of the quadrant \(i.e. ) Tj /F3 10 Tf 0 0 0 rg (pi/4) Tj /F1 10 Tf 0 0 0 rg (\). The value of ) Tj /F3 10 Tf 0 0 0 rg (pi ) Tj /F1 10 Tf 0 0 0 rg (therefore can be extracted by multiplying by 4 the ratio) Tj T* 0 Tw (between the number of points in the quadrant versus the total number of points ) Tj /F3 10 Tf 0 0 0 rg (N) Tj /F1 10 Tf 0 0 0 rg (, for ) Tj /F3 10 Tf 0 0 0 rg (N ) Tj /F1 10 Tf 0 0 0 rg (large:) Tj T* ET Q Q q 1 0 0 1 62.69291 527.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 96 re B* Q q BT 1 0 0 1 0 74 Tm 12 TL /F3 10 Tf 0 0 0 rg (def calc_pi\(N\):) Tj T* ( inside = 0) Tj T* ( for j in xrange\(N\):) Tj T* ( x, y = random\(\), random\(\)) Tj T* ( if x*x + y*y ) Tj (<) Tj ( 1:) Tj T* ( inside += 1) Tj T* ( return \(4.0 * inside\) / N) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 459.8236 cm q 0 0 0 rg BT 1 0 0 1 0 50 Tm /F1 10 Tf 12 TL .046654 Tw (The algorithm is trivially parallelizable: if you have n CPUs, you can compute pi n times with N/n iterations,) Tj T* 0 Tw 1.122488 Tw (sum the results and divide the total by n. I have a Macbook with two cores, therefore I would expect a) Tj T* 0 Tw 2.347984 Tw (speedup factor of 2 with respect to a sequential computation. Moreover, I would expect a threaded) Tj T* 0 Tw 2.827984 Tw (computation to be even slower than a sequential computation, due to the GIL and the scheduling) Tj T* 0 Tw (overhead.) Tj T* ET Q Q q 1 0 0 1 62.69291 429.8236 cm q BT 1 0 0 1 0 14 Tm .313984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here is a script implementing the algorithm and working in three different modes \(parallel mode, threaded) Tj T* 0 Tw (mode and sequential mode\) depending on a ) Tj /F3 10 Tf 0 0 0 rg (mode ) Tj /F1 10 Tf 0 0 0 rg (option:) Tj T* ET Q Q q 1 0 0 1 62.69291 96.62362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 324 re B* Q q BT 1 0 0 1 0 302 Tm 12 TL /F3 10 Tf 0 0 0 rg (# -*- coding: utf-8 -*-) Tj T* (from __future__ import unicode_literals) Tj T* (from __future__ import with_statement) Tj T* (from __future__ import division) Tj T* (import math) Tj T* (from random import random) Tj T* (import multiprocessing) Tj T* (import plac) Tj T* T* T* (class PiCalculator\(object\):) Tj T* ( """Compute \\u03C0 in parallel with threads or processes""") Tj T* T* ( @plac.annotations\() Tj T* ( npoints=\('number of integration points', 'positional', None, int\),) Tj T* ( mode=\('sequential|parallel|threaded', 'option', 'm', str, 'SPT'\)\)) Tj T* ( def __init__\(self, npoints, mode='S'\):) Tj T* ( self.npoints = npoints) Tj T* ( if mode == 'P':) Tj T* ( self.mpcommands = ['calc_pi']) Tj T* ( elif mode == 'T':) Tj T* ( self.thcommands = ['calc_pi']) Tj T* ( elif mode == 'S':) Tj T* ( self.commands = ['calc_pi']) Tj T* ( self.n_cpu = multiprocessing.cpu_count\(\)) Tj T* T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (41) Tj T* -235.3849 0 Td ET Q Q endstream endobj 403 0 obj << /Length 3582 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 235.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 528 re B* Q q BT 1 0 0 1 0 506 Tm 12 TL /F3 10 Tf 0 0 0 rg ( def submit_tasks\(self\):) Tj T* ( npoints = math.ceil\(self.npoints / self.n_cpu\)) Tj T* ( self.i = plac.Interpreter\(self\).__enter__\(\)) Tj T* ( return [self.i.submit\('calc_pi %d' % npoints\)) Tj T* ( for _ in range\(self.n_cpu\)]) Tj T* T* ( def close\(self\):) Tj T* ( self.i.close\(\)) Tj T* T* ( @plac.annotations\(npoints=\('npoints', 'positional', None, int\)\)) Tj T* ( def calc_pi\(self, npoints\):) Tj T* ( counts = 0) Tj T* ( for j in range\(npoints\):) Tj T* ( n, r = divmod\(j, 1000000\)) Tj T* ( if r == 0:) Tj T* ( yield '%dM iterations' % n) Tj T* ( x, y = random\(\), random\(\)) Tj T* ( if x*x + y*y ) Tj (<) Tj ( 1:) Tj T* ( counts += 1) Tj T* ( yield \(4.0 * counts\) / npoints) Tj T* T* ( def run\(self\):) Tj T* ( tasks = self.i.tasks\(\)) Tj T* ( for t in tasks:) Tj T* ( t.run\(\)) Tj T* ( try:) Tj T* ( total = 0) Tj T* ( for task in tasks:) Tj T* ( total += task.result) Tj T* ( except: # the task was killed) Tj T* ( print\(tasks\)) Tj T* ( return) Tj T* ( return total / self.n_cpu) Tj T* T* (if __name__ == '__main__':) Tj T* ( pc = plac.call\(PiCalculator\)) Tj T* ( pc.submit_tasks\(\)) Tj T* ( try:) Tj T* ( import time) Tj T* ( t0 = time.time\(\)) Tj T* ( print\('%f in %f seconds ' % \(pc.run\(\), time.time\(\) - t0\)\)) Tj T* ( finally:) Tj T* ( pc.close\(\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 155.8236 cm q BT 1 0 0 1 0 62 Tm .381797 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice the ) Tj /F3 10 Tf 0 0 0 rg (submit_tasks ) Tj /F1 10 Tf 0 0 0 rg (method, which instantiates and initializes a ) Tj /F3 10 Tf 0 0 0 rg (plac.Interpreter ) Tj /F1 10 Tf 0 0 0 rg (object and) Tj T* 0 Tw 3.38152 Tw (submits a number of commands corresponding to the number of available CPUs. The ) Tj /F3 10 Tf 0 0 0 rg (calc_pi) Tj T* 0 Tw 2.06561 Tw /F1 10 Tf 0 0 0 rg (command yields a log message for each million interactions, in order to monitor the progress of the) Tj T* 0 Tw 1.751318 Tw (computation. The ) Tj /F3 10 Tf 0 0 0 rg (run ) Tj /F1 10 Tf 0 0 0 rg (method starts all the submitted commands in parallel and sums the results. It) Tj T* 0 Tw 1.17104 Tw (returns the average value of ) Tj /F3 10 Tf 0 0 0 rg (pi ) Tj /F1 10 Tf 0 0 0 rg (after the slowest CPU has finished its job \(if the CPUs are equal and) Tj T* 0 Tw (equally busy they should finish more or less at the same time\).) Tj T* ET Q Q q 1 0 0 1 62.69291 137.8236 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here are the results on my old Macbook with Ubuntu 10.04 and Python 2.6, for 10 million of iterations:) Tj T* ET Q Q q 1 0 0 1 62.69291 92.62362 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python picalculator.py -mP 10000000 # two processes) Tj T* (3.141904 in 5.744545 seconds) Tj T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (42) Tj T* -235.3849 0 Td ET Q Q endstream endobj 404 0 obj << /Length 5555 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 703.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 60 re B* Q q 0 0 0 rg BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL ($ python picalculator.py -mT 10000000 # two threads) Tj T* (3.141272 in 13.875645 seconds) Tj T* ($ python picalculator.py -mS 10000000 # sequential) Tj T* (3.141586 in 11.353841 seconds) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 671.8236 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 1.711751 Tw (As you see using processes one gets a 2x speedup indeed, where the threaded mode is some 20%) Tj T* 0 Tw (slower than the sequential mode.) Tj T* ET Q Q q 1 0 0 1 62.69291 617.8236 cm q BT 1 0 0 1 0 38 Tm .051098 Tw 12 TL /F1 10 Tf 0 0 0 rg (Since the pattern "submit a bunch of tasks, start them and collect the results" is so common, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides) Tj T* 0 Tw .38561 Tw (an utility function ) Tj /F3 10 Tf 0 0 0 rg (runp\(genseq,) Tj ( ) Tj (mode='p'\) ) Tj /F1 10 Tf 0 0 0 rg (to start a bunch of generators and return a list of results.) Tj T* 0 Tw .45436 Tw (By default ) Tj /F3 10 Tf 0 0 0 rg (runp ) Tj /F1 10 Tf 0 0 0 rg (use processes, but you can use threads by passing ) Tj /F3 10 Tf 0 0 0 rg (mode='t') Tj /F1 10 Tf 0 0 0 rg (. With ) Tj /F3 10 Tf 0 0 0 rg (runp ) Tj /F1 10 Tf 0 0 0 rg (the parallel) Tj T* 0 Tw (pi calculation becomes a one-liner:) Tj T* ET Q Q q 1 0 0 1 62.69291 584.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 480 24 re B* Q q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL (sum\(task.result for task in plac.runp\(calc_pi\(N\) for i in range\(ncpus\)\)\)/ncpus) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 552.6236 cm q BT 1 0 0 1 0 14 Tm .00936 Tw 12 TL /F1 10 Tf 0 0 0 rg (The file ) Tj /F3 10 Tf 0 0 0 rg (test_runp ) Tj /F1 10 Tf 0 0 0 rg (in the ) Tj /F3 10 Tf 0 0 0 rg (doc ) Tj /F1 10 Tf 0 0 0 rg (directory of the plac distribution shows another usage example. Note that) Tj T* 0 Tw (if one of the tasks fails for some reason, you will get the exception object instead of the result.) Tj T* ET Q Q q 1 0 0 1 62.69291 522.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Monitor support) Tj T* ET Q Q q 1 0 0 1 62.69291 408.6236 cm q BT 1 0 0 1 0 98 Tm 2.968443 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides experimental support for monitoring the output of concurrent commands, at least for) Tj T* 0 Tw 1.727126 Tw (platforms where multiprocessing is fully supported. You can define your own monitor class, simply by) Tj T* 0 Tw 5.37872 Tw (inheriting from ) Tj /F3 10 Tf 0 0 0 rg (plac.Monitor ) Tj /F1 10 Tf 0 0 0 rg (and overriding the methods ) Tj /F3 10 Tf 0 0 0 rg (add_listener\(self,) Tj ( ) Tj (taskno\)) Tj /F1 10 Tf 0 0 0 rg (,) Tj T* 0 Tw 25.38744 Tw /F3 10 Tf 0 0 0 rg (del_listener\(self,) Tj ( ) Tj (taskno\)) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (notify_listener\(self,) Tj ( ) Tj (taskno,) Tj ( ) Tj (msg\)) Tj /F1 10 Tf 0 0 0 rg (,) Tj T* 0 Tw 2.276647 Tw /F3 10 Tf 0 0 0 rg (read_queue\(self\)) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F3 10 Tf 0 0 0 rg (start\(self\) ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (stop\(self\)) Tj /F1 10 Tf 0 0 0 rg (. Then you can add a monitor object to any) Tj T* 0 Tw .14748 Tw /F3 10 Tf 0 0 0 rg (plac.Interpreter ) Tj /F1 10 Tf 0 0 0 rg (object by calling the ) Tj /F3 10 Tf 0 0 0 rg (add_monitor ) Tj /F1 10 Tf 0 0 0 rg (method. For convenience, ) Tj /F3 10 Tf 0 0 0 rg (plac ) Tj /F1 10 Tf 0 0 0 rg (comes with a) Tj T* 0 Tw .539431 Tw (very simple ) Tj /F3 10 Tf 0 0 0 rg (TkMonitor ) Tj /F1 10 Tf 0 0 0 rg (based on Tkinter \(I chose Tkinter because it is easy to use and in the standard) Tj T* 0 Tw .383516 Tw (library, but you can use any GUI\): you can look at how the ) Tj /F3 10 Tf 0 0 0 rg (TkMonitor ) Tj /F1 10 Tf 0 0 0 rg (is implemented in ) Tj /F3 10 Tf 0 0 0 rg (plac_tk.py) Tj T* 0 Tw /F1 10 Tf 0 0 0 rg (and adapt it. Here is an usage example of the ) Tj /F3 10 Tf 0 0 0 rg (TkMonitor) Tj /F1 10 Tf 0 0 0 rg (:) Tj T* ET Q Q q 1 0 0 1 62.69291 219.4236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 180 re B* Q q 0 0 0 rg BT 1 0 0 1 0 158 Tm /F3 10 Tf 12 TL (from __future__ import with_statement) Tj T* (import plac) Tj T* T* (class Hello\(object\):) Tj T* ( mpcommands = ['hello', 'quit']) Tj T* ( def hello\(self\):) Tj T* ( yield 'hello') Tj T* ( def quit\(self\):) Tj T* ( raise plac.Interpreter.Exit) Tj T* T* (if __name__ == '__main__':) Tj T* ( i = plac.Interpreter\(Hello\(\)\)) Tj T* ( i.add_monitor\(plac.TkMonitor\('tkmon'\)\)) Tj T* ( i.interact\(\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 175.4236 cm q BT 1 0 0 1 0 26 Tm .607209 Tw 12 TL /F1 10 Tf 0 0 0 rg (Try to run the ) Tj /F3 10 Tf 0 0 0 rg (hello ) Tj /F1 10 Tf 0 0 0 rg (command in the interactive interpreter: each time, a new text widget will be added) Tj T* 0 Tw .295868 Tw (displaying the output of the command. Note that if ) Tj /F3 10 Tf 0 0 0 rg (Tkinter ) Tj /F1 10 Tf 0 0 0 rg (is not installed correctly on your system, the) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (TkMonitor ) Tj /F1 10 Tf 0 0 0 rg (class will not be available.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (43) Tj T* -235.3849 0 Td ET Q Q endstream endobj 405 0 obj << /Length 6271 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 747.0236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The plac server) Tj T* ET Q Q q 1 0 0 1 62.69291 597.0236 cm q BT 1 0 0 1 0 134 Tm 1.258443 Tw 12 TL /F1 10 Tf 0 0 0 rg (A command-line oriented interface can be easily converted into a socket-based interface. Starting from) Tj T* 0 Tw 1.47561 Tw (release 0.7 ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (features a built-in server which is able to accept commands from multiple clients and) Tj T* 0 Tw .994692 Tw (execute them. The server works by instantiating a separate interpreter for each client, so that if a client) Tj T* 0 Tw .889269 Tw (interpreter dies for any reason, the other interpreters keep working. To avoid external dependencies the) Tj T* 0 Tw .872209 Tw (server is based on the ) Tj /F3 10 Tf 0 0 0 rg (asynchat ) Tj /F1 10 Tf 0 0 0 rg (module in the standard library, but it would not be difficult to replace) Tj T* 0 Tw .538876 Tw (the server with a different one \(for instance, a Twisted server\). Notice that at the moment the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (server) Tj T* 0 Tw 1.042488 Tw (does not work with to Python 3.2+ due to changes to ) Tj /F3 10 Tf 0 0 0 rg (asynchat) Tj /F1 10 Tf 0 0 0 rg (. In time I will fix this and other known) Tj T* 0 Tw .175697 Tw (issues. You should consider the server functionality still experimental and subject to changes. Also, notice) Tj T* 0 Tw 1.02248 Tw (that since ) Tj /F3 10 Tf 0 0 0 rg (asynchat) Tj /F1 10 Tf 0 0 0 rg (-based servers are asynchronous, any blocking command in the interpreter should) Tj T* 0 Tw .232488 Tw (be run in a separated process or thread. The default port for the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (server is 2199, and the command to) Tj T* 0 Tw 2.609984 Tw (signal end-of-connection is EOF. For instance, here is how you could manage remote import on a) Tj T* 0 Tw (database \(say a SQLite db\):) Tj T* ET Q Q q 1 0 0 1 62.69291 467.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (import plac) Tj T* (from importer2 import FakeImporter) Tj T* T* (def main\(port=2199\):) Tj T* ( main = FakeImporter\('dsn'\)) Tj T* ( plac.Interpreter\(main\).start_server\(port\)) Tj T* ( ) Tj T* (if __name__ == '__main__':) Tj T* ( plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 447.8236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (You can connect to the server with ) Tj /F3 10 Tf 0 0 0 rg (telnet ) Tj /F1 10 Tf 0 0 0 rg (on port 2199, as follows:) Tj T* ET Q Q q 1 0 0 1 62.69291 270.6236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 168 re B* Q q BT 1 0 0 1 0 146 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ telnet localhost 2199) Tj T* (Trying ::1...) Tj T* (Trying 127.0.0.1...) Tj T* (Connected to localhost.) Tj T* (Escape character is '^]'.) Tj T* (i) Tj (>) Tj ( import_file f1) Tj T* (i) Tj (>) Tj ( .list) Tj T* (<) Tj (ThreadedTask 1 [import_file f1] RUNNING) Tj (>) Tj T* (i) Tj (>) Tj ( .out) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (i) Tj (>) Tj ( EOF) Tj T* (Connection closed by foreign host.) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 240.6236 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Summary) Tj T* ET Q Q q 1 0 0 1 62.69291 198.6236 cm q BT 1 0 0 1 0 26 Tm 2.203318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Once ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (claimed to be the easiest command-line arguments parser in the world. Having read this) Tj T* 0 Tw .673322 Tw (document you may think that it is not so easy after all. But it is a false impression. Actually the rules are) Tj T* 0 Tw (quite simple:) Tj T* ET Q Q q 1 0 0 1 62.69291 192.6236 cm Q q 1 0 0 1 62.69291 192.6236 cm Q q 1 0 0 1 62.69291 180.6236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (1.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (if you want to implement a command-line script, use ) Tj /F3 10 Tf 0 0 0 rg (plac.call) Tj /F1 10 Tf 0 0 0 rg (;) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 174.6236 cm Q q 1 0 0 1 62.69291 126.6236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 33 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (2.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 33 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (if you want to implement a command interpreter, use ) Tj /F3 10 Tf 0 0 0 rg (plac.Interpreter) Tj /F1 10 Tf 0 0 0 rg (:) Tj T* ET Q Q q 1 0 0 1 23 27 cm Q q 1 0 0 1 23 27 cm Q q 1 0 0 1 23 15 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (for an interactive interpreter, call the ) Tj /F3 10 Tf 0 0 0 rg (.interact ) Tj /F1 10 Tf 0 0 0 rg (method;) Tj T* ET Q Q q Q Q q 1 0 0 1 23 9 cm Q q 1 0 0 1 23 -3 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 -3 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (for a batch interpreter, call the ) Tj /F3 10 Tf 0 0 0 rg (.execute ) Tj /F1 10 Tf 0 0 0 rg (method;) Tj T* ET Q Q q Q Q q 1 0 0 1 23 -3 cm Q q Q Q q 1 0 0 1 62.69291 120.6236 cm Q q 1 0 0 1 62.69291 96.62362 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (3.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm 5.126647 Tw 12 TL /F1 10 Tf 0 0 0 rg (for testing call the ) Tj /F3 10 Tf 0 0 0 rg (Interpreter.check ) Tj /F1 10 Tf 0 0 0 rg (method in the appropriate context or use the) Tj T* 0 Tw /F3 10 Tf 0 0 0 rg (Interpreter.doctest ) Tj /F1 10 Tf 0 0 0 rg (feature;) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 90.62362 cm Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (44) Tj T* -235.3849 0 Td ET Q Q endstream endobj 406 0 obj << /Length 4980 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (4.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm 1.356457 Tw 12 TL /F1 10 Tf 0 0 0 rg (if you need to go to a lower level, you may need to call the ) Tj /F3 10 Tf 0 0 0 rg (Interpreter.send ) Tj /F1 10 Tf 0 0 0 rg (method which) Tj T* 0 Tw (returns a \(finished\) ) Tj /F3 10 Tf 0 0 0 rg (Task ) Tj /F1 10 Tf 0 0 0 rg (object;) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 735.0236 cm Q q 1 0 0 1 62.69291 711.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (5.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm 1.112126 Tw 12 TL /F1 10 Tf 0 0 0 rg (long running commands can be executed in the background as threads or processes: just declare) Tj T* 0 Tw (them in the lists ) Tj /F3 10 Tf 0 0 0 rg (thcommands ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (mpcommands ) Tj /F1 10 Tf 0 0 0 rg (respectively;) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 705.0236 cm Q q 1 0 0 1 62.69291 681.0236 cm 0 0 0 rg BT /F1 10 Tf 12 TL ET q 1 0 0 1 6 9 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (6.) Tj T* -5.66 0 Td ET Q Q q 1 0 0 1 23 -3 cm q BT 1 0 0 1 0 14 Tm 2.171647 Tw 12 TL /F1 10 Tf 0 0 0 rg (the ) Tj /F3 10 Tf 0 0 0 rg (.start_server ) Tj /F1 10 Tf 0 0 0 rg (method starts an asynchronous server on the given port number \(default) Tj T* 0 Tw (2199\).) Tj T* ET Q Q q Q Q q 1 0 0 1 62.69291 681.0236 cm Q q 1 0 0 1 62.69291 663.0236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Moreover, remember that ) Tj /F3 10 Tf 0 0 0 rg (plac_runner.py ) Tj /F1 10 Tf 0 0 0 rg (is your friend.) Tj T* ET Q Q q 1 0 0 1 62.69291 634.6772 cm n 0 14.17323 m 469.8898 14.17323 l S Q q 1 0 0 1 62.69291 604.6772 cm q BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Appendix: custom annotation objects) Tj T* ET Q Q q 1 0 0 1 62.69291 574.6772 cm q BT 1 0 0 1 0 14 Tm 1.097318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (uses an ) Tj /F3 10 Tf 0 0 0 rg (Annotation ) Tj /F1 10 Tf 0 0 0 rg (class to convert the tuples in the function signature to annotation) Tj T* 0 Tw (objects, i.e. objects with six attributes: ) Tj /F3 10 Tf 0 0 0 rg (help,) Tj ( ) Tj (kind,) Tj ( ) Tj (short,) Tj ( ) Tj (type,) Tj ( ) Tj (choices,) Tj ( ) Tj (metavar) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET Q Q q 1 0 0 1 62.69291 544.6772 cm q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .083735 Tw (Advanced users can implement their own annotation objects. For instance, here is an example of how you) Tj T* 0 Tw (could implement annotations for positional arguments:) Tj T* ET Q Q q 1 0 0 1 62.69291 415.4772 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 120 re B* Q q 0 0 0 rg BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (# annotations.py) Tj T* (class Positional\(object\):) Tj T* ( def __init__\(self, help='', type=None, choices=None, metavar=None\):) Tj T* ( self.help = help) Tj T* ( self.kind = 'positional') Tj T* ( self.abbrev = None) Tj T* ( self.type = type) Tj T* ( self.choices = choices) Tj T* ( self.metavar = metavar) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 395.4772 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can use such annotation objects as follows:) Tj T* ET Q Q q 1 0 0 1 62.69291 218.2772 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 168 re B* Q q 0 0 0 rg BT 1 0 0 1 0 146 Tm /F3 10 Tf 12 TL (# example11.py) Tj T* (import plac) Tj T* (from annotations import Positional) Tj T* T* (@plac.annotations\() Tj T* ( i=Positional\("This is an int", int\),) Tj T* ( n=Positional\("This is a float", float\),) Tj T* ( rest=Positional\("Other arguments"\)\)) Tj T* (def main\(i, n, *rest\):) Tj T* ( print\(i, n, rest\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 198.2772 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage message you get:) Tj T* ET Q Q q 1 0 0 1 62.69291 93.07717 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 96 re B* Q q 0 0 0 rg BT 1 0 0 1 0 74 Tm /F3 10 Tf 12 TL (usage: example11.py [-h] i n [rest [rest ...]]) Tj T* T* (positional arguments:) Tj T* ( i This is an int) Tj T* ( n This is a float) Tj T* ( rest Other arguments) Tj T* T* ET Q Q Q Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (45) Tj T* -235.3849 0 Td ET Q Q endstream endobj 407 0 obj << /Length 1051 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 727.8236 cm q q 1 0 0 1 0 0 cm q 1 0 0 1 6.6 6.6 cm q .662745 .662745 .662745 RG .5 w .960784 .960784 .862745 rg n -6 -6 468.6898 36 re B* Q q 0 0 0 rg BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET Q Q Q Q Q q 1 0 0 1 62.69291 683.8236 cm q BT 1 0 0 1 0 26 Tm .713516 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can go on and define ) Tj /F3 10 Tf 0 0 0 rg (Option ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F3 10 Tf 0 0 0 rg (Flag ) Tj /F1 10 Tf 0 0 0 rg (classes, if you like. Using custom annotation objects you) Tj T* 0 Tw .17528 Tw (could do advanced things like extracting the annotations from a configuration file or from a database, but I) Tj T* 0 Tw (expect such use cases to be quite rare: the default mechanism should work pretty well for most users.) Tj T* ET Q Q q 1 0 0 1 56.69291 56.69291 cm q 0 0 0 rg BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (46) Tj T* -235.3849 0 Td ET Q Q endstream endobj 408 0 obj << /Nums [ 0 409 0 R 1 410 0 R 2 411 0 R 3 412 0 R 4 413 0 R 5 414 0 R 6 415 0 R 7 416 0 R 8 417 0 R 9 418 0 R 10 419 0 R 11 420 0 R 12 421 0 R 13 422 0 R 14 423 0 R 15 424 0 R 16 425 0 R 17 426 0 R 18 427 0 R 19 428 0 R 20 429 0 R 21 430 0 R 22 431 0 R 23 432 0 R 24 433 0 R 25 434 0 R 26 435 0 R 27 436 0 R 28 437 0 R 29 438 0 R 30 439 0 R 31 440 0 R 32 441 0 R 33 442 0 R 34 443 0 R 35 444 0 R 36 445 0 R 37 446 0 R 38 447 0 R 39 448 0 R 40 449 0 R 41 450 0 R 42 451 0 R 43 452 0 R 44 453 0 R 45 454 0 R ] >> endobj 409 0 obj << /S /D /St 1 >> endobj 410 0 obj << /S /D /St 2 >> endobj 411 0 obj << /S /D /St 3 >> endobj 412 0 obj << /S /D /St 4 >> endobj 413 0 obj << /S /D /St 5 >> endobj 414 0 obj << /S /D /St 6 >> endobj 415 0 obj << /S /D /St 7 >> endobj 416 0 obj << /S /D /St 8 >> endobj 417 0 obj << /S /D /St 9 >> endobj 418 0 obj << /S /D /St 10 >> endobj 419 0 obj << /S /D /St 11 >> endobj 420 0 obj << /S /D /St 12 >> endobj 421 0 obj << /S /D /St 13 >> endobj 422 0 obj << /S /D /St 14 >> endobj 423 0 obj << /S /D /St 15 >> endobj 424 0 obj << /S /D /St 16 >> endobj 425 0 obj << /S /D /St 17 >> endobj 426 0 obj << /S /D /St 18 >> endobj 427 0 obj << /S /D /St 19 >> endobj 428 0 obj << /S /D /St 20 >> endobj 429 0 obj << /S /D /St 21 >> endobj 430 0 obj << /S /D /St 22 >> endobj 431 0 obj << /S /D /St 23 >> endobj 432 0 obj << /S /D /St 24 >> endobj 433 0 obj << /S /D /St 25 >> endobj 434 0 obj << /S /D /St 26 >> endobj 435 0 obj << /S /D /St 27 >> endobj 436 0 obj << /S /D /St 28 >> endobj 437 0 obj << /S /D /St 29 >> endobj 438 0 obj << /S /D /St 30 >> endobj 439 0 obj << /S /D /St 31 >> endobj 440 0 obj << /S /D /St 32 >> endobj 441 0 obj << /S /D /St 33 >> endobj 442 0 obj << /S /D /St 34 >> endobj 443 0 obj << /S /D /St 35 >> endobj 444 0 obj << /S /D /St 36 >> endobj 445 0 obj << /S /D /St 37 >> endobj 446 0 obj << /S /D /St 38 >> endobj 447 0 obj << /S /D /St 39 >> endobj 448 0 obj << /S /D /St 40 >> endobj 449 0 obj << /S /D /St 41 >> endobj 450 0 obj << /S /D /St 42 >> endobj 451 0 obj << /S /D /St 43 >> endobj 452 0 obj << /S /D /St 44 >> endobj 453 0 obj << /S /D /St 45 >> endobj 454 0 obj << /S /D /St 46 >> endobj xref 0 455 0000000000 65535 f 0000000075 00000 n 0000000140 00000 n 0000000250 00000 n 0000000365 00000 n 0000000554 00000 n 0000000741 00000 n 0000000928 00000 n 0000001036 00000 n 0000001277 00000 n 0000001447 00000 n 0000001618 00000 n 0000001790 00000 n 0000001962 00000 n 0000002134 00000 n 0000002306 00000 n 0000002479 00000 n 0000002652 00000 n 0000002825 00000 n 0000002998 00000 n 0000003171 00000 n 0000003344 00000 n 0000003517 00000 n 0000003690 00000 n 0000003863 00000 n 0000004036 00000 n 0000004209 00000 n 0000004382 00000 n 0000004555 00000 n 0000004728 00000 n 0000004901 00000 n 0000005074 00000 n 0000005247 00000 n 0000005420 00000 n 0000005593 00000 n 0000005766 00000 n 0000005939 00000 n 0000006112 00000 n 0000006285 00000 n 0000006458 00000 n 0000006631 00000 n 0000006804 00000 n 0000006977 00000 n 0000007150 00000 n 0000007323 00000 n 0000007496 00000 n 0000007669 00000 n 0000007842 00000 n 0000008015 00000 n 0000008188 00000 n 0000008361 00000 n 0000008534 00000 n 0000008707 00000 n 0000008880 00000 n 0000009053 00000 n 0000009226 00000 n 0000009399 00000 n 0000009572 00000 n 0000009745 00000 n 0000009918 00000 n 0000010091 00000 n 0000010264 00000 n 0000010437 00000 n 0000010610 00000 n 0000010783 00000 n 0000010956 00000 n 0000011129 00000 n 0000011302 00000 n 0000011475 00000 n 0000011648 00000 n 0000011821 00000 n 0000011994 00000 n 0000012167 00000 n 0000012340 00000 n 0000012513 00000 n 0000012686 00000 n 0000012859 00000 n 0000013032 00000 n 0000013205 00000 n 0000013378 00000 n 0000013551 00000 n 0000013724 00000 n 0000014479 00000 n 0000014652 00000 n 0000014825 00000 n 0000015023 00000 n 0000015222 00000 n 0000015408 00000 n 0000015593 00000 n 0000015795 00000 n 0000015983 00000 n 0000016169 00000 n 0000016357 00000 n 0000016543 00000 n 0000016731 00000 n 0000016917 00000 n 0000017105 00000 n 0000017292 00000 n 0000017480 00000 n 0000017599 00000 n 0000017939 00000 n 0000018137 00000 n 0000018338 00000 n 0000018524 00000 n 0000018713 00000 n 0000018902 00000 n 0000019090 00000 n 0000019277 00000 n 0000019466 00000 n 0000019655 00000 n 0000019952 00000 n 0000020138 00000 n 0000020327 00000 n 0000020516 00000 n 0000020705 00000 n 0000020962 00000 n 0000021151 00000 n 0000021340 00000 n 0000021565 00000 n 0000021814 00000 n 0000022003 00000 n 0000022228 00000 n 0000022415 00000 n 0000022604 00000 n 0000022793 00000 n 0000023058 00000 n 0000023271 00000 n 0000023460 00000 n 0000023649 00000 n 0000023890 00000 n 0000024079 00000 n 0000024268 00000 n 0000024509 00000 n 0000024698 00000 n 0000024885 00000 n 0000025072 00000 n 0000025259 00000 n 0000025448 00000 n 0000025635 00000 n 0000025908 00000 n 0000026121 00000 n 0000026310 00000 n 0000026493 00000 n 0000026707 00000 n 0000026956 00000 n 0000027145 00000 n 0000027333 00000 n 0000027574 00000 n 0000027762 00000 n 0000027949 00000 n 0000028136 00000 n 0000028323 00000 n 0000028512 00000 n 0000028699 00000 n 0000028886 00000 n 0000029075 00000 n 0000029264 00000 n 0000029451 00000 n 0000029640 00000 n 0000029827 00000 n 0000030016 00000 n 0000030205 00000 n 0000030402 00000 n 0000030591 00000 n 0000030780 00000 n 0000030976 00000 n 0000031173 00000 n 0000031362 00000 n 0000031549 00000 n 0000031802 00000 n 0000031991 00000 n 0000032408 00000 n 0000032632 00000 n 0000032819 00000 n 0000033006 00000 n 0000033195 00000 n 0000033384 00000 n 0000033573 00000 n 0000033846 00000 n 0000034033 00000 n 0000034266 00000 n 0000034455 00000 n 0000034644 00000 n 0000034833 00000 n 0000035027 00000 n 0000035219 00000 n 0000035415 00000 n 0000035602 00000 n 0000035791 00000 n 0000035980 00000 n 0000036167 00000 n 0000036356 00000 n 0000036545 00000 n 0000036870 00000 n 0000037058 00000 n 0000037254 00000 n 0000037443 00000 n 0000037632 00000 n 0000037821 00000 n 0000038015 00000 n 0000038204 00000 n 0000038391 00000 n 0000038579 00000 n 0000038766 00000 n 0000038954 00000 n 0000039143 00000 n 0000039330 00000 n 0000039517 00000 n 0000039706 00000 n 0000039895 00000 n 0000040084 00000 n 0000040273 00000 n 0000040462 00000 n 0000040659 00000 n 0000040847 00000 n 0000041072 00000 n 0000041261 00000 n 0000041507 00000 n 0000041694 00000 n 0000041881 00000 n 0000042105 00000 n 0000042292 00000 n 0000042479 00000 n 0000042668 00000 n 0000042857 00000 n 0000043342 00000 n 0000043531 00000 n 0000043720 00000 n 0000043909 00000 n 0000044098 00000 n 0000044287 00000 n 0000044467 00000 n 0000044656 00000 n 0000044845 00000 n 0000045034 00000 n 0000045223 00000 n 0000045423 00000 n 0000045740 00000 n 0000045953 00000 n 0000046142 00000 n 0000046375 00000 n 0000046564 00000 n 0000046753 00000 n 0000046942 00000 n 0000047139 00000 n 0000047328 00000 n 0000047526 00000 n 0000047714 00000 n 0000047995 00000 n 0000048193 00000 n 0000048382 00000 n 0000048571 00000 n 0000048759 00000 n 0000048947 00000 n 0000049212 00000 n 0000049411 00000 n 0000049600 00000 n 0000049789 00000 n 0000049978 00000 n 0000050235 00000 n 0000050424 00000 n 0000050613 00000 n 0000050809 00000 n 0000051058 00000 n 0000051244 00000 n 0000051477 00000 n 0000051690 00000 n 0000051879 00000 n 0000052112 00000 n 0000052305 00000 n 0000052494 00000 n 0000052697 00000 n 0000052886 00000 n 0000053075 00000 n 0000053271 00000 n 0000053467 00000 n 0000053748 00000 n 0000053935 00000 n 0000054124 00000 n 0000054311 00000 n 0000054500 00000 n 0000054689 00000 n 0000054878 00000 n 0000055068 00000 n 0000055349 00000 n 0000055536 00000 n 0000055769 00000 n 0000055958 00000 n 0000056191 00000 n 0000056404 00000 n 0000056592 00000 n 0000056825 00000 n 0000057014 00000 n 0000057222 00000 n 0000057411 00000 n 0000057660 00000 n 0000057849 00000 n 0000058038 00000 n 0000058279 00000 n 0000058492 00000 n 0000058681 00000 n 0000058889 00000 n 0000059130 00000 n 0000059319 00000 n 0000059507 00000 n 0000059696 00000 n 0000059885 00000 n 0000060142 00000 n 0000060331 00000 n 0000060564 00000 n 0000060777 00000 n 0000060966 00000 n 0000061155 00000 n 0000061396 00000 n 0000061585 00000 n 0000061774 00000 n 0000061963 00000 n 0000062152 00000 n 0000062409 00000 n 0000062598 00000 n 0000062831 00000 n 0000063044 00000 n 0000063157 00000 n 0000063420 00000 n 0000063501 00000 n 0000063694 00000 n 0000063832 00000 n 0000063985 00000 n 0000064138 00000 n 0000064303 00000 n 0000064444 00000 n 0000064592 00000 n 0000064728 00000 n 0000064870 00000 n 0000065010 00000 n 0000065149 00000 n 0000065305 00000 n 0000065457 00000 n 0000065590 00000 n 0000065732 00000 n 0000065921 00000 n 0000066042 00000 n 0000066205 00000 n 0000066354 00000 n 0000066492 00000 n 0000066633 00000 n 0000066780 00000 n 0000066924 00000 n 0000067063 00000 n 0000067201 00000 n 0000067349 00000 n 0000067500 00000 n 0000067644 00000 n 0000067784 00000 n 0000067945 00000 n 0000068096 00000 n 0000068268 00000 n 0000068405 00000 n 0000068543 00000 n 0000068681 00000 n 0000068811 00000 n 0000068955 00000 n 0000069394 00000 n 0000072393 00000 n 0000082066 00000 n 0000088072 00000 n 0000092624 00000 n 0000096983 00000 n 0000100940 00000 n 0000106026 00000 n 0000110047 00000 n 0000115335 00000 n 0000119566 00000 n 0000125415 00000 n 0000130421 00000 n 0000134415 00000 n 0000137997 00000 n 0000147212 00000 n 0000152765 00000 n 0000157995 00000 n 0000164309 00000 n 0000171763 00000 n 0000177022 00000 n 0000181079 00000 n 0000186702 00000 n 0000193531 00000 n 0000199695 00000 n 0000204502 00000 n 0000209211 00000 n 0000212625 00000 n 0000218080 00000 n 0000223073 00000 n 0000227985 00000 n 0000232881 00000 n 0000237582 00000 n 0000242150 00000 n 0000246136 00000 n 0000252321 00000 n 0000256747 00000 n 0000261474 00000 n 0000266846 00000 n 0000271577 00000 n 0000276265 00000 n 0000281072 00000 n 0000284712 00000 n 0000290325 00000 n 0000296654 00000 n 0000301692 00000 n 0000302801 00000 n 0000303369 00000 n 0000303407 00000 n 0000303445 00000 n 0000303483 00000 n 0000303521 00000 n 0000303559 00000 n 0000303597 00000 n 0000303635 00000 n 0000303673 00000 n 0000303711 00000 n 0000303750 00000 n 0000303789 00000 n 0000303828 00000 n 0000303867 00000 n 0000303906 00000 n 0000303945 00000 n 0000303984 00000 n 0000304023 00000 n 0000304062 00000 n 0000304101 00000 n 0000304140 00000 n 0000304179 00000 n 0000304218 00000 n 0000304257 00000 n 0000304296 00000 n 0000304335 00000 n 0000304374 00000 n 0000304413 00000 n 0000304452 00000 n 0000304491 00000 n 0000304530 00000 n 0000304569 00000 n 0000304608 00000 n 0000304647 00000 n 0000304686 00000 n 0000304725 00000 n 0000304764 00000 n 0000304803 00000 n 0000304842 00000 n 0000304881 00000 n 0000304920 00000 n 0000304959 00000 n 0000304998 00000 n 0000305037 00000 n 0000305076 00000 n 0000305115 00000 n trailer << /ID % ReportLab generated PDF document -- digest (http://www.reportlab.com) [(\031s\220N\004\241\226\325\0177\213c\374\222\240\024) (\031s\220N\004\241\226\325\0177\213c\374\222\240\024)] /Info 322 0 R /Root 321 0 R /Size 455 >> startxref 305154 %%EOF plac-0.9.6/doc/dry_run.py0000664000175000017500000000030412740075202016302 0ustar michelemichele00000000000000def main(dry_run: ('Dry run', 'flag', 'd')): if dry_run: print('Doing nothing') else: print('Doing something') if __name__ == '__main__': import plac; plac.call(main) plac-0.9.6/doc/example8.py0000664000175000017500000000032412740075202016345 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/test_server.py0000664000175000017500000000174612740075202017200 0ustar michelemichele00000000000000import multiprocessing, subprocess, random, time import plac from ishelve2 import ShelveInterface i = plac.Interpreter(ShelveInterface(configfile=None)) COMMANDS = ['''\ help set a 1 ''', '''\ set b 1 wrong command showall '''] def telnet(commands, port): po = subprocess.Popen(['telnet', 'localhost', str(port)], stdin=subprocess.PIPE) try: for cmd in commands.splitlines(): po.stdin.write((cmd + '\n').encode('ascii')) time.sleep(.1) # wait a bit for the server to answer finally: po.stdin.close() def test(): port = random.choice(range(2000, 20000)) server = multiprocessing.Process(target=i.start_server, args=(port,)) server.start() clients = [] for cmds in COMMANDS: cl = multiprocessing.Process(target=telnet, args=(cmds, port)) clients.append(cl) cl.start() for cl in clients: cl.join() server.terminate() # should trap the output and check it plac-0.9.6/doc/tkmon.py0000664000175000017500000000051612740075202015755 0ustar michelemichele00000000000000from __future__ import with_statement import plac class Hello(object): mpcommands = ['hello', 'quit'] def hello(self): yield 'hello' def quit(self): raise plac.Interpreter.Exit if __name__ == '__main__': i = plac.Interpreter(Hello()) i.add_monitor(plac.TkMonitor('tkmon')) i.interact() plac-0.9.6/doc/read_stdin.py0000664000175000017500000000044712740075202016744 0ustar michelemichele00000000000000""" 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)) plac-0.9.6/doc/example5.help0000664000175000017500000000033512740075202016644 0ustar michelemichele00000000000000usage: example5.py [-h] dsn [table] [today] Do something on the database positional arguments: dsn table [product] today [YYYY-MM-DD] optional arguments: -h, --help show this help message and exit plac-0.9.6/doc/ishelve3.py0000664000175000017500000000037212740075202016347 0ustar michelemichele00000000000000# ishelve3.py from ishelve2 import ShelveInterface if __name__ == '__main__': import plac; plac.Interpreter.call(ShelveInterface) ## try the following: # $ python ishelve3.py delete # $ python ishelve3.py set a 1 # $ python ishelve3.py showall plac-0.9.6/doc/example10.help0000664000175000017500000000035412740075202016721 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/picalculator.py0000664000175000017500000000367212740075216017322 0ustar michelemichele00000000000000# -*- coding: utf-8 -*- from __future__ import with_statement from __future__ import division import math from random import random import multiprocessing import plac class PiCalculator(object): """Compute \u03C0 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): npoints = math.ceil(self.npoints / self.n_cpu) self.i = plac.Interpreter(self).__enter__() return [self.i.submit('calc_pi %d' % npoints) 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 range(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() plac-0.9.6/doc/test_plac.py0000664000175000017500000001645112740075202016610 0ustar michelemichele00000000000000""" The tests are runnable with nose, with py.test, or even as standalone script """ import os import sys import datetime import doctest import subprocess import plac import plac_core sys_argv0 = sys.argv[0] docdir = os.path.dirname(os.path.abspath(__file__)) os.chdir(docdir) PLAC_RUNNER = os.path.join(os.path.dirname(docdir), 'plac_runner.py') # ####################### helpers ###################### # def fix_today(text): return text.replace('YYYY-MM-DD', str(datetime.date.today())) 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_core._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 = fix_today(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' p5 = parser_from(lambda dry_run=False: None, dry_run=('Dry run', 'flag', 'x')) def test_p5(): arg = p5.parse_args(['--dry-run']) assert arg.dry_run is True, arg.dry_run 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_sub_help(): c = Cmds() c.add_help = True expect(SystemExit, plac.call, c, ['commit', '-h']) 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.rst', 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, '-b', batch] def test_placet(): for placet in os.listdir('.'): if placet.endswith('.placet'): yield check_script, [PLAC_RUNNER, '-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) plac-0.9.6/doc/test_runp.py0000664000175000017500000000111012740075202016637 0ustar michelemichele00000000000000""" This test should work on Linux if you have Tkinter installed. """ from __future__ import with_statement import plac, time def gen(n): for i in range(n + 1): yield str(i) time.sleep(.1) def err(): yield 1/0 def test1(): assert ['3', '5', '10'] == plac.runp([gen(3), gen(5), gen(10)]) def test2(): result, error = plac.runp([gen(3), err()]) assert result == '3' and error.__class__ == ZeroDivisionError def test3(): t0 = time.time() plac.runp([gen(9), gen(9)]) assert int(time.time() - t0) == 1 # it must take 1 second, not 2 plac-0.9.6/doc/example3.help0000664000175000017500000000023412740075202016640 0ustar michelemichele00000000000000usage: example3.py [-h] dsn Do something with the database positional arguments: dsn optional arguments: -h, --help show this help message and exit plac-0.9.6/doc/example9.py0000664000175000017500000000033612740075202016351 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/test_pi.py0000664000175000017500000000034312740075202016272 0ustar michelemichele00000000000000from picalculator import PiCalculator def test(): pc = PiCalculator(10, 'T') tasks = pc.submit_tasks() for task in tasks: task.run() print(sum(task.result for task in tasks) / pc.n_cpu) pc.close() plac-0.9.6/doc/example6.py0000664000175000017500000000030312740075202016340 0ustar michelemichele00000000000000# example6.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) plac-0.9.6/doc/example5_.py0000664000175000017500000000043612740075202016505 0ustar michelemichele00000000000000# example5_.py from datetime import date # the first example with a function annotation def main(dsn: "the database dsn", table='product', today=date.today()): "Do something on the database" print(dsn, table, today) if __name__ == '__main__': import plac; plac.call(main) plac-0.9.6/doc/example5.py0000664000175000017500000000033212740075202016341 0ustar michelemichele00000000000000# example5.py from datetime import date def main(dsn, table='product', today=date.today()): "Do something on the database" print(dsn, table, today) if __name__ == '__main__': import plac; plac.call(main) plac-0.9.6/doc/importer2.py0000664000175000017500000000126712740075202016554 0ustar michelemichele00000000000000import 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) plac-0.9.6/doc/example12.help0000664000175000017500000000037012740075202016721 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/dbcli.py0000664000175000017500000000167512740075202015711 0ustar michelemichele00000000000000# dbcli.py import plac from sqlsoup import SQLSoup @plac.annotations( db=plac.Annotation("Connection string", type=SQLSoup), header=plac.Annotation("Header", 'flag', 'H'), sqlcmd=plac.Annotation("SQL command", 'option', 'c', str, metavar="SQL"), delimiter=plac.Annotation("Column separator", 'option', 'd'), scripts=plac.Annotation("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(open(script).read()) yield 'executed %s' % script if __name__ == '__main__': for output in plac.call(main): print(output) plac-0.9.6/doc/importer3.py0000664000175000017500000000105312740075202016546 0ustar michelemichele00000000000000import 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) plac-0.9.6/doc/shelve_interpreter.help0000664000175000017500000000065312740075202021040 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/example7.py0000664000175000017500000000037712740075202016354 0ustar michelemichele00000000000000# 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) plac-0.9.6/doc/sql_interface.py0000664000175000017500000000173612740075202017451 0ustar michelemichele00000000000000import os import plac from sqlsoup import SQLSoup SQLKEYWORDS = set(['help', '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> ') plac-0.9.6/doc/example9.help0000664000175000017500000000030312740075202016643 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/dbcli.help0000664000175000017500000000063212740075202016201 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/test_ishelve_more.py0000664000175000017500000000052312740075202020343 0ustar michelemichele00000000000000# test_ishelve_more.py from __future__ import with_statement import ishelve import plac 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') plac-0.9.6/doc/example7_.help0000664000175000017500000000035312740075202017005 0ustar michelemichele00000000000000usage: 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 plac-0.9.6/doc/example8_.help0000664000175000017500000000037212740075202017007 0ustar michelemichele00000000000000usage: example8_.py [-h] [-c select * from table] dsn positional arguments: dsn optional arguments: -h, --help show this help message and exit -c select * from table, --command select * from table SQL query plac-0.9.6/doc/example10.py0000664000175000017500000000104112740075202016413 0ustar michelemichele00000000000000# 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)) plac-0.9.6/plac.egg-info/0000775000175000017500000000000012740075376016131 5ustar michelemichele00000000000000plac-0.9.6/plac.egg-info/PKG-INFO0000664000175000017500000000370012740075371017221 0ustar michelemichele00000000000000Metadata-Version: 1.1 Name: plac Version: 0.9.6 Summary: The smartest command line arguments parser in the world Home-page: https://github.com/micheles/plac Author: Michele Simionato Author-email: michele.simionato@gmail.com License: BSD License Description: Installation ------------- If you are lazy, just perform :: $ pip install plac which will install the module on your system (and possibly argparse too, if it is not already installed). 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 Testing -------- Run :: $ python doc/test_plac.py or :: $ nosetests doc or :: $ py.test doc Some tests will fail if sqlsoup is not installed. Run a ``pip install sqlsoup`` or just ignore them. Documentation -------------- The source code and the documentation are hosted on GitHub. Here is the full documentation: https://github.com/micheles/plac/blob/0.9.6/doc/plac.pdf Keywords: command line arguments parser Platform: All Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities plac-0.9.6/plac.egg-info/top_level.txt0000664000175000017500000000004012740075371020650 0ustar michelemichele00000000000000plac plac_core plac_ext plac_tk plac-0.9.6/plac.egg-info/dependency_links.txt0000664000175000017500000000000112740075371022172 0ustar michelemichele00000000000000 plac-0.9.6/plac.egg-info/not-zip-safe0000664000175000017500000000000112740075357020356 0ustar michelemichele00000000000000 plac-0.9.6/plac.egg-info/SOURCES.txt0000664000175000017500000000226212740075371020012 0ustar michelemichele00000000000000CHANGES.md MANIFEST.in README.rst plac.py plac_core.py plac_ext.py plac_runner.py plac_tk.py setup.cfg setup.py doc/annotations.py doc/dbcli.help doc/dbcli.py doc/dry_run.py doc/example1.py doc/example10.help doc/example10.py doc/example11.help doc/example11.py doc/example12.help doc/example12.py doc/example2.py doc/example3.help doc/example3.py doc/example4.py doc/example5.help doc/example5.py doc/example5_.py doc/example6.help doc/example6.py doc/example7.help doc/example7.py doc/example7_.help doc/example7_.py doc/example8.help doc/example8.py doc/example8_.help doc/example8_.py doc/example9.help doc/example9.py doc/importer1.py doc/importer2.py doc/importer3.py doc/importer_ui.py doc/ishelve.help doc/ishelve.py doc/ishelve2.py doc/ishelve3.py doc/picalculator.py doc/plac.html doc/plac.pdf doc/read_stdin.py doc/server_ex.py doc/shelve_interpreter.help doc/shelve_interpreter.py doc/sql_interface.py doc/test_ishelve.py doc/test_ishelve_more.py doc/test_pi.py doc/test_plac.py doc/test_runp.py doc/test_server.py doc/tkmon.py doc/vcs.help doc/vcs.py plac.egg-info/PKG-INFO plac.egg-info/SOURCES.txt plac.egg-info/dependency_links.txt plac.egg-info/not-zip-safe plac.egg-info/top_level.txtplac-0.9.6/README.rst0000664000175000017500000000150412740075216015200 0ustar michelemichele00000000000000Installation ------------- If you are lazy, just perform :: $ pip install plac which will install the module on your system (and possibly argparse too, if it is not already installed). 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 Testing -------- Run :: $ python doc/test_plac.py or :: $ nosetests doc or :: $ py.test doc Some tests will fail if sqlsoup is not installed. Run a ``pip install sqlsoup`` or just ignore them. Documentation -------------- The source code and the documentation are hosted on GitHub. Here is the full documentation: https://github.com/micheles/plac/blob/0.9.6/doc/plac.pdf plac-0.9.6/setup.cfg0000664000175000017500000000013012740075376015333 0ustar michelemichele00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_svn_revision = 0 tag_date = 0 plac-0.9.6/plac_runner.py0000775000175000017500000000452212740075202016374 0ustar michelemichele00000000000000#!/usr/bin/env python from __future__ import with_statement import os import sys import shlex 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) plactool.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) plactool.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) plac-0.9.6/plac_core.py0000664000175000017500000003064112740075216016016 0ustar michelemichele00000000000000# this module should be kept Python 2.3 compatible import re import sys import inspect import argparse from gettext import gettext as _ 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__', {}) def getargspec(callableobj): """Given a callable return an object with attributes .args, .varargs, .varkw, .defaults. It tries to do the "right thing" with functions, methods, classes and generic callables.""" if inspect.isfunction(callableobj): argspec = getfullargspec(callableobj) elif inspect.ismethod(callableobj): argspec = getfullargspec(callableobj) del argspec.args[0] # remove first argument elif inspect.isclass(callableobj): if callableobj.__init__ is object.__init__: # to avoid an error argspec = getfullargspec(lambda self: None) else: argspec = getfullargspec(callableobj.__init__) del argspec.args[0] # remove first argument elif hasattr(callableobj, '__call__'): argspec = getfullargspec(callableobj.__call__) del argspec.args[0] # remove first argument else: raise TypeError(_('Could not determine the signature of ') + str(callableobj)) return argspec 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=None, 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, (str, bytes)): 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 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 alias(self, arg): "Can be overridden to preprocess command-line arguments" return arg def consume(self, args): """Call the underlying function with the args. Works also for command containers, by dispatching to the right subparser.""" arglist = [self.alias(a) for a in 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 if not hasattr(self, 'argspec'): raise SystemExit 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', '')) add_help = getattr(obj, 'add_help', True) for cmd in commands: func = getattr(obj, cmd[prefixlen:]) # strip the prefix self.subparsers.add_parser( cmd, add_help=add_help, help=func.__doc__, **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.""" self.func = obj self.argspec = getargspec(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.help is None: a.help = '[%s]' % str(dflt) # dflt can be a tuple if a.kind in ('option', 'flag'): if a.abbrev: shortlong = (prefix + a.abbrev, prefix*2 + name.replace('_', '-')) else: shortlong = (prefix + name.replace('_', '-'),) 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): "May raise a SystemExit" miss = getattr(self.obj, '__missing__', lambda name: self.error('No command %r' % name)) return miss(name) 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, (str, bytes)) def call(obj, arglist=sys.argv[1:], eager=True, version=None): """ 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. """ parser = parser_from(obj) if version: parser.add_argument( '--version', '-v', action='version', version=version) cmd, result = parser.consume(arglist) if iterable(result) and eager: # listify the result return list(result) return result plac-0.9.6/plac_ext.py0000664000175000017500000011374112740075216015671 0ustar michelemichele00000000000000# this module requires Python 2.6+ from __future__ import with_statement from contextlib import contextmanager from operator import attrgetter from gettext import gettext as _ import imp import inspect import os import sys import cmd import shlex import subprocess import argparse import itertools import traceback import multiprocessing import signal import threading import plac_core if sys.version < '3': def exec_(_code_, _globs_=None, _locs_=None): if _globs_ is None: frame = sys._getframe(1) _globs_ = frame.f_globals if _locs_ is None: _locs_ = frame.f_locals del frame elif _locs_ is None: _locs_ = _globs_ exec("""exec _code_ in _globs_, _locs_""") exec(''' def raise_(tp, value=None, tb=None): raise tp, value, tb ''') else: exec_ = eval('exec') def raise_(tp, value=None, tb=None): """ A function that matches the Python 2.x ``raise`` statement. This allows re-raising exceptions with the cls value and traceback on Python 2 and 3. """ if value is not None and isinstance(tp, Exception): raise TypeError("instance exception may not have a separate value") if value is not None: exc = tp(value) else: exc = tp if exc.__traceback__ is not tb: raise exc.with_traceback(tb) raise exc def decode(val): """ Decode an object assuming the encoding is UTF-8. """ try: # assume it is an encoded bytes object return val.decode('utf-8') except AttributeError: # it was an already decoded unicode object return val # ############################ 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 = [c.upper() for c in completions] import readline self.rl = readline readline.parse_and_bind("tab: complete") readline.set_completer(self.complete) def __enter__(self): self.old_completer = self.rl.get_completer() try: if self.histfile: self.rl.read_history_file(self.histfile) except IOError: # the first time pass return self def __exit__(self, etype, exc, tb): self.rl.set_completer(self.old_completer) if self.histfile: self.rl.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, '') # ################# help functionality in plac interpreters ################# # class HelpSummary(object): "Build the help summary consistently with the cmd module" @classmethod def add(cls, obj, specialcommands): p = plac_core.parser_from(obj) 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.commands), 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) p.helpsummary = str(c.stdout) def __init__(self): self._ls = [] def write(self, s): self._ls.append(s) def __str__(self): return ''.join(self._ls) class PlacFormatter(argparse.RawDescriptionHelpFormatter): def _metavar_formatter(self, action, default_metavar): 'Remove special commands from the usage message' choices = action.choices or {} action.choices = dict((n, c) for n, c in choices.items() if not n.startswith('.')) return super(PlacFormatter, self)._metavar_formatter( action, default_metavar) def format_help(self): "Attached to plac_core.ArgumentParser for plac interpreters" try: return self.helpsummary except AttributeError: return super(plac_core.ArgumentParser, self).format_help() plac_core.ArgumentParser.format_help = format_help def default_help(obj, cmd=None): "The default help functionality in plac interpreters" parser = plac_core.parser_from(obj) if cmd is None: yield parser.format_help() return subp = parser.subparsers._name_parser_map.get(cmd) if subp is None: yield _('Unknown command %s' % cmd) elif getattr(obj, '_interact_', False): # in interactive mode formatter = subp._get_formatter() formatter._prog = cmd # remove the program name from the usage formatter.add_usage( subp.usage, [a for a in subp._actions if a.dest != 'help'], subp._mutually_exclusive_groups) formatter.add_text(subp.description) for action_group in subp._action_groups: formatter.start_section(action_group.title) formatter.add_text(action_group.description) formatter.add_arguments(a for a in action_group._group_actions if a.dest != 'help') formatter.end_section() yield formatter.format_help() else: # regular argparse help yield subp.format_help() # ######################## 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, dic) makeobj = dic['makeobj'] makeobj.add_help = False 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): """ 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 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 notify(self, msg): "Notifies the underlying monitor. To be implemented" 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) self.notify(decode(value)) yield except Interpreter.Exit: # wanted exit self._regular_exit() raise except (GeneratorExit, TerminatedProcess, KeyboardInterrupt): # soft termination self.status = 'KILLED' except: # unexpected exception self.etype, self.exc, tb = sys.exc_info() self.tb = ''.join(traceback.format_tb(tb)) if stringify_tb else tb self.status = 'ABORTED' else: self._regular_exit() def _regular_exit(self): self.status = 'FINISHED' try: self.str = '\n'.join(map(decode, 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, (str, bytes)): return self.tb else: return ''.join(traceback.format_tb(self.tb)) @property def result(self): self.wait() if self.exc: if isinstance(self.tb, (str, bytes)): raise self.etype(self.tb) else: 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 notify(self, msg): self.man.notify_listener(self.no, msg) def __init__(self, no, arglist, genobj, manager): """ The monitor has a .send method and a .man multiprocessing.Manager """ self.no = no self.arglist = arglist self._genobj = self._wrap(genobj, stringify_tb=True) self.man = manager self._outlist = manager.mp.list() self.ns = manager.mp.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 TaskManager(object): """ Store the given commands into a task registry. Provides methods to manage the submitted tasks. """ cmdprefix = '.' specialcommands = set(['.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']) interact = getattr(obj, '_interact_', False) self.parser = plac_core.parser_from( obj, prog='' if interact else None, formatter_class=PlacFormatter) HelpSummary.add(obj, self.specialcommands) self.man = Manager() if obj.mpcommands else None signal.signal(signal.SIGTERM, terminatedProcess) def close(self): "Kill all the running tasks" for task in self.registry.values(): try: if task.status == 'RUNNING': task.kill() task.wait() except: # task killed, nothing to wait pass if self.man: self.man.stop() 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.values() if t.status == status] else: tasks = [t for t in self.registry.values()] 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 taskno not 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, fname=None): 'show the output of a given task (and optionally save it to a file)' 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)) if fname: open(fname, 'w').write(outstr) yield 'saved output of %d into %s' % (taskno, fname) return yield task if len(task.outlist) > 20 and use_less: less(outstr) # has no meaning for a plac server 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' # ########################## SyncProcess ############################# # class Process(subprocess.Popen): "Start the interpreter specified by the params in a subprocess" def __init__(self, params): signal.signal(signal.SIGPIPE, signal.SIG_DFL) # to avoid broken pipe messages code = '''import plac, sys sys.argv[0] = '<%s>' plac.Interpreter(plac.import_main(*%s)).interact(prompt='i>\\n') ''' % (params[0], params) subprocess.Popen.__init__( self, [sys.executable, '-u', '-c', code], stdin=subprocess.PIPE, stdout=subprocess.PIPE) self.man = multiprocessing.Manager() def close(self): "Close stdin and stdout" self.stdin.close() self.stdout.close() self.man.shutdown() 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() class StartStopObject(object): started = False def start(self): pass def stop(self): pass class Monitor(StartStopObject): """ Base monitor class with methods add_listener/del_listener/notify_listener read_queue and and start/stop. """ def __init__(self, name, queue=None): self.name = name self.queue = queue or multiprocessing.Queue() def add_listener(self, taskno): pass def del_listener(self, taskno): pass def notify_listener(self, taskno, msg): pass def start(self): pass def stop(self): pass def read_queue(self): pass class Manager(StartStopObject): """ The plac Manager contains a multiprocessing.Manager and a set of slave monitor processes to which we can send commands. There is a manager for each interpreter with mpcommands. """ def __init__(self): self.registry = {} self.started = False self.mp = None def add(self, monitor): 'Add or replace a monitor in the registry' proc = multiprocessing.Process(None, monitor.start, monitor.name) proc.queue = monitor.queue self.registry[monitor.name] = proc def delete(self, name): 'Remove a named monitor from the registry' del self.registry[name] # can be called more than once def start(self): if self.mp is None: self.mp = multiprocessing.Manager() for monitor in self.registry.values(): monitor.start() self.started = True def stop(self): for monitor in self.registry.values(): monitor.queue.close() monitor.terminate() if self.mp: self.mp.shutdown() self.mp = None self.started = False def notify_listener(self, taskno, msg): for monitor in self.registry.values(): monitor.queue.put(('notify_listener', taskno, msg)) def add_listener(self, no): for monitor in self.registry.values(): monitor.queue.put(('add_listener', no)) # ######################### plac server ############################# # import asyncore import asynchat import 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__(None, None, None) 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. """ class Exit(Exception): pass 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.man = self.tm.man self.parser = self.tm.parser if self.commands: 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', 'mpcommands', 'thcommands'): setattr(self, attrname, set(getattr(self.__class__, attrname, []))) setattr(obj, attrname, set(getattr(obj, attrname, []))) self.commands = obj.commands self.mpcommands.update(obj.mpcommands) self.thcommands.update(obj.thcommands) if (obj.commands or obj.mpcommands or obj.thcommands) and \ not hasattr(obj, 'help'): # add default help obj.help = default_help.__get__(obj, obj.__class__) self.commands.add('help') def __enter__(self): "Start the inner interpreter loop" self._interpreter = self._make_interpreter() self._interpreter.send(None) return self def __exit__(self, exctype, exc, tb): "Close the inner interpreter and the task manager" self.close(exctype, exc, tb) 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, (str, bytes)): arglist = self.split(line, self.commentchar) else: # expects a list of strings arglist = line if not arglist: return nulltask m = self.tm.man # manager if m and not m.started: m.start() task = self._interpreter.send(arglist) # nonblocking if not plac_core._match_cmd(arglist[0], self.tm.specialcommands): self.tm.registry[task.no] = task if m: m.add_listener(task.no) 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, exctype=None, exc=None, tb=None): "Can be called to close the interpreter prematurely" self.tm.close() if exctype is not None: self._interpreter.throw(exctype, exc, tb) else: 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 SystemExit as e: # for invalid commands if e.args == (0,): # raised as sys.exit(0) errlist = [] else: errlist = [str(e)] task = SynTask(no, arglist, iter(errlist)) continue except: # anything else 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.man) 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: try: 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) if task.exc: raise_(task.etype, task.exc, task.tb) except self.Exit: pass def execute(self, lineiter, verbose=False): "Execute a lineiter of commands in a context and print the output" with self: try: 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) except self.Exit: pass def multiline(self, stdin=sys.stdin, terminator=';', verbose=False): "The multiline mode is especially suited for usage with emacs" with self: try: 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) except self.Exit: pass def interact(self, stdin=sys.stdin, prompt='i> ', verbose=False): "Starts an interactive command loop reading commands from the consolle" try: import readline readline_present = True except ImportError: readline_present = False if stdin is sys.stdin and readline_present: # use readline histfile = os.path.expanduser('~/.%s.history' % self.name) completions = list(self.commands) + list(self.mpcommands) + \ list(self.thcommands) + list(self.tm.specialcommands) self.stdin = ReadlineInput(completions, histfile=histfile) else: self.stdin = stdin self.prompt = prompt self.verbose = verbose intro = self.obj.__doc__ or '' write(intro + '\n') with self: self.obj._interact_ = True 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" try: 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) except self.Exit: pass 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, TerminatedProcess): pass finally: asyncore.close_all() def add_monitor(self, mon): self.man.add(mon) def del_monitor(self, name): self.man.delete(name) @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) out = str(task) if out: print(out) elif i.obj._interact_: i.interact(stdin, prompt, verbose) else: i.parser.print_usage() # ################################## 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'): """Run a sequence of generators in parallel. Mode can be 'p' (use processes) or 't' (use threads). After all of them are finished, return a list of task objects. """ assert mode in 'pt', mode launcher = _TaskLauncher(genseq, mode) res = [] with Interpreter(launcher) as inter: for i in range(len(launcher.genlist)): inter.submit('rungen %d' % (i + 1)).run() for task in inter.tasks(): try: res.append(task.result) except Exception as e: res.append(e) return res plac-0.9.6/plac_tk.py0000664000175000017500000000354012740075216015502 0ustar michelemichele00000000000000from __future__ import print_function import os import sys import Queue import plac_core from Tkinter import Tk from ScrolledText import ScrolledText from plac_ext import Monitor, TerminatedProcess class TkMonitor(Monitor): """ An interface over a dictionary {taskno: scrolledtext widget}, with methods add_listener, del_listener, notify_listener and start/stop. """ def __init__(self, name, queue=None): Monitor.__init__(self, name, queue) self.widgets = {} @plac_core.annotations(taskno=('task number', 'positional', None, int)) def add_listener(self, taskno): "There is a ScrolledText for each task" st = ScrolledText(self.root, height=5) st.insert('end', 'Output of task %d\n' % taskno) st.pack() self.widgets[taskno] = st @plac_core.annotations(taskno=('task number', 'positional', None, int)) def del_listener(self, taskno): del self.widgets[taskno] @plac_core.annotations(taskno=('task number', 'positional', None, int)) def notify_listener(self, taskno, msg): w = self.widgets[taskno] w.insert('end', msg + '\n') w.update() def start(self): 'Start the mainloop' self.root = Tk() self.root.title(self.name) self.root.wm_protocol("WM_DELETE_WINDOW", self.stop) self.root.after(0, self.read_queue) try: self.root.mainloop() except KeyboardInterrupt: print('Process %d killed by CTRL-C' % os.getpid(), file=sys.stderr) except TerminatedProcess: pass def stop(self): self.root.quit() def read_queue(self): try: cmd_args = self.queue.get_nowait() except Queue.Empty: pass else: getattr(self, cmd_args[0])(*cmd_args[1:]) self.root.after(100, self.read_queue) plac-0.9.6/setup.py0000664000175000017500000000353212740075216015226 0ustar michelemichele00000000000000try: 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.rst').read(), author='Michele Simionato', author_email='michele.simionato@gmail.com', url='https://github.com/micheles/plac', license="BSD License", py_modules=['plac_core', 'plac_ext', 'plac_tk', 'plac'], scripts=['plac_runner.py'], install_requires=require('argparse', 'multiprocessing'), keywords="command line arguments parser", platforms=["All"], classifiers=['Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Libraries', 'Topic :: Utilities'], zip_safe=False) plac-0.9.6/plac.py0000664000175000017500000000321612740075216015004 0ustar michelemichele00000000000000# ######################### LICENCE ############################### # # Copyright (c) 2010-2016, 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. """ import sys from plac_core import * __version__ = '0.9.6' if sys.version >= '2.5': from plac_ext import (Interpreter, import_main, ReadlineInput, stdout, runp, Monitor, default_help) try: from plac_tk import TkMonitor except ImportError: pass plac-0.9.6/CHANGES.md0000664000175000017500000001015012740075234015100 0ustar michelemichele00000000000000HISTORY ------- ## Unreleased ## 0.9.6 (2016-07-09) Solved an issue with non-ASCII characters; now any UTF-8 character can go in the help message. Added support for `--version` in plac.call. Modernized the changelog http://keepachangelog.com/ ## 0.9.5 (2016-06-09) Removed an usage of `print >>` that was breaking Python 3, signaled by Quentin Pradet ## 0.9.4 (2016-06-09) Removed use_2to3 in setup.py which was breaking Python 2, signaled by Quentin Pradet ## 0.9.3 (2016-06-07) Fixed the tests on Python 3 and produced an universal wheel instead of relying on 2to3. Enabled Travis builds for Python 3.3, 3.4, 3.5 ## 0.9.2 (2016-06-07) Moved the repository from GoogleCode to GitHub. Included the doc fixes by Nicola Larosa and polished the code base to be PEP 8 compliant. Enabled Travis builds for Python 2.6 and 2.7 ## 0.9.1 (2012-04-23) Options and flags can now contain dashes (i.e. ``--dry-run`` is valid and translated into dry_run, you are not forced to use ``--dry-run`` anymore); restored the monitor support temporarily removed in 0.9.0, fixed an issue with tuple defaults and fixed the display of the help command; specified which features are experimental and which features are fully supported ## 0.9.0 (2011-06-19) Default values are now displayed in the help message by default; removed .help and introduced help; removed the special dotted commands from the usage message; added an ``Interpreter.Exit`` exception; removed the experimental monitor framework because it is too much platform-dependent; added a reference to Argh; now plac has its own space on Google Code ## 0.8.1 (2011-04-11) Removed a stray newline in the output of plac, as signaled by Daniele Pighin; fixed a bug in the doctest method raising non-existing exceptions; turned the notification messages into unicode strings; removed an ugly SystemExit message for invalid commands, signaled by Tuk Bredsdorff ## 0.8.0 (2011-02-16) Added a monitor framework and a TkMonitor ## 0.7.6 (2011-01-13) Fixed the error propagation in ``Interpreter.__exit__``. Added a note about commandline and marrow.script in the documentation ## 0.7.5 (2011-01-01) Fixed a bug with the help of subcommands, signaled by Paul Jacobson; added the ability to save the output of a command into a file; postponed the import of the readline module to avoid buffering issues; fixed a bug with the traceback when in multiprocessing mode ## 0.7.4 (2010-09-04) Fixed the plac_runner switches -i and -s; fixed a bug with multiline output and issue with nosetest ## 0.7.3 (2010-08-31) Put the documentation in a single document; added runp ## 0.7.2 (2010-08-11) Interpreter.call does not start an interpreter automagically anymore; better documented and added tests for the metavar concept (2010-08-31) ## 0.7.1 (2010-08-11) A few bug fixes ## 0.7.0 (2010-08-07) Improved and documented the support for parallel programming; added an asynchronous server; added plac.Interpreter.call ## 0.6.1 (2010-07-12) 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 ## 0.6.0 (2010-07-11) Improved the interactive experience with full readline support and custom help. Added support for long running command, via threads and processes ## 0.5.0 (2010-06-20) 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 ## 0.4.3 (2010-06-11) Fixed the installation procedure to automatically download argparse if needed ## 0.4.2 (2010-06-04) Added missing .help files, made the tests generative and added a note about Clap in the documentation ## 0.4.1 (2010-06-03) Changed the default formatter class and fixed a bug in the display of the default arguments. Added more stringent tests. ## 0.4.0 (2010-06-03) 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. ## 0.3.0 (2010-06-02) First released version.