On Wednesday 15 October 2008 22:47:45 Ben Ford wrote:
> Michael alluded to a few
> improvements too, so I'd be really interested to know how to improve it!
I'm planning on discussing these this evening, but the thing that jumped out
at me most when I was that it's missing some of the newer idioms that we use
(which isn't too surprising). I want to do a proper code review since I'd
like to get this merged, and I want to do that after doing the Axon 1.6
release notes (which are in progress). However I think it's useful to give a
very quick example of what I mean here.
Take this example from kamaelia-aws/aws/sqs.py :
CONTINUE_MSG = "CONTINUE"
class Ticker(threadedcomponent):
"""A Component that sends out a message to wake up an asleep component"""
def __init__(self, timeout=5):
"""Initialize the component with a timeout
:param timeout: The time in second to wait for
:type timeout: int
"""
super(Ticker, self).__init__()
self.timeout = timeout
def __str__(self):
return u"Ticker: %s secs" % self.timeout
def main(self):
while 1:
time.sleep(self.timeout)
self.send(CONTINUE_MSG, "signal")
This boils down to: (skipping docs and __str__)
CONTINUE_MSG = "CONTINUE"
class Ticker(threadedcomponent):
def __init__(self, timeout=5):
super(Ticker, self).__init__()
self.timeout = timeout
def main(self):
while 1:
time.sleep(self.timeout)
self.send(CONTINUE_MSG, "signal")
This isn't as general as it could be, it would be nice to do this:
class Ticker(threadedcomponent):
def __init__(self, timeout=5, message=CONTINUE_MSG):
super(Ticker, self).__init__()
self.timeout = timeout
self.message = message
def main(self):
while 1:
time.sleep(self.timeout)
self.send(self.message, "signal")
Since that makes the component generic. However the signature of
Component.__init__ these days is Component.__init__(self, **argd).
The reason for that is because the signature of
Microprocess.__init__ these days is Microprocess.__init__(self, **argd).
And the reason for that is so that Microprocess.__init__ can do this:
self.__dict__.update(argd)
This means that you can specify defaults like this:
class Ticker(threadedcomponent):
timeout = 5
message = CONTINUE_MSG
def main(self):
while 1:
time.sleep(self.timeout)
self.send(self.message, "signal")
And still override self.timeout or self.message with local defaults like this:
Ticker(timeout=5, message=5)
When you first do this it feels very strange, but you then start to realise
huge advantages. For example, the ServerCore does this:
(http://kamaelia.googlecode.com/svn/trunk/Code/Python/Kamaelia/Kamaeli...)
from Kamaelia.Internet.TCPServer import TCPServer
class ServerCore(Axon.AdaptiveCommsComponent.AdaptiveCommsComponent):
port = 1601
protocol = None
socketOptions=None
TCPS=TCPServer
Now the fun part here is TCPS/TCPServer, since this gets used later on like
this:
if self.socketOptions is None:
self.server = (self.TCPS)(listenport=self.port)
else:
self.server = (self.TCPS)(listenport=self.port,
socketOptions=self.socketOptions)
This means that someone can come in and override TCPS themselves later on -
which has obvious advantages when it comes to mock based testing. However,
TCPServer does the same thing:
(http://kamaelia.googlecode.com/svn/trunk/Code/Python/Kamaelia/Kamaeli...)
from Kamaelia.Internet.ConnectedSocketAdapter import ConnectedSocketAdapter
class TCPServer(Axon.Component.component):
CSA = ConnectedSocketAdapter
...
def createConnectedSocket(self, sock):
...
CSA = (self.CSA)(newsock,self.selectorService)
On the surface of things, this only makes testing easier, but it actually
allows complete repurposing. For example if you had UDP server that could
look like TCPServer/CSA in the behaviour it has regarding inboxes/outboxes &
handling of data, then you could do this:
class UDPServerCore(ServerCore):
port = 1601
protocol = None
socketOptions=None
TCPS=UDPServer
If you wanted to create an echo protocol server based on this change you could
do this directly as follows:
class EchoUDPServerCore(ServerCore):
port = 1601
protocol = None
socketOptions=None
TCPS=UDPServer
class protocol(Axon.Component.component):
def main(self):
while not self.dataReady("control"):
for d in self.Inbox("inbox"):
self.send(d, "outbox")
if not self.anyReady():
self.pause()
yield 1
Or you could write it:
class echoer(Axon.Component.component):
def main(self):
while not self.dataReady("control"):
for d in self.Inbox("inbox"):
self.send(d, "outbox")
if not self.anyReady():
self.pause()
yield 1
class EchoUDPServerCore(ServerCore):
port = 1601
protocol = None
socketOptions=None
TCPS=UDPServer
protocol = echoer
However it also allows more complex arrangements like this:
class GreylistServer(ServerCore):
logfile = config["greylist_log"]
debuglogfile = config["greylist_debuglog"]
socketOptions=(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
port = config["port"]
class TCPS(TCPServer):
CSA = NoActivityTimeout(ConnectedSocketAdapter,
timeout=config["inactivity_timeout"],
debug=False)
class protocol(GreyListingPolicy):
servername = config["servername"]
serverid = config["serverid"]
smtp_ip = config["smtp_ip"]
smtp_port = config["smtp_port"]
allowed_senders = config["allowed_senders"]
allowed_sender_nets = config["allowed_sender_nets"]
allowed_domains = config["allowed_domains"]
whitelisted_triples = config["whitelisted_triples"]
whitelisted_nonstandard_triples = config["whitel... "]
ie it allows someone to be able to go "Hmm, what I want is more or less
that code, but with this component configured in this way, rather than what
it does right now". This won't always be correct for every case by a long
shot, but it does open up all sorts of possibilities... (Primarily it makes it
easier to write something simple once and then allow people to use it to
make more complex systems :)
Michael.
--
http://www.kamaelia.org/Home