Restrictive APIs for Python

W

Will Ware

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
 
G

Gabriel Genellina

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.

In Python, the usual way of saying "don't play with me" is prepending
an underscore: _private
BTW, have you *ever* tested your code?
 
R

Roberto Bonvallet

Will said:
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.

Just don't document those private internals.
Or document that they must not be accessed directly.
 
W

Will Ware

Gabriel said:
In Python, the usual way of saying "don't play with me" is prepending
an underscore: _private

Thanks, I am familiar with that.
BTW, have you *ever* tested your code?

Yes, we have a QA process. The problem is not that the code doesn't
work, it does. It was developed by a mix of more and less experienced
programmers, and early in the code's history, some hadn't been trained
on the benefits of complying to an API, or Pythonic idioms like the
leading underscore. So the code varies in its clarity, and some
maintenance chores aren't as pleasant as they might be.

I have found that the work of more experienced programmers often
includes improving the quality of code written by less experienced
programmers. Is this inconsistent with your own experience?
 
G

Gabriel Genellina

Thanks, I am familiar with that.
So enforce it instead of going against the nature of the language.
Yes, we have a QA process.
The problem is not that the code doesn't
work, it does.

Does it?
Traceback (most recent call last):
File "<stdin>", line 1, in ?

Even if you try to correct this, c is no more an instance of the
intended class, it's a "newklas" now, so isinstance() wont work
anymore. It has lost his docstring so you don't know its purpose and
intended usage anymore; you cannot inspect its methods using dir() so
editors and other tools can't help you writting and documenting code.
And a lot of other things upon which a lot of Python programs relies
on.
Don't go *against* the language, learn to use it the right way on your
own advantage.
It was developed by a mix of more and less experienced
programmers, and early in the code's history, some hadn't been trained
on the benefits of complying to an API, or Pythonic idioms like the
leading underscore. So the code varies in its clarity, and some
maintenance chores aren't as pleasant as they might be.
You can use tools like pylint or pychecker to try to detect and fix
those issues.
I have found that the work of more experienced programmers often
includes improving the quality of code written by less experienced
programmers. Is this inconsistent with your own experience?
If the less experienced guys get the feedback, I feel that's Ok.
Just cut them a finger phalanx each time someone breaks the rules and
see how well your programmers behave :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top