[Mailman-Users] Writing a custom handler

Mark Sapiro mark at msapiro.net
Tue Jul 2 19:16:54 CEST 2013

On 07/02/2013 08:48 AM, Chris Nulk wrote:

> On 7/1/2013 3:35 PM, Mark Sapiro wrote:
>> On 7/1/2013 10:24 AM, Chris Nulk wrote:
>>>      # Go through possible senders.  Check if any of them are
>>>      #   on the global ban list
>>>      for sender in msg.get_senders():
>>>          if sender.lower() in banlist:
>>>              break
>>>      else:
>>>          # None of the sender addresses were in the global ban
>>>          #   list so return and continue with the next pipeline
>>>          #   handler
>>>          return
>>>      # A sender was found on the global ban list.  Log it and
>>>      #   discard the message notifying the list owner
>>>      if sender:
>> How can this be False?
> If I understand the code properly, sender should never be False or None
> but if something happens that I do not expect, I have a way to catch
> it.  Or, am I missing something?

Yes and no. It is usually fine to test for conditions that "can't
happen" just to be on the safe side.

However, in this case, I was thinking it is so immediately obvious that
the only way to get this far is if sender.lower() is in banlist so you
must have had a sender, but just to show that you were right, in
starting to type this I realized that get_senders() possibly could
return a null string and there could be an empty line in banlist, so in
fact sender could be False in this case.

>> You could just import do_discard from Mailman.Handlers.Moderate and
>> use that, but you may want the custom messages. If so, you may also
>> want do_discard_globalban(mlist, msg, sender)
>>> # copied almost verbatim from Mailman/Handlers/Moderate.py
>>> def do_discard_globalban(mlist, msg):
>>>      sender = msg.get_sender
> I take it that should I change the line -
>     do_discard_globalban(mlist, msg, sender)
> I should also update the def for it to -
>     def do_discard_globalban(mlist, msg, sender)
> and I can remove the line -
>     sender = msg.get_sender()

Yes, that is what I meant, but I now realize that sender isn't actually
referenced in the discard code, so I suggest you do the above, but also

         text = MIMEText(Utils.wrap(_("""\
The sender of the attached message is on the Global Ban list. Therefore,
the message
has been automatically discarded.""")),

to something like

         text = MIMEText(Utils.wrap(_("""\
The sender - %(sender)s - of the attached message is on the Global Ban
list. Therefore,
the message
has been automatically discarded.""")),

The i18n._() function will replace %(sender)s with the value of sender.

> I have a few more questions.  My plan for the custom handler is install
> it as the first handler in the global pipeline for all lists.  I have
> seen how to do that in the faqs.   I also believe that once it is
> running with Mailman, it will be in memory and executable - no loading
> and unloading of the handler.  Once Mailman loads, it loads all the
> handlers and is ready to go.

Yes and no. Only IncomingRunner imports the pipeline handlers, and it
does that only when a message arrives. I.e. the first time
IncomingRunner needs to process a message through the pipeline, it will
import the handlers, and once the handlers are imported, they remain in

> My handler reads a file every time it runs.  Not very efficient. Once I
> have read in the banlist from the file, will the banlist always be
> available for future iterations through the code?

You could change your code as follows:

1) Make the banlist global by putting

# First, initialize the banlist
banlist = []

ahead of

def process(mlist, msg, msgdata):

and remove that from the process() definition:

Then make the code inside process() that loads the banlist conditional
on banlist being the empty list, e.g.,

    if not banlist:

Then once the global banlist is loaded with the file data, the file
won't be read again.

> What I would like to do is read the global ban list file once to build
> the ban list but update the ban list if there has been a change in the
> global ban list file.

You have a couple of choices:

1) Do the above and just restart Mailman if you update the banlist.

2) add code to test and remember (in a global) the mod time of the
banlist file and read it only if the file is newer.

Method 2) may involve comparable overhead to just reading the file each
time so may not be worth it. I would either do 1) or just read the file
every time based on how often I expect the file to change.

Note that if posts are infrequent, the overhead of reading the file each
time doesn't much matter, and if posts are frequent, the file will
probably be in an OS disk cache anyway.

Mark Sapiro <mark at msapiro.net>        The highway is for gamblers,
San Francisco Bay Area, California    better use your sense - B. Dylan

More information about the Mailman-Users mailing list