BitterSuite Python Modules

This is a list of what you can do with Python modules. Note that any restrictions or changes made by these modules can be overridden by a determined student, and are meant only to guard against inadvertent usage or to simplify testing. TAs should be instructed to forward code which appears to be doing something suspicious.

It is not a complete list.

Usage notes

Modules declared via (modules "..." "...") are loaded into the main evaluator as if they were =import='ed; that is, there will be a global module variable accessible to the testing system. However, these modules are not automatically loaded into student code or test.py (TODO: is this behaviour really what we want? perhaps (modules) should be loaded into student code and test.py?).

Their effects, however, can have an impact on the student's code.

Disabling/changing functionality

If you import a Python module in a provided module, you can simply alter the module object's members, and the effects will be retained when the student's code runs.

This is very useful for selectively disabling built-in functions (import sys; del sys.__dict__['setrecursionlimit']) or for modifying the behaviour of a built-in or module function (see below).

Overriding built-ins

Built-in functions can be overridden: this includes functions such as map, raw_input, int, and others (for a full list, type dir(builtins) into an interactive Python session).

This is achieved by providing a module which overrides the functions you want changed, and adding that module to the options.ss file.

For example, this module overrides the default raw_input and input functions so that they do not print any prompt. Name this file suppress_prompt.py and place it in the provided/ directory.

# This module redefines [raw_]input to suppress the prompt string.
# Load it by using (modules "suppress_prompt") in options.ss.
old_input = input
old_raw_input = raw_input
__builtins__['input'] = lambda prompt='': old_input()
__builtins__['raw_input'] = lambda prompt='': old_raw_input()

To make the module take effect, you also need this code in options.ss:

(modules "suppress_prompt")

Disabling entire modules

Say that you don't want students to be able to access the "string" module (which contains functions useful for working with strings).

Files in provided/ take precedence over most Python modules (a list of exceptions to this rule is below). Thus, if you created provided/string.py with the contents

raise ImportError("string module not permitted in assignment X")

then student's code will raise this exception if they try to do import string in their code. You do not need to add this to modules.

Caveats: this applies assignment-wide. Hence, please do NOT name provided/ files the same as Python modules unless you explicitly want to shadow the Python module this way.

You cannot override these built-in modules in this way, because they are built-in to the Python interpreter:

UserDict
__builtin__
__main__
codecs
copy_reg
encodings
errno
exceptions
gc
imp
linecache
marshal
os
posix
pwd
readline
thread
types
warnings
zipimport

Generally useful modules

This is a list of modules which may come in useful for multiple assignments in different courses; i.e. they are of general interest.

Preventing raw_input and input from printing a prompt (to permit students to pick their own prompts while maintaining the ability to diff output):

# This module redefines [raw_]input to suppress the prompt string.
# Load it by using (modules "suppress_prompt") in options.ss.
old_input = input
old_raw_input = raw_input
__builtins__['input'] = lambda prompt='': old_input()
__builtins__['raw_input'] = lambda prompt='': old_raw_input()

Restricting file objects to just those operations that they should perform (this replaces file() and open(); because file objects have read-only attributes, a wrapper is needed to simulate the file object):

# This module redefines file() and open() to avoid Python issue5677.
# Writable (and appendable) files have all read methods removed and
# readable files have write methods removed.
# This file should be named file_wrapper.py.

import errno
old_file = file
old_open = open

class file_wrapper: pass
read_methods = ['__iter__','next','read','readinto','readline','readlines','xreadlines']
write_methods = ['truncate','write','writelines']

def newopen(fn, mode='r', *args, **kwargs):
        f=old_open(fn, mode, *args, **kwargs)
        can_write = 'w' in mode or 'a' in mode
        can_read = 'r' in mode or '+' in mode
        if not can_write and not can_read:
                # can't figure out the mode: return original file
                # to avoid problems
                return f
        wrap=file_wrapper()
        wrap._file=f
        def make_invalid_op(msg):
                def invalid_operation(*args, **kwargs):
                        raise IOError(errno.EBADF, msg)
                return invalid_operation
        for attr in old_file.__dict__:
                if attr in read_methods and not can_read:
                        setattr(wrap, attr, make_invalid_op("%s not permitted on writable file"%attr))
                elif attr in write_methods and not can_write:
                        setattr(wrap, attr, make_invalid_op("%s not permitted on readable file"%attr))
                else:
                        setattr(wrap, attr, getattr(f, attr))
        return wrap
def file_meth_wrapper(attr):
        def do_call(*args, **kwargs):
                if len(args) == 0:
                        raise TypeError("descriptor '%s' of 'file' object needs an argument"%attr)
                f=args[0]
                args=args[1:]
                return getattr(f, attr)(*args, **kwargs)
        return do_call
for attr in old_file.__dict__:
        setattr(newopen, attr, file_meth_wrapper(attr))
__builtins__['file']=__builtins__['open']=newopen

Redefining how certain objects are represented (this is useful when students define their own classes, but where a specific representation is needed for output reasons). In this example, the Card class doesn't define str, so Card objects simply appear as .

# This module redefines the builtin str function to make prettier output.
oldstr=__builtins__['str']
import __main__
def newstr(k, *args):
        if hasattr(k, '__class__') and k.__class__.__name__ == 'Card':
                out='%r (this is a Card with %s and %s)'
                if hasattr(k,'suit'): suit='suit '+repr(k.suit)
                else: suit='no suit'
                if hasattr(k,'value'): value='value '+repr(k.value)
                else: value='no value'
                return out%(k,value,suit)
        return oldstr(k, *args)
__main__.str=newstr

-- RobertXiao - 17 Mar 2009

Topic revision: r3 - 2009-04-03 - RobertXiao
 
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2019 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback