MadMode

Dan Connolly's tinkering lab notebook

Capability idioms for python, part 1: scripts vs. modules

In the python top-level script environment, the idomatic "conditional script" stanza is:

if __name__ == "__main__":
    main()

For example, to count the lines in the files given as arguments:

import sys

def main():
    for filename in sys.argv[1:]:
        stream = open(filename)
        qty = len([line for line in stream])
        print qty, filename

if __name__ == '__main__':
    main()

Suppose we re-organize this as:

def main(argv, open_arg, stdout):
    for filename in argv[1:]:
        stream = open_arg(filename)
        qty = len([line for line in stream])
        print >>stdout, qty, filename

if __name__ == '__main__':
    def _script():
        from sys import argv, stdout

        def open_arg(arg):
            if arg not in argv[1:]:
                raise IOError(
                    'only paths given as arguments can be opened')
            return open(arg)

        main(argv=argv[:], stdout=stdout, open_arg=open_arg)

    _script()

Now main() communicates with the outside world only through capabilities passed to it.

Capabilities such as sys.argv and sys.stdout are visible only in the _initial_caps() trusted computing base. Note that this TCB is not executed if this module is imported from another module.

The main() function is fully unit-testable now, as well:

import StringIO


def main(argv, open_arg, stdout):
    r'''
    >>> caps = Mock.caps()
    >>> main(**caps)
    >>> caps['stdout'].getvalue()
    '3 f1\n'
    '''
    for filename in argv[1:]:
        stream = open_arg(filename)
        qty = len([line for line in stream])
        print >>stdout, qty, filename


class Mock(object):
    @classmethod
    def caps(cls):
        argv = ['prog', 'f1']

        def open_arg(x):
            if x in argv[1:]:
                return ['line1', 'line2', 'line3']
            raise IOError()

        out = StringIO.StringIO()

        return dict(argv=argv[:], stdout=out, open_arg=open_arg)

...