views:

19

answers:

1

Looking at twisted.words.protocols.irc.IRCClient, it seems to me like there are some strangely redundant methods. For instance, there is a method 'privmsg' but also a method 'irc_PRIVMSG'

As another example consider 'join' and 'irc_JOIN'

What I want to know is why the redundancy, those are just two examples of many. Are the two different types used in different contexts? Are we supposed to use one type and not another?

+3  A: 

You're on the right track about the two different types of methods being used in different contexts. This can actually be seen quite easily by examining the way IRCClient handles data it receives. First it parses them into lines, then it splits the lines up and passes the pieces to its own handleCommand method:

def handleCommand(self, command, prefix, params):
    """Determine the function to call for the given command and call
    it with the given arguments.
    """
    method = getattr(self, "irc_%s" % command, None)
    try:
        if method is not None:
            method(prefix, params)
        else:
            self.irc_unknown(prefix, command, params)
    except:
        log.deferr()

This is an example of a pattern that's quite common in Twisted protocol implementations and, even more generally, in Python programs as a whole. Some piece of the input is used to construct a method name dynamically. Then getattr is used to look up that method. If it is found, it is called.

Since the server is sending the client lines like "PRIVMSG ..." and "JOIN ...", this results in IRCClient looking up methods like irc_PRIVMSG and irc_JOIN.

These irc_* methods are just called with the split up but otherwise unparsed remainder of the line. This provides all of the information that came with the message, but it's not always the nicest format for the data to be in. For example, JOIN messages include usernames that include a hostmask, but often the hostmask is irrelevant and only the nickname is desired. So JOIN does something that's fairly typical for irc_* methods: it turns the rough data into something more pleasant to work with and passes the result on to userJoined:

def irc_JOIN(self, prefix, params):
    """  
    Called when a user joins a channel.
    """
    nick = string.split(prefix,'!')[0]
    channel = params[-1]
    if nick == self.nickname:
        self.joined(channel)
    else:
        self.userJoined(nick, channel)

You can see that there's also a conditional here, sometimes it calls joined instead of userJoined. This is another example of a transformation from the low-level data into something which is supposed to be more convenient for the application developer to work with.

This layering should help you decide which methods to override when handling events. If the highest level callback, such as userJoined, joined, or privmsg is sufficient for your requirements, then you should use those because they'll make your task the easiest. On the other hand, if they present the data in an inconvenient format or are awkward to use in some other way, you can drop down to the irc_* level. Your method will be called instead of the one defined on IRCClient, so you can handle the data in the lower-level format and the higher level callback won't even be invoked (unless you also invoke the base implementation when you override the method).

You'll also find there are IRC messages which IRCClient doesn't even define an irc_* method for. As we saw above in the handleCommand method, these all go to the irc_unknown callback. But if you define an irc_* method on your IRCClient subclass, then handleCommand will start passing the data to that method. Clearly in these cases, your only choice is to define the irc_* method, since there is no higher-level callback (like privmsg in the irc_PRIVMSG/privmsg case).

You can structure your implementations of irc_* methods similarly to the way IRCClient does, if you like - I usually find it helpful to do so, since it makes unit testing easier and keeps the protocol parsing logic separate from the application logic - but it's up to you.

Jean-Paul Calderone