Inheritance and name clashes

Chris Torek nospam at torek.net
Sun Oct 3 22:31:12 EDT 2010


In article <14cf8b45-a3c0-489f-8aa9-a75f0f326eaa at n3g2000yqb.googlegroups.com>
Rock  <rocco.rossi at gmail.com> wrote:
>I've really been wondering about the following lately. The question is
>this: if there are no (real) private or protected members in Python,
>how can you be sure, when inheriting from another class, that you
>won't wind up overriding, and possibly clobbering some important data
>field of the parent class, which might compromise its entire
>functionality?

You are right, but this is a double-edged feature/bug as it allows
you to override stuff deliberately, including things the original
code-writer did not think you should be able to override.  You
cannot get this particular benefit without a corresponding cost.

For instance (note, this may have been changed in a later version,
I am using rather old code for this particular project) I ran into
an issue with the SimpleXMLRPCServer code (really BaseHTTPServer),
which I fixed with the pipewrap() code below.  I also needed a
number of extra features; see comments.  To do this I had to make
use of a number of things that were not officially exported, nor
really designed to be override-able.

(I've snipped out some [I think] irrelevant code below, but left
enough to illustrate all this.)

    --------

import socket, SocketServer, SimpleXMLRPCServer, xmlrpclib
import errno

def format_ipaddr(addr, do_reverse_lookup = True):
    (host, port) = addr[:2]
    fqdn = socket.getfqdn(host) if do_reverse_lookup else ''
    return '%s[%s]' % (fqdn, host)

# For exceptions to be passed back to rpc caller (other exceptions
# are caught in the MgrServer and logged), use MgrError (or xmlrpclib.Fault).
class MgrError:
    def __init__(self, exc_type = None, exc_val = None):
        self.exc_type = exc_type
        self.exc_val = exc_val
        if exc_type is None or exc_val is None:
            i = sys.exc_info()[:2]
            if exc_type is None:
                self.exc_type = i[0]
            if exc_val is None:
                self.exc_val = i[1]


# This gives us an opportunity to fix the "broken pipe" error that
# occurs when a client disconnects in mid-RPC.
# XXX I think this should really be done in BaseHTTPServer or similar.
# See also <http://trac.edgewall.org/ticket/1183>.
#
# However, we'd still want to override do_POST so that we can
# sneak the client address to the _dispatch function in self.server.
class pipe_eating_rqh(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
    def pipewrap(self, f):
        try:
            f(self)
        except socket.error, (code, msg):
            if (code == errno.EPIPE or code == errno.ECONNRESET or
                    code == 10053): # 10053 for Windows
#               self.log_message('Lost connection to client: %s',
#                   self.address_string())
                logger.info('Lost connection to client: %s',
                    format_ipaddr(self.client_address))
            else:
                raise

    def do_POST(self):
        self.server.client_address = self.client_address
        return self.pipewrap(
            SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST)
    def report_404(self):
        return self.pipewrap(
            SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.report_404)

class MgrServer(SocketServer.ThreadingMixIn,
        SimpleXMLRPCServer.SimpleXMLRPCServer):
    """
    The "Manager Server" adds a few things over a basic XML-RPC server:
    
    - Uses the threading mix-in to run each rpc request in a
      separate thread.
    - Runs an (optional) periodic handler.
    - Logs all requests with the logger.
    - Handles "admin" requests specially.
    - Logs "unexpected" exceptions, so that we catch server bugs
      in the server log.
    """

    def __init__(self, addr, periodic = None, *args, **kwargs):
        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, addr,
            requestHandler = pipe_eating_rqh,
            *args, **kwargs)

        # Note, can't just change self.funcs[] into a dict of
        # tuples without overriding system_methodHelp() too,
        # so we'll use a separate parallel dictionary.
        self.admin_label = {}

        self.periodic = periodic
        if periodic:
            self.socket.settimeout(periodic[0])
        # see __nonzero__ below
        self.register_function(self.__nonzero__)

    def get_request(self):
        while True:
            try:
                result = self.socket.accept()
            except socket.timeout:
                self.periodic[1](*self.periodic[2])
            else:
                return result
        # not reached

    def _dispatch(self, method, params):
        # Taken from SimpleXMLRPCServer.py but then stripped down and
        # modified.
        if method in self.admin_label:
            ... stuff snipped out here ...
        try:
            func = self.funcs[method]
        except KeyError:
            # regular SimpleXMLRPCServer checks for self.instance
            # and if so, for its _dispatch here ... we're not using
            # that so I omit it
            func = None
        if func is not None:
            logger.debug('%s: %s%s',
                format_ipaddr(self.client_address), method, str(params))
            try:
                return func(*params)
            except MgrError, e:
                # Given, e.g., MgrError(ValueError('bad value'))),
                # send the corresponding exc_type / exc_val back
                # via xmlrpclib, which transforms it into a Fault.
                raise e.exc_type, e.exc_val
            except xmlrpclib.Fault:
                # Already a Fault, pass it back unchanged.
                raise
            except TypeError, e:
                # If the parameter count did not match, we will get
                # a TypeError with the traceback ending with our own
                # call at "func(*params)".  We want to pass that back,
                # rather than logging it.
                #
                # If the TypeError happened inside func() or one of
                # its sub-functions, the traceback will continue beyond
                # here, i.e., its tb_next will not be None.
                if sys.exc_info()[2].tb_next is None:
                    raise
                # else fall through to error-logging code
            except:
                pass # fall through to error-logging code

            # Any other exception is assumed to be a bug in the server.
            # Log a traceback for server debugging.
            # is logger.error exc_info thread-safe? let's assume so
            logger.error('internal failure in %s', method, exc_info = True)
            # traceback.format_exc().rstrip()
            raise xmlrpclib.Fault(2000, 'internal failure in ' + method)
        else:
            logger.info('%s: bad request: %s%s',
                format_ipaddr(self.client_address), method, str(params))
            raise Exception('method "%s" is not supported' % method)

    # Tests of the form:
    #   c = new_class_object(params)
    #   if c: ...
    # are turned into calls to the class's __nonzero__ method.
    # We don't do "if server:" in our own server code, but if we did
    # this would get called, and it's reasonable to just define it as
    # True.  Probably the existing SimpleXMLRPCServer (or one of its
    # base classes) should have done this, but they did not.
    #
    # For whatever reason, the xml-rpc library routines also pass
    # a client's __nonzero__ (on his server proxy connection) to us,
    # which reaches our dispatcher above.  By registering this in
    # our __init__, clients can do "if server:" to see if their
    # connection is up.  It's a frill, I admit....
    def __nonzero__(self):
        return True

    def register_admin_function(self, f, name = None):
        ... more stuff snipped out ...

# --END-- threading XML RPC server code
-- 
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W)  +1 801 277 2603
email: gmail (figure it out)      http://web.torek.net/torek/index.html



More information about the Python-list mailing list