| """Bastionification utility. | 
 |  | 
 | A bastion (for another object -- the 'original') is an object that has | 
 | the same methods as the original but does not give access to its | 
 | instance variables.  Bastions have a number of uses, but the most | 
 | obvious one is to provide code executing in restricted mode with a | 
 | safe interface to an object implemented in unrestricted mode. | 
 |  | 
 | The bastionification routine has an optional second argument which is | 
 | a filter function.  Only those methods for which the filter method | 
 | (called with the method name as argument) returns true are accessible. | 
 | The default filter method returns true unless the method name begins | 
 | with an underscore. | 
 |  | 
 | There are a number of possible implementations of bastions.  We use a | 
 | 'lazy' approach where the bastion's __getattr__() discipline does all | 
 | the work for a particular method the first time it is used.  This is | 
 | usually fastest, especially if the user doesn't call all available | 
 | methods.  The retrieved methods are stored as instance variables of | 
 | the bastion, so the overhead is only occurred on the first use of each | 
 | method. | 
 |  | 
 | Detail: the bastion class has a __repr__() discipline which includes | 
 | the repr() of the original object.  This is precomputed when the | 
 | bastion is created. | 
 |  | 
 | """ | 
 |  | 
 | __all__ = ["BastionClass", "Bastion"] | 
 |  | 
 | from types import MethodType | 
 |  | 
 |  | 
 | class BastionClass: | 
 |  | 
 |     """Helper class used by the Bastion() function. | 
 |  | 
 |     You could subclass this and pass the subclass as the bastionclass | 
 |     argument to the Bastion() function, as long as the constructor has | 
 |     the same signature (a get() function and a name for the object). | 
 |  | 
 |     """ | 
 |  | 
 |     def __init__(self, get, name): | 
 |         """Constructor. | 
 |  | 
 |         Arguments: | 
 |  | 
 |         get - a function that gets the attribute value (by name) | 
 |         name - a human-readable name for the original object | 
 |                (suggestion: use repr(object)) | 
 |  | 
 |         """ | 
 |         self._get_ = get | 
 |         self._name_ = name | 
 |  | 
 |     def __repr__(self): | 
 |         """Return a representation string. | 
 |  | 
 |         This includes the name passed in to the constructor, so that | 
 |         if you print the bastion during debugging, at least you have | 
 |         some idea of what it is. | 
 |  | 
 |         """ | 
 |         return "<Bastion for %s>" % self._name_ | 
 |  | 
 |     def __getattr__(self, name): | 
 |         """Get an as-yet undefined attribute value. | 
 |  | 
 |         This calls the get() function that was passed to the | 
 |         constructor.  The result is stored as an instance variable so | 
 |         that the next time the same attribute is requested, | 
 |         __getattr__() won't be invoked. | 
 |  | 
 |         If the get() function raises an exception, this is simply | 
 |         passed on -- exceptions are not cached. | 
 |  | 
 |         """ | 
 |         attribute = self._get_(name) | 
 |         self.__dict__[name] = attribute | 
 |         return attribute | 
 |  | 
 |  | 
 | def Bastion(object, filter = lambda name: name[:1] != '_', | 
 |             name=None, bastionclass=BastionClass): | 
 |     """Create a bastion for an object, using an optional filter. | 
 |  | 
 |     See the Bastion module's documentation for background. | 
 |  | 
 |     Arguments: | 
 |  | 
 |     object - the original object | 
 |     filter - a predicate that decides whether a function name is OK; | 
 |              by default all names are OK that don't start with '_' | 
 |     name - the name of the object; default repr(object) | 
 |     bastionclass - class used to create the bastion; default BastionClass | 
 |  | 
 |     """ | 
 |  | 
 |     # Note: we define *two* ad-hoc functions here, get1 and get2. | 
 |     # Both are intended to be called in the same way: get(name). | 
 |     # It is clear that the real work (getting the attribute | 
 |     # from the object and calling the filter) is done in get1. | 
 |     # Why can't we pass get1 to the bastion?  Because the user | 
 |     # would be able to override the filter argument!  With get2, | 
 |     # overriding the default argument is no security loophole: | 
 |     # all it does is call it. | 
 |     # Also notice that we can't place the object and filter as | 
 |     # instance variables on the bastion object itself, since | 
 |     # the user has full access to all instance variables! | 
 |  | 
 |     def get1(name, object=object, filter=filter): | 
 |         """Internal function for Bastion().  See source comments.""" | 
 |         if filter(name): | 
 |             attribute = getattr(object, name) | 
 |             if type(attribute) == MethodType: | 
 |                 return attribute | 
 |         raise AttributeError, name | 
 |  | 
 |     def get2(name, get1=get1): | 
 |         """Internal function for Bastion().  See source comments.""" | 
 |         return get1(name) | 
 |  | 
 |     if name is None: | 
 |         name = `object` | 
 |     return bastionclass(get2, name) | 
 |  | 
 |  | 
 | def _test(): | 
 |     """Test the Bastion() function.""" | 
 |     class Original: | 
 |         def __init__(self): | 
 |             self.sum = 0 | 
 |         def add(self, n): | 
 |             self._add(n) | 
 |         def _add(self, n): | 
 |             self.sum = self.sum + n | 
 |         def total(self): | 
 |             return self.sum | 
 |     o = Original() | 
 |     b = Bastion(o) | 
 |     testcode = """if 1: | 
 |     b.add(81) | 
 |     b.add(18) | 
 |     print "b.total() =", b.total() | 
 |     try: | 
 |         print "b.sum =", b.sum, | 
 |     except: | 
 |         print "inaccessible" | 
 |     else: | 
 |         print "accessible" | 
 |     try: | 
 |         print "b._add =", b._add, | 
 |     except: | 
 |         print "inaccessible" | 
 |     else: | 
 |         print "accessible" | 
 |     try: | 
 |         print "b._get_.func_defaults =", map(type, b._get_.func_defaults), | 
 |     except: | 
 |         print "inaccessible" | 
 |     else: | 
 |         print "accessible" | 
 |     \n""" | 
 |     exec testcode | 
 |     print '='*20, "Using rexec:", '='*20 | 
 |     import rexec | 
 |     r = rexec.RExec() | 
 |     m = r.add_module('__main__') | 
 |     m.b = b | 
 |     r.r_exec(testcode) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     _test() |