smtp_direct.py is dead and gone.
[mailman.git] / src / mailman / mta / bulk.py
blob046f5cc2dd9c9c78a6a2bbde155234551aa5ccd4
1 # Copyright (C) 2009 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Bulk message delivery."""
20 from __future__ import absolute_import, unicode_literals
22 __metaclass__ = type
23 __all__ = [
24 'BulkDelivery',
28 from mailman.mta.base import BaseDelivery
31 # A mapping of top-level domains to bucket numbers. The zeroth bucket is
32 # reserved for everything else. At one time, these were the most common
33 # domains.
34 CHUNKMAP = dict(
35 com=1,
36 net=2,
37 org=2,
38 edu=3,
39 us=3,
40 ca=3,
45 class BulkDelivery(BaseDelivery):
46 """Deliver messages to the MSA in as few sessions as possible."""
48 def __init__(self, max_recipients=None):
49 """See `BaseDelivery`.
51 :param max_recipients: The maximum number of recipients per delivery
52 chunk. None, zero or less means to group all recipients into one
53 big chunk.
54 :type max_recipients: integer
55 """
56 super(BulkDelivery, self).__init__()
57 self._max_recipients = (max_recipients
58 if max_recipients is not None
59 else 0)
61 def chunkify(self, recipients):
62 """Split a set of recipients into chunks.
64 The `max_recipients` argument given to the constructor specifies the
65 maximum number of recipients in each chunk.
67 :param recipients: The set of recipient email addresses
68 :type recipients: sequence of email address strings
69 :return: A list of chunks, where each chunk is a set containing no
70 more than `max_recipients` number of addresses. The chunk can
71 contain fewer, and no packing is guaranteed.
72 :rtype: list of sets of strings
73 """
74 if self._max_recipients <= 0:
75 yield set(recipients)
76 return
77 # This algorithm was originally suggested by Chuq Von Rospach. Start
78 # by splitting the recipient addresses into top-level domain buckets,
79 # using the "most common" domains. Everything else ends up in the
80 # zeroth bucket.
81 by_bucket = {}
82 for address in recipients:
83 localpart, at, domain = address.partition('@')
84 domain_parts = domain.split('.')
85 bucket_number = CHUNKMAP.get(domain_parts[-1], 0)
86 by_bucket.setdefault(bucket_number, set()).add(address)
87 # Fill chunks by sorting the tld values by length.
88 chunk = set()
89 for tld_chunk in sorted(by_bucket.values(), key=len, reverse=True):
90 while tld_chunk:
91 chunk.add(tld_chunk.pop())
92 if len(chunk) == self._max_recipients:
93 yield chunk
94 chunk = set()
95 # Every tld bucket starts a new chunk, but only if non-empty
96 if len(chunk) > 0:
97 yield chunk
98 chunk = set()
99 # Be sure to include the last chunk, but only if it's non-empty.
100 if len(chunk) > 0:
101 yield chunk
103 def deliver(self, mlist, msg, msgdata):
104 """See `IMailTransportAgentDelivery`."""
105 refused = {}
106 for recipients in self.chunkify(msgdata.get('recipients', set())):
107 chunk_refused = self._deliver_to_recipients(
108 mlist, msg, msgdata, recipients)
109 refused.update(chunk_refused)
110 return refused