=== modified file '.bzrignore'
--- .bzrignore 2014-11-08 15:27:56 +0000
+++ .bzrignore 2015-01-03 05:08:01 +0000
@@ -22,3 +22,4 @@
.coverage
htmlcov
.tox
+__pycache__
=== modified file 'coverage.ini'
--- coverage.ini 2014-11-16 21:28:05 +0000
+++ coverage.ini 2015-01-03 05:08:01 +0000
@@ -4,7 +4,7 @@
omit =
setup*
*/showme.py
- .tox/coverage/lib/python2.7/site-packages/*
+ .tox/coverage/lib/python3.4/site-packages/*
[paths]
source =
=== modified file 'setup.py'
--- setup.py 2014-11-02 19:55:10 +0000
+++ setup.py 2015-01-03 05:08:01 +0000
@@ -15,17 +15,15 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see .
-# Do *not* import unicode_literals. This breaks setuptools.
-from __future__ import absolute_import, print_function
-
import re
import sys
from setuptools import setup, find_packages
from string import Template
-if sys.hexversion < 0x20700f0:
- print('Mailman requires at least Python 2.7')
+
+if sys.hexversion < 0x30400f0:
+ print('Mailman requires at least Python 3.4')
sys.exit(1)
@@ -105,6 +103,7 @@
'mock',
'nose2',
'passlib',
+ 'six',
'sqlalchemy',
'zope.component',
'zope.configuration',
=== modified file 'src/mailman/__init__.py'
--- src/mailman/__init__.py 2014-11-11 15:59:21 +0000
+++ src/mailman/__init__.py 2015-01-03 05:08:01 +0000
@@ -17,13 +17,6 @@
"""The `mailman` package."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- ]
-
-
import sys
=== modified file 'src/mailman/app/bounces.py'
--- src/mailman/app/bounces.py 2014-12-09 01:38:26 +0000
+++ src/mailman/app/bounces.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Application level bounce handling."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ProbeVERP',
'StandardVERP',
@@ -36,10 +33,6 @@
from email.mime.message import MIMEMessage
from email.mime.text import MIMEText
from email.utils import parseaddr
-from string import Template
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.email.message import OwnerNotification, UserNotification
@@ -50,6 +43,10 @@
from mailman.utilities.email import split_email
from mailman.utilities.i18n import make
from mailman.utilities.string import oneline
+from string import Template
+from zope.component import getUtility
+from zope.interface import implementer
+
log = logging.getLogger('mailman.config')
elog = logging.getLogger('mailman.error')
@@ -71,8 +68,8 @@
:type error: Exception
"""
# Bounce a message back to the sender, with an error message if provided
- # in the exception argument.
- if msg.sender is None:
+ # in the exception argument. .sender might be None or the empty string.
+ if not msg.sender:
# We can't bounce the message if we don't know who it's supposed to go
# to.
return
=== modified file 'src/mailman/app/commands.py'
--- src/mailman/app/commands.py 2014-04-28 15:23:35 +0000
+++ src/mailman/app/commands.py 2015-01-03 05:08:01 +0000
@@ -17,19 +17,15 @@
"""Initialize the email commands."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'initialize',
]
-from zope.interface.verify import verifyObject
-
from mailman.config import config
from mailman.interfaces.command import IEmailCommand
from mailman.utilities.modules import find_components
+from zope.interface.verify import verifyObject
=== modified file 'src/mailman/app/docs/hooks.rst'
--- src/mailman/app/docs/hooks.rst 2014-11-09 12:52:58 +0000
+++ src/mailman/app/docs/hooks.rst 2015-01-03 05:08:01 +0000
@@ -18,12 +18,12 @@
... counter = 1
... def pre_hook():
... global counter
- ... print 'pre-hook:', counter
+ ... print('pre-hook:', counter)
... counter += 1
...
... def post_hook():
... global counter
- ... print 'post-hook:', counter
+ ... print('post-hook:', counter)
... counter += 1
... """, file=fp)
>>> fp.close()
@@ -61,6 +61,7 @@
... proc = subprocess.Popen(
... [exe, 'lists', '--domain', 'ignore', '-q'],
... cwd=ConfigLayer.root_directory, env=env,
+ ... universal_newlines=True,
... stdout=subprocess.PIPE, stderr=subprocess.PIPE)
... stdout, stderr = proc.communicate()
... assert proc.returncode == 0, stderr
=== modified file 'src/mailman/app/docs/pipelines.rst'
--- src/mailman/app/docs/pipelines.rst 2014-11-08 15:14:00 +0000
+++ src/mailman/app/docs/pipelines.rst 2015-01-03 05:08:01 +0000
@@ -45,9 +45,9 @@
To: test@example.com
Message-ID:
X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
- Subject: [Test] My first post
X-Mailman-Version: ...
Precedence: list
+ Subject: [Test] My first post
List-Id:
Archived-At: http://lists.example.com/.../4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Archive:
@@ -67,7 +67,7 @@
>>> dump_msgdata(msgdata)
original_sender : aperson@example.com
original_subject: My first post
- recipients : set([])
+ recipients : set()
stripped_subject: My first post
After pipeline processing, the message is now sitting in various other
@@ -84,9 +84,9 @@
To: test@example.com
Message-ID:
X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
- Subject: [Test] My first post
X-Mailman-Version: ...
Precedence: list
+ Subject: [Test] My first post
List-Id:
...
@@ -97,7 +97,7 @@
_parsemsg : False
original_sender : aperson@example.com
original_subject: My first post
- recipients : set([])
+ recipients : set()
stripped_subject: My first post
version : 3
@@ -121,9 +121,9 @@
To: test@example.com
Message-ID:
X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
- Subject: [Test] My first post
X-Mailman-Version: ...
Precedence: list
+ Subject: [Test] My first post
List-Id:
...
@@ -132,10 +132,10 @@
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
- listname : test@example.com
+ listid : test.example.com
original_sender : aperson@example.com
original_subject: My first post
- recipients : set([])
+ recipients : set()
stripped_subject: My first post
version : 3
@@ -152,9 +152,9 @@
To: test@example.com
Message-ID:
X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
- Subject: [Test] My first post
X-Mailman-Version: ...
Precedence: list
+ Subject: [Test] My first post
List-Id:
...
=== modified file 'src/mailman/app/docs/subscriptions.rst'
--- src/mailman/app/docs/subscriptions.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/app/docs/subscriptions.rst 2015-01-03 05:08:01 +0000
@@ -67,13 +67,6 @@
email address. However, the user must have a preferred email address.
::
- >>> service.join('test.example.com', bart.user.user_id,
- ... role=MemberRole.owner)
- Traceback (most recent call last):
- ...
- MissingPreferredAddressError: User must have a preferred address:
-
-
>>> from mailman.utilities.datetime import now
>>> address = list(bart.user.addresses)[0]
>>> address.verified_on = now()
=== modified file 'src/mailman/app/domain.py'
--- src/mailman/app/domain.py 2014-04-28 15:23:35 +0000
+++ src/mailman/app/domain.py 2015-01-03 05:08:01 +0000
@@ -17,18 +17,14 @@
"""Application level domain support."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'handle_DomainDeletingEvent',
]
-from zope.component import getUtility
-
from mailman.interfaces.domain import DomainDeletingEvent
from mailman.interfaces.listmanager import IListManager
+from zope.component import getUtility
=== modified file 'src/mailman/app/events.py'
--- src/mailman/app/events.py 2014-04-28 15:23:35 +0000
+++ src/mailman/app/events.py 2015-01-03 05:08:01 +0000
@@ -17,22 +17,18 @@
"""Global events."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'initialize',
]
-from zope import event
-
from mailman.app import (
domain, membership, moderator, registrar, subscriptions)
from mailman.core import i18n, switchboard
from mailman.languages import manager as language_manager
from mailman.styles import manager as style_manager
from mailman.utilities import passwords
+from zope import event
=== modified file 'src/mailman/app/inject.py'
--- src/mailman/app/inject.py 2014-01-01 14:59:42 +0000
+++ src/mailman/app/inject.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Inject a message into a queue."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'inject_message',
'inject_text',
@@ -28,7 +25,6 @@
from email import message_from_string
from email.utils import formatdate, make_msgid
-
from mailman.config import config
from mailman.email.message import Message
from mailman.utilities.email import add_message_hash
@@ -53,6 +49,8 @@
:type switchboard: string
:param kws: Additional values for the message metadata.
:type kws: dictionary
+ :return: filebase of enqueued message
+ :rtype: string
"""
if switchboard is None:
switchboard = 'in'
@@ -66,13 +64,13 @@
msg['Date'] = formatdate(localtime=True)
msg.original_size = len(msg.as_string())
msgdata = dict(
- listname=mlist.fqdn_listname,
+ listid=mlist.list_id,
original_size=msg.original_size,
)
msgdata.update(kws)
if recipients is not None:
msgdata['recipients'] = recipients
- config.switchboards[switchboard].enqueue(msg, **msgdata)
+ return config.switchboards[switchboard].enqueue(msg, **msgdata)
@@ -95,6 +93,8 @@
:type switchboard: string
:param kws: Additional values for the message metadata.
:type kws: dictionary
+ :return: filebase of enqueued message
+ :rtype: string
"""
message = message_from_string(text, Message)
- inject_message(mlist, message, recipients, switchboard, **kws)
+ return inject_message(mlist, message, recipients, switchboard, **kws)
=== modified file 'src/mailman/app/lifecycle.py'
--- src/mailman/app/lifecycle.py 2014-04-28 15:23:35 +0000
+++ src/mailman/app/lifecycle.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Application level list creation."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'create_list',
'remove_list',
@@ -31,8 +28,6 @@
import shutil
import logging
-from zope.component import getUtility
-
from mailman.config import config
from mailman.interfaces.address import IEmailValidator
from mailman.interfaces.domain import (
@@ -42,6 +37,7 @@
from mailman.interfaces.styles import IStyleManager
from mailman.interfaces.usermanager import IUserManager
from mailman.utilities.modules import call_name
+from zope.component import getUtility
log = logging.getLogger('mailman.error')
=== modified file 'src/mailman/app/membership.py'
--- src/mailman/app/membership.py 2014-04-15 14:03:39 +0000
+++ src/mailman/app/membership.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Application support for membership management."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'add_member',
'delete_member',
@@ -28,8 +25,6 @@
from email.utils import formataddr
-from zope.component import getUtility
-
from mailman.app.notifications import (
send_goodbye_message, send_welcome_message)
from mailman.config import config
@@ -40,6 +35,7 @@
MemberRole, MembershipIsBannedError, NotAMemberError, SubscriptionEvent)
from mailman.interfaces.usermanager import IUserManager
from mailman.utilities.i18n import make
+from zope.component import getUtility
=== modified file 'src/mailman/app/moderator.py'
--- src/mailman/app/moderator.py 2014-12-09 01:38:26 +0000
+++ src/mailman/app/moderator.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Application support for moderators."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'handle_ListDeletingEvent',
'handle_message',
@@ -35,8 +32,6 @@
import logging
from email.utils import formataddr, formatdate, getaddresses, make_msgid
-from zope.component import getUtility
-
from mailman.app.membership import add_member, delete_member
from mailman.app.notifications import send_admin_subscription_notice
from mailman.config import config
@@ -51,6 +46,7 @@
from mailman.interfaces.requests import IListRequests, RequestType
from mailman.utilities.datetime import now
from mailman.utilities.i18n import make
+from zope.component import getUtility
NL = '\n'
@@ -86,14 +82,14 @@
# Message-ID header.
message_id = msg.get('message-id')
if message_id is None:
- msg['Message-ID'] = message_id = make_msgid().decode('ascii')
+ msg['Message-ID'] = message_id = make_msgid()
elif isinstance(message_id, bytes):
message_id = message_id.decode('ascii')
getUtility(IMessageStore).add(msg)
# Prepare the message metadata with some extra information needed only by
# the moderation interface.
msgdata['_mod_message_id'] = message_id
- msgdata['_mod_fqdn_listname'] = mlist.fqdn_listname
+ msgdata['_mod_listid'] = mlist.list_id
msgdata['_mod_sender'] = msg.sender
msgdata['_mod_subject'] = msg.get('subject', _('(no subject)'))
msgdata['_mod_reason'] = reason
@@ -134,7 +130,7 @@
# Start by getting the message from the message store.
msg = message_store.get_message_by_id(message_id)
# Delete moderation-specific entries from the message metadata.
- for key in msgdata.keys():
+ for key in list(msgdata):
if key.startswith('_mod_'):
del msgdata[key]
# Add some metadata to indicate this message has now been approved.
=== modified file 'src/mailman/app/notifications.py'
--- src/mailman/app/notifications.py 2014-01-07 03:43:59 +0000
+++ src/mailman/app/notifications.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Sending notifications."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'send_admin_subscription_notice',
'send_goodbye_message',
@@ -31,9 +28,6 @@
from email.utils import formataddr
from lazr.config import as_boolean
-from urllib2 import URLError
-from zope.component import getUtility
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.email.message import OwnerNotification, UserNotification
@@ -41,6 +35,8 @@
from mailman.interfaces.templates import ITemplateLoader
from mailman.utilities.i18n import make
from mailman.utilities.string import expand, wrap
+from six.moves.urllib_error import URLError
+from zope.component import getUtility
log = logging.getLogger('mailman.error')
@@ -141,7 +137,6 @@
"""
with _.using(mlist.preferred_language.code):
subject = _('$mlist.display_name subscription notification')
- display_name = display_name.encode(language.charset, 'replace')
text = make('adminsubscribeack.txt',
mailing_list=mlist,
listname=mlist.display_name,
=== modified file 'src/mailman/app/registrar.py'
--- src/mailman/app/registrar.py 2014-01-07 03:43:59 +0000
+++ src/mailman/app/registrar.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Implementation of the IUserRegistrar interface."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Registrar',
'handle_ConfirmationNeededEvent',
@@ -28,10 +25,6 @@
import logging
-from zope.component import getUtility
-from zope.event import notify
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.email.message import UserNotification
from mailman.interfaces.address import IEmailValidator
@@ -42,6 +35,9 @@
from mailman.interfaces.templates import ITemplateLoader
from mailman.interfaces.usermanager import IUserManager
from mailman.utilities.datetime import now
+from zope.component import getUtility
+from zope.event import notify
+from zope.interface import implementer
log = logging.getLogger('mailman.error')
=== modified file 'src/mailman/app/replybot.py'
--- src/mailman/app/replybot.py 2014-04-28 15:23:35 +0000
+++ src/mailman/app/replybot.py 2015-01-03 05:08:01 +0000
@@ -21,9 +21,6 @@
# mailing list. The reply governor should really apply site-wide per
# recipient (I think).
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'can_acknowledge',
]
=== modified file 'src/mailman/app/subscriptions.py'
--- src/mailman/app/subscriptions.py 2014-09-22 18:47:02 +0000
+++ src/mailman/app/subscriptions.py 2015-01-03 05:08:01 +0000
@@ -15,11 +15,8 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see .
-"""Module stuff."""
-
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
+"""Handle subscriptions."""
+
__all__ = [
'SubscriptionService',
'handle_ListDeletingEvent',
@@ -108,7 +105,7 @@
# the parameter can either be an email address or a user id.
query = []
if subscriber is not None:
- if isinstance(subscriber, basestring):
+ if isinstance(subscriber, str):
# subscriber is an email address.
address = user_manager.get_address(subscriber)
user = user_manager.get_user(subscriber)
@@ -148,7 +145,7 @@
if mlist is None:
raise NoSuchListError(list_id)
# Is the subscriber an email address or user id?
- if isinstance(subscriber, basestring):
+ if isinstance(subscriber, str):
if display_name is None:
display_name, at, domain = subscriber.partition('@')
# Because we want to keep the REST API simple, there is no
=== modified file 'src/mailman/app/templates.py'
--- src/mailman/app/templates.py 2014-04-14 16:14:13 +0000
+++ src/mailman/app/templates.py 2015-01-03 05:08:01 +0000
@@ -17,30 +17,27 @@
"""Template loader."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TemplateLoader',
]
-import urllib2
-
from contextlib import closing
-from urllib import addinfourl
-from urlparse import urlparse
-from zope.component import getUtility
-from zope.interface import implementer
-
-from mailman.utilities.i18n import TemplateNotFoundError, find
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.templates import ITemplateLoader
+from mailman.utilities.i18n import TemplateNotFoundError, find
+from six.moves.urllib_error import URLError
+from six.moves.urllib_parse import urlparse
+from six.moves.urllib_request import (
+ BaseHandler, build_opener, install_opener, urlopen)
+from six.moves.urllib_response import addinfourl
+from zope.component import getUtility
+from zope.interface import implementer
-class MailmanHandler(urllib2.BaseHandler):
+class MailmanHandler(BaseHandler):
# Handle internal mailman: URLs.
def mailman_open(self, req):
# Parse urls of the form:
@@ -55,9 +52,9 @@
assert parsed.scheme == 'mailman'
# The path can contain one, two, or three components. Since no empty
# path components are legal, filter them out.
- parts = filter(None, parsed.path.split('/'))
+ parts = [p for p in parsed.path.split('/') if p]
if len(parts) == 0:
- raise urllib2.URLError('No template specified')
+ raise URLError('No template specified')
elif len(parts) == 1:
template = parts[0]
elif len(parts) == 2:
@@ -69,25 +66,25 @@
language = getUtility(ILanguageManager).get(part0)
mlist = getUtility(IListManager).get(part0)
if language is None and mlist is None:
- raise urllib2.URLError('Bad language or list name')
+ raise URLError('Bad language or list name')
elif mlist is None:
code = language.code
elif len(parts) == 3:
fqdn_listname, code, template = parts
mlist = getUtility(IListManager).get(fqdn_listname)
if mlist is None:
- raise urllib2.URLError('Missing list')
+ raise URLError('Missing list')
language = getUtility(ILanguageManager).get(code)
if language is None:
- raise urllib2.URLError('No such language')
+ raise URLError('No such language')
code = language.code
else:
- raise urllib2.URLError('No such file')
+ raise URLError('No such file')
# Find the template, mutating any missing template exception.
try:
path, fp = find(template, mlist, code)
except TemplateNotFoundError:
- raise urllib2.URLError('No such file')
+ raise URLError('No such file')
return addinfourl(fp, {}, original_url)
@@ -97,10 +94,10 @@
"""Loader of templates, with caching and support for mailman:// URIs."""
def __init__(self):
- opener = urllib2.build_opener(MailmanHandler())
- urllib2.install_opener(opener)
+ opener = build_opener(MailmanHandler())
+ install_opener(opener)
def get(self, uri):
"""See `ITemplateLoader`."""
- with closing(urllib2.urlopen(uri)) as fp:
- return fp.read().decode('utf-8')
+ with closing(urlopen(uri)) as fp:
+ return fp.read()
=== modified file 'src/mailman/app/tests/test_bounces.py'
--- src/mailman/app/tests/test_bounces.py 2014-01-07 03:43:59 +0000
+++ src/mailman/app/tests/test_bounces.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Testing app.bounces functions."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestBounceMessage',
'TestMaybeForward',
@@ -36,8 +33,6 @@
import tempfile
import unittest
-from zope.component import getUtility
-
from mailman.app.bounces import (
ProbeVERP, StandardVERP, bounce_message, maybe_forward, send_probe)
from mailman.app.lifecycle import create_list
@@ -49,10 +44,9 @@
from mailman.interfaces.pending import IPendings
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import (
- LogFileMark,
- get_queue_messages,
- specialized_message_from_string as mfs)
+ LogFileMark, get_queue_messages, specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
@@ -334,7 +328,7 @@
send_probe(self._member, self._msg)
message = get_queue_messages('virgin')[0].msg
self.assertEqual(
- message['Subject'],
+ message['subject'].encode(),
'=?utf-8?q?ailing-may_ist-lay_Test_obe-pray_essage-may?=')
def test_probe_notice_with_member_nonenglish(self):
@@ -533,7 +527,7 @@
def test_no_sender(self):
# The message won't be bounced if it has no discernible sender.
- self._msg.sender = None
+ del self._msg['from']
bounce_message(self._mlist, self._msg)
items = get_queue_messages('virgin')
# Nothing in the virgin queue means nothing's been bounced.
=== modified file 'src/mailman/app/tests/test_inject.py'
--- src/mailman/app/tests/test_inject.py 2014-01-01 14:59:42 +0000
+++ src/mailman/app/tests/test_inject.py 2015-01-03 05:08:01 +0000
@@ -17,10 +17,9 @@
"""Testing app.inject functions."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
+ 'TestInjectMessage',
+ 'TestInjectText',
]
@@ -64,7 +63,7 @@
self.assertEqual(len(items), 1)
self.assertMultiLineEqual(items[0].msg.as_string(),
self.msg.as_string())
- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
self.assertEqual(items[0].msgdata['original_size'],
len(self.msg.as_string()))
@@ -84,7 +83,7 @@
self.assertEqual(len(items), 1)
self.assertMultiLineEqual(items[0].msg.as_string(),
self.msg.as_string())
- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
self.assertEqual(items[0].msgdata['original_size'],
len(self.msg.as_string()))
@@ -144,7 +143,7 @@
def setUp(self):
self.mlist = create_list('test@example.com')
- self.text = b"""\
+ self.text = """\
From: bart@example.com
To: test@example.com
Subject: A test message
@@ -171,7 +170,7 @@
# Delete that header because it is not in the original text.
del items[0].msg['x-message-id-hash']
self.assertMultiLineEqual(items[0].msg.as_string(), self.text)
- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
self.assertEqual(items[0].msgdata['original_size'],
# Add back the X-Message-ID-Header which was in the
# message contributing to the original_size, but
@@ -196,7 +195,7 @@
# Remove the X-Message-ID-Hash header which isn't in the original text.
del items[0].msg['x-message-id-hash']
self.assertMultiLineEqual(items[0].msg.as_string(), self.text)
- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
self.assertEqual(items[0].msgdata['original_size'],
# Add back the X-Message-ID-Header which was in the
# message contributing to the original_size, but
=== modified file 'src/mailman/app/tests/test_lifecycle.py'
--- src/mailman/app/tests/test_lifecycle.py 2014-03-02 20:59:30 +0000
+++ src/mailman/app/tests/test_lifecycle.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the high level list lifecycle API."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestLifecycle',
]
=== modified file 'src/mailman/app/tests/test_membership.py'
--- src/mailman/app/tests/test_membership.py 2014-03-02 21:38:32 +0000
+++ src/mailman/app/tests/test_membership.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Tests of application level membership functions."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestAddMember',
'TestAddMemberPassword',
@@ -29,8 +26,6 @@
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.app.membership import add_member, delete_member
from mailman.core.constants import system_preferences
@@ -40,6 +35,7 @@
NotAMemberError)
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
=== modified file 'src/mailman/app/tests/test_moderation.py'
--- src/mailman/app/tests/test_moderation.py 2014-01-01 14:59:42 +0000
+++ src/mailman/app/tests/test_moderation.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Moderation tests."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestModeration',
]
@@ -27,8 +24,6 @@
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.app.moderator import handle_message, hold_message
from mailman.interfaces.action import Action
@@ -41,6 +36,7 @@
get_queue_messages, make_testable_runner, specialized_message_from_string)
from mailman.testing.layers import SMTPLayer
from mailman.utilities.datetime import now
+from zope.component import getUtility
=== modified file 'src/mailman/app/tests/test_notifications.py'
--- src/mailman/app/tests/test_notifications.py 2014-01-07 03:43:59 +0000
+++ src/mailman/app/tests/test_notifications.py 2015-01-03 05:08:01 +0000
@@ -17,10 +17,8 @@
"""Test notifications."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
+ 'TestNotifications',
]
@@ -29,8 +27,6 @@
import tempfile
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.app.membership import add_member
from mailman.config import config
@@ -38,6 +34,7 @@
from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.testing.helpers import get_queue_messages
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
=== modified file 'src/mailman/app/tests/test_registration.py'
--- src/mailman/app/tests/test_registration.py 2014-01-07 03:43:59 +0000
+++ src/mailman/app/tests/test_registration.py 2015-01-03 05:08:01 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 by the Free Software Foundation, Inc.
+# Copyright (C) 2012-2014 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
@@ -17,9 +17,6 @@
"""Test email address registration."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestEmailValidation',
'TestRegistration',
@@ -28,14 +25,13 @@
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.interfaces.address import InvalidEmailAddressError
from mailman.interfaces.pending import IPendings
from mailman.interfaces.registrar import ConfirmationNeededEvent, IRegistrar
from mailman.testing.helpers import event_subscribers
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
=== modified file 'src/mailman/app/tests/test_subscriptions.py'
--- src/mailman/app/tests/test_subscriptions.py 2014-01-01 14:59:42 +0000
+++ src/mailman/app/tests/test_subscriptions.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Tests for the subscription service."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestJoin'
]
@@ -28,13 +25,13 @@
import uuid
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.interfaces.address import InvalidEmailAddressError
+from mailman.interfaces.member import MemberRole, MissingPreferredAddressError
from mailman.interfaces.subscriptions import (
MissingUserError, ISubscriptionService)
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
@@ -57,3 +54,14 @@
with self.assertRaises(InvalidEmailAddressError) as cm:
self._service.join('test.example.com', 'bogus')
self.assertEqual(cm.exception.email, 'bogus')
+
+ def test_missing_preferred_address(self):
+ # A user cannot join a mailing list if they have no preferred address.
+ anne = self._service.join(
+ 'test.example.com', 'anne@example.com', 'Anne Person')
+ # Try to join Anne as a user with a different role. Her user has no
+ # preferred address, so this will fail.
+ self.assertRaises(MissingPreferredAddressError,
+ self._service.join,
+ 'test.example.com', anne.user.user_id,
+ role=MemberRole.owner)
=== modified file 'src/mailman/app/tests/test_templates.py'
--- src/mailman/app/tests/test_templates.py 2014-04-14 16:14:13 +0000
+++ src/mailman/app/tests/test_templates.py 2015-01-03 05:08:01 +0000
@@ -17,27 +17,24 @@
"""Test the template downloader API."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestTemplateLoader',
]
import os
+import six
import shutil
-import urllib2
import tempfile
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.templates import ITemplateLoader
from mailman.testing.layers import ConfigLayer
+from six.moves.urllib_error import URLError
+from zope.component import getUtility
@@ -98,32 +95,32 @@
self.assertEqual(content, 'Test content')
def test_uri_not_found(self):
- with self.assertRaises(urllib2.URLError) as cm:
+ with self.assertRaises(URLError) as cm:
self._loader.get('mailman:///missing.txt')
self.assertEqual(cm.exception.reason, 'No such file')
def test_shorter_url_error(self):
- with self.assertRaises(urllib2.URLError) as cm:
+ with self.assertRaises(URLError) as cm:
self._loader.get('mailman:///')
self.assertEqual(cm.exception.reason, 'No template specified')
def test_short_url_error(self):
- with self.assertRaises(urllib2.URLError) as cm:
+ with self.assertRaises(URLError) as cm:
self._loader.get('mailman://')
self.assertEqual(cm.exception.reason, 'No template specified')
def test_bad_language(self):
- with self.assertRaises(urllib2.URLError) as cm:
+ with self.assertRaises(URLError) as cm:
self._loader.get('mailman:///xx/demo.txt')
self.assertEqual(cm.exception.reason, 'Bad language or list name')
def test_bad_mailing_list(self):
- with self.assertRaises(urllib2.URLError) as cm:
+ with self.assertRaises(URLError) as cm:
self._loader.get('mailman:///missing@example.com/demo.txt')
self.assertEqual(cm.exception.reason, 'Bad language or list name')
def test_too_many_path_components(self):
- with self.assertRaises(urllib2.URLError) as cm:
+ with self.assertRaises(URLError) as cm:
self._loader.get('mailman:///missing@example.com/en/foo/demo.txt')
self.assertEqual(cm.exception.reason, 'No such file')
@@ -132,8 +129,8 @@
test_text = b'\xe4\xb8\xad'
path = os.path.join(self.var_dir, 'templates', 'site', 'it')
os.makedirs(path)
- with open(os.path.join(path, 'demo.txt'), 'w') as fp:
- print(test_text, end='', file=fp)
+ with open(os.path.join(path, 'demo.txt'), 'wb') as fp:
+ fp.write(test_text)
content = self._loader.get('mailman:///it/demo.txt')
- self.assertTrue(isinstance(content, unicode))
+ self.assertIsInstance(content, six.text_type)
self.assertEqual(content, test_text.decode('utf-8'))
=== modified file 'src/mailman/archiving/mailarchive.py'
--- src/mailman/archiving/mailarchive.py 2014-12-09 01:38:26 +0000
+++ src/mailman/archiving/mailarchive.py 2015-01-03 05:08:01 +0000
@@ -17,21 +17,16 @@
"""The Mail-Archive.com archiver."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'MailArchive',
]
-from urllib import quote
-from urlparse import urljoin
-from zope.interface import implementer
-
from mailman.config import config
from mailman.config.config import external_configuration
from mailman.interfaces.archiver import ArchivePolicy, IArchiver
+from six.moves.urllib_parse import quote, urljoin
+from zope.interface import implementer
@@ -77,5 +72,5 @@
if mlist.archive_policy is ArchivePolicy.public:
config.switchboards['out'].enqueue(
msg,
- listname=mlist.fqdn_listname,
+ listid=mlist.list_id,
recipients=[self.recipient])
=== modified file 'src/mailman/archiving/mhonarc.py'
--- src/mailman/archiving/mhonarc.py 2014-12-09 01:38:26 +0000
+++ src/mailman/archiving/mhonarc.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""MHonArc archiver."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'MHonArc',
]
@@ -28,13 +25,12 @@
import logging
import subprocess
-from urlparse import urljoin
-from zope.interface import implementer
-
from mailman.config import config
from mailman.config.config import external_configuration
from mailman.interfaces.archiver import IArchiver
from mailman.utilities.string import expand
+from six.moves.urllib_parse import urljoin
+from zope.interface import implementer
log = logging.getLogger('mailman.archiver')
@@ -84,7 +80,7 @@
command = expand(self.command, substitutions)
proc = subprocess.Popen(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- shell=True)
+ universal_newlines=True, shell=True)
stdout, stderr = proc.communicate(msg.as_string())
if proc.returncode != 0:
log.error('%s: mhonarc subprocess had non-zero exit code: %s' %
=== modified file 'src/mailman/archiving/prototype.py'
--- src/mailman/archiving/prototype.py 2014-12-09 01:38:26 +0000
+++ src/mailman/archiving/prototype.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Prototypical permalinking archiver."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Prototype',
]
@@ -30,14 +27,13 @@
import logging
from datetime import timedelta
+from flufl.lock import Lock, TimeOutError
from mailbox import Maildir
-from urlparse import urljoin
-
-from flufl.lock import Lock, TimeOutError
-from zope.interface import implementer
-
from mailman.config import config
from mailman.interfaces.archiver import IArchiver
+from six.moves.urllib_parse import urljoin
+from zope.interface import implementer
+
log = logging.getLogger('mailman.error')
=== modified file 'src/mailman/archiving/tests/test_prototype.py'
--- src/mailman/archiving/tests/test_prototype.py 2014-01-01 14:59:42 +0000
+++ src/mailman/archiving/tests/test_prototype.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the prototype archiver."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestPrototypeArchiver',
]
@@ -33,7 +30,6 @@
from email import message_from_file
from flufl.lock import Lock
-
from mailman.app.lifecycle import create_list
from mailman.archiving.prototype import Prototype
from mailman.config import config
@@ -89,13 +85,13 @@
def _find(self, path):
all_filenames = set()
for dirpath, dirnames, filenames in os.walk(path):
- if not isinstance(dirpath, unicode):
- dirpath = unicode(dirpath)
+ if isinstance(dirpath, bytes):
+ dirpath = dirpath.decode('utf-8')
all_filenames.add(dirpath)
for filename in filenames:
new_filename = filename
- if not isinstance(filename, unicode):
- new_filename = unicode(filename)
+ if isinstance(filename, bytes):
+ new_filename = filename.decode('utf-8')
all_filenames.add(os.path.join(dirpath, new_filename))
return all_filenames
=== modified file 'src/mailman/bin/export.py'
--- src/mailman/bin/export.py 2014-01-01 14:59:42 +0000
+++ src/mailman/bin/export.py 2015-01-03 05:08:01 +0000
@@ -134,7 +134,7 @@
print >> self._fp, '<%s%s/>' % (_name, attrs)
else:
# The value might contain angle brackets.
- value = escape(unicode(_value))
+ value = escape(_value.decode('utf-8'))
print >> self._fp, '<%s%s>%s%s>' % (_name, attrs, value, _name)
def _do_list_categories(self, mlist, k, subcat=None):
=== modified file 'src/mailman/bin/gate_news.py'
--- src/mailman/bin/gate_news.py 2014-11-20 01:29:44 +0000
+++ src/mailman/bin/gate_news.py 2015-01-03 05:08:01 +0000
@@ -149,7 +149,7 @@
# Post the message to the locked list
inq = Switchboard(config.INQUEUE_DIR)
inq.enqueue(msg,
- listname=mlist.internal_name(),
+ listid=mlist.list_id,
fromusenet=True)
log.info('posted to list %s: %7d', listname, num)
except nntplib.NNTPError as e:
=== modified file 'src/mailman/bin/mailman.py'
--- src/mailman/bin/mailman.py 2014-01-01 14:59:42 +0000
+++ src/mailman/bin/mailman.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The 'mailman' command dispatcher."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'main',
]
@@ -28,13 +25,13 @@
import os
import argparse
-from zope.interface.verify import verifyObject
-
+from functools import cmp_to_key
from mailman.core.i18n import _
from mailman.core.initialize import initialize
from mailman.interfaces.command import ICLISubCommand
from mailman.utilities.modules import find_components
from mailman.version import MAILMAN_VERSION_FULL
+from zope.interface.verify import verifyObject
@@ -77,9 +74,14 @@
return -1
elif other.name == 'help':
return 1
+ elif command.name < other.name:
+ return -1
+ elif command.name == other.name:
+ return 0
else:
- return cmp(command.name, other.name)
- subcommands.sort(cmp=sort_function)
+ assert command.name > other.name
+ return 1
+ subcommands.sort(key=cmp_to_key(sort_function))
for command in subcommands:
command_parser = subparser.add_parser(
command.name, help=_(command.__doc__))
=== modified file 'src/mailman/bin/master.py'
--- src/mailman/bin/master.py 2014-11-20 01:29:44 +0000
+++ src/mailman/bin/master.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Master subprocess watcher."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Loop',
'main',
@@ -37,7 +34,6 @@
from enum import Enum
from flufl.lock import Lock, NotLockedError, TimeOutError
from lazr.config import as_boolean
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.core.logging import reopen
@@ -357,7 +353,7 @@
# Set the environment variable which tells the runner that it's
# running under bin/master control. This subtly changes the error
# behavior of bin/runner.
- os.environ['MAILMAN_UNDER_MASTER_CONTROL'] = '1'
+ env = {'MAILMAN_UNDER_MASTER_CONTROL': '1'}
# Craft the command line arguments for the exec() call.
rswitch = '--runner=' + spec
# Wherever master lives, so too must live the runner script.
@@ -365,14 +361,21 @@
# config.PYTHON, which is the absolute path to the Python interpreter,
# must be given as argv[0] due to Python's library search algorithm.
args = [sys.executable, sys.executable, exe, rswitch]
- if self._config_file is not None:
- args.extend(['-C', self._config_file])
+ # Always pass the explicit path to the configuration file to the
+ # sub-runners. This avoids any debate about which cfg file is used.
+ config_file = (config.filename if self._config_file is None
+ else self._config_file)
+ args.extend(['-C', config_file])
log = logging.getLogger('mailman.runner')
log.debug('starting: %s', args)
+ # We must pass this environment variable through if it's set,
+ # otherwise runner processes will not have the correct VAR_DIR.
+ var_dir = os.environ.get('MAILMAN_VAR_DIR')
+ if var_dir is not None:
+ env['MAILMAN_VAR_DIR'] = var_dir
# For the testing framework, if this environment variable is set, pass
# it on to the subprocess.
coverage_env = os.environ.get('COVERAGE_PROCESS_START')
- env = dict()
if coverage_env is not None:
env['COVERAGE_PROCESS_START'] = coverage_env
args.append(env)
=== modified file 'src/mailman/bin/onebounce.py'
--- src/mailman/bin/onebounce.py 2014-04-28 15:23:35 +0000
+++ src/mailman/bin/onebounce.py 2015-01-03 05:08:01 +0000
@@ -18,9 +18,6 @@
"""Test bounce detection on message files."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'main',
]
=== modified file 'src/mailman/bin/runner.py'
--- src/mailman/bin/runner.py 2014-11-16 21:28:05 +0000
+++ src/mailman/bin/runner.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The runner process."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'main',
]
=== modified file 'src/mailman/bin/tests/test_master.py'
--- src/mailman/bin/tests/test_master.py 2014-11-01 16:49:15 +0000
+++ src/mailman/bin/tests/test_master.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test master watcher utilities."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestMasterLock',
]
=== modified file 'src/mailman/chains/accept.py'
--- src/mailman/chains/accept.py 2014-04-28 15:23:35 +0000
+++ src/mailman/chains/accept.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The terminal 'accept' chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AcceptChain',
]
@@ -27,12 +24,11 @@
import logging
-from zope.event import notify
-
from mailman.chains.base import TerminalChainBase
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.chain import AcceptEvent
+from zope.event import notify
log = logging.getLogger('mailman.vette')
=== modified file 'src/mailman/chains/base.py'
--- src/mailman/chains/base.py 2014-11-11 15:59:21 +0000
+++ src/mailman/chains/base.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Base class for terminal chains."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Chain',
'Link',
@@ -27,11 +24,10 @@
]
-from zope.interface import implementer
-
from mailman.config import config
from mailman.interfaces.chain import (
IChain, IChainIterator, IChainLink, IMutableChain, LinkAction)
+from zope.interface import implementer
=== modified file 'src/mailman/chains/builtin.py'
--- src/mailman/chains/builtin.py 2014-01-01 14:59:42 +0000
+++ src/mailman/chains/builtin.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The default built-in starting chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'BuiltInChain',
]
@@ -27,12 +24,11 @@
import logging
-from zope.interface import implementer
-
from mailman.chains.base import Link
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.chain import IChain, LinkAction
+from zope.interface import implementer
log = logging.getLogger('mailman.vette')
=== modified file 'src/mailman/chains/discard.py'
--- src/mailman/chains/discard.py 2014-04-28 15:23:35 +0000
+++ src/mailman/chains/discard.py 2015-01-03 05:08:01 +0000
@@ -17,20 +17,17 @@
"""The terminal 'discard' chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'DiscardChain',
]
import logging
-from zope.event import notify
from mailman.chains.base import TerminalChainBase
from mailman.core.i18n import _
from mailman.interfaces.chain import DiscardEvent
+from zope.event import notify
log = logging.getLogger('mailman.vette')
=== modified file 'src/mailman/chains/headers.py'
--- src/mailman/chains/headers.py 2014-01-01 14:59:42 +0000
+++ src/mailman/chains/headers.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The header-matching chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'HeaderMatchChain',
]
@@ -28,13 +25,12 @@
import re
import logging
-from zope.interface import implementer
-
from mailman.chains.base import Chain, Link
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.chain import LinkAction
from mailman.interfaces.rules import IRule
+from zope.interface import implementer
log = logging.getLogger('mailman.error')
@@ -122,7 +118,7 @@
"""See `IMutableChain`."""
# Remove all dynamically created rules. Use the keys so we can mutate
# the dictionary inside the loop.
- for rule_name in config.rules.keys():
+ for rule_name in list(config.rules):
if rule_name.startswith('header-match-'):
del config.rules[rule_name]
self._extended_links = []
=== modified file 'src/mailman/chains/hold.py'
--- src/mailman/chains/hold.py 2014-01-01 14:59:42 +0000
+++ src/mailman/chains/hold.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The terminal 'hold' chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'HoldChain',
]
@@ -30,10 +27,6 @@
from email.mime.message import MIMEMessage
from email.mime.text import MIMEText
from email.utils import formatdate, make_msgid
-from zope.component import getUtility
-from zope.event import notify
-from zope.interface import implementer
-
from mailman.app.moderator import hold_message
from mailman.app.replybot import can_acknowledge
from mailman.chains.base import TerminalChainBase
@@ -47,6 +40,9 @@
from mailman.interfaces.usermanager import IUserManager
from mailman.utilities.i18n import make
from mailman.utilities.string import oneline, wrap
+from zope.component import getUtility
+from zope.event import notify
+from zope.interface import implementer
log = logging.getLogger('mailman.vette')
@@ -157,7 +153,7 @@
if original_subject is None:
original_subject = _('(no subject)')
else:
- original_subject = oneline(original_subject, charset)
+ original_subject = oneline(original_subject, in_unicode=True)
substitutions = dict(
listname = mlist.fqdn_listname,
subject = original_subject,
=== modified file 'src/mailman/chains/moderation.py'
--- src/mailman/chains/moderation.py 2014-01-01 14:59:42 +0000
+++ src/mailman/chains/moderation.py 2015-01-03 05:08:01 +0000
@@ -34,21 +34,17 @@
members, while `hold` is the default for nonmembers.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ModerationChain',
]
-from zope.interface import implementer
-
from mailman.chains.base import Link
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.action import Action
from mailman.interfaces.chain import IChain, LinkAction
+from zope.interface import implementer
=== modified file 'src/mailman/chains/owner.py'
--- src/mailman/chains/owner.py 2014-01-01 14:59:42 +0000
+++ src/mailman/chains/owner.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The standard -owner posting chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'BuiltInOwnerChain',
]
@@ -27,12 +24,11 @@
import logging
-from zope.event import notify
-
from mailman.chains.base import TerminalChainBase
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.chain import AcceptOwnerEvent
+from zope.event import notify
log = logging.getLogger('mailman.vette')
=== modified file 'src/mailman/chains/reject.py'
--- src/mailman/chains/reject.py 2014-04-28 15:23:35 +0000
+++ src/mailman/chains/reject.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The terminal 'reject' chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'RejectChain',
]
@@ -27,12 +24,11 @@
import logging
-from zope.event import notify
-
from mailman.app.bounces import bounce_message
from mailman.chains.base import TerminalChainBase
from mailman.core.i18n import _
from mailman.interfaces.chain import RejectEvent
+from zope.event import notify
log = logging.getLogger('mailman.vette')
=== modified file 'src/mailman/chains/tests/test_base.py'
--- src/mailman/chains/tests/test_base.py 2014-11-11 15:59:21 +0000
+++ src/mailman/chains/tests/test_base.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the base chain stuff."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestMiscellaneous',
]
=== modified file 'src/mailman/chains/tests/test_headers.py'
--- src/mailman/chains/tests/test_headers.py 2014-01-01 14:59:42 +0000
+++ src/mailman/chains/tests/test_headers.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the header chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestHeaderChain',
]
=== modified file 'src/mailman/chains/tests/test_hold.py'
--- src/mailman/chains/tests/test_hold.py 2014-04-28 15:23:35 +0000
+++ src/mailman/chains/tests/test_hold.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Additional tests for the hold chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestAutorespond',
]
@@ -27,14 +24,13 @@
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.chains.hold import autorespond_to_sender
from mailman.interfaces.autorespond import IAutoResponseSet, Response
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import configuration, get_queue_messages
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
=== modified file 'src/mailman/chains/tests/test_owner.py'
--- src/mailman/chains/tests/test_owner.py 2014-01-01 14:59:42 +0000
+++ src/mailman/chains/tests/test_owner.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the owner chain."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestOwnerChain',
]
@@ -32,8 +29,7 @@
from mailman.core.chains import process
from mailman.interfaces.chain import AcceptOwnerEvent
from mailman.testing.helpers import (
- event_subscribers,
- get_queue_messages,
+ event_subscribers, get_queue_messages,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
=== modified file 'src/mailman/commands/cli_aliases.py'
--- src/mailman/commands/cli_aliases.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_aliases.py 2015-01-03 05:08:01 +0000
@@ -17,20 +17,16 @@
"""Generate Mailman alias files for your MTA."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Aliases',
]
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
from mailman.utilities.modules import call_name
+from zope.interface import implementer
=== modified file 'src/mailman/commands/cli_conf.py'
--- src/mailman/commands/cli_conf.py 2014-11-08 00:31:21 +0000
+++ src/mailman/commands/cli_conf.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Print the mailman configuration."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Conf'
]
@@ -29,11 +26,10 @@
from contextlib import closing
from lazr.config._config import Section
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
+from zope.interface import implementer
=== modified file 'src/mailman/commands/cli_control.py'
--- src/mailman/commands/cli_control.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_control.py 2015-01-03 05:08:01 +0000
@@ -15,11 +15,8 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see .
-"""Module stuff."""
-
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
+"""Start/stop/reopen/restart commands."""
+
__all__ = [
'Reopen',
'Restart',
@@ -34,12 +31,11 @@
import signal
import logging
-from zope.interface import implementer
-
from mailman.bin.master import WatcherState, master_state
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
+from zope.interface import implementer
qlog = logging.getLogger('mailman.runner')
@@ -124,8 +120,8 @@
# subprocesses to calculate their path to the $VAR_DIR. Before we
# chdir() though, calculate the absolute path to the configuration
# file.
- config_path = (os.path.abspath(args.config)
- if args.config else None)
+ config_path = (config.filename if args.config is None
+ else os.path.abspath(args.config))
os.environ['MAILMAN_VAR_DIR'] = config.VAR_DIR
os.chdir(config.VAR_DIR)
# Exec the master watcher.
@@ -135,8 +131,9 @@
]
if args.force:
execl_args.append('--force')
- if config_path:
- execl_args.extend(['-C', config_path])
+ # Always pass the config file path to the master projects, so there's
+ # no confusion about which cfg is being used.
+ execl_args.extend(['-C', config_path])
qlog.debug('starting: %s', execl_args)
os.execl(*execl_args)
# We should never get here.
=== modified file 'src/mailman/commands/cli_help.py'
--- src/mailman/commands/cli_help.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_help.py 2015-01-03 05:08:01 +0000
@@ -17,18 +17,14 @@
"""The 'help' subcommand."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Help',
]
+from mailman.interfaces.command import ICLISubCommand
from zope.interface import implementer
-from mailman.interfaces.command import ICLISubCommand
-
@implementer(ICLISubCommand)
=== modified file 'src/mailman/commands/cli_import.py'
--- src/mailman/commands/cli_import.py 2014-04-14 16:14:13 +0000
+++ src/mailman/commands/cli_import.py 2015-01-03 05:08:01 +0000
@@ -17,25 +17,21 @@
"""Importing list data into Mailman 3."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Import21',
]
import sys
-import cPickle
-
-from zope.component import getUtility
-from zope.interface import implementer
from mailman.core.i18n import _
from mailman.database.transaction import transactional
from mailman.interfaces.command import ICLISubCommand
from mailman.interfaces.listmanager import IListManager
from mailman.utilities.importer import import_config_pck, Import21Error
+from six.moves import cPickle
+from zope.component import getUtility
+from zope.interface import implementer
@@ -78,7 +74,7 @@
assert len(args.pickle_file) == 1, (
'Unexpected positional arguments: %s' % args.pickle_file)
filename = args.pickle_file[0]
- with open(filename) as fp:
+ with open(filename, 'rb') as fp:
while True:
try:
config_dict = cPickle.load(fp)
=== modified file 'src/mailman/commands/cli_info.py'
--- src/mailman/commands/cli_info.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_info.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Information about this Mailman instance."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Info'
]
@@ -28,13 +25,12 @@
import sys
from lazr.config import as_boolean
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
from mailman.rest.helpers import path_to
from mailman.version import MAILMAN_VERSION_FULL
+from zope.interface import implementer
=== modified file 'src/mailman/commands/cli_inject.py'
--- src/mailman/commands/cli_inject.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_inject.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""bin/mailman inject"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Inject',
]
@@ -27,14 +24,13 @@
import sys
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.app.inject import inject_text
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
from mailman.interfaces.listmanager import IListManager
+from zope.component import getUtility
+from zope.interface import implementer
@@ -49,7 +45,7 @@
self.parser = parser
command_parser.add_argument(
'-q', '--queue',
- type=unicode, help=_("""
+ help=_("""
The name of the queue to inject the message to. QUEUE must be one
of the directories inside the qfiles directory. If omitted, the
incoming queue is used."""))
@@ -59,7 +55,7 @@
help=_('Show a list of all available queue names and exit.'))
command_parser.add_argument(
'-f', '--filename',
- type=unicode, help=_("""
+ help=_("""
Name of file containing the message to inject. If not given, or
'-' (without the quotes) standard input is used."""))
# Required positional argument.
=== modified file 'src/mailman/commands/cli_lists.py'
--- src/mailman/commands/cli_lists.py 2014-11-20 01:29:44 +0000
+++ src/mailman/commands/cli_lists.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The 'lists' subcommand."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Create',
'Lists',
@@ -27,9 +24,6 @@
]
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.app.lifecycle import create_list, remove_list
from mailman.core.constants import system_preferences
from mailman.core.i18n import _
@@ -43,6 +37,8 @@
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.listmanager import IListManager, ListAlreadyExistsError
from mailman.utilities.i18n import make
+from zope.component import getUtility
+from zope.interface import implementer
COMMASPACE = ', '
@@ -135,12 +131,12 @@
self.parser = parser
command_parser.add_argument(
'--language',
- type=unicode, metavar='CODE', help=_("""\
+ metavar='CODE', help=_("""\
Set the list's preferred language to CODE, which must be a
registered two letter language code."""))
command_parser.add_argument(
'-o', '--owner',
- type=unicode, action='append', default=[],
+ action='append', default=[],
dest='owners', metavar='OWNER', help=_("""\
Specify a listowner email address. If the address is not
currently registered with Mailman, the address is registered and
=== modified file 'src/mailman/commands/cli_members.py'
--- src/mailman/commands/cli_members.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_members.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The 'members' subcommand."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Members',
]
@@ -29,11 +26,6 @@
import codecs
from email.utils import formataddr, parseaddr
-from operator import attrgetter
-from passlib.utils import generate_password as generate
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.app.membership import add_member
from mailman.config import config
from mailman.core.i18n import _
@@ -42,6 +34,10 @@
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.member import (
AlreadySubscribedError, DeliveryMode, DeliveryStatus)
+from operator import attrgetter
+from passlib.utils import generate_password as generate
+from zope.component import getUtility
+from zope.interface import implementer
@@ -197,8 +193,6 @@
continue
# Parse the line and ensure that the values are unicodes.
display_name, email = parseaddr(line)
- display_name = display_name.decode(fp.encoding)
- email = email.decode(fp.encoding)
# Give the user a default, user-friendly password.
password = generate(int(config.passwords.password_length))
try:
=== modified file 'src/mailman/commands/cli_qfile.py'
--- src/mailman/commands/cli_qfile.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_qfile.py 2015-01-03 05:08:01 +0000
@@ -17,24 +17,22 @@
"""Getting information out of a qfile."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'QFile',
]
-import cPickle
-
-from pprint import PrettyPrinter
-from zope.interface import implementer
+import six
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
from mailman.utilities.interact import interact
-
-
+from pprint import PrettyPrinter
+from six.moves import cPickle
+from zope.interface import implementer
+
+
+# This is deliberately called 'm' for use with --interactive.
m = []
@@ -71,7 +69,7 @@
"""See `ICLISubCommand`."""
printer = PrettyPrinter(indent=4)
assert len(args.qfile) == 1, 'Wrong number of positional arguments'
- with open(args.qfile[0]) as fp:
+ with open(args.qfile[0], 'rb') as fp:
while True:
try:
m.append(cPickle.load(fp))
@@ -82,7 +80,7 @@
for i, obj in enumerate(m):
count = i + 1
print(_('<----- start object $count ----->'))
- if isinstance(obj, basestring):
+ if isinstance(obj, six.string_types):
print(obj)
else:
printer.pprint(obj)
=== modified file 'src/mailman/commands/cli_status.py'
--- src/mailman/commands/cli_status.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_status.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""bin/mailman status."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Status',
]
@@ -27,11 +24,10 @@
import socket
-from zope.interface import implementer
-
from mailman.bin.master import WatcherState, master_state
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
+from zope.interface import implementer
=== modified file 'src/mailman/commands/cli_unshunt.py'
--- src/mailman/commands/cli_unshunt.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_unshunt.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The 'unshunt' command."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Unshunt',
]
@@ -27,11 +24,10 @@
import sys
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
+from zope.interface import implementer
@@ -62,7 +58,7 @@
which_queue = msgdata.get('whichq', 'in')
if not args.discard:
config.switchboards[which_queue].enqueue(msg, msgdata)
- except Exception as error:
+ except Exception:
print(_('Cannot unshunt message $filebase, skipping:\n$error'),
file=sys.stderr)
else:
=== modified file 'src/mailman/commands/cli_version.py'
--- src/mailman/commands/cli_version.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_version.py 2015-01-03 05:08:01 +0000
@@ -17,18 +17,14 @@
"""The Mailman version."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Version',
]
-from zope.interface import implementer
-
from mailman.interfaces.command import ICLISubCommand
from mailman.version import MAILMAN_VERSION_FULL
+from zope.interface import implementer
=== modified file 'src/mailman/commands/cli_withlist.py'
--- src/mailman/commands/cli_withlist.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/cli_withlist.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""bin/mailman withlist"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Shell',
'Withlist',
@@ -30,15 +27,15 @@
import sys
from lazr.config import as_boolean
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
from mailman.interfaces.listmanager import IListManager
from mailman.utilities.interact import DEFAULT_BANNER, interact
from mailman.utilities.modules import call_name
+from zope.component import getUtility
+from zope.interface import implementer
+
# Global holding onto the open mailing list.
m = None
=== modified file 'src/mailman/commands/docs/echo.rst'
--- src/mailman/commands/docs/echo.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/echo.rst 2015-01-03 05:08:01 +0000
@@ -24,7 +24,7 @@
>>> from mailman.email.message import Message
>>> print(command.process(mlist, Message(), {}, ('foo', 'bar'), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(str(results))
The results of your email command are provided below.
echo foo bar
=== modified file 'src/mailman/commands/docs/help.rst'
--- src/mailman/commands/docs/help.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/help.rst 2015-01-03 05:08:01 +0000
@@ -25,7 +25,7 @@
>>> from mailman.email.message import Message
>>> print(help.process(mlist, Message(), {}, (), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
confirm - Confirm a subscription request.
@@ -44,19 +44,19 @@
>>> results = Results()
>>> print(help.process(mlist, Message(), {}, ('help',), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
help [command]
Get help about available email commands.
-
+
Some commands have even more detailed help.
>>> results = Results()
>>> print(help.process(mlist, Message(), {}, ('join',), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
join [digest=]
=== modified file 'src/mailman/commands/docs/info.rst'
--- src/mailman/commands/docs/info.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/info.rst 2015-01-03 05:08:01 +0000
@@ -62,20 +62,21 @@
Python ...
...
File system paths:
- ARCHIVE_DIR = /var/lib/mailman/archives
- BIN_DIR = /sbin
- DATA_DIR = /var/lib/mailman/data
- ETC_DIR = /etc
- EXT_DIR = /etc/mailman.d
- LIST_DATA_DIR = /var/lib/mailman/lists
- LOCK_DIR = /var/lock/mailman
- LOCK_FILE = /var/lock/mailman/master.lck
- LOG_DIR = /var/log/mailman
- MESSAGES_DIR = /var/lib/mailman/messages
- PID_FILE = /var/run/mailman/master.pid
- QUEUE_DIR = /var/spool/mailman
- TEMPLATE_DIR = .../mailman/templates
- VAR_DIR = /var/lib/mailman
+ ARCHIVE_DIR = /var/lib/mailman/archives
+ BIN_DIR = /sbin
+ CFG_FILE = .../test.cfg
+ DATA_DIR = /var/lib/mailman/data
+ ETC_DIR = /etc
+ EXT_DIR = /etc/mailman.d
+ LIST_DATA_DIR = /var/lib/mailman/lists
+ LOCK_DIR = /var/lock/mailman
+ LOCK_FILE = /var/lock/mailman/master.lck
+ LOG_DIR = /var/log/mailman
+ MESSAGES_DIR = /var/lib/mailman/messages
+ PID_FILE = /var/run/mailman/master.pid
+ QUEUE_DIR = /var/spool/mailman
+ TEMPLATE_DIR = .../mailman/templates
+ VAR_DIR = /var/lib/mailman
.. _`Filesystem Hierarchy Standard`: http://www.pathname.com/fhs/
=== modified file 'src/mailman/commands/docs/inject.rst'
--- src/mailman/commands/docs/inject.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/inject.rst 2015-01-03 05:08:01 +0000
@@ -94,7 +94,7 @@
>>> dump_msgdata(items[0].msgdata)
_parsemsg : False
- listname : test@example.com
+ listid : test.example.com
original_size: 203
version : 3
@@ -122,7 +122,7 @@
>>> dump_msgdata(items[0].msgdata)
_parsemsg : False
- listname : test@example.com
+ listid : test.example.com
original_size: 203
version : 3
@@ -133,7 +133,7 @@
The message text can also be provided on standard input.
::
- >>> from StringIO import StringIO
+ >>> from six import StringIO
# Remember: we've got unicode literals turned on.
>>> standard_in = StringIO(str("""\
@@ -167,7 +167,7 @@
>>> dump_msgdata(items[0].msgdata)
_parsemsg : False
- listname : test@example.com
+ listid : test.example.com
original_size: 211
version : 3
@@ -195,7 +195,7 @@
_parsemsg : False
bar : two
foo : one
- listname : test@example.com
+ listid : test.example.com
original_size: 203
version : 3
=== modified file 'src/mailman/commands/docs/members.rst'
--- src/mailman/commands/docs/members.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/members.rst 2015-01-03 05:08:01 +0000
@@ -229,15 +229,14 @@
taken from standard input.
::
- >>> from StringIO import StringIO
+ >>> from six import StringIO
>>> fp = StringIO()
- >>> fp.encoding = 'us-ascii'
>>> for address in ('dperson@example.com',
... 'Elly Person ',
... 'fperson@example.com (Fred Person)',
... ):
... print(address, file=fp)
- >>> fp.seek(0)
+ >>> filepos = fp.seek(0)
>>> import sys
>>> sys.stdin = fp
=== modified file 'src/mailman/commands/docs/membership.rst'
--- src/mailman/commands/docs/membership.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/membership.rst 2015-01-03 05:08:01 +0000
@@ -45,7 +45,7 @@
>>> from mailman.email.message import Message
>>> print(join.process(mlist, Message(), {}, (), results))
ContinueProcessing.no
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
join: No valid address found to subscribe
@@ -60,7 +60,7 @@
>>> results = Results()
>>> print(subscribe.process(mlist, Message(), {}, (), results))
ContinueProcessing.no
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
subscribe: No valid address found to subscribe
@@ -79,7 +79,7 @@
>>> results = Results()
>>> print(join.process(mlist, msg, {}, (), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
Confirmation email sent to Anne Person
@@ -150,7 +150,7 @@
>>> results = Results()
>>> print(confirm.process(mlist, msg, {}, (token,), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
Confirmed
@@ -208,7 +208,7 @@
>>> results = Results()
>>> print(confirm.process(mlist_2, msg, {}, (token,), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
Confirmed
@@ -241,7 +241,7 @@
>>> results = Results()
>>> print(leave.process(mlist_2, msg, {}, (), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
Anne Person left baker@example.com
@@ -278,7 +278,7 @@
>>> print(leave.process(mlist, msg, {}, (), results))
ContinueProcessing.no
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
Invalid or unverified email address: anne.person@example.org
@@ -299,7 +299,7 @@
>>> print(leave.process(mlist, msg, {}, (), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
Anne Person left alpha@example.com
@@ -354,7 +354,7 @@
>>> print(confirm.process(mlist, msg, {}, (token,), results))
ContinueProcessing.yes
- >>> print(unicode(results))
+ >>> print(results)
The results of your email command are provided below.
Confirmed
=== modified file 'src/mailman/commands/docs/qfile.rst'
--- src/mailman/commands/docs/qfile.rst 2012-03-26 12:04:00 +0000
+++ src/mailman/commands/docs/qfile.rst 2015-01-03 05:08:01 +0000
@@ -47,7 +47,6 @@
>>> command.process(FakeArgs)
[----- start pickle -----]
<----- start object 1 ----->
- From nobody ...
From: aperson@example.com
To: test@example.com
Subject: Uh oh
@@ -55,11 +54,7 @@
I borkeded Mailman.
<----- start object 2 ----->
- { u'_parsemsg': False,
- 'bad': u'yes',
- 'bar': u'baz',
- 'foo': 7,
- u'version': 3}
+ {'_parsemsg': False, 'bad': 'yes', 'bar': 'baz', 'foo': 7, 'version': 3}
[----- end pickle -----]
Maybe we don't want to print the contents of the file though, in case we want
=== modified file 'src/mailman/commands/docs/withlist.rst'
--- src/mailman/commands/docs/withlist.rst 2014-10-31 03:12:00 +0000
+++ src/mailman/commands/docs/withlist.rst 2015-01-03 05:08:01 +0000
@@ -52,10 +52,10 @@
>>> with open(os.path.join(config.VAR_DIR, 'showme.py'), 'w') as fp:
... print("""\
... def showme(mailing_list):
- ... print "The list's name is", mailing_list.fqdn_listname
+ ... print("The list's name is", mailing_list.fqdn_listname)
...
... def displayname(mailing_list):
- ... print "The list's display name is", mailing_list.display_name
+ ... print("The list's display name is", mailing_list.display_name)
... """, file=fp)
If the name of the function is the same as the module, then you only need to
=== modified file 'src/mailman/commands/eml_confirm.py'
--- src/mailman/commands/eml_confirm.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/eml_confirm.py 2015-01-03 05:08:01 +0000
@@ -15,22 +15,18 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see .
-"""Module stuff."""
-
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
+"""The 'confirm' email command."""
+
__all__ = [
'Confirm',
]
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.command import ContinueProcessing, IEmailCommand
from mailman.interfaces.registrar import IRegistrar
+from zope.component import getUtility
+from zope.interface import implementer
=== modified file 'src/mailman/commands/eml_echo.py'
--- src/mailman/commands/eml_echo.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/eml_echo.py 2015-01-03 05:08:01 +0000
@@ -17,18 +17,14 @@
"""The email command 'echo'."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Echo',
]
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.command import ContinueProcessing, IEmailCommand
+from zope.interface import implementer
SPACE = ' '
=== modified file 'src/mailman/commands/eml_end.py'
--- src/mailman/commands/eml_end.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/eml_end.py 2015-01-03 05:08:01 +0000
@@ -17,19 +17,15 @@
"""The email commands 'end' and 'stop'."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'End',
'Stop',
]
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.command import ContinueProcessing, IEmailCommand
+from zope.interface import implementer
=== modified file 'src/mailman/commands/eml_help.py'
--- src/mailman/commands/eml_help.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/eml_help.py 2015-01-03 05:08:01 +0000
@@ -17,20 +17,16 @@
"""The email command 'help'."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Help',
]
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ContinueProcessing, IEmailCommand
from mailman.utilities.string import wrap
+from zope.interface import implementer
SPACE = ' '
=== modified file 'src/mailman/commands/eml_membership.py'
--- src/mailman/commands/eml_membership.py 2014-12-09 01:38:26 +0000
+++ src/mailman/commands/eml_membership.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The email commands 'join' and 'subscribe'."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Join',
'Subscribe',
@@ -29,15 +26,14 @@
from email.utils import formataddr, parseaddr
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.command import ContinueProcessing, IEmailCommand
from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.registrar import IRegistrar
from mailman.interfaces.subscriptions import ISubscriptionService
from mailman.interfaces.usermanager import IUserManager
+from zope.component import getUtility
+from zope.interface import implementer
@@ -182,6 +178,7 @@
return ContinueProcessing.yes
+
class Unsubscribe(Leave):
"""The email 'unsubscribe' command (an alias for 'leave')."""
=== modified file 'src/mailman/commands/tests/test_conf.py'
--- src/mailman/commands/tests/test_conf.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/tests/test_conf.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the conf subcommand."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestConf',
]
@@ -31,9 +28,9 @@
import tempfile
import unittest
-from StringIO import StringIO
from mailman.commands.cli_conf import Conf
from mailman.testing.layers import ConfigLayer
+from six import StringIO
=== modified file 'src/mailman/commands/tests/test_confirm.py'
--- src/mailman/commands/tests/test_confirm.py 2014-01-07 03:43:59 +0000
+++ src/mailman/commands/tests/test_confirm.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the `confirm` command."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestConfirm',
]
@@ -27,8 +24,6 @@
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.commands.eml_confirm import Confirm
from mailman.email.message import Message
@@ -37,6 +32,7 @@
from mailman.runners.command import Results
from mailman.testing.helpers import get_queue_messages, reset_the_world
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
=== modified file 'src/mailman/commands/tests/test_control.py'
--- src/mailman/commands/tests/test_control.py 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/tests/test_control.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test some additional corner cases for starting/stopping."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestStart',
'find_master',
@@ -37,11 +34,11 @@
import unittest
from datetime import timedelta, datetime
-
from mailman.commands.cli_control import Start, kill_watcher
from mailman.config import config
from mailman.testing.layers import ConfigLayer
+
SEP = '|'
=== modified file 'src/mailman/commands/tests/test_create.py'
--- src/mailman/commands/tests/test_create.py 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/tests/test_create.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test `bin/mailman create`."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestCreate',
]
=== modified file 'src/mailman/commands/tests/test_help.py'
--- src/mailman/commands/tests/test_help.py 2014-01-01 14:59:42 +0000
+++ src/mailman/commands/tests/test_help.py 2015-01-03 05:08:01 +0000
@@ -17,10 +17,8 @@
"""Additional tests for the `help` email command."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
+ 'TestHelp',
]
@@ -47,11 +45,11 @@
def test_too_many_arguments(self):
# Error message when too many help arguments are given.
results = Results()
- status = self._help.process(self._mlist, Message(), {},
+ status = self._help.process(self._mlist, Message(), {},
('more', 'than', 'one'),
results)
self.assertEqual(status, ContinueProcessing.no)
- self.assertEqual(unicode(results), """\
+ self.assertEqual(str(results), """\
The results of your email command are provided below.
help: too many arguments: more than one
@@ -60,10 +58,10 @@
def test_no_such_command(self):
# Error message when asking for help on an existent command.
results = Results()
- status = self._help.process(self._mlist, Message(), {},
+ status = self._help.process(self._mlist, Message(), {},
('doesnotexist',), results)
self.assertEqual(status, ContinueProcessing.no)
- self.assertEqual(unicode(results), """\
+ self.assertEqual(str(results), """\
The results of your email command are provided below.
help: no such command: doesnotexist
=== modified file 'src/mailman/config/__init__.py'
--- src/mailman/config/__init__.py 2014-04-28 15:23:35 +0000
+++ src/mailman/config/__init__.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Mailman configuration package."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'config',
]
=== modified file 'src/mailman/config/config.py'
--- src/mailman/config/config.py 2014-12-29 22:44:58 +0000
+++ src/mailman/config/config.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Configuration file loading and management."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Configuration',
'external_configuration',
@@ -29,27 +26,28 @@
import os
import sys
+import mailman.templates
-from ConfigParser import SafeConfigParser
from flufl.lock import Lock
from lazr.config import ConfigSchema, as_boolean
-from pkg_resources import resource_stream, resource_string
-from string import Template
-from zope.component import getUtility
-from zope.event import notify
-from zope.interface import implementer
-
-import mailman.templates
-
from mailman import version
from mailman.interfaces.configuration import (
ConfigurationUpdatedEvent, IConfiguration, MissingConfigurationFileError)
from mailman.interfaces.languages import ILanguageManager
from mailman.utilities.filesystem import makedirs
from mailman.utilities.modules import call_name, expand_path
+from pkg_resources import resource_filename, resource_string as resource_bytes
+from six.moves.configparser import ConfigParser, RawConfigParser
+from string import Template
+from unittest.mock import patch
+from zope.component import getUtility
+from zope.event import notify
+from zope.interface import implementer
SPACE = ' '
+SPACERS = '\n'
+
MAILMAN_CFG_TEMPLATE = """\
# AUTOMATICALLY GENERATED BY MAILMAN ON {}
@@ -66,6 +64,11 @@
# enabled: yes
# recipient: your.address@your.domain"""
+class _NonStrictRawConfigParser(RawConfigParser):
+ def __init__(self, *args, **kws):
+ kws['strict'] = False
+ super().__init__(*args, **kws)
+
@implementer(IConfiguration)
@@ -102,30 +105,29 @@
def load(self, filename=None):
"""Load the configuration from the schema and config files."""
- schema_file = config_file = None
- try:
- schema_file = resource_stream('mailman.config', 'schema.cfg')
- schema = ConfigSchema('schema.cfg', schema_file)
- # If a configuration file was given, load it now too. First, load
- # the absolute minimum default configuration, then if a
- # configuration filename was given by the user, push it.
- config_file = resource_stream('mailman.config', 'mailman.cfg')
- self._config = schema.loadFile(config_file, 'mailman.cfg')
- if filename is not None:
- self.filename = filename
- with open(filename) as user_config:
- self._config.push(filename, user_config.read())
- finally:
- if schema_file:
- schema_file.close()
- if config_file:
- config_file.close()
- self._post_process()
+ schema_file = resource_filename('mailman.config', 'schema.cfg')
+ schema = ConfigSchema(schema_file)
+ # If a configuration file was given, load it now too. First, load
+ # the absolute minimum default configuration, then if a
+ # configuration filename was given by the user, push it.
+ config_file = resource_filename('mailman.config', 'mailman.cfg')
+ self._config = schema.load(config_file)
+ if filename is None:
+ self._post_process()
+ else:
+ self.filename = filename
+ with open(filename, 'r', encoding='utf-8') as user_config:
+ self.push(filename, user_config.read())
def push(self, config_name, config_string):
"""Push a new configuration onto the stack."""
self._clear()
- self._config.push(config_name, config_string)
+ # In Python 3, the RawConfigParser() must be created with
+ # strict=False, otherwise we'll get a DuplicateSectionError.
+ # See https://bugs.launchpad.net/lazr.config/+bug/1397779
+ with patch('lazr.config._config.RawConfigParser',
+ _NonStrictRawConfigParser):
+ self._config.push(config_name, config_string)
self._post_process()
def pop(self, config_name):
@@ -164,6 +166,7 @@
# path is relative.
var_dir = os.environ.get('MAILMAN_VAR_DIR', category.var_dir)
substitutions = dict(
+ cwd = os.getcwd(),
argv = bin_dir,
# Directories.
bin_dir = category.bin_dir,
@@ -185,26 +188,32 @@
lock_file = category.lock_file,
pid_file = category.pid_file,
)
+ # Add the path to the .cfg file, if one was given on the command line.
+ if self.filename is not None:
+ substitutions['cfg_file'] = self.filename
# Now, perform substitutions recursively until there are no more
# variables with $-vars in them, or until substitutions are not
# helping any more.
last_dollar_count = 0
while True:
+ expandables = []
# Mutate the dictionary during iteration.
- dollar_count = 0
- for key in substitutions.keys():
+ for key in substitutions:
raw_value = substitutions[key]
value = Template(raw_value).safe_substitute(substitutions)
if '$' in value:
# Still more work to do.
- dollar_count += 1
+ expandables.append((key, value))
substitutions[key] = value
- if dollar_count == 0:
+ if len(expandables) == 0:
break
- if dollar_count == last_dollar_count:
- print('Path expansion infloop detected', file=sys.stderr)
+ if len(expandables) == last_dollar_count:
+ print('Path expansion infloop detected:\n',
+ SPACERS.join('\t{}: {}'.format(key, value)
+ for key, value in sorted(expandables)),
+ file=sys.stderr)
sys.exit(1)
- last_dollar_count = dollar_count
+ last_dollar_count = len(expandables)
# Ensure that all paths are normalized and made absolute. Handle the
# few special cases first. Most of these are due to backward
# compatibility.
@@ -269,7 +278,7 @@
-def load_external(path, encoding=None):
+def load_external(path):
"""Load the configuration file named by path.
:param path: A string naming the location of the external configuration
@@ -278,21 +287,16 @@
value must name a ``.cfg`` file located within Python's import path,
however the trailing ``.cfg`` suffix is implied (don't provide it
here).
- :param encoding: The encoding to apply to the data read from path. If
- None, then bytes will be returned.
- :return: A unicode string or bytes, depending on ``encoding``.
+ :return: The contents of the configuration file.
+ :rtype: str
"""
# Is the context coming from a file system or Python path?
if path.startswith('python:'):
resource_path = path[7:]
package, dot, resource = resource_path.rpartition('.')
- config_string = resource_string(package, resource + '.cfg')
- else:
- with open(path, 'rb') as fp:
- config_string = fp.read()
- if encoding is None:
- return config_string
- return config_string.decode(encoding)
+ return resource_bytes(package, resource + '.cfg').decode('utf-8')
+ with open(path, 'r', encoding='utf-8') as fp:
+ return fp.read()
def external_configuration(path):
@@ -308,7 +312,7 @@
"""
# Is the context coming from a file system or Python path?
cfg_path = expand_path(path)
- parser = SafeConfigParser()
+ parser = ConfigParser()
files = parser.read(cfg_path)
if files != [cfg_path]:
raise MissingConfigurationFileError(path)
=== modified file 'src/mailman/config/mailman.cfg'
--- src/mailman/config/mailman.cfg 2014-03-02 22:59:30 +0000
+++ src/mailman/config/mailman.cfg 2015-01-03 05:08:01 +0000
@@ -23,9 +23,13 @@
# /var/tmp/mailman
[paths.dev]
-# Convenient development layout where everything is put in the current
-# directory.
-var_dir: var
+# Convenient development layout where everything is put in a directory above
+# where the mailman.cfg file lives.
+var_dir: $cfg_file/../..
+
+[paths.here]
+# Layout where the var directory is put in the current working directory.
+var_dir: $cwd/var
[paths.fhs]
# Filesystem Hiearchy Standard 2.3
=== modified file 'src/mailman/config/schema.cfg'
--- src/mailman/config/schema.cfg 2014-11-02 19:55:10 +0000
+++ src/mailman/config/schema.cfg 2015-01-03 05:08:01 +0000
@@ -59,7 +59,7 @@
post_hook:
# Which paths.* file system layout to use.
-layout: dev
+layout: here
# Can MIME filtered messages be preserved by list owners?
filtered_messages_are_preservable: no
=== modified file 'src/mailman/config/tests/test_archivers.py'
--- src/mailman/config/tests/test_archivers.py 2014-01-01 14:59:42 +0000
+++ src/mailman/config/tests/test_archivers.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Site-wide archiver configuration tests."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestArchivers',
]
=== modified file 'src/mailman/config/tests/test_configuration.py'
--- src/mailman/config/tests/test_configuration.py 2014-11-09 12:52:58 +0000
+++ src/mailman/config/tests/test_configuration.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the system-wide global configuration."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestConfiguration',
'TestConfigurationErrors',
@@ -32,6 +29,7 @@
import tempfile
import unittest
+from contextlib import ExitStack
from mailman.config.config import (
Configuration, external_configuration, load_external)
from mailman.interfaces.configuration import (
@@ -65,26 +63,13 @@
class TestExternal(unittest.TestCase):
"""Test external configuration file loading APIs."""
- def test_load_external_by_filename_as_bytes(self):
+ def test_load_external_by_filename(self):
filename = resource_filename('mailman.config', 'postfix.cfg')
contents = load_external(filename)
- self.assertIsInstance(contents, bytes)
- self.assertEqual(contents[:9], b'[postfix]')
+ self.assertEqual(contents[:9], '[postfix]')
- def test_load_external_by_path_as_bytes(self):
+ def test_load_external_by_path(self):
contents = load_external('python:mailman.config.postfix')
- self.assertIsInstance(contents, bytes)
- self.assertEqual(contents[:9], b'[postfix]')
-
- def test_load_external_by_filename_as_string(self):
- filename = resource_filename('mailman.config', 'postfix.cfg')
- contents = load_external(filename, encoding='utf-8')
- self.assertIsInstance(contents, unicode)
- self.assertEqual(contents[:9], '[postfix]')
-
- def test_load_external_by_path_as_string(self):
- contents = load_external('python:mailman.config.postfix', 'utf-8')
- self.assertIsInstance(contents, unicode)
self.assertEqual(contents[:9], '[postfix]')
def test_external_configuration_by_filename(self):
@@ -121,24 +106,32 @@
# Use a fake sys.exit() function that records that it was called, and
# that prevents further processing.
config = Configuration()
- # Suppress warning messages in the test output.
- with self.assertRaises(SystemExit) as cm, mock.patch('sys.stderr'):
+ # Suppress warning messages in the test output. Also, make sure that
+ # the config.load() call doesn't break global state.
+ with ExitStack() as resources:
+ resources.enter_context(mock.patch('sys.stderr'))
+ resources.enter_context(mock.patch.object(config, '_clear'))
+ cm = resources.enter_context(self.assertRaises(SystemExit))
config.load(filename)
self.assertEqual(cm.exception.args, (1,))
def test_path_expansion_infloop(self):
- # A path expansion never completes because it references a
- # non-existent substitution variable.
+ # A path expansion never completes because it references a non-existent
+ # substitution variable.
fd, filename = tempfile.mkstemp()
self.addCleanup(os.remove, filename)
os.close(fd)
with open(filename, 'w') as fp:
print("""\
-[paths.dev]
+[paths.here]
log_dir: $nopath/log_dir
""", file=fp)
config = Configuration()
- # Suppress warning messages in the test output.
- with self.assertRaises(SystemExit) as cm, mock.patch('sys.stderr'):
+ # Suppress warning messages in the test output. Also, make sure that
+ # the config.load() call doesn't break global state.
+ with ExitStack() as resources:
+ resources.enter_context(mock.patch('sys.stderr'))
+ resources.enter_context(mock.patch.object(config, '_clear'))
+ cm = resources.enter_context(self.assertRaises(SystemExit))
config.load(filename)
self.assertEqual(cm.exception.args, (1,))
=== modified file 'src/mailman/core/chains.py'
--- src/mailman/core/chains.py 2014-04-28 15:23:35 +0000
+++ src/mailman/core/chains.py 2015-01-03 05:08:01 +0000
@@ -17,21 +17,17 @@
"""Application support for chain processing."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'initialize',
'process',
]
-from zope.interface.verify import verifyObject
-
from mailman.chains.base import Chain, TerminalChainBase
from mailman.config import config
from mailman.interfaces.chain import LinkAction, IChain
from mailman.utilities.modules import find_components
+from zope.interface.verify import verifyObject
=== modified file 'src/mailman/core/constants.py'
--- src/mailman/core/constants.py 2014-01-01 14:59:42 +0000
+++ src/mailman/core/constants.py 2015-01-03 05:08:01 +0000
@@ -17,21 +17,17 @@
"""Various constants and enumerations."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'system_preferences',
]
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.config import config
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.member import DeliveryMode, DeliveryStatus
from mailman.interfaces.preferences import IPreferences
+from zope.component import getUtility
+from zope.interface import implementer
=== modified file 'src/mailman/core/docs/runner.rst'
--- src/mailman/core/docs/runner.rst 2014-11-01 03:35:02 +0000
+++ src/mailman/core/docs/runner.rst 2015-01-03 05:08:01 +0000
@@ -55,7 +55,7 @@
... A test message.
... """)
>>> switchboard = config.switchboards['test']
- >>> filebase = switchboard.enqueue(msg, listname=mlist.fqdn_listname,
+ >>> filebase = switchboard.enqueue(msg, listid=mlist.list_id,
... foo='yes', bar='no')
>>> runner.run()
>>> print(runner.msg.as_string())
@@ -69,7 +69,7 @@
bar : no
foo : yes
lang : en
- listname : test@example.com
+ listid : test.example.com
version : 3
XXX More of the Runner API should be tested.
=== modified file 'src/mailman/core/errors.py'
--- src/mailman/core/errors.py 2014-04-28 15:23:35 +0000
+++ src/mailman/core/errors.py 2015-01-03 05:08:01 +0000
@@ -26,9 +26,6 @@
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AlreadyReceivingDigests',
'AlreadyReceivingRegularDeliveries',
=== modified file 'src/mailman/core/i18n.py'
--- src/mailman/core/i18n.py 2014-04-28 15:23:35 +0000
+++ src/mailman/core/i18n.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Internationalization."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'_',
'ctime',
@@ -28,11 +25,12 @@
import time
+import mailman.messages
+
from flufl.i18n import PackageStrategy, registry
-
-import mailman.messages
from mailman.interfaces.configuration import ConfigurationUpdatedEvent
+
_ = None
=== modified file 'src/mailman/core/initialize.py'
--- src/mailman/core/initialize.py 2014-11-09 12:52:58 +0000
+++ src/mailman/core/initialize.py 2015-01-03 05:08:01 +0000
@@ -24,9 +24,6 @@
by the command line arguments.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'initialize',
'initialize_1',
@@ -38,16 +35,15 @@
import os
import sys
-
-from pkg_resources import resource_string
-from zope.component import getUtility
-from zope.configuration import xmlconfig
-
import mailman.config.config
import mailman.core.logging
from mailman.interfaces.database import IDatabaseFactory
from mailman.utilities.modules import call_name
+from pkg_resources import resource_string as resource_bytes
+from zope.component import getUtility
+from zope.configuration import xmlconfig
+
# The test infrastructure uses this to prevent the search and loading of any
# existing configuration file. Otherwise the existence of say a
@@ -109,8 +105,8 @@
:param config_path: The path to the configuration file.
:type config_path: string
"""
- zcml = resource_string('mailman.config', 'configure.zcml')
- xmlconfig.string(zcml)
+ zcml = resource_bytes('mailman.config', 'configure.zcml')
+ xmlconfig.string(zcml.decode('utf-8'))
# By default, set the umask so that only owner and group can read and
# write our files. Specifically we must have g+rw and we probably want
# o-rwx although I think in most cases it doesn't hurt if other can read
=== modified file 'src/mailman/core/logging.py'
--- src/mailman/core/logging.py 2014-12-29 22:50:07 +0000
+++ src/mailman/core/logging.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Logging initialization, using Python's standard logging package."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'initialize',
'reopen',
=== modified file 'src/mailman/core/pipelines.py'
--- src/mailman/core/pipelines.py 2014-01-01 14:59:42 +0000
+++ src/mailman/core/pipelines.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Built-in pipelines."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'BasePipeline',
'OwnerPipeline',
@@ -32,9 +29,6 @@
import logging
-from zope.interface import implementer
-from zope.interface.verify import verifyObject
-
from mailman.app.bounces import bounce_message
from mailman.config import config
from mailman.core import errors
@@ -42,6 +36,8 @@
from mailman.interfaces.handler import IHandler
from mailman.interfaces.pipeline import IPipeline
from mailman.utilities.modules import find_components
+from zope.interface import implementer
+from zope.interface.verify import verifyObject
dlog = logging.getLogger('mailman.debug')
@@ -120,6 +116,7 @@
'cleanse',
'cleanse-dkim',
'cook-headers',
+ 'subject-prefix',
'rfc-2369',
'to-archive',
'to-digest',
=== modified file 'src/mailman/core/rules.py'
--- src/mailman/core/rules.py 2014-04-28 15:23:35 +0000
+++ src/mailman/core/rules.py 2015-01-03 05:08:01 +0000
@@ -17,19 +17,15 @@
"""Various rule helpers"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'initialize',
]
-from zope.interface.verify import verifyObject
-
from mailman.config import config
from mailman.interfaces.rules import IRule
from mailman.utilities.modules import find_components
+from zope.interface.verify import verifyObject
=== modified file 'src/mailman/core/runner.py'
--- src/mailman/core/runner.py 2014-12-09 11:25:45 +0000
+++ src/mailman/core/runner.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The process runner base class."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Runner',
]
@@ -30,12 +27,7 @@
import logging
import traceback
-from cStringIO import StringIO
from lazr.config import as_boolean, as_timedelta
-from zope.component import getUtility
-from zope.event import notify
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.core.logging import reopen
@@ -44,6 +36,10 @@
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.runner import IRunner, RunnerCrashEvent
from mailman.utilities.string import expand
+from six.moves import cStringIO as StringIO
+from zope.component import getUtility
+from zope.event import notify
+from zope.interface import implementer
dlog = logging.getLogger('mailman.debug')
@@ -218,16 +214,26 @@
# them out of our sight.
#
# Find out which mailing list this message is destined for.
+ mlist = None
missing = object()
- listname = msgdata.get('listname', missing)
- mlist = (None
- if listname is missing
- else getUtility(IListManager).get(unicode(listname)))
+ # First try to dig out the target list by id. If there's no list-id
+ # in the metadata, fall back to the fqdn list name for backward
+ # compatibility.
+ list_manager = getUtility(IListManager)
+ list_id = msgdata.get('listid', missing)
+ fqdn_listname = None
+ if list_id is missing:
+ fqdn_listname = msgdata.get('listname', missing)
+ # XXX Deprecate.
+ if fqdn_listname is not missing:
+ mlist = list_manager.get(fqdn_listname)
+ else:
+ mlist = list_manager.get_by_list_id(list_id)
if mlist is None:
+ identifier = (list_id if list_id is not None else fqdn_listname)
elog.error(
'%s runner "%s" shunting message for missing list: %s',
- msg['message-id'], self.name,
- ('n/a' if listname is missing else listname))
+ msg['message-id'], self.name, identifier)
config.switchboards['shunt'].enqueue(msg, msgdata)
return
# Now process this message. We also want to set up the language
=== modified file 'src/mailman/core/switchboard.py'
--- src/mailman/core/switchboard.py 2014-11-20 01:29:44 +0000
+++ src/mailman/core/switchboard.py 2015-01-03 05:08:01 +0000
@@ -24,9 +24,6 @@
dictionary is written.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Switchboard',
'handle_ConfigurationUpdatedEvent',
@@ -37,22 +34,22 @@
import time
import email
import pickle
-import cPickle
import hashlib
import logging
-from zope.interface import implementer
-
from mailman.config import config
from mailman.email.message import Message
from mailman.interfaces.configuration import ConfigurationUpdatedEvent
from mailman.interfaces.switchboard import ISwitchboard
from mailman.utilities.filesystem import makedirs
from mailman.utilities.string import expand
-
-
-# 20 bytes of all bits set, maximum hashlib.sha.digest() value.
-shamax = 0xffffffffffffffffffffffffffffffffffffffffL
+from six.moves import cPickle
+from zope.interface import implementer
+
+
+# 20 bytes of all bits set, maximum hashlib.sha.digest() value. We do it this
+# way for Python 2/3 compatibility.
+shamax = int('0xffffffffffffffffffffffffffffffffffffffff', 16)
# Small increment to add to time in case two entries have the same time. This
# prevents skipping one of two entries with the same time until the next pass.
DELTA = .0001
@@ -92,7 +89,7 @@
self.queue_directory = queue_directory
# If configured to, create the directory if it doesn't yet exist.
if config.create_paths:
- makedirs(self.queue_directory, 0770)
+ makedirs(self.queue_directory, 0o770)
# Fast track for no slices
self._lower = None
self._upper = None
@@ -112,37 +109,37 @@
# of parallel runner processes.
data = _metadata.copy()
data.update(_kws)
- listname = data.get('listname', '--nolist--')
+ list_id = data.get('listid', '--nolist--')
# Get some data for the input to the sha hash.
- now = time.time()
+ now = repr(time.time())
if data.get('_plaintext'):
protocol = 0
msgsave = cPickle.dumps(str(_msg), protocol)
else:
protocol = pickle.HIGHEST_PROTOCOL
msgsave = cPickle.dumps(_msg, protocol)
- # listname is unicode but the input to the hash function must be an
- # 8-bit string (eventually, a bytes object).
- hashfood = msgsave + listname.encode('utf-8') + repr(now)
+ # The list-id field is a string but the input to the hash function must
+ # be bytes.
+ hashfood = msgsave + list_id.encode('utf-8') + now.encode('utf-8')
# Encode the current time into the file name for FIFO sorting. The
# file name consists of two parts separated by a '+': the received
# time for this message (i.e. when it first showed up on this system)
# and the sha hex digest.
- filebase = repr(now) + '+' + hashlib.sha1(hashfood).hexdigest()
+ filebase = now + '+' + hashlib.sha1(hashfood).hexdigest()
filename = os.path.join(self.queue_directory, filebase + '.pck')
tmpfile = filename + '.tmp'
# Always add the metadata schema version number
data['version'] = config.QFILE_SCHEMA_VERSION
# Filter out volatile entries. Use .keys() so that we can mutate the
# dictionary during the iteration.
- for k in data.keys():
+ for k in list(data):
if k.startswith('_'):
del data[k]
# We have to tell the dequeue() method whether to parse the message
# object or not.
data['_parsemsg'] = (protocol == 0)
# Write to the pickle file the message object and metadata.
- with open(tmpfile, 'w') as fp:
+ with open(tmpfile, 'wb') as fp:
fp.write(msgsave)
cPickle.dump(data, fp, protocol)
fp.flush()
@@ -156,7 +153,7 @@
filename = os.path.join(self.queue_directory, filebase + '.pck')
backfile = os.path.join(self.queue_directory, filebase + '.bak')
# Read the message object and metadata.
- with open(filename) as fp:
+ with open(filename, 'rb') as fp:
# Move the file to the backup file name for processing. If this
# process crashes uncleanly the .bak file will be used to
# re-instate the .pck file in order to try again.
@@ -207,13 +204,13 @@
# Throw out any files which don't match our bitrange. BAW: test
# performance and end-cases of this algorithm. MAS: both
# comparisons need to be <= to get complete range.
- if lower is None or (lower <= long(digest, 16) <= upper):
+ if lower is None or (lower <= int(digest, 16) <= upper):
key = float(when)
while key in times:
key += DELTA
times[key] = filebase
# FIFO sort
- return [times[key] for key in sorted(times)]
+ return [times[k] for k in sorted(times)]
def recover_backup_files(self):
"""See `ISwitchboard`."""
@@ -228,7 +225,8 @@
dst = os.path.join(self.queue_directory, filebase + '.pck')
with open(src, 'rb+') as fp:
try:
- msg = cPickle.load(fp)
+ # Throw away the message object.
+ cPickle.load(fp)
data_pos = fp.tell()
data = cPickle.load(fp)
except Exception as error:
=== modified file 'src/mailman/core/system.py'
--- src/mailman/core/system.py 2014-01-01 14:59:42 +0000
+++ src/mailman/core/system.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""System information."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'system',
]
@@ -27,10 +24,9 @@
import sys
-from zope.interface import implementer
-
from mailman import version
from mailman.interfaces.system import ISystem
+from zope.interface import implementer
=== modified file 'src/mailman/core/tests/test_pipelines.py'
--- src/mailman/core/tests/test_pipelines.py 2014-01-01 14:59:42 +0000
+++ src/mailman/core/tests/test_pipelines.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the core modification pipelines."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestOwnerPipeline',
'TestPostingPipeline',
@@ -28,9 +25,6 @@
import unittest
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.core.errors import DiscardMessage, RejectMessage
@@ -40,11 +34,11 @@
from mailman.interfaces.pipeline import IPipeline
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import (
- LogFileMark,
- get_queue_messages,
- reset_the_world,
+ LogFileMark, get_queue_messages, reset_the_world,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
+from zope.interface import implementer
@@ -175,5 +169,5 @@
pipeline_name='default-owner-pipeline')
messages = get_queue_messages('out', sort_on='to')
self.assertEqual(len(messages), 1)
- self.assertEqual(messages[0].msgdata['recipients'],
+ self.assertEqual(messages[0].msgdata['recipients'],
set(('anne@example.com', 'bart@example.com')))
=== modified file 'src/mailman/core/tests/test_runner.py'
--- src/mailman/core/tests/test_runner.py 2014-12-11 02:49:39 +0000
+++ src/mailman/core/tests/test_runner.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test some Runner base class behavior."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestRunner',
]
@@ -70,7 +67,7 @@
Message-ID:
""")
- config.switchboards['in'].enqueue(msg, listname='test@example.com')
+ config.switchboards['in'].enqueue(msg, listid='test.example.com')
with event_subscribers(self._got_event):
runner.run()
# We should now have exactly one event, which will contain the
@@ -81,7 +78,7 @@
self.assertTrue(isinstance(event, RunnerCrashEvent))
self.assertEqual(event.mailing_list, self._mlist)
self.assertEqual(event.message['message-id'], '')
- self.assertEqual(event.metadata['listname'], 'test@example.com')
+ self.assertEqual(event.metadata['listid'], 'test.example.com')
self.assertTrue(isinstance(event.error, RuntimeError))
self.assertEqual(str(event.error), 'borked')
self.assertTrue(isinstance(event.runner, CrashingRunner))
=== modified file 'src/mailman/database/alembic/__init__.py'
--- src/mailman/database/alembic/__init__.py 2014-10-13 21:03:25 +0000
+++ src/mailman/database/alembic/__init__.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Alembic configuration initization."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'alembic_cfg',
]
=== modified file 'src/mailman/database/alembic/env.py'
--- src/mailman/database/alembic/env.py 2014-10-13 14:19:01 +0000
+++ src/mailman/database/alembic/env.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Alembic migration environment."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'run_migrations_offline',
'run_migrations_online',
@@ -28,11 +25,10 @@
from alembic import context
from contextlib import closing
-from sqlalchemy import create_engine
-
from mailman.config import config
from mailman.database.model import Model
from mailman.utilities.string import expand
+from sqlalchemy import create_engine
=== modified file 'src/mailman/database/alembic/versions/51b7f92bd06c_initial.py'
--- src/mailman/database/alembic/versions/51b7f92bd06c_initial.py 2014-10-13 19:24:24 +0000
+++ src/mailman/database/alembic/versions/51b7f92bd06c_initial.py 2015-01-03 05:08:01 +0000
@@ -29,9 +29,6 @@
Create Date: 2014-10-10 09:53:35.624472
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'downgrade',
'upgrade',
=== modified file 'src/mailman/database/base.py'
--- src/mailman/database/base.py 2014-11-01 17:46:36 +0000
+++ src/mailman/database/base.py 2015-01-03 05:08:01 +0000
@@ -15,9 +15,8 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see .
-from __future__ import absolute_import, print_function, unicode_literals
+"""Common database support."""
-__metaclass__ = type
__all__ = [
'SABaseDatabase',
]
@@ -25,17 +24,15 @@
import logging
+from mailman.config import config
+from mailman.interfaces.database import IDatabase
+from mailman.utilities.string import expand
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from zope.interface import implementer
-from mailman.config import config
-from mailman.interfaces.database import IDatabase
-from mailman.utilities.string import expand
-
log = logging.getLogger('mailman.database')
-NL = '\n'
=== modified file 'src/mailman/database/factory.py'
--- src/mailman/database/factory.py 2014-10-13 19:24:24 +0000
+++ src/mailman/database/factory.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Database factory."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'DatabaseFactory',
'DatabaseTestingFactory',
@@ -33,16 +30,15 @@
from alembic.migration import MigrationContext
from alembic.script import ScriptDirectory
from flufl.lock import Lock
-from sqlalchemy import MetaData
-from zope.interface import implementer
-from zope.interface.verify import verifyObject
-
from mailman.config import config
from mailman.database.alembic import alembic_cfg
from mailman.database.model import Model
from mailman.interfaces.database import (
DatabaseError, IDatabase, IDatabaseFactory)
from mailman.utilities.modules import call_name
+from sqlalchemy import MetaData
+from zope.interface import implementer
+from zope.interface.verify import verifyObject
LAST_STORM_SCHEMA_VERSION = '20130406000000'
=== modified file 'src/mailman/database/model.py'
--- src/mailman/database/model.py 2014-11-01 16:49:15 +0000
+++ src/mailman/database/model.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Base class for all database classes."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Model',
]
=== modified file 'src/mailman/database/postgresql.py'
--- src/mailman/database/postgresql.py 2014-09-28 00:17:05 +0000
+++ src/mailman/database/postgresql.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""PostgreSQL database support."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'PostgreSQLDatabase',
]
=== modified file 'src/mailman/database/sqlite.py'
--- src/mailman/database/sqlite.py 2014-09-23 13:09:45 +0000
+++ src/mailman/database/sqlite.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""SQLite database support."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'SQLiteDatabase',
]
@@ -28,7 +25,7 @@
import os
from mailman.database.base import SABaseDatabase
-from urlparse import urlparse
+from six.moves.urllib_parse import urlparse
=== modified file 'src/mailman/database/tests/test_factory.py'
--- src/mailman/database/tests/test_factory.py 2014-11-29 17:46:49 +0000
+++ src/mailman/database/tests/test_factory.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test database schema migrations"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestSchemaManager',
]
@@ -28,17 +25,16 @@
import unittest
import alembic.command
-from mock import patch
-from sqlalchemy import MetaData, Table, Column, Integer, Unicode
-from sqlalchemy.exc import ProgrammingError, OperationalError
-from sqlalchemy.schema import Index
-
from mailman.config import config
from mailman.database.alembic import alembic_cfg
from mailman.database.factory import LAST_STORM_SCHEMA_VERSION, SchemaManager
from mailman.database.model import Model
from mailman.interfaces.database import DatabaseError
from mailman.testing.layers import ConfigLayer
+from mock import patch
+from sqlalchemy import MetaData, Table, Column, Integer, Unicode
+from sqlalchemy.exc import ProgrammingError, OperationalError
+from sqlalchemy.schema import Index
=== modified file 'src/mailman/database/transaction.py'
--- src/mailman/database/transaction.py 2014-01-01 14:59:42 +0000
+++ src/mailman/database/transaction.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Transactional support."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'dbconnection',
'transaction',
@@ -28,7 +25,6 @@
from contextlib import contextmanager
-
from mailman.config import config
=== modified file 'src/mailman/database/types.py'
--- src/mailman/database/types.py 2014-09-28 00:17:05 +0000
+++ src/mailman/database/types.py 2015-01-03 05:08:01 +0000
@@ -15,17 +15,14 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see .
-"""Storm type conversions."""
-
-
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
+"""Database type conversions."""
+
__all__ = [
'Enum',
'UUID',
]
+
import uuid
from sqlalchemy import Integer
=== modified file 'src/mailman/docs/DEVELOP.rst'
--- src/mailman/docs/DEVELOP.rst 2012-12-30 23:24:26 +0000
+++ src/mailman/docs/DEVELOP.rst 2015-01-03 05:08:01 +0000
@@ -3,7 +3,9 @@
==================
The following documentation is generated from the internal developer
-documentation. This documentation is also used by the test suite.
+documentation. This documentation is also used by the test suite. Another
+good source of architectural information is available in the chapter written
+by Barry Warsaw for the `Architecture of Open Source Applications`_.
For now, this will have to suffice as an overview of the Mailman system.
@@ -154,3 +156,4 @@
.. _`Python pickles`: http://docs.python.org/2/library/pickle.html
+.. _`Architecture of Open Source Applications`: http://www.aosabook.org/en/mailman.html
=== modified file 'src/mailman/docs/INTRODUCTION.rst'
--- src/mailman/docs/INTRODUCTION.rst 2014-01-01 14:59:42 +0000
+++ src/mailman/docs/INTRODUCTION.rst 2015-01-03 05:08:01 +0000
@@ -82,7 +82,7 @@
Requirements
============
-Mailman 3.0 requires `Python 2.7`_.
+Mailman 3 requires `Python 3.4`_ or newer.
.. _`GNU Mailman`: http://www.list.org
@@ -90,4 +90,4 @@
.. _`Getting Started`: START.html
.. _Python: http://www.python.org
.. _FAQ: http://wiki.list.org/display/DOC/Frequently+Asked+Questions
-.. _`Python 2.7`: http://www.python.org/download/releases/2.7.3/
+.. _`Python 3.4`: https://www.python.org/downloads/release/python-342/
=== modified file 'src/mailman/docs/NEWS.rst'
--- src/mailman/docs/NEWS.rst 2014-12-30 19:43:56 +0000
+++ src/mailman/docs/NEWS.rst 2015-01-03 05:08:01 +0000
@@ -8,11 +8,35 @@
Here is a history of user visible changes to Mailman.
-3.0 beta 6 -- "Show Don't Tell"
-===============================
-(2015-XX-XX)
-
-
+<<<<<<< TREE
+3.0 beta 6 -- "Show Don't Tell"
+===============================
+(2015-XX-XX)
+
+
+=======
+3.0 beta 6 -- "Show Don't Tell"
+===============================
+(2015-XX-XX)
+
+Configuration
+-------------
+ * When specifying a file system path in the [paths.*] section, $cfg_file can
+ be used to expand into the path of the ``-C`` option if given. In the
+ default ``[paths.dev]`` section, ``$var_dir`` is now specified relative to
+ ``$cfg_file`` so that it won't accidentally be relative to the current
+ working directory, if ``-C`` is given.
+ * ``$cwd`` is now an additional substitution variable for the ``mailman.cfg``
+ file's ``[paths.*]`` sections. A new ``[paths.here]`` section is added,
+ which puts the ``var_dir`` in ``$cwd``. It is made the default layout.
+
+REST
+----
+ * You can now view the contents of, inject messages into, and delete messages
+ from the various queue directories via the ``/queues`` resource.
+
+
+>>>>>>> MERGE-SOURCE
3.0 beta 5 -- "Carve Away The Stone"
====================================
(2014-12-29)
@@ -49,6 +73,7 @@
Development
-----------
+ * Python 3.4 is now the minimum requirement.
* You no longer have to create a virtual environment separately when running
the test suite. Just use `tox`.
* You no longer have to edit `src/mailman/testing/testing.cfg` to run the
=== modified file 'src/mailman/docs/START.rst'
--- src/mailman/docs/START.rst 2014-11-09 12:52:58 +0000
+++ src/mailman/docs/START.rst 2015-01-03 05:08:01 +0000
@@ -39,12 +39,11 @@
Requirements
============
-Python 2.7 is required. It can either be the default 'python' on your
-``$PATH`` or it can be accessible via the ``python2.7`` binary. If
-your operating system does not include Python, see http://www.python.org
-for information about downloading installers (where available) and
-installing it from source (when necessary or preferred). Python 3 is
-not yet supported.
+Python 3.4 or newer is required. It can either be the default 'python3' on
+your ``$PATH`` or it can be accessible via the ``python3.4`` binary. If your
+operating system does not include Python, see http://www.python.org for
+information about downloading installers (where available) and installing it
+from source (when necessary or preferred). Python 2 is not supported.
You may need some additional dependencies, which are either available from
your OS vendor, or can be downloaded automatically from the `Python
@@ -80,9 +79,9 @@
You do have access to the virtualenv, and you can use this to run individual
tests, e.g.::
- $ .tox/py27/bin/python -m nose2 -vv -P user
+ $ .tox/py34/bin/python -m nose2 -vv -P user
-Use `.tox/py27/bin/python -m nose2 --help` for more options.
+Use `.tox/py34/bin/python -m nose2 --help` for more options.
If you want to run the full test suite against the PostgreSQL database, set
the database up as described in :doc:`DATABASE`, then create a `postgres.cfg`
@@ -112,23 +111,23 @@
First, create a virtual environment. By default ``virtualenv`` uses the
``python`` executable it finds first on your ``$PATH``. Make sure this is
-Python 2.7 (just start the interactive interpreter and check the version in
+Python 3.4 (just start the interactive interpreter and check the version in
the startup banner). The directory you install the virtualenv into is up to
-you, but for purposes of this document, we'll install it into ``/tmp/py27``::
-
- % virtualenv --system-site-packages /tmp/py27
-
-If your default Python is not version 2.7, use the ``--python`` option to
+you, but for purposes of this document, we'll install it into ``/tmp/mm3``::
+
+ % virtualenv -p python3 --system-site-packages /tmp/mm3
+
+If your default Python is not version 3.4, use the ``--python`` option to
specify the Python executable. You can use the command name if this version
is on your ``PATH``::
- % virtualenv --system-site-packages --python=python2.7 /tmp/py27
+ % virtualenv --system-site-packages --python=python3.4 /tmp/mm3
-or you may specify the full path to any Python 2.7 executable.
+or you may specify the full path to any Python 3.4 executable.
Now, activate the virtual environment and set it up for development::
- % source /tmp/py27/bin/activate
+ % source /tmp/mm3/bin/activate
% python setup.py develop
Sit back and have some Kombucha while you wait for everything to download and
=== modified file 'src/mailman/docs/STYLEGUIDE.rst'
--- src/mailman/docs/STYLEGUIDE.rst 2014-01-01 14:59:42 +0000
+++ src/mailman/docs/STYLEGUIDE.rst 2015-01-03 05:08:01 +0000
@@ -15,33 +15,25 @@
This document contains a style guide for Python programming, as used in GNU
Mailman. `PEP 8`_ is the basis for this style guide so it's recommendations
should be followed except for the differences outlined here. This document
-assumes the use of Python 2.7, but not (yet) Python 3.
+assumes the use of Python 3.
-* After file comments (e.g. license block), add a ``__metaclass__`` definition
- so that all classes will be new-style. Following that, add an ``__all__``
- section that names, one-per-line, all the public names exported by this
- module. You should enable absolute imports and unicode literals. See the
+* After file comments (e.g. license block), add an ``__all__`` section that
+ names, one-per-line, all the public names exported by this module. See the
`GNU Mailman Python template`_ as an example.
* Imports are always put at the top of the file, just after any module
comments and docstrings, and before module globals and constants, but after
- any ``__future__`` imports, or ``__metaclass__`` and ``__all__``
- definitions.
+ any ``__all__`` definitions.
Imports should be grouped, with the order being:
- 1. non-from imports for standard and third party libraries
- 2. non-from imports from the application
- 3. from-imports from the standard and third party libraries
- 4. from-imports from the application
-
- From-imports should follow non-from imports. Dotted imports should follow
- non-dotted imports. Non-dotted imports should be grouped by increasing
- length, while dotted imports should be grouped alphabetically.
-
-* In general, there should be one class per module. Keep files small, but
- it's okay to group related code together. List everything exported from the
- module in the ``__all__``.
+ 1. non-from imports, grouped from shorted module name to longest module
+ name, with ties being broken by alphabetical order.
+ 3. from-imports grouped alphabetically.
+
+* In general, there should be one class per module. This is not a
+ hard-and-fast rule. Keep files small, but it's okay to group related code
+ together. List everything exported from the module in the ``__all__``.
* Right hanging comments are discouraged, in favor of preceding comments.
E.g. bad::
=== modified file 'src/mailman/docs/__init__.py'
--- src/mailman/docs/__init__.py 2014-04-28 15:23:35 +0000
+++ src/mailman/docs/__init__.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""General Mailman doc tests."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'layer',
]
=== modified file 'src/mailman/email/message.py'
--- src/mailman/email/message.py 2014-12-11 02:49:39 +0000
+++ src/mailman/email/message.py 2015-01-03 05:08:01 +0000
@@ -23,9 +23,6 @@
attributes.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Message',
'MultipartDigestMessage',
@@ -40,7 +37,6 @@
from email.header import Header
from email.mime.multipart import MIMEMultipart
-
from mailman.config import config
@@ -149,8 +145,8 @@
subject = ('(no subject)' if subject is None else subject)
if text is not None:
self.set_payload(text.encode(charset), charset)
- self['Subject'] = Header(subject.encode(charset), charset,
- header_name='Subject', errors='replace')
+ self['Subject'] = Header(
+ subject, charset, header_name='Subject', errors='replace')
self['From'] = sender
if isinstance(recipients, (list, set, tuple)):
self['To'] = COMMASPACE.join(recipients)
@@ -198,7 +194,7 @@
reduced_list_headers=True,
)
if mlist is not None:
- enqueue_kws['listname'] = mlist.fqdn_listname
+ enqueue_kws['listid'] = mlist.list_id
enqueue_kws.update(_kws)
virginq.enqueue(self, **enqueue_kws)
@@ -227,7 +223,7 @@
virginq = config.switchboards['virgin']
# The message metadata better have a `recip' attribute
virginq.enqueue(self,
- listname=mlist.fqdn_listname,
+ listid=mlist.list_id,
recipients=self.recipients,
nodecorate=True,
reduced_list_headers=True,
=== modified file 'src/mailman/email/tests/test_message.py'
--- src/mailman/email/tests/test_message.py 2014-12-09 01:38:26 +0000
+++ src/mailman/email/tests/test_message.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the message API."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestMessage',
'TestMessageSubclass',
@@ -27,8 +24,8 @@
import unittest
+
from email.parser import FeedParser
-
from mailman.app.lifecycle import create_list
from mailman.email.message import Message, UserNotification
from mailman.testing.helpers import get_queue_messages
@@ -66,7 +63,7 @@
class TestMessageSubclass(unittest.TestCase):
def test_i18n_filenames(self):
parser = FeedParser(_factory=Message)
- parser.feed(b"""\
+ parser.feed("""\
Message-ID:
Content-Type: multipart/mixed; boundary="------------050607040206050605060208"
@@ -88,6 +85,6 @@
attachment = msg.get_payload(1)
try:
filename = attachment.get_filename()
- except TypeError as e:
- self.fail(e)
+ except TypeError as error:
+ self.fail(error)
self.assertEqual(filename, u'd\xe9jeuner.txt')
=== modified file 'src/mailman/email/validate.py'
--- src/mailman/email/validate.py 2014-01-01 14:59:42 +0000
+++ src/mailman/email/validate.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Email address validation."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Validator',
]
@@ -27,11 +24,10 @@
import re
-from zope.interface import implementer
-
from mailman.interfaces.address import (
IEmailValidator, InvalidEmailAddressError)
from mailman.utilities.email import split_email
+from zope.interface import implementer
# What other characters should be disallowed?
=== modified file 'src/mailman/handlers/acknowledge.py'
--- src/mailman/handlers/acknowledge.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/acknowledge.py 2015-01-03 05:08:01 +0000
@@ -20,23 +20,19 @@
This only happens if the sender has set their AcknowledgePosts attribute.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Acknowledge',
]
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.email.message import UserNotification
from mailman.interfaces.handler import IHandler
from mailman.interfaces.languages import ILanguageManager
from mailman.utilities.i18n import make
from mailman.utilities.string import oneline
+from zope.component import getUtility
+from zope.interface import implementer
@@ -67,14 +63,13 @@
language = (language_manager[msgdata['lang']]
if 'lang' in msgdata
else member.preferred_language)
- charset = language_manager[language.code].charset
# Now get the acknowledgement template.
display_name = mlist.display_name
text = make('postack.txt',
mailing_list=mlist,
language=language.code,
wrap=False,
- subject=oneline(original_subject, charset),
+ subject=oneline(original_subject, in_unicode=True),
list_name=mlist.list_name,
display_name=display_name,
listinfo_url=mlist.script_url('listinfo'),
=== modified file 'src/mailman/handlers/after_delivery.py'
--- src/mailman/handlers/after_delivery.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/after_delivery.py 2015-01-03 05:08:01 +0000
@@ -17,19 +17,15 @@
"""Perform some bookkeeping after a successful post."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AfterDelivery',
]
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.utilities.datetime import now
+from zope.interface import implementer
=== modified file 'src/mailman/handlers/avoid_duplicates.py'
--- src/mailman/handlers/avoid_duplicates.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/avoid_duplicates.py 2015-01-03 05:08:01 +0000
@@ -23,19 +23,15 @@
warning header, or pass it through, depending on the user's preferences.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AvoidDuplicates',
]
from email.utils import getaddresses, formataddr
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
COMMASPACE = ', '
=== modified file 'src/mailman/handlers/cleanse.py'
--- src/mailman/handlers/cleanse.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/cleanse.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Cleanse certain headers from all messages."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Cleanse',
]
@@ -28,11 +25,10 @@
import logging
from email.utils import formataddr
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.handlers.cook_headers import uheader
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
log = logging.getLogger('mailman.post')
=== modified file 'src/mailman/handlers/cleanse_dkim.py'
--- src/mailman/handlers/cleanse_dkim.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/cleanse_dkim.py 2015-01-03 05:08:01 +0000
@@ -25,20 +25,16 @@
originating at the Mailman server for the outgoing message.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'CleanseDKIM',
]
from lazr.config import as_boolean
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
=== modified file 'src/mailman/handlers/cook_headers.py'
--- src/mailman/handlers/cook_headers.py 2014-12-09 11:25:45 +0000
+++ src/mailman/handlers/cook_headers.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Cook a message's headers."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'CookHeaders',
]
@@ -27,21 +24,18 @@
import re
-from email.errors import HeaderParseError
-from email.header import Header, decode_header, make_header
+from email.header import Header
from email.utils import parseaddr, formataddr, getaddresses
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.interfaces.mailinglist import Personalization, ReplyToMunging
from mailman.version import VERSION
+from zope.interface import implementer
COMMASPACE = ', '
MAXLINELEN = 78
-
-nonascii = re.compile('[^\s!-~]')
+NONASCII = re.compile('[^\s!-~]')
@@ -54,12 +48,12 @@
specified.
"""
charset = mlist.preferred_language.charset
- if nonascii.search(s):
+ if NONASCII.search(s):
# use list charset but ...
if charset == 'us-ascii':
charset = 'iso-8859-1'
else:
- # there is no nonascii so ...
+ # there is no non-ascii so ...
charset = 'us-ascii'
return Header(s, charset, maxlinelen, header_name, continuation_ws)
@@ -78,13 +72,6 @@
msgdata['original_sender'] = msg.sender
# VirginRunner sets _fasttrack for internally crafted messages.
fasttrack = msgdata.get('_fasttrack')
- if not msgdata.get('isdigest') and not fasttrack:
- try:
- prefix_subject(mlist, msg, msgdata)
- except (UnicodeError, ValueError):
- # TK: Sometimes subject header is not MIME encoded for 8bit
- # simply abort prefixing.
- pass
# Add Precedence: and other useful headers. None of these are standard
# and finding information on some of them are fairly difficult. Some are
# just common practice, and we'll add more here as they become necessary.
@@ -171,114 +158,6 @@
-def prefix_subject(mlist, msg, msgdata):
- """Maybe add a subject prefix.
-
- Add the subject prefix unless the message is a digest or is being fast
- tracked (e.g. internally crafted, delivered to a single user such as the
- list admin).
- """
- if not mlist.subject_prefix.strip():
- return
- prefix = mlist.subject_prefix
- subject = msg.get('subject', '')
- # Try to figure out what the continuation_ws is for the header
- if isinstance(subject, Header):
- lines = str(subject).splitlines()
- else:
- lines = subject.splitlines()
- ws = '\t'
- if len(lines) > 1 and lines[1] and lines[1][0] in ' \t':
- ws = lines[1][0]
- msgdata['original_subject'] = subject
- # The subject may be multilingual but we take the first charset as major
- # one and try to decode. If it is decodable, returned subject is in one
- # line and cset is properly set. If fail, subject is mime-encoded and
- # cset is set as us-ascii. See detail for ch_oneline() (CookHeaders one
- # line function).
- subject, cset = ch_oneline(subject)
- # TK: Python interpreter has evolved to be strict on ascii charset code
- # range. It is safe to use unicode string when manupilating header
- # contents with re module. It would be best to return unicode in
- # ch_oneline() but here is temporary solution.
- subject = unicode(subject, cset)
- # If the subject_prefix contains '%d', it is replaced with the
- # mailing list sequential number. Sequential number format allows
- # '%d' or '%05d' like pattern.
- prefix_pattern = re.escape(prefix)
- # unescape '%' :-<
- prefix_pattern = '%'.join(prefix_pattern.split(r'\%'))
- p = re.compile('%\d*d')
- if p.search(prefix, 1):
- # prefix have number, so we should search prefix w/number in subject.
- # Also, force new style.
- prefix_pattern = p.sub(r'\s*\d+\s*', prefix_pattern)
- subject = re.sub(prefix_pattern, '', subject)
- rematch = re.match('((RE|AW|SV|VS)(\[\d+\])?:\s*)+', subject, re.I)
- if rematch:
- subject = subject[rematch.end():]
- recolon = 'Re:'
- else:
- recolon = ''
- # At this point, subject may become null if someone post mail with
- # subject: [subject prefix]
- if subject.strip() == '':
- subject = _('(no subject)')
- cset = mlist.preferred_language.charset
- # and substitute %d in prefix with post_id
- try:
- prefix = prefix % mlist.post_id
- except TypeError:
- pass
- # Get the header as a Header instance, with proper unicode conversion
- if not recolon:
- h = uheader(mlist, prefix, 'Subject', continuation_ws=ws)
- else:
- h = uheader(mlist, prefix, 'Subject', continuation_ws=ws)
- h.append(recolon)
- # TK: Subject is concatenated and unicode string.
- subject = subject.encode(cset, 'replace')
- h.append(subject, cset)
- del msg['subject']
- msg['Subject'] = h
- ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws)
- ss.append(subject, cset)
- msgdata['stripped_subject'] = ss
-
-
-
-def ch_oneline(headerstr):
- # Decode header string in one line and convert into single charset.
- # Return (string, cset) tuple as check for failure.
- try:
- d = decode_header(headerstr)
- # At this point, we should rstrip() every string because some
- # MUA deliberately add trailing spaces when composing return
- # message.
- d = [(s.rstrip(), c) for (s, c) in d]
- # Find all charsets in the original header. We use 'utf-8' rather
- # than using the first charset (in mailman 2.1.x) if multiple
- # charsets are used.
- csets = []
- for (s, c) in d:
- if c and c not in csets:
- csets.append(c)
- if len(csets) == 0:
- cset = 'us-ascii'
- elif len(csets) == 1:
- cset = csets[0]
- else:
- cset = 'utf-8'
- h = make_header(d)
- ustr = unicode(h)
- oneline = ''.join(ustr.splitlines())
- return oneline.encode(cset, 'replace'), cset
- except (LookupError, UnicodeError, ValueError, HeaderParseError):
- # possibly charset problem. return with undecoded string in one line.
- return ''.join(headerstr.splitlines()), 'us-ascii'
-
-
-
@implementer(IHandler)
class CookHeaders:
"""Modify message headers."""
=== modified file 'src/mailman/handlers/decorate.py'
--- src/mailman/handlers/decorate.py 2014-04-14 16:14:13 +0000
+++ src/mailman/handlers/decorate.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Decorate a message by sticking the header and footer around it."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Decorate',
'decorate',
@@ -31,15 +28,14 @@
import logging
from email.mime.text import MIMEText
-from urllib2 import URLError
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.email.message import Message
from mailman.interfaces.handler import IHandler
from mailman.interfaces.templates import ITemplateLoader
from mailman.utilities.string import expand
+from six.moves.urllib_error import URLError
+from zope.component import getUtility
+from zope.interface import implementer
log = logging.getLogger('mailman.error')
=== modified file 'src/mailman/handlers/docs/acknowledge.rst'
--- src/mailman/handlers/docs/acknowledge.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/acknowledge.rst 2015-01-03 05:08:01 +0000
@@ -113,9 +113,9 @@
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
- listname : test@example.com
+ listid : test.example.com
nodecorate : True
- recipients : set([u'aperson@example.com'])
+ recipients : {'aperson@example.com'}
reduced_list_headers: True
...
>>> print(messages[0].msg.as_string())
@@ -150,9 +150,9 @@
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
- listname : test@example.com
+ listid : test.example.com
nodecorate : True
- recipients : set([u'aperson@example.com'])
+ recipients : {'aperson@example.com'}
reduced_list_headers: True
...
>>> print(messages[0].msg.as_string())
=== modified file 'src/mailman/handlers/docs/avoid-duplicates.rst'
--- src/mailman/handlers/docs/avoid-duplicates.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/avoid-duplicates.rst 2015-01-03 05:08:01 +0000
@@ -71,7 +71,7 @@
>>> msgdata = recips.copy()
>>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recipients'])
- [u'aperson@example.com', u'bperson@example.com']
+ ['aperson@example.com', 'bperson@example.com']
>>> print(msg.as_string())
From: Claire Person
@@ -89,7 +89,7 @@
>>> msgdata = recips.copy()
>>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recipients'])
- [u'bperson@example.com']
+ ['bperson@example.com']
>>> print(msg.as_string())
From: Claire Person
CC: aperson@example.com
@@ -109,7 +109,7 @@
>>> msgdata = recips.copy()
>>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recipients'])
- [u'aperson@example.com', u'bperson@example.com']
+ ['aperson@example.com', 'bperson@example.com']
>>> print(msg.as_string())
From: Claire Person
CC: bperson@example.com
@@ -128,7 +128,7 @@
>>> msgdata = recips.copy()
>>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recipients'])
- [u'bperson@example.com']
+ ['bperson@example.com']
>>> print(msg.as_string())
From: Claire Person
To: aperson@example.com
@@ -147,7 +147,7 @@
>>> msgdata = recips.copy()
>>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recipients'])
- [u'bperson@example.com']
+ ['bperson@example.com']
>>> print(msg.as_string())
From: Claire Person
Resent-To: aperson@example.com
@@ -166,7 +166,7 @@
>>> msgdata = recips.copy()
>>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recipients'])
- [u'bperson@example.com']
+ ['bperson@example.com']
>>> print(msg.as_string())
From: Claire Person
Resent-Cc: aperson@example.com
=== modified file 'src/mailman/handlers/docs/digests.rst'
--- src/mailman/handlers/docs/digests.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/digests.rst 2015-01-03 05:08:01 +0000
@@ -82,11 +82,13 @@
>>> mlist.digest_size_threshold = 1
>>> mlist.volume = 2
>>> mlist.next_digest_number = 10
+ >>> digest_path = os.path.join(mlist.data_path, 'digest.mmdf')
>>> size = 0
>>> for msg in message_factory:
... process(mlist, msg, {})
- ... size += len(str(msg))
- ... if size >= mlist.digest_size_threshold * 1024:
+ ... # When the digest reaches the proper size, it is renamed. So we
+ ... # can break out of this list when the file disappears.
+ ... if not os.path.exists(digest_path):
... break
>>> sum(1 for msg in digest_mbox(mlist))
=== modified file 'src/mailman/handlers/docs/file-recips.rst'
--- src/mailman/handlers/docs/file-recips.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/file-recips.rst 2015-01-03 05:08:01 +0000
@@ -34,26 +34,6 @@
recipients: 7
-Missing file
-============
-
-The include file must live inside the list's data directory, under the name
-``members.txt``. If the file doesn't exist, the list of recipients will be
-empty.
-
- >>> import os
- >>> file_path = os.path.join(mlist.data_path, 'members.txt')
- >>> open(file_path)
- Traceback (most recent call last):
- ...
- IOError: [Errno ...]
- No such file or directory: u'.../_xtest@example.com/members.txt'
- >>> msgdata = {}
- >>> handler.process(mlist, msg, msgdata)
- >>> dump_list(msgdata['recipients'])
- *Empty*
-
-
Existing file
=============
@@ -61,16 +41,15 @@
addresses are returned as the set of recipients.
::
- >>> fp = open(file_path, 'w')
- >>> try:
+ >>> import os
+ >>> file_path = os.path.join(mlist.data_path, 'members.txt')
+ >>> with open(file_path, 'w', encoding='utf-8') as fp:
... print('bperson@example.com', file=fp)
... print('cperson@example.com', file=fp)
... print('dperson@example.com', file=fp)
... print('eperson@example.com', file=fp)
... print('fperson@example.com', file=fp)
... print('gperson@example.com', file=fp)
- ... finally:
- ... fp.close()
>>> msgdata = {}
>>> handler.process(mlist, msg, msgdata)
=== modified file 'src/mailman/handlers/docs/filtering.rst'
--- src/mailman/handlers/docs/filtering.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/filtering.rst 2015-01-03 05:08:01 +0000
@@ -26,6 +26,8 @@
A simple filtering setting will just search the content types of the messages
parts, discarding all parts with a matching MIME type. If the message's outer
content type matches the filter, the entire message will be discarded.
+However, if we turn off content filtering altogether, then the handler
+short-circuits.
::
>>> from mailman.interfaces.mime import FilterAction
@@ -42,14 +44,6 @@
... """)
>>> process = config.handlers['mime-delete'].process
- >>> process(mlist, msg, {})
- Traceback (most recent call last):
- ...
- DiscardMessage: The message's content type was explicitly disallowed
-
-However, if we turn off content filtering altogether, then the handler
-short-circuits.
-
>>> mlist.filter_content = False
>>> msgdata = {}
>>> process(mlist, msg, msgdata)
@@ -74,15 +68,15 @@
MIME-Version: 1.0
xxxxx
- >>> msgdata
- {u'isdigest': True}
+ >>> dump_msgdata(msgdata)
+ isdigest: True
Simple multipart filtering
==========================
-If one of the subparts in a multipart message matches the filter type, then
-just that subpart will be stripped.
+If one of the subparts in a ``multipart`` message matches the filter type,
+then just that subpart will be stripped.
::
>>> msg = message_from_string("""\
@@ -241,8 +235,8 @@
>>> try:
... print("""\
... import sys
- ... print 'Converted text/html to text/plain'
- ... print 'Filename:', sys.argv[1]
+ ... print('Converted text/html to text/plain')
+ ... print('Filename:', sys.argv[1])
... """, file=fp)
... finally:
... fp.close()
=== modified file 'src/mailman/handlers/docs/nntp.rst'
--- src/mailman/handlers/docs/nntp.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/nntp.rst 2015-01-03 05:08:01 +0000
@@ -63,5 +63,5 @@
>>> dump_msgdata(messages[0].msgdata)
_parsemsg: False
- listname : test@example.com
+ listid : test.example.com
version : 3
=== modified file 'src/mailman/handlers/docs/replybot.rst'
--- src/mailman/handlers/docs/replybot.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/replybot.rst 2015-01-03 05:08:01 +0000
@@ -49,9 +49,9 @@
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
- listname : _xtest@example.com
+ listid : _xtest.example.com
nodecorate : True
- recipients : set([u'aperson@example.com'])
+ recipients : {'aperson@example.com'}
reduced_list_headers: True
version : 3
@@ -141,9 +141,9 @@
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
- listname : _xtest@example.com
+ listid : _xtest.example.com
nodecorate : True
- recipients : set([u'asystem@example.com'])
+ recipients : {'asystem@example.com'}
reduced_list_headers: True
version : 3
=== modified file 'src/mailman/handlers/docs/rfc-2369.rst'
--- src/mailman/handlers/docs/rfc-2369.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/rfc-2369.rst 2015-01-03 05:08:01 +0000
@@ -13,7 +13,7 @@
..
This is a helper function for the following section.
>>> def list_headers(msg, only=None):
- ... if isinstance(only, basestring):
+ ... if isinstance(only, str):
... only = (only.lower(),)
... elif only is None:
... only = set(header.lower() for header in msg.keys()
=== modified file 'src/mailman/handlers/docs/subject-munging.rst'
--- src/mailman/handlers/docs/subject-munging.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/subject-munging.rst 2015-01-03 05:08:01 +0000
@@ -1,44 +1,42 @@
-===============
-Subject munging
-===============
+================
+Subject prefixes
+================
-Messages that flow through the global pipeline get their headers *cooked*,
-which basically means that their headers go through several mostly unrelated
-transformations. Some headers get added, others get changed. Some of these
-changes depend on mailing list settings and others depend on how the message
-is getting sent through the system. We'll take things one-by-one.
+Mailing lists can define a *subject prefix* which gets added to the front of
+any ``Subject`` text. This can be used to quickly identify which mailing list
+the message was posted to.
>>> mlist = create_list('test@example.com')
+The default list style gives the mailing list a default prefix.
+
+ >>> print(mlist.subject_prefix)
+ [Test]
+
+This can be changed to anything, but typically ends with a trailing space.
+
+ >>> mlist.subject_prefix = '[XTest] '
+ >>> process = config.handlers['subject-prefix'].process
+
+
+No Subject
+==========
+
+If the original message has no ``Subject``, then a canned one is used.
+
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ...
+ ... A message of great import.
+ ... """)
+ >>> process(mlist, msg, {})
+ >>> print(msg['subject'])
+ [XTest] (no subject)
+
Inserting a prefix
==================
-Another thing header cooking does is *munge* the ``Subject`` header by
-inserting the subject prefix for the list at the front. If there's no subject
-header in the original message, Mailman uses a canned default. In order to do
-subject munging, a mailing list must have a preferred language.
-::
-
- >>> mlist.subject_prefix = '[XTest] '
- >>> mlist.preferred_language = 'en'
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... A message of great import.
- ... """)
- >>> msgdata = {}
-
- >>> from mailman.handlers.cook_headers import process
- >>> process(mlist, msg, msgdata)
-
-The original subject header is stored in the message metadata.
-
- >>> msgdata['original_subject']
- u''
- >>> print(msg['subject'])
- [XTest] (no subject)
-
If the original message had a ``Subject`` header, then the prefix is inserted
at the beginning of the header's value.
@@ -50,35 +48,13 @@
... """)
>>> msgdata = {}
>>> process(mlist, msg, msgdata)
+ >>> print(msg['subject'])
+ [XTest] Something important
+
+The original ``Subject`` is available in the metadata.
+
>>> print(msgdata['original_subject'])
Something important
- >>> print(msg['subject'])
- [XTest] Something important
-
-``Subject`` headers are not munged for digest messages.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: Something important
- ...
- ... A message of great import.
- ... """)
- >>> process(mlist, msg, dict(isdigest=True))
- >>> print(msg['subject'])
- Something important
-
-Nor are they munged for *fast tracked* messages, which are generally defined
-as messages that Mailman crafts internally.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: Something important
- ...
- ... A message of great import.
- ... """)
- >>> process(mlist, msg, dict(_fasttrack=True))
- >>> print(msg['subject'])
- Something important
If a ``Subject`` header already has a prefix, usually following a ``Re:``
marker, another one will not be added but the prefix will be moved to the
@@ -95,8 +71,7 @@
[XTest] Re: Something important
If the ``Subject`` header has a prefix at the front of the header text, that's
-where it will stay. This is called *new style* prefixing and is the only
-option available in Mailman 3.
+where it will stay.
>>> msg = message_from_string("""\
... From: aperson@example.com
@@ -122,10 +97,10 @@
...
... """)
>>> process(mlist, msg, {})
- >>> print(msg['subject'])
+ >>> print(msg['subject'].encode())
[XTest] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?=
- >>> unicode(msg['subject'])
- u'[XTest] \u30e1\u30fc\u30eb\u30de\u30f3'
+ >>> print(str(msg['subject']))
+ [XTest] メールマン
Prefix numbers
@@ -178,10 +153,10 @@
...
... """)
>>> process(mlist, msg, {})
- >>> print(msg['subject'])
+ >>> print(msg['subject'].encode())
[XTest 456] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?=
- >>> unicode(msg['subject'])
- u'[XTest 456] \u30e1\u30fc\u30eb\u30de\u30f3'
+ >>> print(msg['subject'])
+ [XTest 456] メールマン
Even more fun is when the internationalized ``Subject`` header already has a
prefix, possibly with a different posting number.
@@ -191,13 +166,10 @@
...
... """)
>>> process(mlist, msg, {})
- >>> print(msg['subject'])
+ >>> print(msg['subject'].encode())
[XTest 456] Re: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?=
-
-..
- # XXX This requires Python email patch #1681333 to succeed.
- # >>> unicode(msg['subject'])
- # u'[XTest 456] Re: \u30e1\u30fc\u30eb\u30de\u30f3'
+ >>> print(msg['subject'])
+ [XTest 456] Re: メールマン
As before, old style subject prefixes are re-ordered.
@@ -206,14 +178,11 @@
...
... """)
>>> process(mlist, msg, {})
- >>> print(msg['subject'])
+ >>> print(msg['subject'].encode())
[XTest 456] Re:
=?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?=
-
-..
- # XXX This requires Python email patch #1681333 to succeed.
- # >>> unicode(msg['subject'])
- # u'[XTest 456] Re: \u30e1\u30fc\u30eb\u30de\u30f3'
+ >>> print(msg['subject'])
+ [XTest 456] Re: メールマン
In this test case, we get an extra space between the prefix and the original
=== modified file 'src/mailman/handlers/docs/tagger.rst'
--- src/mailman/handlers/docs/tagger.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/tagger.rst 2015-01-03 05:08:01 +0000
@@ -55,7 +55,7 @@
>>> msgdata['topichits']
- [u'bar fight']
+ ['bar fight']
Scanning body lines
@@ -114,7 +114,7 @@
Keywords: barbaz
>>> msgdata['topichits']
- [u'bar fight']
+ ['bar fight']
However, scanning stops at the first body line that doesn't look like a
header.
@@ -161,7 +161,7 @@
>>> print(msg['x-topics'])
bar fight
>>> msgdata['topichits']
- [u'bar fight']
+ ['bar fight']
Scanning sub-parts
@@ -175,14 +175,14 @@
... Subject: Was
... Keywords: Raw
... Content-Type: multipart/alternative; boundary="BOUNDARY"
- ...
+ ...
... --BOUNDARY
... From: sabo
... To: obas
- ...
+ ...
... Subject: farbaw
... Keywords: barbaz
- ...
+ ...
... --BOUNDARY--
... """)
>>> msgdata = {}
@@ -203,7 +203,7 @@
--BOUNDARY--
>>> msgdata['topichits']
- [u'bar fight']
+ ['bar fight']
But the tagger will not descend into non-text parts.
@@ -211,23 +211,23 @@
... Subject: Was
... Keywords: Raw
... Content-Type: multipart/alternative; boundary=BOUNDARY
- ...
- ... --BOUNDARY
- ... From: sabo
- ... To: obas
- ... Content-Type: message/rfc822
- ...
- ... Subject: farbaw
- ... Keywords: barbaz
- ...
- ... --BOUNDARY
- ... From: sabo
- ... To: obas
- ... Content-Type: message/rfc822
- ...
- ... Subject: farbaw
- ... Keywords: barbaz
- ...
+ ...
+ ... --BOUNDARY
+ ... From: sabo
+ ... To: obas
+ ... Content-Type: message/rfc822
+ ...
+ ... Subject: farbaw
+ ... Keywords: barbaz
+ ...
+ ... --BOUNDARY
+ ... From: sabo
+ ... To: obas
+ ... Content-Type: message/rfc822
+ ...
+ ... Subject: farbaw
+ ... Keywords: barbaz
+ ...
... --BOUNDARY--
... """)
>>> msgdata = {}
=== modified file 'src/mailman/handlers/docs/to-outgoing.rst'
--- src/mailman/handlers/docs/to-outgoing.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/handlers/docs/to-outgoing.rst 2015-01-03 05:08:01 +0000
@@ -37,6 +37,6 @@
_parsemsg: False
bar : 2
foo : 1
- listname : test@example.com
+ listid : test.example.com
verp : True
version : 3
=== modified file 'src/mailman/handlers/file_recipients.py'
--- src/mailman/handlers/file_recipients.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/file_recipients.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Get the normal delivery recipients from a Sendmail style :include: file."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'FileRecipients',
]
@@ -28,10 +25,9 @@
import os
import errno
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
=== modified file 'src/mailman/handlers/member_recipients.py'
--- src/mailman/handlers/member_recipients.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/member_recipients.py 2015-01-03 05:08:01 +0000
@@ -23,22 +23,18 @@
SendmailDeliver and BulkDeliver modules.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'MemberRecipients',
]
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core import errors
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.interfaces.member import DeliveryStatus
from mailman.utilities.string import wrap
+from zope.interface import implementer
=== modified file 'src/mailman/handlers/mime_delete.py'
--- src/mailman/handlers/mime_delete.py 2014-11-20 01:29:44 +0000
+++ src/mailman/handlers/mime_delete.py 2015-01-03 05:08:01 +0000
@@ -24,9 +24,6 @@
contents.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'MIMEDelete',
]
@@ -41,9 +38,6 @@
from email.mime.message import MIMEMessage
from email.mime.text import MIMEText
from lazr.config import as_boolean
-from os.path import splitext
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core import errors
from mailman.core.i18n import _
@@ -52,6 +46,8 @@
from mailman.interfaces.handler import IHandler
from mailman.utilities.string import oneline
from mailman.version import VERSION
+from os.path import splitext
+from zope.interface import implementer
log = logging.getLogger('mailman.error')
@@ -245,7 +241,7 @@
filename = tempfile.mktemp('.html')
fp = open(filename, 'w')
try:
- fp.write(subpart.get_payload(decode=True))
+ fp.write(subpart.get_payload())
fp.close()
cmd = os.popen(config.HTML_TO_PLAIN_TEXT_COMMAND %
{'filename': filename})
=== modified file 'src/mailman/handlers/owner_recipients.py'
--- src/mailman/handlers/owner_recipients.py 2014-11-29 20:10:59 +0000
+++ src/mailman/handlers/owner_recipients.py 2015-01-03 05:08:01 +0000
@@ -17,20 +17,16 @@
"""Calculate the list owner recipients (includes moderators)."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'OwnerRecipients',
]
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.interfaces.member import DeliveryStatus
+from zope.interface import implementer
=== modified file 'src/mailman/handlers/replybot.py'
--- src/mailman/handlers/replybot.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/replybot.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Handler for automatic responses."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Replybot',
]
@@ -27,9 +24,6 @@
import logging
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.email.message import UserNotification
from mailman.interfaces.autorespond import (
@@ -38,6 +32,8 @@
from mailman.interfaces.usermanager import IUserManager
from mailman.utilities.datetime import today
from mailman.utilities.string import expand, wrap
+from zope.component import getUtility
+from zope.interface import implementer
log = logging.getLogger('mailman.error')
=== modified file 'src/mailman/handlers/rfc_2369.py'
--- src/mailman/handlers/rfc_2369.py 2014-11-08 15:14:00 +0000
+++ src/mailman/handlers/rfc_2369.py 2015-01-03 05:08:01 +0000
@@ -17,22 +17,18 @@
"""RFC 2369 List-* and related headers."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'RFC2369',
]
from email.utils import formataddr
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.handlers.cook_headers import uheader
from mailman.interfaces.archiver import ArchivePolicy
from mailman.interfaces.mailinglist import IListArchiverSet
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
CONTINUATION = ',\n\t'
=== added file 'src/mailman/handlers/subject_prefix.py'
--- src/mailman/handlers/subject_prefix.py 1970-01-01 00:00:00 +0000
+++ src/mailman/handlers/subject_prefix.py 2015-01-03 05:08:01 +0000
@@ -0,0 +1,184 @@
+# Copyright (C) 2014 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see .
+
+"""Subject header prefix munging."""
+
+__all__ = [
+ 'SubjectPrefix',
+ ]
+
+
+import re
+
+from email.header import Header, make_header, decode_header
+from mailman.core.i18n import _
+from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
+
+
+RE_PATTERN = '((RE|AW|SV|VS)(\[\d+\])?:\s*)+'
+ASCII_CHARSETS = (None, 'ascii', 'us-ascii')
+EMPTYSTRING = ''
+
+
+
+def ascii_header(mlist, msgdata, subject, prefix, prefix_pattern, ws):
+ if mlist.preferred_language.charset not in ASCII_CHARSETS:
+ return None
+ for chunk, charset in decode_header(subject.encode()):
+ if charset not in ASCII_CHARSETS:
+ return None
+ subject_text = EMPTYSTRING.join(str(subject).splitlines())
+ rematch = re.match(RE_PATTERN, subject_text, re.I)
+ if rematch:
+ subject_text = subject_text[rematch.end():]
+ recolon = 'Re: '
+ else:
+ recolon = ''
+ # At this point, the subject may become null if someone posted mail
+ # with "Subject: [subject prefix]".
+ if subject_text.strip() == '':
+ with _.using(mlist.preferred_language.code):
+ subject_text = _('(no subject)')
+ else:
+ subject_text = re.sub(prefix_pattern, '', subject_text)
+ msgdata['stripped_subject'] = subject_text
+ lines = subject_text.splitlines()
+ first_line = [lines[0]]
+ if recolon:
+ first_line.insert(0, recolon)
+ if prefix:
+ first_line.insert(0, prefix)
+ subject_text = EMPTYSTRING.join(first_line)
+ return Header(subject_text, continuation_ws=ws)
+
+
+def all_same_charset(mlist, msgdata, subject, prefix, prefix_pattern, ws):
+ list_charset = mlist.preferred_language.charset
+ chunks = []
+ for chunk, charset in decode_header(subject.encode()):
+ if charset is None:
+ charset = 'us-ascii'
+ chunks.append(chunk.decode(charset))
+ if charset != list_charset:
+ return None
+ subject_text = EMPTYSTRING.join(chunks)
+ rematch = re.match(RE_PATTERN, subject_text, re.I)
+ if rematch:
+ subject_text = subject_text[rematch.end():]
+ recolon = 'Re: '
+ else:
+ recolon = ''
+ # At this point, the subject may become null if someone posted mail
+ # with "Subject: [subject prefix]".
+ if subject_text.strip() == '':
+ with _.push(mlist.preferred_language.code):
+ subject_text = _('(no subject)')
+ else:
+ subject_text = re.sub(prefix_pattern, '', subject_text)
+ msgdata['stripped_subject'] = subject_text
+ lines = subject_text.splitlines()
+ first_line = [lines[0]]
+ if recolon:
+ first_line.insert(0, recolon)
+ if prefix:
+ first_line.insert(0, prefix)
+ subject_text = EMPTYSTRING.join(first_line)
+ return Header(subject_text, charset=list_charset, continuation_ws=ws)
+
+
+def mixed_charsets(mlist, msgdata, subject, prefix, prefix_pattern, ws):
+ list_charset = mlist.preferred_language.charset
+ chunks = decode_header(subject.encode())
+ if len(chunks) == 0:
+ with _.push(mlist.preferred_language.code):
+ subject_text = _('(no subject)')
+ chunks = [(prefix, list_charset),
+ (subject_text, list_charset),
+ ]
+ return make_header(chunks, continuation_ws=ws)
+ # Only search the first chunk for Re and existing prefix.
+ chunk_text, chunk_charset = chunks[0]
+ if chunk_charset is None:
+ chunk_charset = 'us-ascii'
+ first_text = chunk_text.decode(chunk_charset)
+ first_text = re.sub(prefix_pattern, '', first_text).lstrip()
+ rematch = re.match(RE_PATTERN, first_text, re.I)
+ if rematch:
+ first_text = 'Re: ' + first_text[rematch.end():]
+ chunks[0] = (first_text, chunk_charset)
+ # The subject text stripped of the prefix, for use in the NNTP gateway.
+ msgdata['stripped_subject'] = str(make_header(chunks, continuation_ws=ws))
+ chunks.insert(0, (prefix, list_charset))
+ return make_header(chunks, continuation_ws=ws)
+
+
+
+@implementer(IHandler)
+class SubjectPrefix:
+ """Add a list-specific prefix to the Subject header value."""
+
+ name = 'subject-prefix'
+ description = _('Add a list-specific prefix to the Subject header value.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ if msgdata.get('isdigest') or msgdata.get('_fasttrack'):
+ return
+ prefix = mlist.subject_prefix
+ if not prefix.strip():
+ return
+ subject = msg.get('subject', '')
+ # Turn the value into a Header instance and try to figure out what
+ # continuation whitespace is being used.
+ # Save the original Subject.
+ msgdata['original_subject'] = subject
+ if isinstance(subject, Header):
+ subject_text = str(subject)
+ else:
+ subject = make_header(decode_header(subject))
+ subject_text = str(subject)
+ lines = subject_text.splitlines()
+ ws = '\t'
+ if len(lines) > 1 and lines[1] and lines[1][0] in ' \t':
+ ws = lines[1][0]
+ # If the subject_prefix contains '%d', it is replaced with the mailing
+ # list's sequence number. The sequential number format allows '%d' or
+ # '%05d' like pattern.
+ prefix_pattern = re.escape(prefix)
+ # Unescape '%'.
+ prefix_pattern = '%'.join(prefix_pattern.split(r'\%'))
+ p = re.compile('%\d*d')
+ if p.search(prefix, 1):
+ # The prefix has number, so we should search prefix w/number in
+ # subject. Also, force new style.
+ prefix_pattern = p.sub(r'\s*\d+\s*', prefix_pattern)
+ # Substitute %d in prefix with post_id
+ try:
+ prefix = prefix % mlist.post_id
+ except TypeError:
+ pass
+ for handler in (ascii_header,
+ all_same_charset,
+ mixed_charsets,
+ ):
+ new_subject = handler(
+ mlist, msgdata, subject, prefix, prefix_pattern, ws)
+ if new_subject is not None:
+ del msg['subject']
+ msg['Subject'] = new_subject
+ return
=== modified file 'src/mailman/handlers/tagger.py'
--- src/mailman/handlers/tagger.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/tagger.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Extract topics from the original mail message."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Tagger',
]
@@ -29,15 +26,14 @@
import email.iterators
import email.parser
-from zope.interface import implementer
-
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
OR = '|'
CRNL = '\r\n'
-EMPTYBYTES = b''
+EMPTYSTRING = ''
NLTAB = '\n\t'
@@ -104,7 +100,7 @@
reader = list(email.iterators.body_line_iterator(msg))
while numlines is None or lineno < numlines:
try:
- line = bytes(reader.pop(0))
+ line = reader.pop(0)
except IndexError:
break
# Blank lines don't count
@@ -115,7 +111,7 @@
# Concatenate those body text lines with newlines, and then create a new
# message object from those lines.
p = _ForgivingParser()
- msg = p.parsestr(EMPTYBYTES.join(lines))
+ msg = p.parsestr(EMPTYSTRING.join(lines))
return msg.get_all('subject', []) + msg.get_all('keywords', [])
=== modified file 'src/mailman/handlers/tests/test_cook_headers.py'
--- src/mailman/handlers/tests/test_cook_headers.py 2014-12-11 02:49:39 +0000
+++ src/mailman/handlers/tests/test_cook_headers.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the cook_headers handler."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestCookHeaders',
]
@@ -50,6 +47,6 @@
for msg in messages:
try:
cook_headers.process(self._mlist, msg, {})
- except AttributeError as e:
+ except AttributeError as error:
# LP: #1130696 would raise an AttributeError on .sender
- self.fail(e)
+ self.fail(error)
=== added file 'src/mailman/handlers/tests/test_file_recips.py'
--- src/mailman/handlers/tests/test_file_recips.py 1970-01-01 00:00:00 +0000
+++ src/mailman/handlers/tests/test_file_recips.py 2015-01-03 05:08:01 +0000
@@ -0,0 +1,73 @@
+# Copyright (C) 2014 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see .
+
+"""Test file-recips handler."""
+
+__all__ = [
+ 'TestFileRecips',
+ ]
+
+
+import os
+import unittest
+
+from mailman.app.lifecycle import create_list
+from mailman.config import config
+from mailman.testing.helpers import specialized_message_from_string as mfs
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestFileRecips(unittest.TestCase):
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ self._handler = config.handlers['file-recipients'].process
+ self._msg = mfs("""\
+From: aperson@example.com
+
+A message.
+""")
+
+ def test_file_is_missing(self):
+ # It is not an error for the list's the members.txt file to be
+ # missing. The missing file is just ignored.
+ msgdata = {}
+ self._handler(self._mlist, self._msg, msgdata)
+ self.assertEqual(msgdata['recipients'], set())
+
+ def test_file_exists(self):
+ # Like above, but the file exists and contains recipients.
+ path = os.path.join(self._mlist.data_path, 'members.txt')
+ with open(path, 'w', encoding='utf-8') as fp:
+ print('bperson@example.com', file=fp)
+ print('cperson@example.com', file=fp)
+ print('dperson@example.com', file=fp)
+ print('eperson@example.com', file=fp)
+ print('fperson@example.com', file=fp)
+ print('gperson@example.com', file=fp)
+ msgdata = {}
+ self._handler(self._mlist, self._msg, msgdata)
+ self.assertEqual(msgdata['recipients'], set((
+ 'bperson@example.com',
+ 'cperson@example.com',
+ 'dperson@example.com',
+ 'eperson@example.com',
+ 'fperson@example.com',
+ 'gperson@example.com',
+ )))
=== added file 'src/mailman/handlers/tests/test_filter.py'
--- src/mailman/handlers/tests/test_filter.py 1970-01-01 00:00:00 +0000
+++ src/mailman/handlers/tests/test_filter.py 2015-01-03 05:08:01 +0000
@@ -0,0 +1,57 @@
+# Copyright (C) 2014 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see .
+
+"""Test the filter handler."""
+
+__all__ = [
+ 'TestFilters',
+ ]
+
+
+import unittest
+
+from mailman.app.lifecycle import create_list
+from mailman.config import config
+from mailman.core.errors import DiscardMessage
+from mailman.interfaces.mime import FilterAction
+from mailman.testing.helpers import specialized_message_from_string as mfs
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestFilters(unittest.TestCase):
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+
+ def test_discard_when_outer_type_matches(self):
+ # When the outer MIME type of the message matches a filter type, the
+ # entire message is discarded.
+ self._mlist.filter_content = True
+ self._mlist.filter_types = ['image/jpeg']
+ self._mlist.filter_action = FilterAction.discard
+ msg = mfs("""\
+From: aperson@example.com
+Content-Type: image/jpeg
+MIME-Version: 1.0
+
+xxxxx
+""")
+ self.assertRaises(DiscardMessage,
+ config.handlers['mime-delete'].process,
+ self._mlist, msg, {})
=== modified file 'src/mailman/handlers/tests/test_mimedel.py'
--- src/mailman/handlers/tests/test_mimedel.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/tests/test_mimedel.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the mime_delete handler."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestDispose',
]
@@ -27,8 +24,6 @@
import unittest
-from zope.component import getUtility
-
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.core import errors
@@ -40,6 +35,7 @@
LogFileMark, configuration, get_queue_messages,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
=== modified file 'src/mailman/handlers/tests/test_recipients.py'
--- src/mailman/handlers/tests/test_recipients.py 2014-11-29 20:10:59 +0000
+++ src/mailman/handlers/tests/test_recipients.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Testing various recipients stuff."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestMemberRecipients',
'TestOwnerRecipients',
@@ -28,13 +25,14 @@
import unittest
-from zope.component import getUtility
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.interfaces.member import DeliveryMode, DeliveryStatus, MemberRole
from mailman.interfaces.usermanager import IUserManager
-from mailman.testing.helpers import specialized_message_from_string as mfs
+from mailman.testing.helpers import (
+ configuration, specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
+from zope.component import getUtility
@@ -199,23 +197,14 @@
self._process(self._mlist, self._msg, msgdata)
self.assertEqual(msgdata['recipients'], set(('noreply@example.com',)))
- def test_site_admin_unicode(self):
- # Since the config file is read as bytes, the site_owner is also a
- # bytes and must be converted to unicode when used as a fallback.
+ @configuration('mailman', site_owner='siteadmin@example.com')
+ def test_no_owners_site_owner_fallback(self):
+ # The list has no owners or moderators, but there is a non-default
+ # site owner defined. That owner gets the message.
self._cris.unsubscribe()
self._dave.unsubscribe()
self.assertEqual(self._mlist.administrators.member_count, 0)
msgdata = {}
- # In order to properly mimic the testing environment, use
- # config.push()/config.pop() directly instead of using the
- # configuration() context manager.
- config.push('test_site_admin_unicode', b"""\
-[mailman]
-site_owner: siteadmin@example.com
-""")
- try:
- self._process(self._mlist, self._msg, msgdata)
- finally:
- config.pop('test_site_admin_unicode')
- self.assertEqual(len(msgdata['recipients']), 1)
- self.assertIsInstance(list(msgdata['recipients'])[0], unicode)
+ self._process(self._mlist, self._msg, msgdata)
+ self.assertEqual(msgdata['recipients'],
+ set(('siteadmin@example.com',)))
=== added file 'src/mailman/handlers/tests/test_subject_prefix.py'
--- src/mailman/handlers/tests/test_subject_prefix.py 1970-01-01 00:00:00 +0000
+++ src/mailman/handlers/tests/test_subject_prefix.py 2015-01-03 05:08:01 +0000
@@ -0,0 +1,129 @@
+# Copyright (C) 2014 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see .
+
+"""Test the Subject header prefix munging.."""
+
+__all__ = [
+ 'TestSubjectPrefix',
+ ]
+
+
+import unittest
+
+from mailman.app.lifecycle import create_list
+from mailman.config import config
+from mailman.email.message import Message
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestSubjectPrefix(unittest.TestCase):
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ self._process = config.handlers['subject-prefix'].process
+
+ def test_isdigest(self):
+ # If the message is destined for the digest, the Subject header does
+ # not get touched.
+ msg = Message()
+ msg['Subject'] = 'A test message'
+ self._process(self._mlist, msg, dict(isdigest=True))
+ self.assertEqual(str(msg['subject']), 'A test message')
+
+ def test_fasttrack(self):
+ # Messages internally crafted are 'fast tracked' and don't get their
+ # Subjects prefixed either.
+ msg = Message()
+ msg['Subject'] = 'A test message'
+ self._process(self._mlist, msg, dict(_fasttrack=True))
+ self.assertEqual(str(msg['subject']), 'A test message')
+
+ def test_whitespace_only_prefix(self):
+ # If the Subject prefix only contains whitespace, ignore it.
+ self._mlist.subject_prefix = ' '
+ msg = Message()
+ msg['Subject'] = 'A test message'
+ self._process(self._mlist, msg, dict(_fasttrack=True))
+ self.assertEqual(str(msg['subject']), 'A test message')
+
+ def test_save_original_subject(self):
+ # When the Subject gets prefixed, the original is saved in the message
+ # metadata.
+ msgdata = {}
+ msg = Message()
+ msg['Subject'] = 'A test message'
+ self._process(self._mlist, msg, msgdata)
+ self.assertEqual(msgdata['original_subject'], 'A test message')
+
+ def test_prefix(self):
+ # The Subject gets prefixed. The prefix gets automatically set by the
+ # list style when the list gets created.
+ msg = Message()
+ msg['Subject'] = 'A test message'
+ self._process(self._mlist, msg, {})
+ self.assertEqual(str(msg['subject']), '[Test] A test message')
+
+ def test_no_double_prefix(self):
+ # Don't add a prefix if the subject already contains one.
+ msg = Message()
+ msg['Subject'] = '[Test] A test message'
+ self._process(self._mlist, msg, {})
+ self.assertEqual(str(msg['subject']), '[Test] A test message')
+
+ def test_re_prefix(self):
+ # The subject has a Re: prefix. Make sure that gets preserved, but
+ # after the list prefix.
+ msg = Message()
+ msg['Subject'] = 'Re: [Test] A test message'
+ self._process(self._mlist, msg, {})
+ self.assertEqual(str(msg['subject']), '[Test] Re: A test message')
+
+ def test_multiline_subject(self):
+ # The subject appears on multiple lines.
+ msg = Message()
+ msg['Subject'] = '\n A test message'
+ self._process(self._mlist, msg, {})
+ self.assertEqual(str(msg['subject']), '[Test] A test message')
+
+ def test_i18n_prefix(self):
+ # The Subject header is encoded, but the prefix is still added.
+ msg = Message()
+ msg['Subject'] = '=?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?='
+ self._process(self._mlist, msg, {})
+ subject = msg['subject']
+ self.assertEqual(subject.encode(),
+ '[Test] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?=')
+ self.assertEqual(str(subject), '[Test] メールマン')
+
+ def test_i18n_subject_with_sequential_prefix_and_re(self):
+ # The mailing list defines a sequential prefix, and the original
+ # Subject has a prefix with a different sequence number, *and* it also
+ # contains a Re: prefix. Make sure the sequence gets updated and all
+ # the bits get put back together in the right order.
+ self._mlist.subject_prefix = '[Test %d]'
+ self._mlist.post_id = 456
+ msg = Message()
+ msg['Subject'] = \
+ '[Test 123] Re: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?='
+ self._process(self._mlist, msg, {})
+ subject = msg['subject']
+ self.assertEqual(
+ subject.encode(),
+ '[Test 456] Re: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?=')
+ self.assertEqual(str(subject), '[Test 456] Re: メールマン')
=== modified file 'src/mailman/handlers/tests/test_to_digest.py'
--- src/mailman/handlers/tests/test_to_digest.py 2014-11-29 22:21:50 +0000
+++ src/mailman/handlers/tests/test_to_digest.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Test the to_digest handler."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'TestToDigest',
]
=== modified file 'src/mailman/handlers/to_archive.py'
--- src/mailman/handlers/to_archive.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/to_archive.py 2015-01-03 05:08:01 +0000
@@ -17,20 +17,16 @@
"""Add the message to the archives."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ToArchive',
]
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.archiver import ArchivePolicy
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
=== modified file 'src/mailman/handlers/to_digest.py'
--- src/mailman/handlers/to_digest.py 2014-11-25 11:15:42 +0000
+++ src/mailman/handlers/to_digest.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Add the message to the list's current digest."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ToDigest',
]
@@ -27,8 +24,6 @@
import os
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.email.message import Message
@@ -36,6 +31,7 @@
from mailman.interfaces.handler import IHandler
from mailman.utilities.datetime import now as right_now
from mailman.utilities.mailbox import Mailbox
+from zope.interface import implementer
@@ -55,7 +51,7 @@
mailbox_path = os.path.join(mlist.data_path, 'digest.mmdf')
# Lock the mailbox and append the message.
with Mailbox(mailbox_path, create=True) as mbox:
- mbox.add(msg.as_string())
+ mbox.add(msg)
# Calculate the current size of the mailbox file. This will not tell
# us exactly how big the resulting MIME and rfc1153 digest will
# actually be, but it's the most easily available metric to decide
@@ -75,7 +71,7 @@
os.rename(mailbox_path, mailbox_dest)
config.switchboards['digest'].enqueue(
Message(),
- listname=mlist.fqdn_listname,
+ listid=mlist.list_id,
digest_path=mailbox_dest,
volume=volume,
digest_number=digest_number)
=== modified file 'src/mailman/handlers/to_outgoing.py'
--- src/mailman/handlers/to_outgoing.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/to_outgoing.py 2015-01-03 05:08:01 +0000
@@ -22,19 +22,15 @@
recipient should just be placed in the out queue directly.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ToOutgoing',
]
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
@@ -47,5 +43,4 @@
def process(self, mlist, msg, msgdata):
"""See `IHandler`."""
- config.switchboards['out'].enqueue(
- msg, msgdata, listname=mlist.fqdn_listname)
+ config.switchboards['out'].enqueue(msg, msgdata, listid=mlist.list_id)
=== modified file 'src/mailman/handlers/to_usenet.py'
--- src/mailman/handlers/to_usenet.py 2014-01-01 14:59:42 +0000
+++ src/mailman/handlers/to_usenet.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Move the message to the mail->news queue."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ToUsenet',
]
@@ -27,14 +24,13 @@
import logging
-from zope.interface import implementer
-
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
+from zope.interface import implementer
+
COMMASPACE = ', '
-
log = logging.getLogger('mailman.error')
@@ -65,5 +61,4 @@
COMMASPACE.join(error))
return
# Put the message in the news runner's queue.
- config.switchboards['nntp'].enqueue(
- msg, msgdata, listname=mlist.fqdn_listname)
+ config.switchboards['nntp'].enqueue(msg, msgdata, listid=mlist.list_id)
=== modified file 'src/mailman/interfaces/action.py'
--- src/mailman/interfaces/action.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/action.py 2015-01-03 05:08:01 +0000
@@ -17,7 +17,6 @@
"""Message actions."""
-__metaclass__ = type
__all__ = [
'Action',
'FilterAction',
=== modified file 'src/mailman/interfaces/address.py'
--- src/mailman/interfaces/address.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/address.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for email address related information."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AddressAlreadyLinkedError',
'AddressError',
@@ -33,10 +30,9 @@
]
+from mailman.interfaces.errors import MailmanError
from zope.interface import Interface, Attribute
-from mailman.interfaces.errors import MailmanError
-
class EmailError(MailmanError):
=== modified file 'src/mailman/interfaces/archiver.py'
--- src/mailman/interfaces/archiver.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/archiver.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for archiving schemes."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ArchivePolicy',
'ClobberDate',
=== modified file 'src/mailman/interfaces/autorespond.py'
--- src/mailman/interfaces/autorespond.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/autorespond.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Autoresponder."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ALWAYS_REPLY',
'IAutoResponseRecord',
@@ -33,6 +30,7 @@
from enum import Enum
from zope.interface import Interface, Attribute
+
ALWAYS_REPLY = timedelta()
=== modified file 'src/mailman/interfaces/bans.py'
--- src/mailman/interfaces/bans.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/bans.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Manager of email address bans."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IBan',
'IBanManager',
=== modified file 'src/mailman/interfaces/bounce.py'
--- src/mailman/interfaces/bounce.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/bounce.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface to bounce detection components."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'BounceContext',
'IBounceEvent',
=== modified file 'src/mailman/interfaces/chain.py'
--- src/mailman/interfaces/chain.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/chain.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interfaces describing the basics of chains and links."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AcceptEvent',
'AcceptOwnerEvent',
=== modified file 'src/mailman/interfaces/command.py'
--- src/mailman/interfaces/command.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/command.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interfaces defining email commands."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ContinueProcessing',
'ICLISubCommand',
=== modified file 'src/mailman/interfaces/configuration.py'
--- src/mailman/interfaces/configuration.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/configuration.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Configuration system interface."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ConfigurationUpdatedEvent',
'IConfiguration',
@@ -27,10 +24,9 @@
]
+from mailman.core.errors import MailmanError
from zope.interface import Interface
-from mailman.core.errors import MailmanError
-
class MissingConfigurationFileError(MailmanError):
=== modified file 'src/mailman/interfaces/database.py'
--- src/mailman/interfaces/database.py 2014-11-01 16:49:15 +0000
+++ src/mailman/interfaces/database.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interfaces for database interaction."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'DatabaseError',
'IDatabase',
=== modified file 'src/mailman/interfaces/digests.py'
--- src/mailman/interfaces/digests.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/digests.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""One last digest."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IOneLastDigest'
]
=== modified file 'src/mailman/interfaces/domain.py'
--- src/mailman/interfaces/domain.py 2014-11-01 16:49:15 +0000
+++ src/mailman/interfaces/domain.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface representing domains."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'BadDomainSpecificationError',
'DomainCreatedEvent',
=== modified file 'src/mailman/interfaces/errors.py'
--- src/mailman/interfaces/errors.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/errors.py 2015-01-03 05:08:01 +0000
@@ -22,9 +22,6 @@
interfaces.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'MailmanError',
]
=== modified file 'src/mailman/interfaces/handler.py'
--- src/mailman/interfaces/handler.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/handler.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface describing a pipeline handler."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IHandler',
]
=== modified file 'src/mailman/interfaces/languages.py'
--- src/mailman/interfaces/languages.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/languages.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interfaces for managing languages."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ILanguage',
'ILanguageManager',
=== modified file 'src/mailman/interfaces/listmanager.py'
--- src/mailman/interfaces/listmanager.py 2014-11-01 16:49:15 +0000
+++ src/mailman/interfaces/listmanager.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for list storage, deleting, and finding."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IListManager',
'ListAlreadyExistsError',
=== modified file 'src/mailman/interfaces/mailinglist.py'
--- src/mailman/interfaces/mailinglist.py 2014-04-14 16:14:13 +0000
+++ src/mailman/interfaces/mailinglist.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for a mailing list."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IAcceptableAlias',
'IAcceptableAliasSet',
@@ -32,10 +29,9 @@
from enum import Enum
+from mailman.interfaces.member import MemberRole
from zope.interface import Interface, Attribute
-from mailman.interfaces.member import MemberRole
-
class Personalization(Enum):
=== modified file 'src/mailman/interfaces/member.py'
--- src/mailman/interfaces/member.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/member.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface describing the basics of a member."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AlreadySubscribedError',
'DeliveryMode',
@@ -37,10 +34,9 @@
from enum import Enum
+from mailman.core.errors import MailmanError
from zope.interface import Interface, Attribute
-from mailman.core.errors import MailmanError
-
class DeliveryMode(Enum):
=== modified file 'src/mailman/interfaces/messages.py'
--- src/mailman/interfaces/messages.py 2014-09-22 21:38:00 +0000
+++ src/mailman/interfaces/messages.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""The message storage service."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IMessage',
'IMessageStore',
=== modified file 'src/mailman/interfaces/mime.py'
--- src/mailman/interfaces/mime.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/mime.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""MIME content filtering."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'FilterAction',
'FilterType',
=== modified file 'src/mailman/interfaces/mlistrequest.py'
--- src/mailman/interfaces/mlistrequest.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/mlistrequest.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for a web request accessing a mailing list."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IMailingListRequest',
]
=== modified file 'src/mailman/interfaces/mta.py'
--- src/mailman/interfaces/mta.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/mta.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for mail transport agent integration."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IMailTransportAgentAliases',
'IMailTransportAgentDelivery',
@@ -27,10 +24,9 @@
]
+from mailman.core.errors import MailmanError
from zope.interface import Interface
-from mailman.core.errors import MailmanError
-
class SomeRecipientsFailed(MailmanError):
=== modified file 'src/mailman/interfaces/nntp.py'
--- src/mailman/interfaces/nntp.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/nntp.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""NNTP and newsgroup interfaces."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'NewsgroupModeration',
]
=== modified file 'src/mailman/interfaces/pending.py'
--- src/mailman/interfaces/pending.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/pending.py 2015-01-03 05:08:01 +0000
@@ -22,9 +22,6 @@
confirmation.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IPendable',
'IPended',
=== modified file 'src/mailman/interfaces/permissions.py'
--- src/mailman/interfaces/permissions.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/permissions.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interfaces for various permissions."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IPostingPermission',
]
=== modified file 'src/mailman/interfaces/pipeline.py'
--- src/mailman/interfaces/pipeline.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/pipeline.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for describing pipelines."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IPipeline',
]
@@ -37,4 +34,3 @@
def __iter__():
"""Iterate over all the handlers in this pipeline."""
-
=== modified file 'src/mailman/interfaces/preferences.py'
--- src/mailman/interfaces/preferences.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/preferences.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for preferences."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IPreferences',
]
=== modified file 'src/mailman/interfaces/registrar.py'
--- src/mailman/interfaces/registrar.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/registrar.py 2015-01-03 05:08:01 +0000
@@ -22,9 +22,6 @@
or confirmation, while this interface does.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ConfirmationNeededEvent',
'IRegistrar',
=== modified file 'src/mailman/interfaces/requests.py'
--- src/mailman/interfaces/requests.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/requests.py 2015-01-03 05:08:01 +0000
@@ -21,9 +21,6 @@
moderators, such as subscription requests and held messages.
"""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IListRequests',
'RequestType',
=== modified file 'src/mailman/interfaces/roster.py'
--- src/mailman/interfaces/roster.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/roster.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for a roster of members."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IRoster',
]
=== modified file 'src/mailman/interfaces/rules.py'
--- src/mailman/interfaces/rules.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/rules.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface describing the basics of rules."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IRule',
]
=== modified file 'src/mailman/interfaces/runner.py'
--- src/mailman/interfaces/runner.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/runner.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for runners."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IRunner',
'RunnerCrashEvent',
=== modified file 'src/mailman/interfaces/styles.py'
--- src/mailman/interfaces/styles.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/styles.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interfaces for list styles."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'DuplicateStyleError',
'IStyle',
@@ -27,8 +24,8 @@
]
+from mailman.interfaces.errors import MailmanError
from zope.interface import Interface, Attribute
-from mailman.interfaces.errors import MailmanError
=== modified file 'src/mailman/interfaces/subscriptions.py'
--- src/mailman/interfaces/subscriptions.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/subscriptions.py 2015-01-03 05:08:01 +0000
@@ -17,18 +17,14 @@
"""Membership interface for REST."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ISubscriptionService',
]
-from zope.interface import Interface
-
from mailman.interfaces.errors import MailmanError
from mailman.interfaces.member import DeliveryMode, MemberRole
+from zope.interface import Interface
=== modified file 'src/mailman/interfaces/switchboard.py'
--- src/mailman/interfaces/switchboard.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/switchboard.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface for switchboards."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ISwitchboard',
]
=== modified file 'src/mailman/interfaces/system.py'
--- src/mailman/interfaces/system.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/system.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""System information."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ISystem',
]
=== modified file 'src/mailman/interfaces/templates.py'
--- src/mailman/interfaces/templates.py 2014-04-14 16:14:13 +0000
+++ src/mailman/interfaces/templates.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Template downloader with cache."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'ITemplateLoader',
]
=== modified file 'src/mailman/interfaces/user.py'
--- src/mailman/interfaces/user.py 2014-01-01 14:59:42 +0000
+++ src/mailman/interfaces/user.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface describing the basics of a user."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IUser',
'PasswordChangeEvent',
@@ -27,10 +24,9 @@
]
+from mailman.interfaces.address import AddressError
from zope.interface import Interface, Attribute
-from mailman.interfaces.address import AddressError
-
class UnverifiedAddressError(AddressError):
=== modified file 'src/mailman/interfaces/usermanager.py'
--- src/mailman/interfaces/usermanager.py 2014-04-28 15:23:35 +0000
+++ src/mailman/interfaces/usermanager.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Interface describing the user management service."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'IUserManager',
]
=== modified file 'src/mailman/languages/language.py'
--- src/mailman/languages/language.py 2014-01-01 14:59:42 +0000
+++ src/mailman/languages/language.py 2015-01-03 05:08:01 +0000
@@ -18,18 +18,14 @@
"""The representation of a language."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Language',
]
+from mailman.interfaces.languages import ILanguage
from zope.interface import implementer
-from mailman.interfaces.languages import ILanguage
-
@implementer(ILanguage)
=== modified file 'src/mailman/languages/manager.py'
--- src/mailman/languages/manager.py 2014-01-01 14:59:42 +0000
+++ src/mailman/languages/manager.py 2015-01-03 05:08:01 +0000
@@ -17,20 +17,16 @@
"""Language manager."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'LanguageManager',
]
-from zope.component import getUtility
-from zope.interface import implementer
-
from mailman.interfaces.configuration import ConfigurationUpdatedEvent
from mailman.interfaces.languages import ILanguageManager
from mailman.languages.language import Language
+from zope.component import getUtility
+from zope.interface import implementer
=== modified file 'src/mailman/model/address.py'
--- src/mailman/model/address.py 2014-11-01 16:49:15 +0000
+++ src/mailman/model/address.py 2015-01-03 05:08:01 +0000
@@ -17,26 +17,22 @@
"""Model for addresses."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'Address',
]
from email.utils import formataddr
+from mailman.database.model import Model
+from mailman.interfaces.address import (
+ AddressVerificationEvent, IAddress, IEmailValidator)
+from mailman.utilities.datetime import now
from sqlalchemy import Column, DateTime, ForeignKey, Integer, Unicode
from sqlalchemy.orm import relationship, backref
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
-from mailman.database.model import Model
-from mailman.interfaces.address import (
- AddressVerificationEvent, IAddress, IEmailValidator)
-from mailman.utilities.datetime import now
-
@implementer(IAddress)
=== modified file 'src/mailman/model/autorespond.py'
--- src/mailman/model/autorespond.py 2014-11-01 16:49:15 +0000
+++ src/mailman/model/autorespond.py 2015-01-03 05:08:01 +0000
@@ -17,25 +17,21 @@
"""Module stuff."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'AutoResponseRecord',
'AutoResponseSet',
]
-from sqlalchemy import Column, Date, ForeignKey, Integer, desc
-from sqlalchemy.orm import relationship
-from zope.interface import implementer
-
from mailman.database.model import Model
from mailman.database.transaction import dbconnection
from mailman.database.types import Enum
from mailman.interfaces.autorespond import (
IAutoResponseRecord, IAutoResponseSet, Response)
from mailman.utilities.datetime import today
+from sqlalchemy import Column, Date, ForeignKey, Integer, desc
+from sqlalchemy.orm import relationship
+from zope.interface import implementer
=== modified file 'src/mailman/model/bans.py'
--- src/mailman/model/bans.py 2014-09-22 18:47:02 +0000
+++ src/mailman/model/bans.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Ban manager."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'BanManager',
]
@@ -27,12 +24,11 @@
import re
-from sqlalchemy import Column, Integer, Unicode
-from zope.interface import implementer
-
from mailman.database.model import Model
from mailman.database.transaction import dbconnection
from mailman.interfaces.bans import IBan, IBanManager
+from sqlalchemy import Column, Integer, Unicode
+from zope.interface import implementer
=== modified file 'src/mailman/model/bounce.py'
--- src/mailman/model/bounce.py 2014-12-09 01:38:26 +0000
+++ src/mailman/model/bounce.py 2015-01-03 05:08:01 +0000
@@ -17,9 +17,6 @@
"""Bounce support."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'BounceEvent',
'BounceProcessor',
@@ -27,15 +24,14 @@
-from sqlalchemy import Boolean, Column, DateTime, Integer, Unicode
-from zope.interface import implementer
-
from mailman.database.model import Model
from mailman.database.transaction import dbconnection
from mailman.database.types import Enum
from mailman.interfaces.bounce import (
BounceContext, IBounceEvent, IBounceProcessor)
from mailman.utilities.datetime import now
+from sqlalchemy import Boolean, Column, DateTime, Integer, Unicode
+from zope.interface import implementer
=== modified file 'src/mailman/model/digests.py'
--- src/mailman/model/digests.py 2014-09-22 18:47:02 +0000
+++ src/mailman/model/digests.py 2015-01-03 05:08:01 +0000
@@ -17,22 +17,18 @@
"""One last digest."""
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
__all__ = [
'OneLastDigest',
]
-from sqlalchemy import Column, Integer, ForeignKey
-from sqlalchemy.orm import relationship
-from zope.interface import implementer
-
from mailman.database.model import Model
from mailman.database.types import Enum
from mailman.interfaces.digests import IOneLastDigest
from mailman.interfaces.member import DeliveryMode
+from sqlalchemy import Column, Integer, ForeignKey
+from sqlalchemy.orm import relationship
+from zope.interface import implementer
=== modified file 'src/mailman/model/docs/addresses.rst'
--- src/mailman/model/docs/addresses.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/addresses.rst 2015-01-03 05:08:01 +0000
@@ -205,23 +205,9 @@
FPERSON@example.com
Because addresses are case-insensitive for all other purposes, you cannot
-create an address that differs only in case.
-
- >>> user_manager.create_address('fperson@example.com')
- Traceback (most recent call last):
- ...
- ExistingAddressError: FPERSON@example.com
- >>> user_manager.create_address('fperson@EXAMPLE.COM')
- Traceback (most recent call last):
- ...
- ExistingAddressError: FPERSON@example.com
- >>> user_manager.create_address('FPERSON@example.com')
- Traceback (most recent call last):
- ...
- ExistingAddressError: FPERSON@example.com
-
-You can get the address using either the lower cased version or case-preserved
-version. In fact, searching for an address is case insensitive.
+create an address that differs only in case. You can get the address using
+either the lower cased version or case-preserved version. In fact, searching
+for an address is case insensitive.
>>> print(user_manager.get_address('fperson@example.com').email)
fperson@example.com
=== modified file 'src/mailman/model/docs/domains.rst'
--- src/mailman/model/docs/domains.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/domains.rst 2015-01-03 05:08:01 +0000
@@ -108,12 +108,7 @@
base_url: http://lists.example.net,
contact_address: postmaster@example.com>
- >>> print(manager['doesnotexist.com'])
- Traceback (most recent call last):
- ...
- KeyError: u'doesnotexist.com'
-
-As with a dictionary, you can also get the domain. If the domain does not
+As with dictionaries, you can also get the domain. If the domain does not
exist, ``None`` or a default is returned.
::
@@ -128,13 +123,6 @@
>>> print(manager.get('doesnotexist.com', 'blahdeblah'))
blahdeblah
-Non-existent domains cannot be removed.
-
- >>> manager.remove('doesnotexist.com')
- Traceback (most recent call last):
- ...
- KeyError: u'doesnotexist.com'
-
Confirmation tokens
===================
=== modified file 'src/mailman/model/docs/languages.rst'
--- src/mailman/model/docs/languages.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/languages.rst 2015-01-03 05:08:01 +0000
@@ -62,7 +62,7 @@
>>> mgr.add('pl', 'iso-8859-2', 'Polish')
>>> sorted(mgr.codes)
- [u'en', u'it', u'pl']
+ ['en', 'it', 'pl']
You can iterate over all the known languages.
@@ -89,7 +89,7 @@
>>> print(mgr['xx'].code)
Traceback (most recent call last):
...
- KeyError: u'xx'
+ KeyError: 'xx'
>>> print(mgr.get('it').description)
Italian
>>> print(mgr.get('xx'))
=== modified file 'src/mailman/model/docs/listmanager.rst'
--- src/mailman/model/docs/listmanager.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/listmanager.rst 2015-01-03 05:08:01 +0000
@@ -34,22 +34,6 @@
>>> print(mlist.list_id)
test.example.com
-If you try to create a mailing list with the same name as an existing list,
-you will get an exception.
-
- >>> list_manager.create('test@example.com')
- Traceback (most recent call last):
- ...
- ListAlreadyExistsError: test@example.com
-
-It is an error to create a mailing list that isn't a fully qualified list name
-(i.e. posting address).
-
- >>> list_manager.create('foo')
- Traceback (most recent call last):
- ...
- InvalidEmailAddressError: foo
-
Deleting a mailing list
=======================
=== modified file 'src/mailman/model/docs/mailinglist.rst'
--- src/mailman/model/docs/mailinglist.rst 2014-10-31 03:12:00 +0000
+++ src/mailman/model/docs/mailinglist.rst 2015-01-03 05:08:01 +0000
@@ -114,7 +114,8 @@
An alternative way of subscribing to a mailing list is as a user with a
preferred address. This way the user can change their subscription address
just by changing their preferred address.
-::
+
+The user must have a preferred address.
>>> from mailman.utilities.datetime import now
>>> user = user_manager.create_user('dperson@example.com', 'Dave Person')
@@ -122,6 +123,8 @@
>>> address.verified_on = now()
>>> user.preferred_address = address
+The preferred address is used in the subscription.
+
>>> mlist.subscribe(user)
on aardvark@example.com
as MemberRole.member>
@@ -132,6 +135,10 @@
on aardvark@example.com
as MemberRole.member>
+If the user's preferred address changes, their subscribed email address also
+changes automatically.
+::
+
>>> new_address = user.register('dave.person@example.com')
>>> new_address.verified_on = now()
>>> user.preferred_address = new_address
@@ -143,31 +150,12 @@
-A user is not allowed to subscribe more than once to the mailing list.
-
- >>> mlist.subscribe(user)
- Traceback (most recent call last):
- ...
- AlreadySubscribedError:
- is already a MemberRole.member of mailing list aardvark@example.com
-
-However, they are allowed to subscribe again with a specific address, even if
-this address is their preferred address.
+A user is allowed to explicitly subscribe again with a specific address, even
+if this address is their preferred address.
>>> mlist.subscribe(user.preferred_address)
-A user cannot subscribe to a mailing list without a preferred address.
-
- >>> user = user_manager.create_user('eperson@example.com', 'Elly Person')
- >>> address = list(user.addresses)[0]
- >>> address.verified_on = now()
- >>> mlist.subscribe(user)
- Traceback (most recent call last):
- ...
- MissingPreferredAddressError: User must have a preferred address:
-
-
.. _`RFC 2369`: http://www.faqs.org/rfcs/rfc2369.html
=== modified file 'src/mailman/model/docs/membership.rst'
--- src/mailman/model/docs/membership.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/membership.rst 2015-01-03 05:08:01 +0000
@@ -2,10 +2,10 @@
List memberships
================
-Users represent people in Mailman. Users control email addresses, and rosters
-are collections of members. A member gives an email address a role, such as
-`member`, `administrator`, or `moderator`. Even nonmembers are represented by
-a roster.
+Users represent people in Mailman, members represent subscriptions. Users
+control email addresses, and rosters are collections of members. A member
+ties a subscribed email address to a role, such as `member`, `administrator`,
+or `moderator`. Even non-members are represented by a roster.
Roster sets are collections of rosters and a mailing list has a single roster
set that contains all its members, regardless of that member's role.
@@ -228,18 +228,6 @@
fperson@example.com MemberRole.nonmember
-Double subscriptions
-====================
-
-It is an error to subscribe someone to a list with the same role twice.
-
- >>> mlist.subscribe(address_1, MemberRole.owner)
- Traceback (most recent call last):
- ...
- AlreadySubscribedError: aperson@example.com is already a MemberRole.owner
- of mailing list ant@example.com
-
-
Moderation actions
==================
@@ -276,7 +264,7 @@
When a user is subscribed to a mailing list via a specific address they
control (as opposed to being subscribed with their preferred address), they
can change their delivery address by setting the appropriate parameter. Note
-though that the address their changing to must be verified.
+though that the address they're changing to must be verified.
>>> bee = create_list('bee@example.com')
>>> gwen = user_manager.create_user('gwen@example.com')
@@ -290,20 +278,6 @@
>>> new_address = gwen.register('gperson@example.com')
-She wants to change her membership in the `test` mailing list to use her new
-address, but the address is not yet verified.
-
- >>> gwen_member.address = new_address
- Traceback (most recent call last):
- ...
- UnverifiedAddressError: gperson@example.com
-
-Her membership has not changed.
-
- >>> for m in bee.members.members:
- ... print(m.member_id.int, m.mailing_list.list_id, m.address.email)
- 7 bee.example.com gwen@example.com
-
Gwen verifies her email address, and updates her membership.
>>> from mailman.utilities.datetime import now
=== modified file 'src/mailman/model/docs/messagestore.rst'
--- src/mailman/model/docs/messagestore.rst 2014-09-28 00:17:05 +0000
+++ src/mailman/model/docs/messagestore.rst 2015-01-03 05:08:01 +0000
@@ -6,28 +6,20 @@
``X-Message-ID-Hash`` headers. Either of these values can be combined with
the message's ``List-Archive`` header to create a globally unique URI to the
message object in the internet facing interface of the message store. The
-``X-Message-ID-Hash`` is the Base32 SHA1 hash of the ``Message-ID``.
+``X-Message-ID-Hash`` is the base-32 SHA1 hash of the ``Message-ID``.
>>> from mailman.interfaces.messages import IMessageStore
>>> from zope.component import getUtility
>>> message_store = getUtility(IMessageStore)
-If you try to add a message to the store which is missing the ``Message-ID``
-header, you will get an exception.
+A message with a ``Message-ID`` header can be stored.
>>> msg = message_from_string("""\
... Subject: An important message
+ ... Message-ID: <87myycy5eh.fsf@uwakimon.sk.tsukuba.ac.jp>
...
... This message is very important.
... """)
- >>> message_store.add(msg)
- Traceback (most recent call last):
- ...
- ValueError: Exactly one Message-ID header required
-
-However, if the message has a ``Message-ID`` header, it can be stored.
-
- >>> msg['Message-ID'] = '<87myycy5eh.fsf@uwakimon.sk.tsukuba.ac.jp>'
>>> x_message_id_hash = message_store.add(msg)
>>> print(x_message_id_hash)
AGDWSNXXKCWEILKKNYTBOHRDQGOX3Y35
@@ -97,15 +89,7 @@
================================
You delete a message from the storage service by providing the ``Message-ID``
-for the message you want to delete. If you try to delete a ``Message-ID``
-that isn't in the store, you get an exception.
-
- >>> message_store.delete_message('nothing')
- Traceback (most recent call last):
- ...
- LookupError: nothing
-
-But if you delete an existing message, it really gets deleted.
+for the message you want to delete.
>>> message_id = message['message-id']
>>> message_store.delete_message(message_id)
=== modified file 'src/mailman/model/docs/pending.rst'
--- src/mailman/model/docs/pending.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/pending.rst 2015-01-03 05:08:01 +0000
@@ -33,12 +33,12 @@
>>> len(token)
40
-There's not much you can do with tokens except to `confirm` them, which
-basically means returning the ``IPendable`` structure (as a dictionary) from
-the database that matches the token. If the token isn't in the database, None
-is returned.
+There's not much you can do with tokens except to *confirm* them, which
+basically means returning the `IPendable` structure (as a dictionary) from the
+database that matches the token. If the token isn't in the database, None is
+returned.
- >>> pendable = pendingdb.confirm(bytes('missing'))
+ >>> pendable = pendingdb.confirm(b'missing')
>>> print(pendable)
None
>>> pendable = pendingdb.confirm(token)
=== modified file 'src/mailman/model/docs/registration.rst'
--- src/mailman/model/docs/registration.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/registration.rst 2015-01-03 05:08:01 +0000
@@ -8,7 +8,7 @@
be verified before Mailman will send them any list traffic.
The ``IUserManager`` manages users, but it does so at a fairly low level.
-Specifically, it does not handle verifications, email address syntax validity
+Specifically, it does not handle verification, email address syntax validity
checks, etc. The ``IRegistrar`` is the interface to the object handling all
this stuff.
@@ -19,7 +19,7 @@
Here is a helper function to check the token strings.
>>> def check_token(token):
- ... assert isinstance(token, basestring), 'Not a string'
+ ... assert isinstance(token, str), 'Not a string'
... assert len(token) == 40, 'Unexpected length: %d' % len(token)
... assert token.isalnum(), 'Not alphanumeric'
... print('ok')
@@ -47,31 +47,6 @@
honestly, not as much as probably should be done. Still, some patently bad
addresses are rejected outright.
- >>> registrar.register(mlist, '')
- Traceback (most recent call last):
- ...
- InvalidEmailAddressError
- >>> registrar.register(mlist, 'some name@example.com')
- Traceback (most recent call last):
- ...
- InvalidEmailAddressError: some name@example.com
- >>> registrar.register(mlist, '