Argpext 1.3.2 — Documentation

Hierarchical command line interface

Hierarchical command line interface utility of the argpext package aims to provide very efficient bindings between callable python objects, such as functions, or generators to the command line. Implementation proceeds through two primary components: the Task and Node classes.

Linking a standalone function to command line

In this example, a function f(m,n) takes integer arguments m and n.

Our objective is to link this function to the command line interface, so we can execute it from DOS/Unix and Python command prompt.

We require that using the command line alone we should be able to:

  • See the help message documenting the usage of command line interface
  • Specify the arguments required by the function
  • Execute the function based on the value of argument we have specified
  • See the string representation of the value returned by f(m,n)
  • Access the full-featured value returned by function f(m,n) in Python.

The above requirements are implemented in the code below.

# File "a.py"

from argpext import *

def f(m,n):
    "Return list of integers m to n"
    return list(range(m,1+n))

class T(Task):

    hook = customize(tostring=str)(s2m( f ))

    def populate(self,parser):
        parser.add_argument('m',type=int,help="Specify the value of M.")
        parser.add_argument('-n',type=int,default=3,
                            help="Specify the value of N; the default is %(default)s.")

if __name__ == '__main__':
    T().tdigest()

To link function f(m,n) to the command line, we define class T, an instance of Task, as follows.

By defining its hook member, we establish the linkage between class T and function f.

The s2m function converts the standalone function f into the bound class method.

Expression customize(tostring=str) indicates that we wish to see string representation of the return value of function f(m,n) to appear within the command line output. Without this customize call, the return value will not be displayed.

By defining its populate member we specify how to populate function’s arguments based on the command line inputs. This function requires the command line parser as argument. The parser argument should be treated as variable of the argparse.ArgumentParser type; as such, the parser should be populated exactly as described documentation for the standard Python’s argparse module. In our case, it is populated by a sequence of parser.add_argument( ... ) calls: one call for each argument.

Help messages on usage are displayed by using the -h/--help flag

~$ python a.py --help
usage: a.py [-h] [-n N] m

Return list of integers m to n

positional arguments:
  m           Specify the value of M.

optional arguments:
  -h, --help  show this help message and exit
  -n N        Specify the value of N; the default is 3.

Help messages are also available using the same flag, from the Python command line prompt:

>>> import a
>>> a.T().sdigest('-h')
usage: a.T().sdigest(...) <- [-h] [-n N] m

Return list of integers m to n

positional arguments:
  m           Specify the value of M.

optional arguments:
  -h, --help  show this help message and exit
  -n N        Specify the value of N; the default is 3.
>>>
>>>
>>>

To test the implementation, we execute from the DOS/Unix command prompt:

~$ python a.py 2
[2, 3]
~$ python a.py 2 -n 5
[2, 3, 4, 5]

Equivalently, we execute in Python prompt:

>>> import a
>>> a.T().sdigest('2')
[2, 3]
>>> a.T().sdigest('2 -n 5')
[2, 3, 4, 5]
>>>
>>>
>>>

It is possible to specify argument items explicitly by passing a list of items as argument

>>> import a
>>> x = a.T().sdigest(['2', '-n', '5'])
>>> print( x )
[2, 3, 4, 5]
>>>
>>>
>>>

Linking a standalone generator to command line

In this example, a standalone generator g(m,n) takes integer arguments m and n.

Our objective is to link this generator to the command line interface.

We require that using the command line alone we should be able to:

  • See the help message documenting the usage of command line interface
  • Specify the arguments required by the generator
  • Execute the generator based on the value of argument we have specified
  • See the string representations of the values yielded by the generator immediately as they get provided.
  • Access the full-featured values yielded by the generator g(m,n) in the Python session, immediately as they are yielded by the generator.

The above requirements are implemented as follows

# File "b.py"

from argpext import *

# This is a standalone generator
def g(m,n):
    "Iterate over integers m to n"
    for i in range(m,1+n):
        yield i

class T(Task):

    hook = customize(tostring=str)(s2m( g ))

    def populate(self,parser):
        parser.add_argument('m',type=int,help="Specify the value of M.")
        parser.add_argument('-n',type=int,default=3,
                            help="Specify the value of N; the default is %(default)s.")


if __name__ == '__main__':
    T().tdigest()

To test the implementation, we execute from the SHELL:

~$ # python b.py --help
~$
~$ python b.py 2
2
3
~$
~$ python b.py 2 -n 5
2
3
4
5

We execute in Python:

>>> import b
>>> # b.T().sdigest('-h')
>>>
>>> for x in b.T().sdigest('2'):
...     print( x )
...
2
3
>>> for x in b.T().sdigest('2 -n 5'):
...     print( x )
...
2
3
4
5

Linking a bound method to command line

In order to link a code body to the command line it is not required to have it encapsulated within a standalone function or generator. The following shows an example identical to the example, except that the body of the function f(m,n) is now embedded within the definition of the bound hook method, inside class T declaration, rather than being a standalone task method:

# File "c.py"

from argpext import *

class T(Task):

    @customize(tostring=str)
    def hook(self,m,n):
        "Return list of integers m to n"
        return list(range(m,1+n))

    def populate(self,parser):
        parser.add_argument('m',type=int,help="Specify the value of M.")
        parser.add_argument('-n',type=int,default=3,
                            help="Specify the value of N; the default is %(default)s.")

if __name__ == '__main__':
    T().tdigest()

Yield/Return value display

By default, the return values of functions (or the values yielded by the generators) are not displayed in the command line when they get executed through comand line bindings; this is demonstrated by the following example

# File "d.py"

from argpext import *

class T(Task):
    def hook(self):
        "Return integers 1 to 3"
        return [1,2,3]

if __name__ == '__main__':
    T().tdigest()

Indeed, no output is seen, following the executions below:

~$ python d.py
>>> import d
>>> gn = d.T().sdigest(display=True)
>>> for x in gn:
...     pass
...
>>>
>>>
>>>

In order to enable the return value display, use @customize(tostring=....), and set the tostring argument to a function that accepts the yield/return value object as argument and returns string representation of this object.

Customized display of values returned by task function

# File "e.py"

from argpext import *


def represent(x):
    return 'Displaying: '+','.join([str(q) for q in x])

class T(Task):
    "Return integers 1 to 3, using customized display"

    @customize(tostring=represent)
    def hook(self):
        return [1,2,3]

if __name__ == '__main__':
    T().tdigest()
~$ python e.py
Displaying: 1,2,3
>>> import e
>>> x = e.T().sdigest(display=True)
Displaying: 1,2,3
>>> x
[1, 2, 3]
>>>
>>>
>>>

Customized display of values yielded by task generator

# File "f.py"

from argpext import *


def represent_as_string(x):
    return 'I am number "%s"' % x

class T(Task):

    @customize(tostring=represent_as_string)
    def hook(self):
        "Return integers 1 to 3"
        for element in [1,2,3]:
            yield element

if __name__ == '__main__':
    T().tdigest()
~$ python f.py
I am number "1"
I am number "2"
I am number "3"
>>> import f
>>> gn = f.T().sdigest(display=True)
>>> for x in gn:
...     print( x )
...
I am number "1"
1
I am number "2"
2
I am number "3"
3

It is possible to redirect custom display output into a customized stream; for example:

>>> import io
>>> stream = io.StringIO()
>>>
>>> import e
>>> e.T().sdigest(stream=stream,display=True)
[1, 2, 3]
>>> stream.getvalue()
'Displaying: 1,2,3\n'
>>>
>>>
>>>

Building the first level of hierarchy

In previous sections we presented multiple examples of linking Python code to command line interface. As the number of tasks grows it becomes more and more difficult for the developer to keep track of them, especially if there is an implicit relation between the tasks.

This section presents the Node class, – a tool that allows you to integrate all the related tasks into a single master script, by organizing them elegantly into a task tree structure.

For example, we can quickly organize all the tasks we have defined in previous section within a single script n.py as follows:

# File "n.py"

from argpext import Node

import a, b, c, d, e, f

class T(Node):
    "Integer range sequence methods"
    SUBS = [
        ('a', a.T),
        ('b', b.T),
        ('c', c.T),
        ('d', d.T),
        ('e', e.T),
        ('f', f.T),
    ]

if __name__ == '__main__':
    T().tdigest()

The master script, when executed with the --help/-h flag, produces the master help message, as follows:

~$ python n.py -h
usage: n.py [-h] {a,b,c,d,e,f} ...

Integer range sequence methods

positional arguments:
  {a,b,c,d,e,f}  Description
    a            Return list of integers m to n
    b            Iterate over integers m to n
    c            Return list of integers m to n
    d            Return integers 1 to 3
    e            Return integers 1 to 3, using customized display
    f            Return integers 1 to 3

optional arguments:
  -h, --help     show this help message and exit
>>> import n
>>> n.T().sdigest('-h')
usage: n.T().sdigest(...) <- [-h] {a,b,c,d,e,f} ...

Integer range sequence methods

positional arguments:
  {a,b,c,d,e,f}  Description
    a            Return list of integers m to n
    b            Iterate over integers m to n
    c            Return list of integers m to n
    d            Return integers 1 to 3
    e            Return integers 1 to 3, using customized display
    f            Return integers 1 to 3

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

The positional arguments shown above indicate the list of all tasks available. By passing one of them as an additional positional argument to the master script, we descend to the level of the specific task selected for the execution. In this manner we can execute any of the available tasks.

For example, when selecting task a for execution, we must populate the arguments required by the task, which is done by first retrieving help message as follows:

~$ python n.py a -h
usage: n.py a [-h] [-n N] m

Return list of integers m to n

positional arguments:
  m           Specify the value of M.

optional arguments:
  -h, --help  show this help message and exit
  -n N        Specify the value of N; the default is 3.
>>> import n
>>> n.T().sdigest('a -h')
usage: n.T().sdigest(...) <- a [-h] [-n N] m

Return list of integers m to n

positional arguments:
  m           Specify the value of M.

optional arguments:
  -h, --help  show this help message and exit
  -n N        Specify the value of N; the default is 3.
>>>
>>>
>>>

Having reviewed all the options available for executing the task, we populate task arguments according to the usage and execute the task twice (once with m=2,n=3, and once with m=3,n=5) as follows

~$ python n.py a 2
[2, 3]
~$ python n.py a 2 -n 5
[2, 3, 4, 5]
>>> import n
>>> n.T().sdigest('a 2')
[2, 3]
>>> n.T().sdigest('a 2 -n 5')
[2, 3, 4, 5]
>>>
>>>
>>>

Similarly, we may choose to execute task f. The help message for is generated as follows:

~$ python n.py f -h
usage: n.py f [-h]

Return integers 1 to 3

optional arguments:
  -h, --help  show this help message and exit
>>> import n
>>> n.T().sdigest('f -h')
usage: n.T().sdigest(...) <- f [-h]

Return integers 1 to 3

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

We recall from the section describing the task class, that task f structurally differs from task e in that the former starts an iterator, whereas the latter executes a serial task. Using the master script, task f is executed as follows

~$ python n.py f
I am number "1"
I am number "2"
I am number "3"
>>> import n
>>> for x in n.T().sdigest('f',display=True):
...     print('Also printing here:', x )
...
I am number "1"
Also printing here: 1
I am number "2"
Also printing here: 2
I am number "3"
Also printing here: 3

The objective of the Node class is achieved: as we have demonstrated with the examples above each task is accessible by passing command line arguments to the master script only.

Extending the hierarchy beyond the first level

Using class Node, one can build as many levels of hierarchy as needed. In the following example, we build one additional hierarchy level.

The following script encapsuletes all the tasks considered above, in addition to one new task: todays date:

# File "m.py"

import datetime

from argpext import Task, Node, customize

import n, a

class Today(Task):
    @customize(tostring=str)
    def hook(self):
        "Return todays date"
        return datetime.datetime.today().date()

class T(Node):
    "All tasks in one script"
    SUBS = [
        ('today', Today),
        ('n', n.T),
        ]

if __name__ == '__main__':
    T().tdigest()

Indeed, we execute from the shell, a few of the above considered examples:

~$ python m.py today
2015-02-10
~$ python m.py n a 5 -n 10
[5, 6, 7, 8, 9, 10]
~$ python m.py n f
I am number "1"
I am number "2"
I am number "3"

Similarly, from the Python prompt:

>>> import m
>>>
>>> m.T().sdigest('today')
datetime.date(2015, 2, 10)
>>> m.T().sdigest('n a 5 -n 10')
[5, 6, 7, 8, 9, 10]
>>> for x in m.T().sdigest('n f',display=True):
...     print('Also, printing here:', x )
...
I am number "1"
Also, printing here: 1
I am number "2"
Also, printing here: 2
I am number "3"
Also, printing here: 3

Contents:

Indices and tables