Pranjal's Blog

Proud Fast.ai student

Dlist Pre-Mid-Term report

Following up with the previous post, we will take a look at Threads, What they are? How are they different from Dlist? The need of having Threads in Dlist. Then we will move to Threads’ implementation, followed by concerned models & interfaces. Then we will jump to Dlist rule which will explain how a dlist is detected for an incoming message. Next would be the Dlist runner which handles the built-in commands for a dlist enabled mailing list message. Finally we overview the Override table and its need and some of the migrations done till date.

A thread is a collection of messages that can be arranged in a DAG (or even a tree) by using the reply-to relationship.

A dlist is a collection of messages defined by having a particular mailbox among their addressees.

Thanks to Stephen for those definitions!

Now a dlist which by definition is a collection of messages, needs to be arranged in some order according to my implementation making threads necessary. However its completely debatable why are we calling it a structure since my way of making threads is simply adding another attribute to the message namely ‘thread_id’. This id will keep track of where the message belongs in the collection.

Thread

dlist(model): This holds Thread and ThreadManager classes, Thread class primarily deals with the relationships and variables. ThreadManager includes two methods new_thread() and continue_thread(), new_thread() takes necessary parameters and calls create_thread() which handles the database, the new_thread() method is complete and continue_thread() is in process.

In : from mailman.interfaces.dlist import IThreadManager

In : thread_manager = getUtility(IThreadManager)

In : thread_manager.new_thread(mlist, msg)

In : msg['thread-id']
Out: 'UQK2WXGBPSGASPABLTG3PZKSV3TZCGVE'

Dlist rule

dlist(rule): It contains a check whether a mailing list id dlist enabled or not followed by a match for keyword ‘new’ separated by ‘+’ delimiter in the subaddress. If everything is fine then rule hits.

In : rule = config.rules['dlist']

In : from zope.interface.verify import verifyObject

In : rule = config.rules['dlist']

In : from mailman.interfaces.rules import IRule

In : verifyObject(IRule, rule)
Out: True

In : print(rule.name)
dlist

In : rule.check(mlist, msg, {})
Out: True

In : mlist.dlist_enabled = False

In : rule.check(mlist, msg, {})
Out: False

In : mlist.dlist_enabled = True

In : msg = mfs("""\
From: aperson@example.com
To: test+random@example.com
Subject: This is the subject
Message-ID: random
echo Body
""")

In : rule.check(mlist, msg, {})
Out: False

Dlist runner

dlist(runner): It is derived from CommandRunner and does the same thing except for looking for ‘+new-request’ commands instead of ‘-request’ and processing them. I have added a ‘Thread-Id’ in message details which is visible after the message is processed.

In : from mailman.testing.documentation import specialized_message_from_string as mfs

In : msg = mfs("""\
   ....: From: aperson@example.com
   ....: To: test+new@example.com
   ....: Subject: random text
   ....: Message-ID: random
   ....: echo Body
   ....: """)

In : from mailman.app.inject import  inject_message

In : from mailman.runners.command import CommandRunner

In : from mailman.runners.dlist import DlistRunner

In : from mailman.testing.helpers import get_queue_messages

In : from mailman.testing.helpers import make_testable_runner

In : filebase = inject_message(mlist, msg, switchboard='dlist')

In : dlist = make_testable_runner(DlistRunner)

In : dlist.run()

In : messages = get_queue_messages('virgin')

In : len(messages)
Out: 1

In : print(messages[0].msg)
Subject: The results of your email commands
From: test-bounces@example.com
To: aperson@example.com
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Message-ID: <20150619235055.18864.88398@debian>
Date: Sat, 20 Jun 2015 05:20:55 +0530
Precedence: bulk

The results of your email command are provided below.

- Original message details:
From: aperson@example.com
Subject: random text
Date: Sat, 20 Jun 2015 05:20:54 +0530
Message-ID: random
Thread-ID: UQK2WXGBPSGASPABLTG3PZKSV3TZCGVE

- Results:

- Done.

Override

This model overrides the default user preferences for subscription or unsubscription from a particular thread. By default all ‘First’ posts are received by all the users and if a user wishes to unsubscribe (assuming its his/her default status “Receive all mails”) ‘Override’ will store this change in preference and will unsubscribe the user. If current status is “Only receive first post” and user particularly wishes to subscribe to a thread then Override will store that preference and subscribe the user.

dlist(model): Override and OverrideManager classes are included in the dlist model, Override contains the basic definitions and relationships, OverrideManager included override() method which handles the input and override_in() method which is called internally by override() to handle database.

Upcoming work

Override will be functional soon and then recipients calculation can be done which will be called internally by continue_thread().

Possible Implementation

if mlist.dlist_enabled == True:

	recipients_allpost = set(member.address.email
                         for member in mlist.regular_members.members
                         if member.delivery_status == DeliveryStatus.enabled &&
                         if member.preference.dlist_preference = 2)

	recipients_firstpostonly = set(member.address.email
                         for member in mlist.regular_members.members
                         if member.delivery_status == DeliveryStatus.enabled &&
                         if member.preference.dlist_preference = 2)

if mlist.dlist_enabled == False:

	recipients = set(member.address.email
                         for member in mlist.regular_members.members
                         if member.delivery_status == DeliveryStatus.enabled)

member_recipients(handler): Above definitions will be added to calculate email recipients for a dlist enabled mailing list or otherwise.

Since the tables are up for ‘Thread’ & ‘Override’ and member is introduced to a new preference i.e. ‘dlist_preference’, a member can use ‘override(member_id, thread_id, preference)’ to change the preference.

Challenges

It is easy to design all models and interface and create relevant methods, to some extent it is easy to write a migration, however the ‘thread’ table required a many-to-one relationship with member, ‘override’ table required a many-to-one relationship with member and simultaneously ‘thread’ table required a many-to-many relationship (all relations are backrefs) with ‘override’, handling SQLalchemy for the first time with all these can be a pain, specially working out an association table to handle a many-to-many relationship as in the case of ‘thread’ and ‘override’ table. Also post migrations there were difficulties like ‘mailman not found’ which simply required all Enums in migration to be changed to sa.Integer, sometimes there were ‘ALTER type’ issues which corresponds to new migrations sitting over old database (if only a type is changed) and we are trying to start mailman or mailman shell.

PS: I will try to add syntax highlighting before my next post to make it easier for readers. I’m looking forward for reviews just below the post or on the mailing list.