################################### Argpext |release| --- 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. .. _task: 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. .. _a: .. code-block:: python # 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 .. code-block:: shell ~$ 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: .. code-block:: python >>> 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: .. code-block:: shell ~$ python a.py 2 [2, 3] ~$ python a.py 2 -n 5 [2, 3, 4, 5] Equivalently, we execute in Python prompt: .. code-block:: python >>> 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 .. code-block:: python >>> 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 .. _b: .. code-block:: python # 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*: .. code-block:: 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*: .. code-block:: 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 :ref:`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: .. code-block:: python # 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 .. code-block:: python # 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: .. code-block:: shell ~$ python d.py .. code-block:: python >>> 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 .. code-block:: python # 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() .. code-block:: shell ~$ python e.py Displaying: 1,2,3 .. code-block:: python >>> import e >>> x = e.T().sdigest(display=True) Displaying: 1,2,3 >>> x [1, 2, 3] >>> >>> >>> Customized display of values yielded by task generator .. code-block:: python # 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() .. code-block:: shell ~$ python f.py I am number "1" I am number "2" I am number "3" .. code-block:: python >>> 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: .. code-block:: python >>> 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 :ref:`previous section` within a single script *n.py* as follows: .. code-block:: python # 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: .. code-block:: shell ~$ 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 .. code-block:: python >>> 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: .. code-block:: shell ~$ 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. .. code-block:: python >>> 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 .. code-block:: shell ~$ python n.py a 2 [2, 3] ~$ python n.py a 2 -n 5 [2, 3, 4, 5] .. code-block:: python >>> 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: .. code-block:: shell ~$ 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 .. code-block:: python >>> 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 :ref:`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 .. code-block:: shell ~$ python n.py f I am number "1" I am number "2" I am number "3" .. code-block:: python >>> 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: .. code-block:: python # 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: .. code-block:: shell ~$ 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: .. code-block:: python >>> 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: ############################### .. toctree:: :maxdepth: 2 ############################### Indices and tables ############################### * :ref:`genindex` * :ref:`modindex` * :ref:`search`