163 lines
4.7 KiB
Python
163 lines
4.7 KiB
Python
|
"""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.
|
||
|
|
||
|
"""
|
||
|
|
||
|
__version__ = '$Revision$'
|
||
|
# $Source$
|
||
|
|
||
|
|
||
|
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)
|
||
|
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"
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
_test()
|