| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 1 | """Bastionification utility. | 
 | 2 |  | 
 | 3 | A bastion (for another object -- the 'original') is an object that has | 
 | 4 | the same methods as the original but does not give access to its | 
 | 5 | instance variables.  Bastions have a number of uses, but the most | 
 | 6 | obvious one is to provide code executing in restricted mode with a | 
 | 7 | safe interface to an object implemented in unrestricted mode. | 
 | 8 |  | 
 | 9 | The bastionification routine has an optional second argument which is | 
 | 10 | a filter function.  Only those methods for which the filter method | 
 | 11 | (called with the method name as argument) returns true are accessible. | 
 | 12 | The default filter method returns true unless the method name begins | 
 | 13 | with an underscore. | 
 | 14 |  | 
 | 15 | There are a number of possible implementations of bastions.  We use a | 
 | 16 | 'lazy' approach where the bastion's __getattr__() discipline does all | 
 | 17 | the work for a particular method the first time it is used.  This is | 
 | 18 | usually fastest, especially if the user doesn't call all available | 
 | 19 | methods.  The retrieved methods are stored as instance variables of | 
 | 20 | the bastion, so the overhead is only occurred on the first use of each | 
 | 21 | method. | 
 | 22 |  | 
 | 23 | Detail: the bastion class has a __repr__() discipline which includes | 
 | 24 | the repr() of the original object.  This is precomputed when the | 
 | 25 | bastion is created. | 
 | 26 |  | 
 | 27 | """ | 
 | 28 |  | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 29 |  | 
 | 30 | from types import MethodType | 
 | 31 |  | 
 | 32 |  | 
 | 33 | class BastionClass: | 
 | 34 |  | 
 | 35 |     """Helper class used by the Bastion() function. | 
 | 36 |  | 
 | 37 |     You could subclass this and pass the subclass as the bastionclass | 
 | 38 |     argument to the Bastion() function, as long as the constructor has | 
 | 39 |     the same signature (a get() function and a name for the object). | 
 | 40 |  | 
 | 41 |     """ | 
 | 42 |  | 
 | 43 |     def __init__(self, get, name): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 44 |         """Constructor. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 45 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 46 |         Arguments: | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 47 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 48 |         get - a function that gets the attribute value (by name) | 
 | 49 |         name - a human-readable name for the original object | 
 | 50 |                (suggestion: use repr(object)) | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 51 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 52 |         """ | 
 | 53 |         self._get_ = get | 
 | 54 |         self._name_ = name | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 55 |  | 
 | 56 |     def __repr__(self): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 57 |         """Return a representation string. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 58 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 59 |         This includes the name passed in to the constructor, so that | 
 | 60 |         if you print the bastion during debugging, at least you have | 
 | 61 |         some idea of what it is. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 62 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 63 |         """ | 
 | 64 |         return "<Bastion for %s>" % self._name_ | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 65 |  | 
 | 66 |     def __getattr__(self, name): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 67 |         """Get an as-yet undefined attribute value. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 68 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 69 |         This calls the get() function that was passed to the | 
 | 70 |         constructor.  The result is stored as an instance variable so | 
 | 71 |         that the next time the same attribute is requested, | 
 | 72 |         __getattr__() won't be invoked. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 73 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 74 |         If the get() function raises an exception, this is simply | 
 | 75 |         passed on -- exceptions are not cached. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 76 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 77 |         """ | 
 | 78 |         attribute = self._get_(name) | 
 | 79 |         self.__dict__[name] = attribute | 
 | 80 |         return attribute | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 81 |  | 
 | 82 |  | 
 | 83 | def Bastion(object, filter = lambda name: name[:1] != '_', | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 84 |             name=None, bastionclass=BastionClass): | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 85 |     """Create a bastion for an object, using an optional filter. | 
 | 86 |  | 
 | 87 |     See the Bastion module's documentation for background. | 
 | 88 |  | 
 | 89 |     Arguments: | 
 | 90 |  | 
 | 91 |     object - the original object | 
 | 92 |     filter - a predicate that decides whether a function name is OK; | 
 | 93 |              by default all names are OK that don't start with '_' | 
 | 94 |     name - the name of the object; default repr(object) | 
 | 95 |     bastionclass - class used to create the bastion; default BastionClass | 
 | 96 |  | 
 | 97 |     """ | 
 | 98 |  | 
 | 99 |     # Note: we define *two* ad-hoc functions here, get1 and get2. | 
 | 100 |     # Both are intended to be called in the same way: get(name). | 
 | 101 |     # It is clear that the real work (getting the attribute | 
 | 102 |     # from the object and calling the filter) is done in get1. | 
 | 103 |     # Why can't we pass get1 to the bastion?  Because the user | 
 | 104 |     # would be able to override the filter argument!  With get2, | 
 | 105 |     # overriding the default argument is no security loophole: | 
 | 106 |     # all it does is call it. | 
 | 107 |     # Also notice that we can't place the object and filter as | 
 | 108 |     # instance variables on the bastion object itself, since | 
 | 109 |     # the user has full access to all instance variables! | 
 | 110 |  | 
 | 111 |     def get1(name, object=object, filter=filter): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 112 |         """Internal function for Bastion().  See source comments.""" | 
 | 113 |         if filter(name): | 
 | 114 |             attribute = getattr(object, name) | 
 | 115 |             if type(attribute) == MethodType: | 
 | 116 |                 return attribute | 
 | 117 |         raise AttributeError, name | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 118 |  | 
 | 119 |     def get2(name, get1=get1): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 120 |         """Internal function for Bastion().  See source comments.""" | 
 | 121 |         return get1(name) | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 122 |  | 
 | 123 |     if name is None: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 124 |         name = `object` | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 125 |     return bastionclass(get2, name) | 
 | 126 |  | 
 | 127 |  | 
 | 128 | def _test(): | 
 | 129 |     """Test the Bastion() function.""" | 
 | 130 |     class Original: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 131 |         def __init__(self): | 
 | 132 |             self.sum = 0 | 
 | 133 |         def add(self, n): | 
 | 134 |             self._add(n) | 
 | 135 |         def _add(self, n): | 
 | 136 |             self.sum = self.sum + n | 
 | 137 |         def total(self): | 
 | 138 |             return self.sum | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 139 |     o = Original() | 
 | 140 |     b = Bastion(o) | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 141 |     testcode = """if 1: | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 142 |     b.add(81) | 
 | 143 |     b.add(18) | 
 | 144 |     print "b.total() =", b.total() | 
 | 145 |     try: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 146 |         print "b.sum =", b.sum, | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 147 |     except: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 148 |         print "inaccessible" | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 149 |     else: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 150 |         print "accessible" | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 151 |     try: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 152 |         print "b._add =", b._add, | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 153 |     except: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 154 |         print "inaccessible" | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 155 |     else: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 156 |         print "accessible" | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 157 |     try: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 158 |         print "b._get_.func_defaults =", b._get_.func_defaults, | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 159 |     except: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 160 |         print "inaccessible" | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 161 |     else: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 162 |         print "accessible" | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 163 |     \n""" | 
 | 164 |     exec testcode | 
 | 165 |     print '='*20, "Using rexec:", '='*20 | 
 | 166 |     import rexec | 
 | 167 |     r = rexec.RExec() | 
 | 168 |     m = r.add_module('__main__') | 
 | 169 |     m.b = b | 
 | 170 |     r.r_exec(testcode) | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 171 |  | 
 | 172 |  | 
 | 173 | if __name__ == '__main__': | 
 | 174 |     _test() |