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