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