Restrictive APIs for Python

Will Ware will.ware at gmail.com
Fri Dec 15 09:31:12 EST 2006


Python has no inherent provision for a restrictive API that blocks
accesses to methods and variables outside an allowed set.
Inexperienced Python programmers may fail to adhere to an agreed-upon
API, directly accessing the private internals of a class. Adherence to
defined APIs is a good thing. This function allows a class to specify
its API, and raise AttributeErrors for disallowed accesses.

def restrictiveApi(klas):
    class newklas:
        def __init__(self, *args):
            self.__inst = apply(klas, args)
        def __getattr__(self, attr):
            # If the attribute is in the permitted API, then return
            # the correct thing, no matter who asks for it.
            #
            if attr in self.__inst._PUBLIC:
                return getattr(self.__inst, attr)
            # If the attribute is outside the permitted API, then
            # return it only if the calling class is in the list of
            # friends. Otherwise raise an AttributeError.
            #
            elif hasattr(self.__inst, '_FRIENDS'):
                # find the class of the method that called us
                try:
                    raise Exception
                except:
                    import sys
                    tb = sys.exc_info()[2]
                    callerClass = tb.tb_frame.f_back.\
                                  f_locals['self'].__class__
                # if it's a friend class, return the requested thing
                if callerClass.__name__ in self.__inst._FRIENDS:
                    return getattr(self.__inst, attr)
            # if not a friend, raise an AttributeError
            raise AttributeError, attr
    return newklas

To use this, a class needs to define two class variables, _PUBLIC and
_FRIENDS, both being lists (or tuples) of strings. The _PUBLIC list
gives the names of all methods and variables that should be considered
public, i.e. any other class may use them. The _FRIENDS list gives the
names of classes that are allowed free access to all methods and
variables in the protected class. The _FRIENDS list is optional.

Having defined _PUBLIC and optionally _FRIENDS, use something like the
following to protect your class. Restricting the API will incur a
performance overhead, so it's best to do it under the control of some
sort of debug flag.

if debug_flag:
    from restrictive import restrictiveApi
    MyClass = restrictiveApi(MyClass)

======== Examples ==========

class ClassUnderTest:
    # This class has a private variable called privateX. It can be
    # set using the setX() method or gotten using the x() method.
    # If another class appears in the _FRIENDS list, that class
    # can access privateX directly.
    #
    _PUBLIC = ('x', 'setX')
    _FRIENDS = ('FriendClass',)
    def __init__(self, x): # __init__ is always callable by anybody
        self.setX(x)
    def x(self): # callable by anybody
        return self.privateX
    def setX(self, x): # callable by anybody
        self.privateX = x

ClassUnderTest = restrictiveApi(ClassUnderTest)

class FriendClass:
    def getX(self, cut):
        return cut.privateX # this works fine

class StrangerClass:
    def getX(self, cut):
        return cut.privateX # this raises an AttributeError




More information about the Python-list mailing list