User interfaces

Often is useful to add one or more interface to our programs, the simpliest and esiest way (from the developer point of view) is a develop a command-line interface (CLI).

Python in the standard library provides a package (argparse) to make this job easier.

In [1]:
%%file test_cli.py
import argparse

# create a parser
parser = argparse.ArgumentParser(description='Test the argparse library.')
# add arguments
parser.add_argument('integers', metavar='N', type=int, nargs='+',
                   help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
                   const=sum, default=max,
                   help='sum the integers (default: find the max)')

args = parser.parse_args()
print(args.accumulate(args.integers))
Writing test_cli.py
In [10]:
!python test_cli.py -h
usage: test_cli.py [-h] [--sum] N [N ...]

Test the argparse library.

positional arguments:
  N           an integer for the accumulator

optional arguments:
  -h, --help  show this help message and exit
  --sum       sum the integers (default: find the max)
In [11]:
!python test_cli.py 0 1 2 3 4 5
5
In [12]:
!python test_cli.py 0 1 2 3 4 5 --sum
15
In [13]:
!python test_cli.py 0. 1. 2. 3. 4. 5.
usage: test_cli.py [-h] [--sum] N [N ...]
test_cli.py: error: argument N: invalid int value: '0.'

Optional parameters for ArgumentParser:

  • prog - The name of the program (default: sys.argv[0])
  • usage - The string describing the program usage (default: generated from arguments added to parser)
  • description - Text to display before the argument help (default: none)
  • epilog - Text to display after the argument help (default: none)
  • parents - A list of ArgumentParser objects whose arguments should also be included
  • formatter_class - A class for customizing the help output
  • prefix_chars - The set of characters that prefix optional arguments (default: ‘-‘)
  • fromfile_prefix_chars - The set of characters that prefix files from which additional arguments should be read (default: None)
  • argument_default - The global default value for arguments (default: None)
  • conflict_handler - The strategy for resolving conflicting optionals (usually unnecessary)
  • add_help - Add a -h/–help option to the parser (default: True)

See a complete list of examples: http://docs.python.org/3.3/library/argparse.html?highlight=optparser#argumentparser-objects

In [1]:
import argparse

# create a parser
parser = argparse.ArgumentParser(prog='TestArgParser', 
                                 description='Test the argparse library.',
                                 epilog='This is the end...')
In [21]:
parser.print_help()
usage: TestArgParser [-h]

Test the argparse library.

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

This is the end...
In [22]:
# positional arguments
parser.add_argument('pos', nargs='+', help='pos help')

# optional parameter
parser.add_argument('--opt', nargs='?', help='bar help')

parser.print_help()
usage: TestArgParser [-h] [--opt [OPT]] pos [pos ...]

Test the argparse library.

positional arguments:
  pos          pos help

optional arguments:
  -h, --help   show this help message and exit
  --opt [OPT]  bar help

This is the end...

Define how a single command-line argument should be parsed. Each parameter has its own more detailed description below, but in short they are:

  • name or flags - Either a name or a list of option strings, e.g. foo or -f, --foo.
  • action - The basic type of action to be taken when this argument is encountered at the command line.
  • nargs - The number of command-line arguments that should be consumed.
  • const - A constant value required by some action and nargs selections.
  • default - The value produced if the argument is absent from the command line.
  • type - The type to which the command-line argument should be converted.
  • choices - A container of the allowable values for the argument.
  • required - Whether or not the command-line option may be omitted (optionals only).
  • help - A brief description of what the argument does.
  • metavar - A name for the argument in usage messages.
  • dest - The name of the attribute to be added to the object returned by parse_args().

http://docs.python.org/3.3/library/argparse.html?highlight=optparser#the-add-argument-method

In [148]:
parser = argparse.ArgumentParser(prog='MyPROG')

# optional argument
parser.add_argument('-f', '--foo')

# positional arguments
parser.add_argument('bar')

#---
parser.parse_args(['BAR'])
Out[148]:
Namespace(bar='BAR', foo=None)
In [27]:
parser.parse_args(['BAR', '-f', 'foo'])
Out[27]:
Namespace(bar='BAR', foo='foo')

Action

In [28]:
parser = argparse.ArgumentParser()
parser.add_argument('-g', '--gravity', action='store_const', const=9.81)

#---
parser.parse_args(['-g', ])
Out[28]:
Namespace(gravity=9.81)
In [29]:
parser.parse_args(['-g', '10.0'])
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2
usage: -c [-h] [-g]
-c: error: unrecognized arguments: 10.0
To exit: use 'exit', 'quit', or Ctrl-D.
In [30]:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='store_true')
parser.add_argument('--bar', action='store_false')

#---
parser.parse_args('--foo --bar'.split())
Out[30]:
Namespace(bar=False, foo=True)
In [31]:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='append')

#---
parser.parse_args('--foo 1 --foo 2'.split())
Out[31]:
Namespace(foo=['1', '2'])
In [32]:
import numpy as np

parser = argparse.ArgumentParser()
parser.add_argument('--sum', dest='methods', action='append_const', const=np.sum)
parser.add_argument('--mean', dest='methods', action='append_const', const=np.mean)
parser.add_argument('--std', dest='methods', action='append_const', const=np.std)

#---
parser.parse_args('--sum --std --mean'.split())
Out[32]:
Namespace(methods=[<function sum at 0x7f518bee1950>, <function std at 0x7f518bee24d0>, <function mean at 0x7f518bee2440>])
In [33]:
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v', action='count')

#---
parser.parse_args(['-vvv', ])
Out[33]:
Namespace(verbose=3)

nargs

In [2]:
parser = argparse.ArgumentParser()
parser.add_argument('--latlon', nargs=2, type=float, metavar='L')
parser.add_argument('name', nargs=1, metavar='NAME')
parser.print_help()
usage: -c [-h] [--latlon L L] NAME

positional arguments:
  NAME

optional arguments:
  -h, --help    show this help message and exit
  --latlon L L
In [152]:
parser.parse_args('Bolzano --latlon 46.31 11.22'.split())
Out[152]:
Namespace(latlon=[46.31, 11.22], name=['Bolzano'])
In [153]:
parser = argparse.ArgumentParser()
parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
                    default=sys.stdin)
parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'),
                    default=sys.stdout)

#---
parser.parse_args(['input.txt', 'output.txt'])
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2
usage: -c [-h] [infile] [outfile]
-c: error: argument infile: can't open 'input.txt': [Errno 2] No such file or directory: 'input.txt'
To exit: use 'exit', 'quit', or Ctrl-D.
In [154]:
parser.parse_args(['geo_objects.py', 'output.txt'])
Out[154]:
Namespace(infile=<_io.TextIOWrapper name='geo_objects.py' mode='r' encoding='UTF-8'>, outfile=<_io.TextIOWrapper name='output.txt' mode='w' encoding='UTF-8'>)
In [155]:
parser.parse_args([])
Out[155]:
Namespace(infile=<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>, outfile=<IPython.kernel.zmq.iostream.OutStream object at 0x7f5189125cd0>)
In [156]:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs='*')
parser.add_argument('--bar', nargs='+')
parser.add_argument('baz', nargs='*')

#---
parser.parse_args('a b --foo x y z --bar 1 2'.split())
Out[156]:
Namespace(bar=['1', '2'], baz=['a', 'b'], foo=['x', 'y', 'z'])
In [53]:
parser.parse_args('a b --foo x y --bar'.split())
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2
usage: -c [-h] [--foo [FOO [FOO ...]]] [--bar BAR [BAR ...]] [baz [baz ...]]
-c: error: argument --bar: expected at least one argument
To exit: use 'exit', 'quit', or Ctrl-D.
In [52]:
parser.parse_args('a b --foo'.split())
Out[52]:
Namespace(bar=None, baz=['a', 'b'], foo=[])
In [157]:
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo')
parser.add_argument('command')
parser.add_argument('args', nargs=argparse.REMAINDER)

#---
parser.parse_args('--foo B cmd --arg1 XX ZZ'.split())
Out[157]:
Namespace(args=['--arg1', 'XX', 'ZZ'], command='cmd', foo='B')
In [158]:
def is_odd(string):
    value = int(string)
    if value % 2 == 0:
        return value
    raise argparse.ArgumentTypeError("%r is not odd" % string)
    

parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('odd', type=is_odd)

#---
parser.parse_args('9'.split())
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2
usage: PROG [-h] odd
PROG: error: argument odd: '9' is not odd
To exit: use 'exit', 'quit', or Ctrl-D.
In [64]:
parser.parse_args('4'.split())
Out[64]:
Namespace(odd=4)

Choices

In [71]:
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('foo', type=int, choices=range(5, 10))
parser.add_argument('--bar', required=True)

#---
parser.parse_args('7 --bar 5'.split())
Out[71]:
Namespace(bar='5', foo=7)
In [72]:
parser.parse_args('1 --bar 5'.split())
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2
usage: PROG [-h] --bar BAR {5,6,7,8,9}
PROG: error: argument foo: invalid choice: 1 (choose from 5, 6, 7, 8, 9)
To exit: use 'exit', 'quit', or Ctrl-D.
In [74]:
parser.parse_args('7 --bar'.split())
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2
usage: PROG [-h] --bar BAR {5,6,7,8,9}
PROG: error: argument --bar: expected one argument
To exit: use 'exit', 'quit', or Ctrl-D.
In [68]:
parser = argparse.ArgumentParser(prog='TestChoices')
parser.add_argument('methods', choices=['sum', 'mean', 'std'])

#---
parser.parse_args(['std'])
Out[68]:
Namespace(methods='std')
In [69]:
parser.parse_args(['skew'])
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2
usage: TestChoices [-h] {sum,mean,std}
TestChoices: error: argument methods: invalid choice: 'skew' (choose from 'sum', 'mean', 'std')
To exit: use 'exit', 'quit', or Ctrl-D.
In [75]:
parser = argparse.ArgumentParser(prog='frobble')
parser.add_argument('bar', nargs='?', type=int, default=42,
                    help='the bar to %(prog)s (default: %(default)s)')

#---
parser.print_help()
usage: frobble [-h] [bar]

positional arguments:
  bar         the bar to frobble (default: 42)

optional arguments:
  -h, --help  show this help message and exit
In [80]:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', dest='bar')

#---
parser.parse_args('--foo XXX'.split())
Out[80]:
Namespace(bar='XXX')

Time for coding!

Add a command line interface to use the geo_object library.

Read configuration file

In [81]:
import configparser
In [82]:
config = configparser.ConfigParser()
config['CONNECTION'] = {'user': 'pietro',
                        'passwd': 'passwd',
                        'server': 'pippo.org'}
config['OTHER'] = {'option': 23}

with open('example_config.ini', 'w') as cfg_file:
    config.write(cfg_file)
In [85]:
%%sh 
head example_config.ini
[CONNECTION]
passwd = passwd
server = pippo.org
user = pietro

[OTHER]
option = 23

In [115]:
sample_config = """
[You can use comments]
# like this
; or this

# By default only in an empty line.
# Inline comments can be harmful because they prevent users
# from using the delimiting characters as parts of values.
# That being said, this can be customized.

    [Sections Can Be Indented]
        can_values_be_as_well = True
        does_that_mean_anything_special = False
        purpose = formatting for readability
        multiline_values = are
            handled just fine as
            long as they are indented
            deeper than the first line
            of a value
        # Did I mention we can indent comments, too?
"""
config = configparser.ConfigParser(allow_no_value=True)
config.read_string(sample_config)
In [116]:
config.sections()
Out[116]:
['You can use comments', 'Sections Can Be Indented']
In [117]:
for key in config['You can use comments']:
    print(key, ' => ',  config['You can use comments'][key])
In [120]:
for key in config['Sections Can Be Indented']:
    print(key, ' => ', config['Sections Can Be Indented'][key])
can_values_be_as_well  =>  True
does_that_mean_anything_special  =>  False
purpose  =>  formatting for readability
multiline_values  =>  are
handled just fine as
long as they are indented
deeper than the first line
of a value
In [122]:
sample_config = """
[mysqld]
  user = mysql
  pid-file = /var/run/mysqld/mysqld.pid
  skip-external-locking
  old_passwords = 1
  skip-bdb
  # we don't need ACID today
  skip-innodb
"""
config = configparser.ConfigParser(allow_no_value=True)
config.read_string(sample_config)
In [123]:
config.sections()
Out[123]:
['mysqld']
In [124]:
config['mysqld'].get('user', 'pietro')
Out[124]:
'mysql'
In [125]:
config['mysqld']['user']
Out[125]:
'mysql'
In [126]:
config['mysqld']['skip-bdb']
In [103]:
sample_config = """
[Common]
home_dir: /Users
library_dir: /Library
system_dir: /System
macports_dir: /opt/local

[Frameworks]
Python: 3.2
path: ${Common:system_dir}/Library/Frameworks/

[Arthur]
nickname: Two Sheds
last_name: Jackson
my_dir: ${Common:home_dir}/twosheds
my_pictures: ${my_dir}/Pictures
python_dir: ${Frameworks:path}/Python/Versions/${Frameworks:Python}"""
config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
config.read_string(sample_config)
In [104]:
config['Arthur']['my_dir']
Out[104]:
'/Users/twosheds'

YAML

The configparser does not support nested section, when you need nested configuration file you can split the configuration in several configuration files.

I found also useful to use the YAML standard that support nested and hierarchical sections.

In [159]:
sample_config = """
%YAML 1.2
---
YAML: YAML Ain't Markup Language

What It Is: YAML is a human friendly data serialization
  standard for all programming languages.

YAML Resources:
  YAML 1.2 (3rd Edition): http://yaml.org/spec/1.2/spec.html
  YAML 1.1 (2nd Edition): http://yaml.org/spec/1.1/
  YAML 1.0 (1st Edition): http://yaml.org/spec/1.0/

Projects:
  C/C++ Libraries:
  - libyaml       # "C" Fast YAML 1.1
  - Syck          # (dated) "C" YAML 1.0
  - yaml-cpp      # C++ YAML 1.1 implementation
  Ruby:
  - psych         # libyaml wrapper (in Ruby core for 1.9.2)
  - RbYaml        # YAML 1.1 (PyYaml Port)
  - yaml4r        # YAML 1.0, standard library syck binding
  Python:
  - PyYaml        # YAML 1.1, pure python and libyaml binding
  - PySyck        # YAML 1.0, syck binding
"""
In [161]:
import yaml
cfg = yaml.load(sample_config)
In [163]:
from pprint import pprint

pprint(cfg)
{'Projects': {'C/C++ Libraries': ['libyaml', 'Syck', 'yaml-cpp'],
              'Python': ['PyYaml', 'PySyck'],
              'Ruby': ['psych', 'RbYaml', 'yaml4r']},
 'What It Is': 'YAML is a human friendly data serialization standard for all programming languages.',
 'YAML': "YAML Ain't Markup Language",
 'YAML Resources': {'YAML 1.0 (1st Edition)': 'http://yaml.org/spec/1.0/',
                    'YAML 1.1 (2nd Edition)': 'http://yaml.org/spec/1.1/',
                    'YAML 1.2 (3rd Edition)': 'http://yaml.org/spec/1.2/spec.html'}}

Command GUI

Another option is develop a GUI thta run in a terminal, for this kind of interface you can use two main libraries:

In [5]:
%%file cmd_gui_example.py
#!/usr/bin/env python
# encoding: utf-8

import npyscreen
class TestApp(npyscreen.NPSApp):
    def main(self):
        # These lines create the form and populate it with widgets.
        # A fairly complex screen in only 8 or so lines of code - a line for each control.
        F  = npyscreen.Form(name = "Welcome to Npyscreen",)
        t  = F.add(npyscreen.TitleText, name = "Text:",)
        fn = F.add(npyscreen.TitleFilename, name = "Filename:")
        fn2 = F.add(npyscreen.TitleFilenameCombo, name="Filename2:")
        dt = F.add(npyscreen.TitleDateCombo, name = "Date:")
        s  = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider")
        ml = F.add(npyscreen.MultiLineEdit,
               value = """try typing here!\nMutiline text, press ^R to reformat.\n""",
               max_height=5, rely=9)
        ms = F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One",
                values = ["Option1","Option2","Option3"], scroll_exit=True)
        ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several",
                values = ["Option1","Option2","Option3"], scroll_exit=True)

        # This lets the user interact with the Form.
        F.edit()

        print(ms.get_selected_objects())

if __name__ == "__main__":
    App = TestApp()
    App.run()
Overwriting cmd_gui_example.py
In [ ]:
#python cmd_gui_example.py

Basic Graphic User Interface (GUI)

In [1]:
from formlayout import fedit

person = [('Name', 'Pietro'),
          ('Age', 30),
          ('Sex', [0, 'Male', 'Female']),
          ('Size', 12.1),
          ('Eyes', 'green'),
          ('Married', True),
          ]

results = fedit(person, title="Describe yourself",
                comment="This is just an <b>example</b>.")
In [135]:
results
Out[135]:
['Pietro', 30, 0, 12.1, '#008000', True]
In [2]:
course = [
          (person, 'teacher', "Teacher of the course"),
          (person, 'student0', "Student of the course"),
          (person, 'student1', "Student of the course"),
          (person, 'student2', "Student of the course"),
          (person, 'student3', "Student of the course")
          ]
In [3]:
results = fedit(course, 'EURAC Python Course 2014')
In [4]:
results
Out[4]:
[['Pietro', 30, 0, 12.1, '#008000', True],
 ['Pietro', 30, 0, 12.1, '#008000', True],
 ['Pietro', 30, 0, 12.1, '#008000', True],
 ['Pie', 35, 1, 12.1, '#008000', True],
 ['Pietro', 30, 0, 12.1, '#008000', True]]
In [5]:
courses = [
           (course, 'Python', 'EURAC Python Course 2014'),
           (course, 'Java', 'Develop mobile apps'),
           (course, 'Javascript', 'For web and mobile'),]
In [6]:
results = fedit(courses, 'EURAC Courses 2014')
In [146]:
results
Out[146]:
[[['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True]],
 [['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True]],
 [['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True],
  ['Pietro', 30, 0, 12.1, '#008000', True]]]

Pyqtgraph

In [147]:
%%sh 
python -m pyqtgraph.examples
In [ ]: