diff options
132 files changed, 1040 insertions, 1008 deletions
@@ -108,6 +108,7 @@ case second `m'. Any other spelling is incorrect.""", 'zc.buildout', 'zope.component', 'zope.configuration', + 'zope.event', 'zope.interface', 'zope.testing<4', ], diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py index 4107a8c6e..0a291d671 100644 --- a/src/mailman/app/bounces.py +++ b/src/mailman/app/bounces.py @@ -17,7 +17,7 @@ """Application level bounce handling.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -38,7 +38,7 @@ from email.mime.text import MIMEText from email.utils import parseaddr from string import Template from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -176,9 +176,9 @@ class ProbeVERP(_BaseVERPParser): +@implementer(IPendable) class _ProbePendable(dict): """The pendable dictionary for probe messages.""" - implements(IPendable) def send_probe(member, msg): diff --git a/src/mailman/app/docs/lifecycle.rst b/src/mailman/app/docs/lifecycle.rst index 08a25ccff..9a3337123 100644 --- a/src/mailman/app/docs/lifecycle.rst +++ b/src/mailman/app/docs/lifecycle.rst @@ -44,10 +44,10 @@ Creating a list applies its styles Start by registering a test style. :: - >>> from zope.interface import implements + >>> from zope.interface import implementer >>> from mailman.interfaces.styles import IStyle - >>> class TestStyle(object): - ... implements(IStyle) + >>> @implementer(IStyle) + ... class TestStyle(object): ... name = 'test' ... priority = 10 ... def apply(self, mailing_list): diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py index 030a504f7..63e3c5144 100644 --- a/src/mailman/app/registrar.py +++ b/src/mailman/app/registrar.py @@ -28,7 +28,7 @@ __all__ = [ import logging from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.app.notifications import send_welcome_message from mailman.core.i18n import _ @@ -47,17 +47,16 @@ log = logging.getLogger('mailman.error') +@implementer(IPendable) class PendableRegistration(dict): - implements(IPendable) PEND_KEY = 'registration' +@implementer(IRegistrar) class Registrar: """Handle registrations and confirmations for subscriptions.""" - implements(IRegistrar) - def register(self, mlist, email, display_name=None, delivery_mode=None): """See `IUserRegistrar`.""" if delivery_mode is None: diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py index 60f8cdebe..ebbe14492 100644 --- a/src/mailman/app/subscriptions.py +++ b/src/mailman/app/subscriptions.py @@ -17,7 +17,7 @@ """Module stuff.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -31,11 +31,12 @@ from operator import attrgetter from storm.expr import And, Or from uuid import UUID from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.app.membership import add_member, delete_member from mailman.config import config from mailman.core.constants import system_preferences +from mailman.database.transaction import dbconnection from mailman.interfaces.address import IEmailValidator from mailman.interfaces.listmanager import ( IListManager, ListDeletedEvent, NoSuchListError) @@ -61,11 +62,10 @@ def _membership_sort_key(member): +@implementer(ISubscriptionService) class SubscriptionService: """Subscription services for the REST API.""" - implements(ISubscriptionService) - __name__ = 'members' def get_members(self): @@ -90,9 +90,10 @@ class SubscriptionService: sorted(by_role.get('member', []), key=address_of_member)) return all_members - def get_member(self, member_id): + @dbconnection + def get_member(self, store, member_id): """See `ISubscriptionService`.""" - members = config.db.store.find( + members = store.find( Member, Member._member_id == member_id) if members.count() == 0: @@ -101,7 +102,9 @@ class SubscriptionService: assert members.count() == 1, 'Too many matching members' return members[0] - def find_members(self, subscriber=None, fqdn_listname=None, role=None): + @dbconnection + def find_members(self, store, + subscriber=None, fqdn_listname=None, role=None): """See `ISubscriptionService`.""" # If `subscriber` is a user id, then we'll search for all addresses # which are controlled by the user, otherwise we'll just search for @@ -137,7 +140,7 @@ class SubscriptionService: query.append(Member.mailing_list == fqdn_listname) if role is not None: query.append(Member.role == role) - results = config.db.store.find(Member, And(*query)) + results = store.find(Member, And(*query)) return sorted(results, key=_membership_sort_key) def __iter__(self): diff --git a/src/mailman/app/templates.py b/src/mailman/app/templates.py index a91231cfc..f29781e58 100644 --- a/src/mailman/app/templates.py +++ b/src/mailman/app/templates.py @@ -31,7 +31,7 @@ from contextlib import closing from urllib import addinfourl from urlparse import urlparse from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.utilities.i18n import TemplateNotFoundError, find from mailman.interfaces.languages import ILanguageManager @@ -92,11 +92,10 @@ class MailmanHandler(urllib2.BaseHandler): +@implementer(ITemplateLoader) class TemplateLoader: """Loader of templates, with caching and support for mailman:// URIs.""" - implements(ITemplateLoader) - def __init__(self): opener = urllib2.build_opener(MailmanHandler()) urllib2.install_opener(opener) diff --git a/src/mailman/archiving/mailarchive.py b/src/mailman/archiving/mailarchive.py index c72cde11c..69ca77e52 100644 --- a/src/mailman/archiving/mailarchive.py +++ b/src/mailman/archiving/mailarchive.py @@ -17,7 +17,7 @@ """The Mail-Archive.com archiver.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,21 +27,20 @@ __all__ = [ from urllib import quote from urlparse import urljoin -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.archiver import IArchiver +@implementer(IArchiver) class MailArchive: """Public archiver at the Mail-Archive.com. Messages get archived at http://go.mail-archive.com. """ - implements(IArchiver) - name = 'mail-archive' @staticmethod diff --git a/src/mailman/archiving/mhonarc.py b/src/mailman/archiving/mhonarc.py index 0beeed73e..7f0af6cd6 100644 --- a/src/mailman/archiving/mhonarc.py +++ b/src/mailman/archiving/mhonarc.py @@ -17,7 +17,7 @@ """MHonArc archiver.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -29,7 +29,7 @@ import logging import subprocess from urlparse import urljoin -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.archiver import IArchiver @@ -40,11 +40,10 @@ log = logging.getLogger('mailman.archiver') +@implementer(IArchiver) class MHonArc: """Local MHonArc archiver.""" - implements(IArchiver) - name = 'mhonarc' @staticmethod diff --git a/src/mailman/archiving/prototype.py b/src/mailman/archiving/prototype.py index 453c6c770..3ce51ddb5 100644 --- a/src/mailman/archiving/prototype.py +++ b/src/mailman/archiving/prototype.py @@ -17,7 +17,7 @@ """Prototypical permalinking archiver.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -34,7 +34,7 @@ from mailbox import Maildir from urlparse import urljoin from flufl.lock import Lock, TimeOutError -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.archiver import IArchiver @@ -43,6 +43,7 @@ log = logging.getLogger('mailman.error') +@implementer(IArchiver) class Prototype: """A prototype of a third party archiver. @@ -50,8 +51,6 @@ class Prototype: servers and archivers: <http://wiki.list.org/display/DEV/Stable+URLs>. """ - implements(IArchiver) - name = 'prototype' @staticmethod diff --git a/src/mailman/archiving/tests/test_prototype.py b/src/mailman/archiving/tests/test_prototype.py index 29f6ba1cb..bc1cee8b9 100644 --- a/src/mailman/archiving/tests/test_prototype.py +++ b/src/mailman/archiving/tests/test_prototype.py @@ -37,6 +37,7 @@ from flufl.lock import Lock from mailman.app.lifecycle import create_list from mailman.archiving.prototype import Prototype from mailman.config import config +from mailman.database.transaction import transaction from mailman.testing.helpers import LogFileMark from mailman.testing.helpers import ( specialized_message_from_string as mfs) @@ -61,8 +62,8 @@ X-Message-ID-Hash: MS6QLWERIJLGCRF44J7USBFDELMNT2BW Tests are better than no tests but the water deserves to be swum. """) - self._mlist = create_list('test@example.com') - config.db.commit() + with transaction(): + self._mlist = create_list('test@example.com') # Set up a temporary directory for the prototype archiver so that it's # easier to clean up. self._tempdir = tempfile.mkdtemp() diff --git a/src/mailman/bin/set_members.py b/src/mailman/bin/set_members.py deleted file mode 100644 index 6ec66af06..000000000 --- a/src/mailman/bin/set_members.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright (C) 2007-2012 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 <http://www.gnu.org/licenses/>. - -import csv -import optparse - -from zope.component import getUtility - -from mailman import Utils -from mailman import passwords -from mailman.app.membership import add_member -from mailman.app.notifications import ( - send_admin_subscription_notice, send_welcome_message) -from mailman.configuration import config -from mailman.core.i18n import _ -from mailman.initialize import initialize -from mailman.interfaces.members import DeliveryMode -from mailman.interfaces.usermanager import IUserManager -from mailman.version import MAILMAN_VERSION - - -DELIVERY_MODES = { - 'regular': DeliveryMode.regular, - 'plain': DeliveryMode.plaintext_digests, - 'mime': DeliveryMode.mime_digests, - } - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] csv-file - -Set the membership of a mailing list to that described in a CSV file. Each -row of the CSV file has the following format. Only the address column is -required. - - - email address - - full name (default: the empty string) - - delivery mode (default: regular delivery) [1] - -[1] The delivery mode is a case insensitive string of the following values: - - regular - regular, i.e. immediate delivery - mime - MIME digest delivery - plain - plain text (RFC 1153) digest delivery - -Any address not included in the CSV file is removed from the list membership. -""")) - parser.add_option('-l', '--listname', - type='string', help=_("""\ -Mailng list to set the membership for.""")) - parser.add_option('-w', '--welcome-msg', - type='string', metavar='<y|n>', help=_("""\ -Set whether or not to send the list members a welcome message, overriding -whatever the list's 'send_welcome_msg' setting is.""")) - parser.add_option('-a', '--admin-notify', - type='string', metavar='<y|n>', help=_("""\ -Set whether or not to send the list administrators a notification on the -success/failure of these subscriptions, overriding whatever the list's -'admin_notify_mchanges' setting is.""")) - parser.add_option('-v', '--verbose', action='store_true', - help=_('Increase verbosity')) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if opts.welcome_msg is not None: - ch = opts.welcome_msg[0].lower() - if ch == 'y': - opts.welcome_msg = True - elif ch == 'n': - opts.welcome_msg = False - else: - parser.error(_('Illegal value for -w: $opts.welcome_msg')) - if opts.admin_notify is not None: - ch = opts.admin_notify[0].lower() - if ch == 'y': - opts.admin_notify = True - elif ch == 'n': - opts.admin_notify = False - else: - parser.error(_('Illegal value for -a: $opts.admin_notify')) - return parser, opts, args - - - -def parse_file(filename): - members = {} - with open(filename) as fp: - for row in csv.reader(fp): - if len(row) == 0: - continue - elif len(row) == 1: - address = row[0] - real_name = None - delivery_mode = DeliveryMode.regular - elif len(row) == 2: - address, real_name = row - delivery_mode = DeliveryMode.regular - else: - # Ignore extra columns - address, real_name = row[0:2] - delivery_mode = DELIVERY_MODES.get(row[2].lower()) - if delivery_mode is None: - delivery_mode = DeliveryMode.regular - members[address] = real_name, delivery_mode - return members - - - -def main(): - parser, opts, args = parseargs() - initialize(opts.config) - - mlist = config.db.list_manager.get(opts.listname) - if mlist is None: - parser.error(_('No such list: $opts.listname')) - - # Set up defaults. - if opts.welcome_msg is None: - send_welcome_msg = mlist.send_welcome_msg - else: - send_welcome_msg = opts.welcome_msg - if opts.admin_notify is None: - admin_notify = mlist.admin_notify_mchanges - else: - admin_notify = opts.admin_notify - - # Parse the csv files. - member_data = {} - for filename in args: - member_data.update(parse_file(filename)) - - future_members = set(member_data) - current_members = set(obj.address for obj in mlist.members.addresses) - add_members = future_members - current_members - delete_members = current_members - future_members - change_members = current_members & future_members - - with _.using(mlist.preferred_language): - # Start by removing all the delete members. - for address in delete_members: - print _('deleting address: $address') - member = mlist.members.get_member(address) - member.unsubscribe() - # For all members that are in both lists, update their full name and - # delivery mode. - for address in change_members: - print _('updating address: $address') - real_name, delivery_mode = member_data[address] - member = mlist.members.get_member(address) - member.preferences.delivery_mode = delivery_mode - user = getUtility(IUserManager).get_user(address) - user.real_name = real_name - for address in add_members: - print _('adding address: $address') - real_name, delivery_mode = member_data[address] - password = passwords.make_secret( - Utils.MakeRandomPassword(), - passwords.lookup_scheme(config.PASSWORD_SCHEME)) - add_member(mlist, address, real_name, password, delivery_mode, - mlist.preferred_language, send_welcome_msg, - admin_notify) - if send_welcome_msg: - send_welcome_message(mlist, address, language, delivery_mode) - if admin_notify: - send_admin_subscription_notice(mlist, address, real_name) - - config.db.flush() - - - -if __name__ == '__main__': - main() diff --git a/src/mailman/chains/base.py b/src/mailman/chains/base.py index 8a2e87ee2..b75d66989 100644 --- a/src/mailman/chains/base.py +++ b/src/mailman/chains/base.py @@ -17,7 +17,7 @@ """Base class for terminal chains.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.chain import ( @@ -36,9 +36,9 @@ from mailman.interfaces.chain import ( +@implementer(IChainLink) class Link: """A chain link.""" - implements(IChainLink) def __init__(self, rule, action=None, chain=None, function=None): self.rule = rule @@ -61,13 +61,12 @@ class Link: +@implementer(IChain, IChainIterator) class TerminalChainBase: """A base chain that always matches and executes a method. The method is called '_process()' and must be provided by the subclass. """ - implements(IChain, IChainIterator) - def _process(self, mlist, msg, msgdata): """Process the message for the given mailing list. @@ -93,9 +92,9 @@ class TerminalChainBase: +@implementer(IMutableChain) class Chain: """Generic chain base class.""" - implements(IMutableChain) def __init__(self, name, description): assert name not in config.chains, ( @@ -125,11 +124,10 @@ class Chain: +@implementer(IChainIterator) class ChainIterator: """Generic chain iterator.""" - implements(IChainIterator) - def __init__(self, chain): self._chain = chain diff --git a/src/mailman/chains/builtin.py b/src/mailman/chains/builtin.py index 5d51e075d..7ed60dcec 100644 --- a/src/mailman/chains/builtin.py +++ b/src/mailman/chains/builtin.py @@ -17,7 +17,7 @@ """The default built-in starting chain.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ import logging -from zope.interface import implements +from zope.interface import implementer from mailman.chains.base import Link from mailman.config import config @@ -39,11 +39,10 @@ log = logging.getLogger('mailman.vette') +@implementer(IChain) class BuiltInChain: """Default built-in chain.""" - implements(IChain) - name = 'default-posting-chain' description = _('The built-in moderation chain.') diff --git a/src/mailman/chains/headers.py b/src/mailman/chains/headers.py index d9f8356f8..2dafa07f0 100644 --- a/src/mailman/chains/headers.py +++ b/src/mailman/chains/headers.py @@ -17,7 +17,7 @@ """The header-matching chain.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ import re import logging -from zope.interface import implements +from zope.interface import implementer from mailman.chains.base import Chain, Link from mailman.config import config @@ -60,9 +60,9 @@ def make_link(header, pattern): +@implementer(IRule) class HeaderMatchRule: """Header matching rule used by header-match chain.""" - implements(IRule) # Sequential rule counter. _count = 1 diff --git a/src/mailman/chains/hold.py b/src/mailman/chains/hold.py index f095bc182..a8b7ec57b 100644 --- a/src/mailman/chains/hold.py +++ b/src/mailman/chains/hold.py @@ -17,7 +17,7 @@ """The terminal 'hold' chain.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -33,7 +33,7 @@ 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 implements +from zope.interface import implementer from mailman.app.moderator import hold_message from mailman.app.replybot import can_acknowledge @@ -54,8 +54,8 @@ SEMISPACE = '; ' +@implementer(IPendable) class HeldMessagePendable(dict): - implements(IPendable) PEND_KEY = 'held message' diff --git a/src/mailman/chains/moderation.py b/src/mailman/chains/moderation.py index 9f9633346..6c2ed1180 100644 --- a/src/mailman/chains/moderation.py +++ b/src/mailman/chains/moderation.py @@ -34,7 +34,7 @@ made as to the disposition of the message. `defer` is the default for members, while `hold` is the default for nonmembers. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -42,7 +42,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.chains.base import Link from mailman.config import config @@ -52,15 +52,13 @@ from mailman.interfaces.chain import IChain, LinkAction +@implementer(IChain) class ModerationChain: """Dynamically produce a link jumping to the appropriate terminal chain. The terminal chain will be one of the Accept, Hold, Discard, or Reject chains, based on the member's or nonmember's moderation action setting. """ - - implements(IChain) - name = 'moderation' description = _('Moderation chain') diff --git a/src/mailman/commands/cli_aliases.py b/src/mailman/commands/cli_aliases.py index 7c1577c9c..d692ba356 100644 --- a/src/mailman/commands/cli_aliases.py +++ b/src/mailman/commands/cli_aliases.py @@ -17,7 +17,7 @@ """Generate Mailman alias files for your MTA.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -29,7 +29,7 @@ import sys from operator import attrgetter from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -41,11 +41,10 @@ from mailman.utilities.modules import call_name +@implementer(ICLISubCommand) class Aliases: """Regenerate the aliases appropriate for your MTA.""" - implements(ICLISubCommand) - name = 'aliases' def add(self, parser, command_parser): @@ -91,11 +90,10 @@ class Aliases: +@implementer(IMailTransportAgentLifecycle) class Dummy: """Dummy aliases implementation for simpler output format.""" - implements(IMailTransportAgentLifecycle) - def create(self, mlist): """See `IMailTransportAgentLifecycle`.""" raise NotImplementedError @@ -132,5 +130,5 @@ class Dummy: for mlist in sorted(by_domain[domain], key=sort_key): utility = getUtility(IMailTransportAgentAliases) for alias in utility.aliases(mlist): - print >> fp, alias - print >> fp + print(alias, file=fp) + print(file=fp) diff --git a/src/mailman/commands/cli_control.py b/src/mailman/commands/cli_control.py index 2013d6745..8349feb60 100644 --- a/src/mailman/commands/cli_control.py +++ b/src/mailman/commands/cli_control.py @@ -17,7 +17,7 @@ """Module stuff.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -34,7 +34,7 @@ import errno import signal import logging -from zope.interface import implements +from zope.interface import implementer from mailman.bin.master import WatcherState, master_state from mailman.config import config @@ -46,11 +46,10 @@ qlog = logging.getLogger('mailman.runner') +@implementer(ICLISubCommand) class Start: """Start the Mailman daemons.""" - implements(ICLISubCommand) - name = 'start' def add(self, parser, command_parser): @@ -107,7 +106,7 @@ class Start: 'cleanly. Try using --force.')) def log(message): if not args.quiet: - print message + print(message) # Daemon process startup according to Stevens, Advanced Programming in # the UNIX Environment, Chapter 13. pid = os.fork() @@ -147,27 +146,26 @@ def kill_watcher(sig): pid = int(fp.read().strip()) except (IOError, ValueError) as error: # For i18n convenience - print >> sys.stderr, _('PID unreadable in: $config.PID_FILE') - print >> sys.stderr, error - print >> sys.stderr, _('Is the master even running?') + print(_('PID unreadable in: $config.PID_FILE'), file=sys.stderr) + print(error, file=sys.stderr) + print(_('Is the master even running?'), file=sys.stderr) return try: os.kill(pid, sig) except OSError as error: if error.errno != errno.ESRCH: raise - print >> sys.stderr, _('No child with pid: $pid') - print >> sys.stderr, error - print >> sys.stderr, _('Stale pid file removed.') + print(_('No child with pid: $pid'), file=sys.stderr) + print(error, file=sys.stderr) + print(_('Stale pid file removed.'), file=sys.stderr) os.unlink(config.PID_FILE) +@implementer(ICLISubCommand) class SignalCommand: """Common base class for simple, signal sending commands.""" - implements(ICLISubCommand) - name = None message = None signal = None @@ -184,7 +182,7 @@ class SignalCommand: def process(self, args): """See `ICLISubCommand`.""" if not args.quiet: - print _(self.message) + print(_(self.message)) kill_watcher(self.signal) @@ -204,11 +202,10 @@ class Reopen(SignalCommand): signal = signal.SIGHUP +@implementer(ICLISubCommand) class Restart(SignalCommand): """Stop the Mailman daemons.""" - implements(ICLISubCommand) - name = 'restart' message = _('Restarting the Mailman runners') signal = signal.SIGUSR1 diff --git a/src/mailman/commands/cli_help.py b/src/mailman/commands/cli_help.py index 538d9c520..a85dcd442 100644 --- a/src/mailman/commands/cli_help.py +++ b/src/mailman/commands/cli_help.py @@ -17,7 +17,7 @@ """The 'help' subcommand.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,18 +25,17 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.interfaces.command import ICLISubCommand +@implementer(ICLISubCommand) class Help: # Lowercase, to match argparse's default --help text. """show this help message and exit""" - implements(ICLISubCommand) - name = 'help' def add(self, parser, command_parser): diff --git a/src/mailman/commands/cli_import.py b/src/mailman/commands/cli_import.py index b703f3ffd..f6c016585 100644 --- a/src/mailman/commands/cli_import.py +++ b/src/mailman/commands/cli_import.py @@ -17,7 +17,7 @@ """Importing list data into Mailman 3.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -29,21 +29,20 @@ import sys import cPickle from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config 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 +@implementer(ICLISubCommand) class Import21: """Import Mailman 2.1 list data.""" - implements(ICLISubCommand) - name = 'import21' def add(self, parser, command_parser): @@ -59,6 +58,7 @@ class Import21: 'pickle_file', metavar='FILENAME', nargs=1, help=_('The path to the config.pck file to import.')) + @transactional def process(self, args): """See `ICLISubCommand`.""" # Could be None or sequence of length 0. @@ -90,10 +90,7 @@ class Import21: return else: if not isinstance(config_dict, dict): - print >> sys.stderr, _( - 'Ignoring non-dictionary: {0!r}').format( - config_dict) + print(_('Ignoring non-dictionary: {0!r}').format( + config_dict), file=sys.stderr) continue import_config_pck(mlist, config_dict) - # Commit the changes to the database. - config.db.commit() diff --git a/src/mailman/commands/cli_info.py b/src/mailman/commands/cli_info.py index 24ccec4fb..0e9c72f60 100644 --- a/src/mailman/commands/cli_info.py +++ b/src/mailman/commands/cli_info.py @@ -17,7 +17,7 @@ """Information about this Mailman instance.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ import sys -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -37,11 +37,10 @@ from mailman.version import MAILMAN_VERSION_FULL +@implementer(ICLISubCommand) class Info: """Information about this Mailman instance.""" - implements(ICLISubCommand) - name = 'info' def add(self, parser, command_parser): @@ -65,15 +64,16 @@ class Info: # We don't need to close output because that will happen # automatically when the script exits. output = open(args.output, 'w') - print >> output, MAILMAN_VERSION_FULL - print >> output, 'Python', sys.version - print >> output, 'config file:', config.filename - print >> output, 'db url:', config.db.url - print >> output, 'REST root url:', path_to('/') - print >> output, 'REST credentials: {0}:{1}'.format( - config.webservice.admin_user, config.webservice.admin_pass) + print(MAILMAN_VERSION_FULL, file=output) + print('Python', sys.version, file=output) + print('config file:', config.filename, file=output) + print('db url:', config.db.url, file=output) + print('REST root url:', path_to('/'), file=output) + print('REST credentials: {0}:{1}'.format( + config.webservice.admin_user, config.webservice.admin_pass), + file=output) if args.verbose: - print >> output, 'File system paths:' + print('File system paths:', file=output) longest = 0 paths = {} for attribute in dir(config): @@ -81,5 +81,5 @@ class Info: paths[attribute] = getattr(config, attribute) longest = max(longest, len(attribute)) for attribute in sorted(paths): - print ' {0:{2}} = {1}'.format(attribute, paths[attribute], - longest) + print(' {0:{2}} = {1}'.format( + attribute, paths[attribute], longest)) diff --git a/src/mailman/commands/cli_inject.py b/src/mailman/commands/cli_inject.py index 321a92c78..1434fd2a6 100644 --- a/src/mailman/commands/cli_inject.py +++ b/src/mailman/commands/cli_inject.py @@ -17,7 +17,7 @@ """bin/mailman inject""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ import sys from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.app.inject import inject_text from mailman.config import config @@ -38,11 +38,10 @@ from mailman.interfaces.listmanager import IListManager +@implementer(ICLISubCommand) class Inject: """Inject a message from a file into a mailing list's queue.""" - implements(ICLISubCommand) - name = 'inject' def add(self, parser, command_parser): @@ -82,9 +81,9 @@ class Inject: # Process --show first; if given, print output and exit, ignoring all # other command line switches. if args.show: - print 'Available queues:' + print('Available queues:') for switchboard in sorted(config.switchboards): - print ' ', switchboard + print(' ', switchboard) return # Could be None or sequence of length 0. if args.listname is None: @@ -106,7 +105,7 @@ class Inject: try: message_text = sys.stdin.read() except KeyboardInterrupt: - print 'Interrupted' + print('Interrupted') sys.exit(1) else: with open(args.filename) as fp: diff --git a/src/mailman/commands/cli_lists.py b/src/mailman/commands/cli_lists.py index af6afe22d..cf72c51a8 100644 --- a/src/mailman/commands/cli_lists.py +++ b/src/mailman/commands/cli_lists.py @@ -17,7 +17,7 @@ """The 'lists' subcommand.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,12 +28,12 @@ __all__ = [ from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.app.lifecycle import create_list, remove_list -from mailman.config import config from mailman.core.constants import system_preferences from mailman.core.i18n import _ +from mailman.database.transaction import transaction, transactional from mailman.email.message import UserNotification from mailman.interfaces.address import ( IEmailValidator, InvalidEmailAddressError) @@ -49,11 +49,10 @@ COMMASPACE = ', ' +@implementer(ICLISubCommand) class Lists: """List all mailing lists""" - implements(ICLISubCommand) - name = 'lists' def add(self, parser, command_parser): @@ -98,11 +97,11 @@ class Lists: # Maybe no mailing lists matched. if len(mailing_lists) == 0: if not args.quiet: - print _('No matching mailing lists found') + print(_('No matching mailing lists found')) return count = len(mailing_lists) if not args.quiet: - print _('$count matching mailing lists found:') + print(_('$count matching mailing lists found:')) # Calculate the longest identifier. longest = 0 output = [] @@ -120,16 +119,15 @@ class Lists: else: format_string = '{0:{2}}' for identifier, description in output: - print format_string.format( - identifier, description, longest, 70 - longest) + print(format_string.format( + identifier, description, longest, 70 - longest)) +@implementer(ICLISubCommand) class Create: """Create a mailing list""" - implements(ICLISubCommand) - name = 'create' def add(self, parser, command_parser): @@ -214,13 +212,13 @@ class Create: self.parser.error(_('Undefined domain: $domain')) return # Find the language associated with the code, then set the mailing - # list's preferred language to that. The changes then must be - # committed to the database. - mlist.preferred_language = getUtility(ILanguageManager)[language_code] - config.db.commit() + # list's preferred language to that. + language_manager = getUtility(ILanguageManager) + with transaction(): + mlist.preferred_language = language_manager[language_code] # Do the notification. if not args.quiet: - print _('Created mailing list: $mlist.fqdn_listname') + print(_('Created mailing list: $mlist.fqdn_listname')) if args.notify: d = dict( listname = mlist.fqdn_listname, @@ -242,11 +240,10 @@ class Create: +@implementer(ICLISubCommand) class Remove: """Remove a mailing list""" - implements(ICLISubCommand) - name = 'remove' def add(self, parser, command_parser): @@ -262,11 +259,12 @@ class Remove: The 'fully qualified list name', i.e. the posting address of the mailing list.""")) + @transactional def process(self, args): """See `ICLISubCommand`.""" def log(message): if not args.quiet: - print message + print(message) assert len(args.listname) == 1, ( 'Unexpected positional arguments: %s' % args.listname) fqdn_listname = args.listname[0] @@ -277,4 +275,3 @@ class Remove: else: log(_('Removed list: $fqdn_listname')) remove_list(fqdn_listname, mlist) - config.db.commit() diff --git a/src/mailman/commands/cli_members.py b/src/mailman/commands/cli_members.py index 2bf6be848..aef3991d8 100644 --- a/src/mailman/commands/cli_members.py +++ b/src/mailman/commands/cli_members.py @@ -32,11 +32,12 @@ from email.utils import formataddr, parseaddr from flufl.password import generate from operator import attrgetter from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.app.membership import add_member from mailman.config import config 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.interfaces.member import ( @@ -44,11 +45,10 @@ from mailman.interfaces.member import ( +@implementer(ICLISubCommand) class Members: """Manage list memberships. With no arguments, list all members.""" - implements(ICLISubCommand) - name = 'members' def add(self, parser, command_parser): @@ -177,6 +177,7 @@ class Members: if fp is not sys.stdout: fp.close() + @transactional def add_members(self, mlist, args): """Add the members in a file to a mailing list. @@ -207,9 +208,8 @@ class Members: except AlreadySubscribedError: # It's okay if the address is already subscribed, just # print a warning and continue. - print('Already subscribed (skipping):', + print('Already subscribed (skipping):', email, display_name) finally: if fp is not sys.stdin: fp.close() - config.db.commit() diff --git a/src/mailman/commands/cli_qfile.py b/src/mailman/commands/cli_qfile.py index 78156f08c..b9e0eff02 100644 --- a/src/mailman/commands/cli_qfile.py +++ b/src/mailman/commands/cli_qfile.py @@ -17,7 +17,7 @@ """Getting information out of a qfile.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ import cPickle from pprint import PrettyPrinter -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.command import ICLISubCommand @@ -39,11 +39,10 @@ m = [] +@implementer(ICLISubCommand) class QFile: """Get information out of a queue file.""" - implements(ICLISubCommand) - name = 'qfile' def add(self, parser, command_parser): @@ -79,15 +78,15 @@ class QFile: except EOFError: break if args.doprint: - print _('[----- start pickle -----]') + print(_('[----- start pickle -----]')) for i, obj in enumerate(m): count = i + 1 - print _('<----- start object $count ----->') + print(_('<----- start object $count ----->')) if isinstance(obj, basestring): - print obj + print(obj) else: printer.pprint(obj) - print _('[----- end pickle -----]') + print(_('[----- end pickle -----]')) count = len(m) banner = _("The variable 'm' contains $count objects") if args.interactive: diff --git a/src/mailman/commands/cli_status.py b/src/mailman/commands/cli_status.py index 14b0d976b..9cbaa4f22 100644 --- a/src/mailman/commands/cli_status.py +++ b/src/mailman/commands/cli_status.py @@ -15,9 +15,9 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. -"""Module stuff.""" +"""bin/mailman status.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ import socket -from zope.interface import implements +from zope.interface import implementer from mailman.bin.master import WatcherState, master_state from mailman.core.i18n import _ @@ -35,11 +35,10 @@ from mailman.interfaces.command import ICLISubCommand +@implementer(ICLISubCommand) class Status: """Status of the Mailman system.""" - implements(ICLISubCommand) - name = 'status' def add(self, parser, command_parser): @@ -64,5 +63,5 @@ class Status: 'Invalid enum value: %s' % status) message = _('GNU Mailman is in an unexpected state ' '($hostname != $fqdn_name)') - print message + print(message) return int(status) diff --git a/src/mailman/commands/cli_unshunt.py b/src/mailman/commands/cli_unshunt.py index 4ce711b83..bc50d95ef 100644 --- a/src/mailman/commands/cli_unshunt.py +++ b/src/mailman/commands/cli_unshunt.py @@ -17,7 +17,7 @@ """The 'unshunt' command.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ import sys -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -35,11 +35,10 @@ from mailman.interfaces.command import ICLISubCommand +@implementer(ICLISubCommand) class Unshunt: """Unshunt messages.""" - implements(ICLISubCommand) - name = 'unshunt' def add(self, parser, command_parser): @@ -64,8 +63,8 @@ class Unshunt: if not args.discard: config.switchboards[which_queue].enqueue(msg, msgdata) except Exception as error: - print >> sys.stderr, _( - 'Cannot unshunt message $filebase, skipping:\n$error') + print(_('Cannot unshunt message $filebase, skipping:\n$error'), + file=sys.stderr) else: # Unlink the .bak file left by dequeue() shunt_queue.finish(filebase) diff --git a/src/mailman/commands/cli_version.py b/src/mailman/commands/cli_version.py index 4090b1173..b5e9b65ff 100644 --- a/src/mailman/commands/cli_version.py +++ b/src/mailman/commands/cli_version.py @@ -17,7 +17,7 @@ """The Mailman version.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,18 +25,17 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.interfaces.command import ICLISubCommand from mailman.version import MAILMAN_VERSION_FULL +@implementer(ICLISubCommand) class Version: """Mailman's version.""" - implements(ICLISubCommand) - name = 'version' def add(self, parser, command_parser): @@ -46,4 +45,4 @@ class Version: def process(self, args): """See `ICLISubCommand`.""" - print MAILMAN_VERSION_FULL + print(MAILMAN_VERSION_FULL) diff --git a/src/mailman/commands/cli_withlist.py b/src/mailman/commands/cli_withlist.py index e514c798f..4ccdd8798 100644 --- a/src/mailman/commands/cli_withlist.py +++ b/src/mailman/commands/cli_withlist.py @@ -17,7 +17,7 @@ """bin/mailman withlist""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -31,7 +31,7 @@ import sys from lazr.config import as_boolean from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -47,14 +47,13 @@ r = None +@implementer(ICLISubCommand) class Withlist: """Operate on a mailing list. For detailed help, see --details """ - implements(ICLISubCommand) - name = 'withlist' def add(self, parser, command_parser): @@ -163,7 +162,7 @@ class Withlist: ipshell = InteractiveShellEmbed(banner1=banner, user_ns=overrides) ipshell() except ImportError: - print _('ipython is not available, set use_ipython to no') + print(_('ipython is not available, set use_ipython to no')) def _start_python(self, overrides, banner): # Set the tab completion. @@ -178,25 +177,25 @@ class Withlist: def _details(self): """Print detailed usage.""" # Split this up into paragraphs for easier translation. - print _("""\ + print(_("""\ This script provides you with a general framework for interacting with a -mailing list.""") - print - print _("""\ +mailing list.""")) + print() + print(_("""\ There are two ways to use this script: interactively or programmatically. Using it interactively allows you to play with, examine and modify a mailing list from Python's interactive interpreter. When running interactively, the variable 'm' will be available in the global namespace. It will reference the -mailing list object.""") - print - print _("""\ +mailing list object.""")) + print() + print(_("""\ Programmatically, you can write a function to operate on a mailing list, and this script will take care of the housekeeping (see below for examples). In that case, the general usage syntax is: - % bin/mailman withlist [options] listname [args ...]""") - print - print _("""\ + % bin/mailman withlist [options] listname [args ...]""")) + print() + print(_("""\ Here's an example of how to use the --run option. Say you have a file in the Mailman installation directory called 'listaddr.py', with the following two functions: @@ -205,26 +204,26 @@ functions: print mlist.posting_address def requestaddr(mlist): - print mlist.request_address""") - print - print _("""\ + print mlist.request_address""")) + print() + print(_("""\ You can print the list's posting address by running the following from the command line: % bin/mailman withlist -r listaddr mylist@example.com Importing listaddr ... Running listaddr.listaddr() ... - mylist@example.com""") - print - print _("""\ + mylist@example.com""")) + print() + print(_("""\ And you can print the list's request address by running: % bin/mailman withlist -r listaddr.requestaddr mylist Importing listaddr ... Running listaddr.requestaddr() ... - mylist-request@example.com""") - print - print _("""\ + mylist-request@example.com""")) + print() + print(_("""\ As another example, say you wanted to change the display name for a particular mailing list. You could put the following function in a file called 'change.pw': @@ -236,7 +235,7 @@ mailing list. You could put the following function in a file called and run this from the command line: - % bin/mailman withlist -r change mylist@example.com 'My List'""") + % bin/mailman withlist -r change mylist@example.com 'My List'""")) diff --git a/src/mailman/commands/docs/end.rst b/src/mailman/commands/docs/end.rst index accf91b90..8cd4b2409 100644 --- a/src/mailman/commands/docs/end.rst +++ b/src/mailman/commands/docs/end.rst @@ -13,8 +13,8 @@ processing email messages. The 'end' command takes no arguments. - >>> command.argument_description - '' + >>> print 'DESCRIPTION:', command.argument_description + DESCRIPTION: The command itself is fairly simple; it just stops command processing, and the message isn't even looked at. @@ -31,7 +31,7 @@ The 'stop' command is a synonym for 'end'. stop >>> print command.description An alias for 'end'. - >>> command.argument_description - '' + >>> print 'DESCRIPTION:', command.argument_description + DESCRIPTION: >>> print command.process(mlist, Message(), {}, (), None) ContinueProcessing.no diff --git a/src/mailman/commands/eml_confirm.py b/src/mailman/commands/eml_confirm.py index 55619a503..c82dc64c3 100644 --- a/src/mailman/commands/eml_confirm.py +++ b/src/mailman/commands/eml_confirm.py @@ -17,7 +17,7 @@ """Module stuff.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.command import ContinueProcessing, IEmailCommand @@ -34,11 +34,10 @@ from mailman.interfaces.registrar import IRegistrar +@implementer(IEmailCommand) class Confirm: """The email 'confirm' command.""" - implements(IEmailCommand) - name = 'confirm' argument_description = 'token' description = _('Confirm a subscription request.') @@ -48,7 +47,7 @@ class Confirm: """See `IEmailCommand`.""" # The token must be in the arguments. if len(arguments) == 0: - print >> results, _('No confirmation token found') + print(_('No confirmation token found'), file=results) return ContinueProcessing.no # Make sure we don't try to confirm the same token more than once. token = arguments[0] @@ -60,7 +59,7 @@ class Confirm: results.confirms = tokens succeeded = getUtility(IRegistrar).confirm(token) if succeeded: - print >> results, _('Confirmed') + print(_('Confirmed'), file=results) return ContinueProcessing.yes - print >> results, _('Confirmation token did not match') + print(_('Confirmation token did not match'), file=results) return ContinueProcessing.no diff --git a/src/mailman/commands/eml_echo.py b/src/mailman/commands/eml_echo.py index 06d5ee5e7..885edcbae 100644 --- a/src/mailman/commands/eml_echo.py +++ b/src/mailman/commands/eml_echo.py @@ -25,7 +25,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.command import ContinueProcessing, IEmailCommand @@ -35,9 +35,9 @@ SPACE = ' ' +@implementer(IEmailCommand) class Echo: """The email 'echo' command.""" - implements(IEmailCommand) name = 'echo' argument_description = '[args]' diff --git a/src/mailman/commands/eml_end.py b/src/mailman/commands/eml_end.py index 3cd70813c..32a024205 100644 --- a/src/mailman/commands/eml_end.py +++ b/src/mailman/commands/eml_end.py @@ -17,6 +17,8 @@ """The email commands 'end' and 'stop'.""" +from __future__ import absolute_import, print_function, unicode_literals + __metaclass__ = type __all__ = [ 'End', @@ -24,16 +26,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.command import ContinueProcessing, IEmailCommand +@implementer(IEmailCommand) class End: """The email 'end' command.""" - implements(IEmailCommand) name = 'end' argument_description = '' diff --git a/src/mailman/commands/eml_help.py b/src/mailman/commands/eml_help.py index 6fddb4ef3..a27717179 100644 --- a/src/mailman/commands/eml_help.py +++ b/src/mailman/commands/eml_help.py @@ -25,7 +25,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -37,11 +37,10 @@ SPACE = ' ' +@implementer(IEmailCommand) class Help: """The email 'help' command.""" - implements(IEmailCommand) - name = 'help' argument_description = '[command]' description = _('Get help about available email commands.') @@ -58,14 +57,14 @@ class Help: command = config.commands[command_name] short_description = getattr( command, 'short_description', _('n/a')) - print(format.format(command.name, short_description), + print(format.format(command.name, short_description), file=results) return ContinueProcessing.yes elif len(arguments) == 1: command_name = arguments[0] command = config.commands.get(command_name) if command is None: - print(_('$self.name: no such command: $command_name'), + print(_('$self.name: no such command: $command_name'), file=results) return ContinueProcessing.no print('{0} {1}'.format(command.name, command.argument_description), diff --git a/src/mailman/commands/eml_membership.py b/src/mailman/commands/eml_membership.py index d6f7a47d9..860e42f47 100644 --- a/src/mailman/commands/eml_membership.py +++ b/src/mailman/commands/eml_membership.py @@ -30,7 +30,7 @@ __all__ = [ from email.utils import formataddr, parseaddr from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.command import ContinueProcessing, IEmailCommand @@ -41,11 +41,10 @@ from mailman.interfaces.usermanager import IUserManager +@implementer(IEmailCommand) class Join: """The email 'join' command.""" - implements(IEmailCommand) - name = 'join' # XXX 2012-02-29 BAW: DeliveryMode.summary is not yet supported. argument_description = '[digest=<no|mime|plain>]' @@ -89,7 +88,7 @@ used. if len(members) > 0: print(_('$person is already a member'), file=results) else: - getUtility(IRegistrar).register(mlist, address, + getUtility(IRegistrar).register(mlist, address, display_name, delivery_mode) print(_('Confirmation email sent to $person'), file=results) return ContinueProcessing.yes @@ -131,14 +130,13 @@ class Subscribe(Join): +@implementer(IEmailCommand) class Leave: """The email 'leave' command.""" - implements(IEmailCommand) - name = 'leave' argument_description = '' - description = _("""Leave this mailing list. + description = _("""Leave this mailing list. You may be asked to confirm your request.""") short_description = _('Leave this mailing list.') diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index 48c849148..fc82a2be7 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -32,7 +32,7 @@ from lazr.config import ConfigSchema, as_boolean from pkg_resources import resource_stream from string import Template from zope.component import getUtility -from zope.interface import Interface, implements +from zope.interface import Interface, implementer import mailman.templates @@ -52,11 +52,10 @@ class IConfiguration(Interface): +@implementer(IConfiguration) class Configuration: """The core global configuration object.""" - implements(IConfiguration) - def __init__(self): self.switchboards = {} self.QFILE_SCHEMA_VERSION = version.QFILE_SCHEMA_VERSION diff --git a/src/mailman/core/constants.py b/src/mailman/core/constants.py index 02d46a088..4562f4c74 100644 --- a/src/mailman/core/constants.py +++ b/src/mailman/core/constants.py @@ -17,7 +17,7 @@ """Various constants and enumerations.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.languages import ILanguageManager @@ -35,11 +35,10 @@ from mailman.interfaces.preferences import IPreferences +@implementer(IPreferences) class SystemDefaultPreferences: """The default system preferences.""" - implements(IPreferences) - acknowledge_posts = False hide_address = True receive_list_copy = True diff --git a/src/mailman/core/pipelines.py b/src/mailman/core/pipelines.py index 25bb68030..972417c2c 100644 --- a/src/mailman/core/pipelines.py +++ b/src/mailman/core/pipelines.py @@ -32,7 +32,7 @@ __all__ = [ import logging -from zope.interface import implements +from zope.interface import implementer from zope.interface.verify import verifyObject from mailman.app.bounces import bounce_message @@ -75,11 +75,10 @@ def process(mlist, msg, msgdata, pipeline_name='built-in'): +@implementer(IPipeline) class BasePipeline: """Base pipeline implementation.""" - implements(IPipeline) - _default_handlers = () def __init__(self): diff --git a/src/mailman/core/runner.py b/src/mailman/core/runner.py index e86741c41..a79f19fbc 100644 --- a/src/mailman/core/runner.py +++ b/src/mailman/core/runner.py @@ -17,7 +17,7 @@ """The process runner base class.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -32,14 +32,15 @@ import traceback from cStringIO import StringIO from lazr.config import as_boolean, as_timedelta from zope.component import getUtility -from zope.interface import implements +from zope.event import notify +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ from mailman.core.switchboard import Switchboard from mailman.interfaces.languages import ILanguageManager from mailman.interfaces.listmanager import IListManager -from mailman.interfaces.runner import IRunner +from mailman.interfaces.runner import IRunner, RunnerCrashEvent from mailman.utilities.string import expand @@ -48,9 +49,8 @@ elog = logging.getLogger('mailman.error') +@implementer(IRunner) class Runner: - implements(IRunner) - intercept_signals = True def __init__(self, name, slice=None): @@ -217,7 +217,12 @@ class Runner: language = mlist.preferred_language with _.using(language.code): msgdata['lang'] = language.code - keepqueued = self._dispose(mlist, msg, msgdata) + try: + keepqueued = self._dispose(mlist, msg, msgdata) + except Exception as error: + # Trigger the Zope event and re-raise + notify(RunnerCrashEvent(self, mlist, msg, msgdata, error)) + raise if keepqueued: self.switchboard.enqueue(msg, msgdata) diff --git a/src/mailman/core/switchboard.py b/src/mailman/core/switchboard.py index 7cab4f4ad..c65b92fac 100644 --- a/src/mailman/core/switchboard.py +++ b/src/mailman/core/switchboard.py @@ -24,7 +24,7 @@ written. First, the message is written to the pickle, then the metadata dictionary is written. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -40,7 +40,7 @@ import cPickle import hashlib import logging -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.email.message import Message @@ -63,8 +63,9 @@ elog = logging.getLogger('mailman.error') +@implementer(ISwitchboard) class Switchboard: - implements(ISwitchboard) + """See `ISwitchboard`.""" @staticmethod def initialize(): diff --git a/src/mailman/core/system.py b/src/mailman/core/system.py index ce66761a7..b29567827 100644 --- a/src/mailman/core/system.py +++ b/src/mailman/core/system.py @@ -17,7 +17,7 @@ """System information.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,15 +27,16 @@ __all__ = [ import sys -from zope.interface import implements +from zope.interface import implementer from mailman import version from mailman.interfaces.system import ISystem +@implementer(ISystem) class System: - implements(ISystem) + """See `ISystem`.""" @property def mailman_version(self): diff --git a/src/mailman/core/tests/test_pipelines.py b/src/mailman/core/tests/test_pipelines.py index 8f851de95..8e76cf033 100644 --- a/src/mailman/core/tests/test_pipelines.py +++ b/src/mailman/core/tests/test_pipelines.py @@ -29,7 +29,7 @@ __all__ = [ import unittest from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.app.lifecycle import create_list from mailman.config import config @@ -48,24 +48,24 @@ from mailman.testing.layers import ConfigLayer +@implementer(IHandler) class DiscardingHandler: - implements(IHandler) name = 'discarding' def process(self, mlist, msg, msgdata): raise DiscardMessage('by test handler') +@implementer(IHandler) class RejectHandler: - implements(IHandler) name = 'rejecting' def process(self, mlist, msg, msgdata): raise RejectMessage('by test handler') +@implementer(IPipeline) class DiscardingPipeline: - implements(IPipeline) name = 'test-discarding' description = 'Discarding test pipeline' @@ -73,8 +73,8 @@ class DiscardingPipeline: yield DiscardingHandler() +@implementer(IPipeline) class RejectingPipeline: - implements(IPipeline) name = 'test-rejecting' description = 'Rejectinging test pipeline' diff --git a/src/mailman/core/tests/test_runner.py b/src/mailman/core/tests/test_runner.py new file mode 100644 index 000000000..ad2548adc --- /dev/null +++ b/src/mailman/core/tests/test_runner.py @@ -0,0 +1,89 @@ +# Copyright (C) 2012 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 <http://www.gnu.org/licenses/>. + +"""Test some Runner base class behavior.""" + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'TestRunner', + ] + + +import unittest + +from mailman.app.lifecycle import create_list +from mailman.config import config +from mailman.core.runner import Runner +from mailman.interfaces.runner import RunnerCrashEvent +from mailman.testing.helpers import ( + configuration, event_subscribers, get_queue_messages, + make_testable_runner, specialized_message_from_string as mfs) +from mailman.testing.layers import ConfigLayer + + + +class CrashingRunner(Runner): + def _dispose(self, mlist, msg, msgdata): + raise RuntimeError('borked') + + + +class TestRunner(unittest.TestCase): + """Test the Runner base class behavior.""" + + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('test@example.com') + self._events = [] + + def _got_event(self, event): + self._events.append(event) + + @configuration('runner.crashing', + **{'class': 'mailman.core.tests.CrashingRunner'}) + def test_crash_event(self): + runner = make_testable_runner(CrashingRunner, 'in') + # When an exception occurs in Runner._process_one_file(), a zope.event + # gets triggered containing the exception object. + msg = mfs("""\ +From: anne@example.com +To: test@example.com +Message-ID: <ant> + +""") + config.switchboards['in'].enqueue(msg, listname='test@example.com') + with event_subscribers(self._got_event): + runner.run() + # We should now have exactly one event, which will contain the + # exception, plus additional metadata containing the mailing list, + # message, and metadata. + self.assertEqual(len(self._events), 1) + event = self._events[0] + self.assertTrue(isinstance(event, RunnerCrashEvent)) + self.assertEqual(event.mailing_list, self._mlist) + self.assertEqual(event.message['message-id'], '<ant>') + self.assertEqual(event.metadata['listname'], 'test@example.com') + self.assertTrue(isinstance(event.error, RuntimeError)) + self.assertEqual(event.error.message, 'borked') + self.assertTrue(isinstance(event.runner, CrashingRunner)) + # The message should also have ended up in the shunt queue. + shunted = get_queue_messages('shunt') + self.assertEqual(len(shunted), 1) + self.assertEqual(shunted[0].msg['message-id'], '<ant>') diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py index 34f644afa..55dee2068 100644 --- a/src/mailman/database/base.py +++ b/src/mailman/database/base.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -32,7 +32,7 @@ from lazr.config import as_boolean from pkg_resources import resource_listdir, resource_string from storm.cache import GenerationalCache from storm.locals import create_database, Store -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.database import IDatabase @@ -45,6 +45,7 @@ NL = '\n' +@implementer(IDatabase) class StormBaseDatabase: """The database base class for use with the Storm ORM. @@ -55,8 +56,6 @@ class StormBaseDatabase: # classes. TAG = '' - implements(IDatabase) - def __init__(self): self.url = None self.store = None @@ -194,7 +193,7 @@ class StormBaseDatabase: :param store: The Storm store to load the schema into. :type store: storm.locals.Store` - :param version: The schema version identifier of the form + :param version: The schema version identifier of the form YYYYMMDDHHMMSS. :type version: string :param filename: The file name containing the schema to load. Pass diff --git a/src/mailman/database/transaction.py b/src/mailman/database/transaction.py index 7a6ba00af..295f3d567 100644 --- a/src/mailman/database/transaction.py +++ b/src/mailman/database/transaction.py @@ -21,15 +21,32 @@ from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ + 'dbconnection', + 'transaction', 'transactional', ] +from contextlib import contextmanager + from mailman.config import config -class transactional: +@contextmanager +def transaction(): + """Context manager for ensuring the transaction is complete.""" + try: + yield + except: + config.db.abort() + raise + else: + config.db.commit() + + + +def transactional(function): """Decorator for transactional support. When the function this decorator wraps exits cleanly, the current @@ -38,16 +55,25 @@ class transactional: Either way, the current transaction is completed. """ - def __init__(self, function): - self._function = function + def wrapper(*args, **kws): + try: + rtn = function(*args, **kws) + config.db.commit() + return rtn + except: + config.db.abort() + raise + return wrapper - def __get__(self, obj, type=None): - def wrapper(*args, **kws): - try: - rtn = self._function(obj, *args, **kws) - config.db.commit() - return rtn - except: - config.db.abort() - raise - return wrapper + + +def dbconnection(function): + """Decorator for getting at the database connection. + + Use this to avoid having to access the global `config.db.store` + attribute. This calls the function with `store` as the first argument. + """ + def wrapper(*args, **kws): + # args[0] is self. + return function(args[0], config.db.store, *args[1:], **kws) + return wrapper diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst index 301703d52..fb819ccc4 100644 --- a/src/mailman/docs/NEWS.rst +++ b/src/mailman/docs/NEWS.rst @@ -31,6 +31,10 @@ Architecture or unverified. (LP: #975698) * A `PasswordChangeEvent` is triggered when an `IUser`'s password changes. (LP: #975700) + * When a queue runner gets an exception in its _dispose() method, a + `RunnerCrashEvent` is triggered, which contains references to the queue + runner, mailing list, message, metadata, and exception. Interested parties + can subscribe to that `zope.event` for notification. Configuration ------------- @@ -60,6 +64,7 @@ Bug fixes given by Mark Sapiro. (LP: #949924) * Fixed a typo when returning the configuration file's header match checks. (LP: #953497) + * List-Post should be NO when posting is not allowed. (LP: #987563) 3.0 beta 1 -- "The Twilight Zone" diff --git a/src/mailman/email/validate.py b/src/mailman/email/validate.py index 1861a8121..021e4c073 100644 --- a/src/mailman/email/validate.py +++ b/src/mailman/email/validate.py @@ -27,7 +27,7 @@ __all__ = [ import re -from zope.interface import implements +from zope.interface import implementer from mailman.interfaces.address import ( IEmailValidator, InvalidEmailAddressError) @@ -39,11 +39,10 @@ _badchars = re.compile(r'[][()<>|;^,\000-\037\177-\377]') +@implementer(IEmailValidator) class Validator: """An email address validator.""" - implements(IEmailValidator) - def is_valid(self, email): """See `IEmailValidator`.""" if not email or ' ' in email: diff --git a/src/mailman/handlers/acknowledge.py b/src/mailman/handlers/acknowledge.py index 0e0916337..0366f8ce6 100644 --- a/src/mailman/handlers/acknowledge.py +++ b/src/mailman/handlers/acknowledge.py @@ -20,7 +20,7 @@ This only happens if the sender has set their AcknowledgePosts attribute. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -29,7 +29,7 @@ __all__ = [ from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.email.message import UserNotification @@ -40,9 +40,9 @@ from mailman.utilities.string import oneline +@implementer(IHandler) class Acknowledge: """Send an acknowledgment.""" - implements(IHandler) name = 'acknowledge' description = _("""Send an acknowledgment of a posting.""") diff --git a/src/mailman/handlers/after_delivery.py b/src/mailman/handlers/after_delivery.py index a964804b5..0a3ba2c75 100644 --- a/src/mailman/handlers/after_delivery.py +++ b/src/mailman/handlers/after_delivery.py @@ -17,7 +17,7 @@ """Perform some bookkeeping after a successful post.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,7 +25,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.handler import IHandler @@ -33,11 +33,10 @@ from mailman.utilities.datetime import now +@implementer(IHandler) class AfterDelivery: """Perform some bookkeeping after a successful post.""" - implements(IHandler) - name = 'after-delivery' description = _('Perform some bookkeeping after a successful post.') diff --git a/src/mailman/handlers/avoid_duplicates.py b/src/mailman/handlers/avoid_duplicates.py index ffbc80c85..de1939822 100644 --- a/src/mailman/handlers/avoid_duplicates.py +++ b/src/mailman/handlers/avoid_duplicates.py @@ -23,7 +23,7 @@ has already received a copy, we either drop the message, add a duplicate warning header, or pass it through, depending on the user's preferences. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -32,7 +32,7 @@ __all__ = [ from email.utils import getaddresses, formataddr -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.handler import IHandler @@ -42,11 +42,10 @@ COMMASPACE = ', ' +@implementer(IHandler) class AvoidDuplicates: """If the user wishes it, do not send duplicates of the same message.""" - implements(IHandler) - name = 'avoid-duplicates' description = _('Suppress some duplicates of the same message.') diff --git a/src/mailman/handlers/cleanse.py b/src/mailman/handlers/cleanse.py index 605b843d0..32d3455ca 100644 --- a/src/mailman/handlers/cleanse.py +++ b/src/mailman/handlers/cleanse.py @@ -17,7 +17,7 @@ """Cleanse certain headers from all messages.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ import logging from email.utils import formataddr -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.handlers.cook_headers import uheader @@ -39,11 +39,10 @@ log = logging.getLogger('mailman.post') +@implementer(IHandler) class Cleanse: """Cleanse certain headers from all messages.""" - implements(IHandler) - name = 'cleanse' description = _('Cleanse certain headers from all messages.') diff --git a/src/mailman/handlers/cleanse_dkim.py b/src/mailman/handlers/cleanse_dkim.py index d2cd32636..bc23980b7 100644 --- a/src/mailman/handlers/cleanse_dkim.py +++ b/src/mailman/handlers/cleanse_dkim.py @@ -25,7 +25,7 @@ and it will also give the MTA the opportunity to regenerate valid keys originating at the Mailman server for the outgoing message. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -34,7 +34,7 @@ __all__ = [ from lazr.config import as_boolean -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -42,11 +42,10 @@ from mailman.interfaces.handler import IHandler +@implementer(IHandler) class CleanseDKIM: """Remove DomainKeys headers.""" - implements(IHandler) - name = 'cleanse-dkim' description = _('Remove DomainKeys headers.') diff --git a/src/mailman/handlers/cook_headers.py b/src/mailman/handlers/cook_headers.py index 5d1e416a6..535155ab7 100644 --- a/src/mailman/handlers/cook_headers.py +++ b/src/mailman/handlers/cook_headers.py @@ -17,7 +17,7 @@ """Cook a message's headers.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -30,7 +30,7 @@ import re from email.errors import HeaderParseError from email.header import Header, decode_header, make_header from email.utils import parseaddr, formataddr, getaddresses -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.handler import IHandler @@ -279,11 +279,10 @@ def ch_oneline(headerstr): +@implementer(IHandler) class CookHeaders: """Modify message headers.""" - implements(IHandler) - name = 'cook-headers' description = _('Modify message headers.') diff --git a/src/mailman/handlers/decorate.py b/src/mailman/handlers/decorate.py index d6d156048..c5fad2891 100644 --- a/src/mailman/handlers/decorate.py +++ b/src/mailman/handlers/decorate.py @@ -31,7 +31,7 @@ import logging from email.mime.text import MIMEText from urllib2 import URLError from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.email.message import Message @@ -57,7 +57,7 @@ def process(mlist, msg, msgdata): d['user_address'] = recipient d['user_delivered_to'] = member.address.original_email d['user_language'] = member.preferred_language.description - d['user_name'] = (member.user.display_name + d['user_name'] = (member.user.display_name if member.user.display_name else member.address.original_email) d['user_optionsurl'] = member.options_url @@ -232,11 +232,10 @@ def decorate(mlist, uri, extradict=None): +@implementer(IHandler) class Decorate: """Decorate a message with headers and footers.""" - implements(IHandler) - name = 'decorate' description = _('Decorate a message with headers and footers.') diff --git a/src/mailman/handlers/docs/rfc-2369.rst b/src/mailman/handlers/docs/rfc-2369.rst index 0461f27ba..875603f88 100644 --- a/src/mailman/handlers/docs/rfc-2369.rst +++ b/src/mailman/handlers/docs/rfc-2369.rst @@ -59,13 +59,14 @@ about hiding them. A list owner can turn these headers off. Messages which Mailman generates itself, such as user or owner notifications, have a reduced set of `List-` headers. Specifically, there is no `List-Post`, `List-Archive` or `Archived-At` header. +.. >>> mlist.include_rfc2369_headers = True - >>> mlist.include_list_post_header = False >>> msg = message_from_string("""\ ... From: aperson@example.com ... ... """) + >>> process(mlist, msg, dict(reduced_list_headers=True)) >>> list_headers(msg) ---start--- @@ -84,6 +85,7 @@ List-Post header Discussion lists, to which any subscriber can post, also have a `List-Post` header which contains the `mailto:` URL used to send messages to the list. + >>> mlist.include_rfc2369_headers = True >>> mlist.include_list_post_header = True >>> msg = message_from_string("""\ ... From: aperson@example.com @@ -101,6 +103,28 @@ header which contains the `mailto:` URL used to send messages to the list. <mailto:test-leave@example.com> ---end--- +Some mailing lists are announce, or one-way lists, not discussion lists. +Because the general membership cannot post to these mailing lists, the list +owner can set a flag which adds a special `List-Post` header value, according +to RFC 2369. + + >>> mlist.include_list_post_header = False + >>> msg = message_from_string("""\ + ... From: aperson@example.com + ... + ... """) + >>> process(mlist, msg, {}) + >>> list_headers(msg) + ---start--- + list-help: <mailto:test-request@example.com?subject=help> + list-id: <test.example.com> + list-post: NO + list-subscribe: <http://lists.example.com/listinfo/test@example.com>, + <mailto:test-join@example.com> + list-unsubscribe: <http://lists.example.com/listinfo/test@example.com>, + <mailto:test-leave@example.com> + ---end--- + List-Id header ============== @@ -108,6 +132,7 @@ List-Id header If the mailing list has a description, then it is included in the ``List-Id`` header. + >>> mlist.include_list_post_header = True >>> mlist.description = 'My test mailing list' >>> msg = message_from_string("""\ ... From: aperson@example.com diff --git a/src/mailman/handlers/file_recipients.py b/src/mailman/handlers/file_recipients.py index d087ff2bb..750357a90 100644 --- a/src/mailman/handlers/file_recipients.py +++ b/src/mailman/handlers/file_recipients.py @@ -17,7 +17,7 @@ """Get the normal delivery recipients from a Sendmail style :include: file.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,18 +28,17 @@ __all__ = [ import os import errno -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.handler import IHandler +@implementer(IHandler) class FileRecipients: """Get the normal delivery recipients from an include file.""" - implements(IHandler) - name = 'file-recipients' description = _('Get the normal delivery recipients from an include file.') diff --git a/src/mailman/handlers/member_recipients.py b/src/mailman/handlers/member_recipients.py index 956ea6adc..ec8ed77b1 100644 --- a/src/mailman/handlers/member_recipients.py +++ b/src/mailman/handlers/member_recipients.py @@ -31,7 +31,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core import errors @@ -42,11 +42,10 @@ from mailman.utilities.string import wrap +@implementer(IHandler) class MemberRecipients: """Calculate the regular (i.e. non-digest) recipients of the message.""" - implements(IHandler) - name = 'member-recipients' description = _('Calculate the regular recipients of the message.') diff --git a/src/mailman/handlers/mime_delete.py b/src/mailman/handlers/mime_delete.py index c9c1eb408..52fcc99fa 100644 --- a/src/mailman/handlers/mime_delete.py +++ b/src/mailman/handlers/mime_delete.py @@ -24,7 +24,7 @@ wrapping only single sections after other processing are replaced by their contents. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -42,7 +42,7 @@ 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 implements +from zope.interface import implementer from mailman.config import config from mailman.core import errors @@ -285,11 +285,10 @@ def get_file_ext(m): +@implementer(IHandler) class MIMEDelete: """Filter the MIME content of messages.""" - implements(IHandler) - name = 'mime-delete' description = _('Filter the MIME content of messages.') diff --git a/src/mailman/handlers/owner_recipients.py b/src/mailman/handlers/owner_recipients.py index e431d00cf..66c380635 100644 --- a/src/mailman/handlers/owner_recipients.py +++ b/src/mailman/handlers/owner_recipients.py @@ -25,7 +25,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -34,11 +34,10 @@ from mailman.interfaces.member import DeliveryStatus +@implementer(IHandler) class OwnerRecipients: """Calculate the owner (and moderator) recipients for -owner postings.""" - implements(IHandler) - name = 'owner-recipients' description = _('Calculate the owner and moderator recipients.') diff --git a/src/mailman/handlers/replybot.py b/src/mailman/handlers/replybot.py index 83aa40214..a25f4f30b 100644 --- a/src/mailman/handlers/replybot.py +++ b/src/mailman/handlers/replybot.py @@ -17,7 +17,7 @@ """Handler for automatic responses.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ import logging from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.email.message import UserNotification @@ -44,11 +44,10 @@ log = logging.getLogger('mailman.error') +@implementer(IHandler) class Replybot: """Send automatic responses.""" - implements(IHandler) - name = 'replybot' description = _('Send automatic responses.') diff --git a/src/mailman/handlers/rfc_2369.py b/src/mailman/handlers/rfc_2369.py index ece4e83cb..ea7b9e8dc 100644 --- a/src/mailman/handlers/rfc_2369.py +++ b/src/mailman/handlers/rfc_2369.py @@ -17,7 +17,7 @@ """RFC 2369 List-* and related headers.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ from email.utils import formataddr -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -74,9 +74,13 @@ def process(mlist, msg, msgdata): 'List-Subscribe' : subfieldfmt.format(listinfo, mlist.join_address), }) if not msgdata.get('reduced_list_headers'): - # List-Post: is controlled by a separate attribute - if mlist.include_list_post_header: - headers['List-Post'] = '<mailto:{0}>'.format(mlist.posting_address) + # List-Post: is controlled by a separate attribute, which is somewhat + # misnamed. RFC 2369 requires a value of NO if posting is not + # allowed, i.e. for an announce-only list. + list_post = ('<mailto:{0}>'.format(mlist.posting_address) + if mlist.include_list_post_header + else 'NO') + headers['List-Post'] = list_post # Add RFC 2369 and 5064 archiving headers, if archiving is enabled. if mlist.archive: for archiver in config.archivers: @@ -100,11 +104,10 @@ def process(mlist, msg, msgdata): +@implementer(IHandler) class RFC2369: """Add the RFC 2369 List-* headers.""" - implements(IHandler) - name = 'rfc-2369' description = _('Add the RFC 2369 List-* headers.') diff --git a/src/mailman/handlers/tagger.py b/src/mailman/handlers/tagger.py index 49e004a12..9d78372e6 100644 --- a/src/mailman/handlers/tagger.py +++ b/src/mailman/handlers/tagger.py @@ -17,7 +17,7 @@ """Extract topics from the original mail message.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -29,7 +29,7 @@ import re import email.iterators import email.parser -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.handler import IHandler @@ -178,11 +178,10 @@ class _ForgivingParser(email.parser.HeaderParser): +@implementer(IHandler) class Tagger: """Tag messages with topic matches.""" - implements(IHandler) - name = 'tagger' description = _('Tag messages with topic matches.') diff --git a/src/mailman/handlers/to_archive.py b/src/mailman/handlers/to_archive.py index fd5259a14..0dc5bad1a 100644 --- a/src/mailman/handlers/to_archive.py +++ b/src/mailman/handlers/to_archive.py @@ -17,7 +17,7 @@ """Add the message to the archives.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,7 +25,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -33,11 +33,10 @@ from mailman.interfaces.handler import IHandler +@implementer(IHandler) class ToArchive: """Add the message to the archives.""" - implements(IHandler) - name = 'to-archive' description = _('Add the message to the archives.') diff --git a/src/mailman/handlers/to_digest.py b/src/mailman/handlers/to_digest.py index 71511f136..8067e2c0c 100644 --- a/src/mailman/handlers/to_digest.py +++ b/src/mailman/handlers/to_digest.py @@ -17,7 +17,7 @@ """Add the message to the list's current digest.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ import os -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -39,11 +39,10 @@ from mailman.utilities.mailbox import Mailbox +@implementer(IHandler) class ToDigest: """Add the message to the digest, possibly sending it.""" - implements(IHandler) - name = 'to-digest' description = _('Add the message to the digest, possibly sending it.') diff --git a/src/mailman/handlers/to_outgoing.py b/src/mailman/handlers/to_outgoing.py index 971f87757..a212485c7 100644 --- a/src/mailman/handlers/to_outgoing.py +++ b/src/mailman/handlers/to_outgoing.py @@ -22,7 +22,7 @@ posted to the list membership. Anything else that needs to go out to some recipient should just be placed in the out queue directly. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -30,7 +30,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -38,11 +38,10 @@ from mailman.interfaces.handler import IHandler +@implementer(IHandler) class ToOutgoing: """Send the message to the outgoing queue.""" - implements(IHandler) - name = 'to-outgoing' description = _('Send the message to the outgoing queue.') diff --git a/src/mailman/handlers/to_usenet.py b/src/mailman/handlers/to_usenet.py index 021f8f9e5..79f4c9b1b 100644 --- a/src/mailman/handlers/to_usenet.py +++ b/src/mailman/handlers/to_usenet.py @@ -27,7 +27,7 @@ __all__ = [ import logging -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -39,11 +39,10 @@ log = logging.getLogger('mailman.error') +@implementer(IHandler) class ToUsenet: """Move the message to the outgoing news queue.""" - implements(IHandler) - name = 'to-usenet' description = _('Move the message to the outgoing news queue.') diff --git a/src/mailman/interfaces/database.py b/src/mailman/interfaces/database.py index 0530f83b9..040bce77c 100644 --- a/src/mailman/interfaces/database.py +++ b/src/mailman/interfaces/database.py @@ -17,7 +17,7 @@ """Interfaces for database interaction.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ ] -from zope.interface import Interface +from zope.interface import Attribute, Interface from mailman.interfaces.errors import MailmanError @@ -63,3 +63,6 @@ class IDatabase(Interface): def abort(): """Abort the current transaction.""" + + store = Attribute( + """The underlying Storm store on which you can do queries.""") diff --git a/src/mailman/interfaces/runner.py b/src/mailman/interfaces/runner.py index 4611fa3a7..9a3c9baa4 100644 --- a/src/mailman/interfaces/runner.py +++ b/src/mailman/interfaces/runner.py @@ -22,6 +22,7 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ 'IRunner', + 'RunnerCrashEvent', ] @@ -29,6 +30,18 @@ from zope.interface import Interface, Attribute +class RunnerCrashEvent: + """Triggered when a runner encounters an exception in _dispose().""" + + def __init__(self, runner, mlist, msg, metadata, error): + self.runner = runner + self.mailing_list = mlist + self.message = msg + self.metadata = metadata + self.error = error + + + class IRunner(Interface): """The runner.""" diff --git a/src/mailman/languages/language.py b/src/mailman/languages/language.py index 04f9c6639..effcf9c1b 100644 --- a/src/mailman/languages/language.py +++ b/src/mailman/languages/language.py @@ -18,7 +18,7 @@ """The representation of a language.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,16 +26,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer + from mailman.interfaces.languages import ILanguage +@implementer(ILanguage) class Language: """The representation of a language.""" - implements(ILanguage) - def __init__(self, code, charset, description): self.code = code self.charset = charset diff --git a/src/mailman/languages/manager.py b/src/mailman/languages/manager.py index ca7001102..7844bd87c 100644 --- a/src/mailman/languages/manager.py +++ b/src/mailman/languages/manager.py @@ -17,25 +17,24 @@ """Language manager.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'LanguageManager', ] -from zope.interface import implements +from zope.interface import implementer from mailman.interfaces.languages import ILanguageManager from mailman.languages.language import Language +@implementer(ILanguageManager) class LanguageManager: """Language manager.""" - implements(ILanguageManager) - def __init__(self): # Mapping from 2-letter code to Language instance. self._languages = {} diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py index a12a993a8..d8ab65a80 100644 --- a/src/mailman/model/address.py +++ b/src/mailman/model/address.py @@ -17,7 +17,7 @@ """Model for addresses.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ from email.utils import formataddr from storm.locals import DateTime, Int, Reference, Unicode from zope.event import notify -from zope.interface import implements +from zope.interface import implementer from mailman.database.model import Model from mailman.interfaces.address import AddressVerificationEvent, IAddress @@ -36,8 +36,9 @@ from mailman.utilities.datetime import now +@implementer(IAddress) class Address(Model): - implements(IAddress) + """See `IAddress`.""" id = Int(primary=True) email = Unicode() diff --git a/src/mailman/model/autorespond.py b/src/mailman/model/autorespond.py index 7b42205b4..567dcd19e 100644 --- a/src/mailman/model/autorespond.py +++ b/src/mailman/model/autorespond.py @@ -17,7 +17,7 @@ """Module stuff.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,10 +27,10 @@ __all__ = [ from storm.locals import And, Date, Desc, Int, Reference -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config 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) @@ -38,8 +38,9 @@ from mailman.utilities.datetime import today +@implementer(IAutoResponseRecord) class AutoResponseRecord(Model): - implements(IAutoResponseRecord) + """See `IAutoResponseRecord`.""" id = Int(primary=True) @@ -60,33 +61,37 @@ class AutoResponseRecord(Model): +@implementer(IAutoResponseSet) class AutoResponseSet: - implements(IAutoResponseSet) + """See `IAutoResponseSet`.""" def __init__(self, mailing_list): self._mailing_list = mailing_list - def todays_count(self, address, response_type): + @dbconnection + def todays_count(self, store, address, response_type): """See `IAutoResponseSet`.""" - return config.db.store.find( + return store.find( AutoResponseRecord, And(AutoResponseRecord.address == address, AutoResponseRecord.mailing_list == self._mailing_list, AutoResponseRecord.response_type == response_type, AutoResponseRecord.date_sent == today())).count() - def response_sent(self, address, response_type): + @dbconnection + def response_sent(self, store, address, response_type): """See `IAutoResponseSet`.""" response = AutoResponseRecord( self._mailing_list, address, response_type) - config.db.store.add(response) + store.add(response) - def last_response(self, address, response_type): + @dbconnection + def last_response(self, store, address, response_type): """See `IAutoResponseSet`.""" - results = config.db.store.find( + results = store.find( AutoResponseRecord, And(AutoResponseRecord.address == address, AutoResponseRecord.mailing_list == self._mailing_list, AutoResponseRecord.response_type == response_type) - ).order_by(Desc(AutoResponseRecord.date_sent)) + ).order_by(Desc(AutoResponseRecord.date_sent)) return (None if results.count() == 0 else results.first()) diff --git a/src/mailman/model/bans.py b/src/mailman/model/bans.py index 9dc0c51ba..b6de9336f 100644 --- a/src/mailman/model/bans.py +++ b/src/mailman/model/bans.py @@ -17,7 +17,7 @@ """Ban manager.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,16 +28,17 @@ __all__ = [ import re from storm.locals import Int, Unicode -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config from mailman.database.model import Model +from mailman.database.transaction import dbconnection from mailman.interfaces.bans import IBan, IBanManager +@implementer(IBan) class Ban(Model): - implements(IBan) + """See `IBan`.""" id = Int(primary=True) email = Unicode() @@ -50,46 +51,47 @@ class Ban(Model): +@implementer(IBanManager) class BanManager: - implements(IBanManager) + """See `IBanManager`.""" - def ban(self, email, mailing_list=None): + @dbconnection + def ban(self, store, email, mailing_list=None): """See `IBanManager`.""" - bans = config.db.store.find( - Ban, email=email, mailing_list=mailing_list) + bans = store.find(Ban, email=email, mailing_list=mailing_list) if bans.count() == 0: ban = Ban(email, mailing_list) - config.db.store.add(ban) + store.add(ban) - def unban(self, email, mailing_list=None): + @dbconnection + def unban(self, store, email, mailing_list=None): """See `IBanManager`.""" - ban = config.db.store.find( - Ban, email=email, mailing_list=mailing_list).one() + ban = store.find(Ban, email=email, mailing_list=mailing_list).one() if ban is not None: - config.db.store.remove(ban) + store.remove(ban) - def is_banned(self, email, mailing_list=None): + @dbconnection + def is_banned(self, store, email, mailing_list=None): """See `IBanManager`.""" # A specific mailing list ban is being checked, however the email # address could be banned specifically, or globally. if mailing_list is not None: # Try specific bans first. - bans = config.db.store.find( - Ban, email=email, mailing_list=mailing_list) + bans = store.find(Ban, email=email, mailing_list=mailing_list) if bans.count() > 0: return True # Try global bans next. - bans = config.db.store.find(Ban, email=email, mailing_list=None) + bans = store.find(Ban, email=email, mailing_list=None) if bans.count() > 0: return True # Now try specific mailing list bans, but with a pattern. - bans = config.db.store.find(Ban, mailing_list=mailing_list) + bans = store.find(Ban, mailing_list=mailing_list) for ban in bans: if (ban.email.startswith('^') and re.match(ban.email, email, re.IGNORECASE) is not None): return True # And now try global pattern bans. - bans = config.db.store.find(Ban, mailing_list=None) + bans = store.find(Ban, mailing_list=None) for ban in bans: if (ban.email.startswith('^') and re.match(ban.email, email, re.IGNORECASE) is not None): @@ -97,12 +99,11 @@ class BanManager: else: # The client is asking for global bans. Look up bans on the # specific email address first. - bans = config.db.store.find( - Ban, email=email, mailing_list=None) + bans = store.find(Ban, email=email, mailing_list=None) if bans.count() > 0: return True # And now look for global pattern bans. - bans = config.db.store.find(Ban, mailing_list=None) + bans = store.find(Ban, mailing_list=None) for ban in bans: if (ban.email.startswith('^') and re.match(ban.email, email, re.IGNORECASE) is not None): diff --git a/src/mailman/model/bounce.py b/src/mailman/model/bounce.py index 8c55e3d16..628e076bf 100644 --- a/src/mailman/model/bounce.py +++ b/src/mailman/model/bounce.py @@ -17,7 +17,7 @@ """Bounce support.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,10 +27,10 @@ __all__ = [ from storm.locals import Bool, Int, DateTime, Unicode -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config 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) @@ -38,8 +38,9 @@ from mailman.utilities.datetime import now +@implementer(IBounceEvent) class BounceEvent(Model): - implements(IBounceEvent) + """See `IBounceEvent`.""" id = Int(primary=True) list_name = Unicode() @@ -59,24 +60,27 @@ class BounceEvent(Model): +@implementer(IBounceProcessor) class BounceProcessor: - implements(IBounceProcessor) + """See `IBounceProcessor`.""" - def register(self, mlist, email, msg, where=None): + @dbconnection + def register(self, store, mlist, email, msg, where=None): """See `IBounceProcessor`.""" event = BounceEvent(mlist.fqdn_listname, email, msg, where) - config.db.store.add(event) + store.add(event) return event @property - def events(self): + @dbconnection + def events(self, store): """See `IBounceProcessor`.""" - for event in config.db.store.find(BounceEvent): + for event in store.find(BounceEvent): yield event @property - def unprocessed(self): + @dbconnection + def unprocessed(self, store): """See `IBounceProcessor`.""" - for event in config.db.store.find(BounceEvent, - BounceEvent.processed == False): + for event in store.find(BounceEvent, BounceEvent.processed == False): yield event diff --git a/src/mailman/model/digests.py b/src/mailman/model/digests.py index d7805ebf6..1d422ce8b 100644 --- a/src/mailman/model/digests.py +++ b/src/mailman/model/digests.py @@ -17,7 +17,7 @@ """One last digest.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ from storm.locals import Int, Reference -from zope.interface import implements +from zope.interface import implementer from mailman.database.model import Model from mailman.database.types import Enum @@ -35,8 +35,9 @@ from mailman.interfaces.member import DeliveryMode +@implementer(IOneLastDigest) class OneLastDigest(Model): - implements(IOneLastDigest) + """See `IOneLastDigest`.""" id = Int(primary=True) diff --git a/src/mailman/model/docs/pending.rst b/src/mailman/model/docs/pending.rst index 1bf1ee0e9..3d33dd5da 100644 --- a/src/mailman/model/docs/pending.rst +++ b/src/mailman/model/docs/pending.rst @@ -15,11 +15,14 @@ In order to pend an event, you first need a pending database. The pending database can add any ``IPendable`` to the database, returning a token that can be used in urls and such. +:: - >>> from zope.interface import implements + >>> from zope.interface import implementer >>> from mailman.interfaces.pending import IPendable - >>> class SimplePendable(dict): - ... implements(IPendable) + >>> @implementer(IPendable) + ... class SimplePendable(dict): + ... pass + >>> subscription = SimplePendable( ... type='subscription', ... address='aperson@example.com', diff --git a/src/mailman/model/docs/registration.rst b/src/mailman/model/docs/registration.rst index eecb3a8cd..58e9d7a86 100644 --- a/src/mailman/model/docs/registration.rst +++ b/src/mailman/model/docs/registration.rst @@ -318,12 +318,15 @@ confirm method will just return False. Likewise, if you try to confirm, through the `IUserRegistrar` interface, a token that doesn't match a registration event, you will get ``None``. However, the pending event matched with that token will still be removed. +:: >>> from mailman.interfaces.pending import IPendable - >>> from zope.interface import implements + >>> from zope.interface import implementer + + >>> @implementer(IPendable) + ... class SimplePendable(dict): + ... pass - >>> class SimplePendable(dict): - ... implements(IPendable) >>> pendable = SimplePendable(type='foo', bar='baz') >>> token = pendingdb.add(pendable) >>> registrar.confirm(token) diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py index 49c935740..de6a9005a 100644 --- a/src/mailman/model/domain.py +++ b/src/mailman/model/domain.py @@ -17,7 +17,7 @@ """Domains.""" -from __future__ import unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -29,10 +29,10 @@ __all__ = [ from urlparse import urljoin, urlparse from storm.locals import Int, Unicode from zope.event import notify -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config from mailman.database.model import Model +from mailman.database.transaction import dbconnection from mailman.interfaces.domain import ( BadDomainSpecificationError, DomainCreatedEvent, DomainCreatingEvent, DomainDeletedEvent, DomainDeletingEvent, IDomain, IDomainManager) @@ -40,11 +40,10 @@ from mailman.model.mailinglist import MailingList +@implementer(IDomain) class Domain(Model): """Domains.""" - implements(IDomain) - id = Int(primary=True) mail_host = Unicode() @@ -90,9 +89,10 @@ class Domain(Model): return urlparse(self.base_url).scheme @property - def mailing_lists(self): + @dbconnection + def mailing_lists(self, store): """See `IDomain`.""" - mailing_lists = config.db.store.find( + mailing_lists = store.find( MailingList, MailingList.mail_host == self.mail_host) for mlist in mailing_lists: @@ -114,12 +114,13 @@ class Domain(Model): +@implementer(IDomainManager) class DomainManager: """Domain manager.""" - implements(IDomainManager) - - def add(self, mail_host, + @dbconnection + def add(self, store, + mail_host, description=None, base_url=None, contact_address=None): @@ -131,20 +132,22 @@ class DomainManager: 'Duplicate email host: %s' % mail_host) notify(DomainCreatingEvent(mail_host)) domain = Domain(mail_host, description, base_url, contact_address) - config.db.store.add(domain) + store.add(domain) notify(DomainCreatedEvent(domain)) return domain - def remove(self, mail_host): + @dbconnection + def remove(self, store, mail_host): domain = self[mail_host] notify(DomainDeletingEvent(domain)) - config.db.store.remove(domain) + store.remove(domain) notify(DomainDeletedEvent(mail_host)) return domain - def get(self, mail_host, default=None): + @dbconnection + def get(self, store, mail_host, default=None): """See `IDomainManager`.""" - domains = config.db.store.find(Domain, mail_host=mail_host) + domains = store.find(Domain, mail_host=mail_host) if domains.count() < 1: return default assert domains.count() == 1, ( @@ -159,14 +162,17 @@ class DomainManager: raise KeyError(mail_host) return domain - def __len__(self): - return config.db.store.find(Domain).count() + @dbconnection + def __len__(self, store): + return store.find(Domain).count() - def __iter__(self): + @dbconnection + def __iter__(self, store): """See `IDomainManager`.""" - for domain in config.db.store.find(Domain): + for domain in store.find(Domain): yield domain - def __contains__(self, mail_host): + @dbconnection + def __contains__(self, store, mail_host): """See `IDomainManager`.""" - return config.db.store.find(Domain, mail_host=mail_host).count() > 0 + return store.find(Domain, mail_host=mail_host).count() > 0 diff --git a/src/mailman/model/language.py b/src/mailman/model/language.py index da86b326c..b593721df 100644 --- a/src/mailman/model/language.py +++ b/src/mailman/model/language.py @@ -17,7 +17,7 @@ """Model for languages.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,15 +26,16 @@ __all__ = [ from storm.locals import Int, Unicode -from zope.interface import implements +from zope.interface import implementer from mailman.database import Model from mailman.interfaces import ILanguage +@implementer(ILanguage) class Language(Model): - implements(ILanguage) + """See `ILanguage`.""" id = Int(primary=True) code = Unicode() diff --git a/src/mailman/model/listmanager.py b/src/mailman/model/listmanager.py index 0ea87a082..b4bc4b323 100644 --- a/src/mailman/model/listmanager.py +++ b/src/mailman/model/listmanager.py @@ -17,7 +17,7 @@ """A mailing list manager.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,9 +26,9 @@ __all__ = [ from zope.event import notify -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config +from mailman.database.transaction import dbconnection from mailman.interfaces.address import InvalidEmailAddressError from mailman.interfaces.listmanager import ( IListManager, ListAlreadyExistsError, ListCreatedEvent, ListCreatingEvent, @@ -38,18 +38,18 @@ from mailman.utilities.datetime import now +@implementer(IListManager) class ListManager: """An implementation of the `IListManager` interface.""" - implements(IListManager) - - def create(self, fqdn_listname): + @dbconnection + def create(self, store, fqdn_listname): """See `IListManager`.""" listname, at, hostname = fqdn_listname.partition('@') if len(hostname) == 0: raise InvalidEmailAddressError(fqdn_listname) notify(ListCreatingEvent(fqdn_listname)) - mlist = config.db.store.find( + mlist = store.find( MailingList, MailingList.list_name == listname, MailingList.mail_host == hostname).one() @@ -57,47 +57,53 @@ class ListManager: raise ListAlreadyExistsError(fqdn_listname) mlist = MailingList(fqdn_listname) mlist.created_at = now() - config.db.store.add(mlist) + store.add(mlist) notify(ListCreatedEvent(mlist)) return mlist - def get(self, fqdn_listname): + @dbconnection + def get(self, store, fqdn_listname): """See `IListManager`.""" listname, at, hostname = fqdn_listname.partition('@') - return config.db.store.find(MailingList, - list_name=listname, - mail_host=hostname).one() + return store.find(MailingList, + list_name=listname, + mail_host=hostname).one() - def delete(self, mlist): + @dbconnection + def delete(self, store, mlist): """See `IListManager`.""" fqdn_listname = mlist.fqdn_listname notify(ListDeletingEvent(mlist)) - config.db.store.remove(mlist) + store.remove(mlist) notify(ListDeletedEvent(fqdn_listname)) @property - def mailing_lists(self): + @dbconnection + def mailing_lists(self, store): """See `IListManager`.""" - for mlist in config.db.store.find(MailingList): + for mlist in store.find(MailingList): yield mlist - def __iter__(self): + @dbconnection + def __iter__(self, store): """See `IListManager`.""" - for mlist in config.db.store.find(MailingList): + for mlist in store.find(MailingList): yield mlist @property - def names(self): + @dbconnection + def names(self, store): """See `IListManager`.""" - result_set = config.db.store.find(MailingList) - for mail_host, list_name in result_set.values(MailingList.mail_host, + result_set = store.find(MailingList) + for mail_host, list_name in result_set.values(MailingList.mail_host, MailingList.list_name): yield '{0}@{1}'.format(list_name, mail_host) @property - def name_components(self): + @dbconnection + def name_components(self, store): """See `IListManager`.""" - result_set = config.db.store.find(MailingList) - for mail_host, list_name in result_set.values(MailingList.mail_host, + result_set = store.find(MailingList) + for mail_host, list_name in result_set.values(MailingList.mail_host, MailingList.list_name): yield list_name, mail_host diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py index 18c411fc4..294daa566 100644 --- a/src/mailman/model/mailinglist.py +++ b/src/mailman/model/mailinglist.py @@ -17,7 +17,7 @@ """Model for mailing lists.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -33,7 +33,7 @@ from storm.locals import ( TimeDelta, Unicode) from urlparse import urljoin from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.database.model import Model @@ -68,8 +68,9 @@ UNDERSCORE = '_' +@implementer(IMailingList) class MailingList(Model): - implements(IMailingList) + """See `IMailingList`.""" id = Int(primary=True) @@ -490,8 +491,9 @@ class MailingList(Model): +@implementer(IAcceptableAlias) class AcceptableAlias(Model): - implements(IAcceptableAlias) + """See `IAcceptableAlias`.""" id = Int(primary=True) @@ -505,8 +507,10 @@ class AcceptableAlias(Model): self.alias = alias + +@implementer(IAcceptableAliasSet) class AcceptableAliasSet: - implements(IAcceptableAliasSet) + """See `IAcceptableAliasSet`.""" def __init__(self, mailing_list): self._mailing_list = mailing_list diff --git a/src/mailman/model/member.py b/src/mailman/model/member.py index ae83fb388..b791ea0f2 100644 --- a/src/mailman/model/member.py +++ b/src/mailman/model/member.py @@ -17,7 +17,7 @@ """Model for members.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,11 +27,11 @@ __all__ = [ from storm.locals import Int, Reference, Unicode from storm.properties import UUID from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config from mailman.core.constants import system_preferences from mailman.database.model import Model +from mailman.database.transaction import dbconnection from mailman.database.types import Enum from mailman.interfaces.action import Action from mailman.interfaces.address import IAddress @@ -46,8 +46,9 @@ uid_factory = UniqueIDFactory(context='members') +@implementer(IMember) class Member(Model): - implements(IMember) + """See `IMember`.""" id = Int(primary=True) _member_id = UUID() @@ -176,7 +177,8 @@ class Member(Model): # XXX Um, this is definitely wrong return 'http://example.com/' + self.address.email - def unsubscribe(self): + @dbconnection + def unsubscribe(self, store): """See `IMember`.""" - config.db.store.remove(self.preferences) - config.db.store.remove(self) + store.remove(self.preferences) + store.remove(self) diff --git a/src/mailman/model/message.py b/src/mailman/model/message.py index 3345c64c9..190b4055c 100644 --- a/src/mailman/model/message.py +++ b/src/mailman/model/message.py @@ -17,8 +17,7 @@ """Model for messages.""" - -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,28 +25,28 @@ __all__ = [ ] from storm.locals import AutoReload, Int, RawStr, Unicode -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config from mailman.database.model import Model +from mailman.database.transaction import dbconnection from mailman.interfaces.messages import IMessage +@implementer(IMessage) class Message(Model): """A message in the message store.""" - implements(IMessage) - id = Int(primary=True, default=AutoReload) message_id = Unicode() message_id_hash = RawStr() path = RawStr() # This is a Messge-ID field representation, not a database row id. - def __init__(self, message_id, message_id_hash, path): + @dbconnection + def __init__(self, store, message_id, message_id_hash, path): super(Message, self).__init__() self.message_id = message_id self.message_id_hash = message_id_hash self.path = path - config.db.store.add(self) + store.add(self) diff --git a/src/mailman/model/messagestore.py b/src/mailman/model/messagestore.py index 59490993b..156375e6f 100644 --- a/src/mailman/model/messagestore.py +++ b/src/mailman/model/messagestore.py @@ -17,8 +17,7 @@ """Model for message stores.""" - -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -31,9 +30,10 @@ import base64 import hashlib import cPickle as pickle -from zope.interface import implements +from zope.interface import implementer from mailman.config import config +from mailman.database.transaction import dbconnection from mailman.interfaces.messages import IMessageStore from mailman.model.message import Message from mailman.utilities.filesystem import makedirs @@ -46,10 +46,12 @@ EMPTYSTRING = '' +@implementer(IMessageStore) class MessageStore: - implements(IMessageStore) + """See `IMessageStore`.""" - def add(self, message): + @dbconnection + def add(self, store, message): # Ensure that the message has the requisite headers. message_ids = message.get_all('message-id', []) if len(message_ids) <> 1: @@ -57,8 +59,7 @@ class MessageStore: # Calculate and insert the X-Message-ID-Hash. message_id = message_ids[0] # Complain if the Message-ID already exists in the storage. - existing = config.db.store.find(Message, - Message.message_id == message_id).one() + existing = store.find(Message, Message.message_id == message_id).one() if existing is not None: raise ValueError( 'Message ID already exists in message store: {0}'.format( @@ -104,34 +105,37 @@ class MessageStore: with open(path) as fp: return pickle.load(fp) - def get_message_by_id(self, message_id): - row = config.db.store.find(Message, message_id=message_id).one() + @dbconnection + def get_message_by_id(self, store, message_id): + row = store.find(Message, message_id=message_id).one() if row is None: return None return self._get_message(row) - def get_message_by_hash(self, message_id_hash): + @dbconnection + def get_message_by_hash(self, store, message_id_hash): # It's possible the hash came from a message header, in which case it # will be a Unicode. However when coming from source code, it may be # an 8-string. Coerce to the latter if necessary; it must be # US-ASCII. if isinstance(message_id_hash, unicode): message_id_hash = message_id_hash.encode('ascii') - row = config.db.store.find(Message, - message_id_hash=message_id_hash).one() + row = store.find(Message, message_id_hash=message_id_hash).one() if row is None: return None return self._get_message(row) @property - def messages(self): - for row in config.db.store.find(Message): + @dbconnection + def messages(self, store): + for row in store.find(Message): yield self._get_message(row) - def delete_message(self, message_id): - row = config.db.store.find(Message, message_id=message_id).one() + @dbconnection + def delete_message(self, store, message_id): + row = store.find(Message, message_id=message_id).one() if row is None: raise LookupError(message_id) path = os.path.join(config.MESSAGES_DIR, row.path) os.remove(path) - config.db.store.remove(row) + store.remove(row) diff --git a/src/mailman/model/mime.py b/src/mailman/model/mime.py index c611aab89..462bb9016 100644 --- a/src/mailman/model/mime.py +++ b/src/mailman/model/mime.py @@ -15,9 +15,9 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. -"""Module stuff.""" +"""The content filter.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ from storm.locals import Int, Reference, Unicode -from zope.interface import implements +from zope.interface import implementer from mailman.database.model import Model from mailman.database.types import Enum @@ -34,9 +34,9 @@ from mailman.interfaces.mime import IContentFilter, FilterType +@implementer(IContentFilter) class ContentFilter(Model): """A single filter criteria.""" - implements(IContentFilter) id = Int(primary=True) diff --git a/src/mailman/model/pending.py b/src/mailman/model/pending.py index 557361c6f..727e4f754 100644 --- a/src/mailman/model/pending.py +++ b/src/mailman/model/pending.py @@ -17,7 +17,7 @@ """Implementations of the IPendable and IPending interfaces.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -32,11 +32,12 @@ import hashlib from lazr.config import as_timedelta from storm.locals import DateTime, Int, RawStr, ReferenceSet, Unicode -from zope.interface import implements +from zope.interface import implementer from zope.interface.verify import verifyObject from mailman.config import config from mailman.database.model import Model +from mailman.database.transaction import dbconnection from mailman.interfaces.pending import ( IPendable, IPended, IPendedKeyValue, IPendings) from mailman.utilities.datetime import now @@ -44,11 +45,10 @@ from mailman.utilities.modules import call_name +@implementer(IPendedKeyValue) class PendedKeyValue(Model): """A pended key/value pair, tied to a token.""" - implements(IPendedKeyValue) - def __init__(self, key, value): self.key = key self.value = value @@ -59,11 +59,11 @@ class PendedKeyValue(Model): pended_id = Int() + +@implementer(IPended) class Pended(Model): """A pended event, tied to a token.""" - implements(IPended) - def __init__(self, token, expiration_date): super(Pended, self).__init__() self.token = token @@ -76,17 +76,18 @@ class Pended(Model): +@implementer(IPendable) class UnpendedPendable(dict): - implements(IPendable) + pass +@implementer(IPendings) class Pendings: """Implementation of the IPending interface.""" - implements(IPendings) - - def add(self, pendable, lifetime=None): + @dbconnection + def add(self, store, pendable, lifetime=None): verifyObject(IPendable, pendable) # Calculate the token and the lifetime. if lifetime is None: @@ -104,7 +105,7 @@ class Pendings: token = hashlib.sha1(repr(x)).hexdigest() # In practice, we'll never get a duplicate, but we'll be anal # about checking anyway. - if config.db.store.find(Pended, token=token).count() == 0: + if store.find(Pended, token=token).count() == 0: break else: raise AssertionError('Could not find a valid pendings token') @@ -129,11 +130,11 @@ class Pendings: '\2'.join(value)) keyval = PendedKeyValue(key=key, value=value) pending.key_values.add(keyval) - config.db.store.add(pending) + store.add(pending) return token - def confirm(self, token, expunge=True): - store = config.db.store + @dbconnection + def confirm(self, store, token, expunge=True): # Token can come in as a unicode, but it's stored in the database as # bytes. They must be ascii. pendings = store.find(Pended, token=str(token)) @@ -158,8 +159,8 @@ class Pendings: store.remove(pending) return pendable - def evict(self): - store = config.db.store + @dbconnection + def evict(self, store): right_now = now() for pending in store.find(Pended): if pending.expiration_date < right_now: diff --git a/src/mailman/model/preferences.py b/src/mailman/model/preferences.py index 234c7399e..fdc30a94d 100644 --- a/src/mailman/model/preferences.py +++ b/src/mailman/model/preferences.py @@ -17,7 +17,7 @@ """Model for preferences.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ from storm.locals import Bool, Int, Unicode from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.database.model import Model from mailman.database.types import Enum @@ -37,8 +37,9 @@ from mailman.interfaces.preferences import IPreferences +@implementer(IPreferences) class Preferences(Model): - implements(IPreferences) + """See `IPreferences`.""" id = Int(primary=True) acknowledge_posts = Bool() diff --git a/src/mailman/model/requests.py b/src/mailman/model/requests.py index 4a3efa67f..a92332e4a 100644 --- a/src/mailman/model/requests.py +++ b/src/mailman/model/requests.py @@ -17,7 +17,7 @@ """Implementations of the pending requests interfaces.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,52 +27,56 @@ __all__ = [ from datetime import timedelta from storm.locals import AutoReload, Int, RawStr, Reference, Unicode from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config from mailman.database.model import Model +from mailman.database.transaction import dbconnection from mailman.database.types import Enum from mailman.interfaces.pending import IPendable, IPendings from mailman.interfaces.requests import IListRequests, RequestType +@implementer(IPendable) class DataPendable(dict): - implements(IPendable) + pass +@implementer(IListRequests) class ListRequests: - implements(IListRequests) def __init__(self, mailing_list): self.mailing_list = mailing_list @property - def count(self): - return config.db.store.find( - _Request, mailing_list=self.mailing_list).count() + @dbconnection + def count(self, store): + return store.find(_Request, mailing_list=self.mailing_list).count() - def count_of(self, request_type): - return config.db.store.find( + @dbconnection + def count_of(self, store, request_type): + return store.find( _Request, mailing_list=self.mailing_list, request_type=request_type).count() @property - def held_requests(self): - results = config.db.store.find( - _Request, mailing_list=self.mailing_list) + @dbconnection + def held_requests(self, store): + results = store.find(_Request, mailing_list=self.mailing_list) for request in results: yield request - def of_type(self, request_type): - results = config.db.store.find( + @dbconnection + def of_type(self, store, request_type): + results = store.find( _Request, mailing_list=self.mailing_list, request_type=request_type) for request in results: yield request - def hold_request(self, request_type, key, data=None): + @dbconnection + def hold_request(self, store, request_type, key, data=None): if request_type not in RequestType: raise TypeError(request_type) if data is None: @@ -87,11 +91,12 @@ class ListRequests: token = getUtility(IPendings).add(pendable, timedelta(days=5000)) data_hash = token request = _Request(key, request_type, self.mailing_list, data_hash) - config.db.store.add(request) + store.add(request) return request.id - def get_request(self, request_id, request_type=None): - result = config.db.store.get(_Request, request_id) + @dbconnection + def get_request(self, store, request_id, request_type=None): + result = store.get(_Request, request_id) if result is None: return None if request_type is not None and result.request_type != request_type: @@ -104,13 +109,14 @@ class ListRequests: data.update(pendable) return result.key, data - def delete_request(self, request_id): - request = config.db.store.get(_Request, request_id) + @dbconnection + def delete_request(self, store, request_id): + request = store.get(_Request, request_id) if request is None: raise KeyError(request_id) # Throw away the pended data. getUtility(IPendings).confirm(request.data_hash) - config.db.store.remove(request) + store.remove(request) diff --git a/src/mailman/model/roster.py b/src/mailman/model/roster.py index 48d434ab1..56dad4bc8 100644 --- a/src/mailman/model/roster.py +++ b/src/mailman/model/roster.py @@ -22,7 +22,7 @@ the ones that fit a particular role. These are used as the member, owner, moderator, and administrator roster filters. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -38,9 +38,9 @@ __all__ = [ from storm.expr import And, Or -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config +from mailman.database.transaction import dbconnection from mailman.interfaces.member import DeliveryMode, MemberRole from mailman.interfaces.roster import IRoster from mailman.model.address import Address @@ -48,6 +48,7 @@ from mailman.model.member import Member +@implementer(IRoster) class AbstractRoster: """An abstract IRoster class. @@ -57,15 +58,14 @@ class AbstractRoster: This requires that subclasses implement the 'members' property. """ - implements(IRoster) - role = None def __init__(self, mlist): self._mlist = mlist - def _query(self): - return config.db.store.find( + @dbconnection + def _query(self, store): + return store.find( Member, mailing_list=self._mlist.fqdn_listname, role=self.role) @@ -101,9 +101,10 @@ class AbstractRoster: for member in self.members: yield member.address - def get_member(self, address): + @dbconnection + def get_member(self, store, address): """See `IRoster`.""" - results = config.db.store.find( + results = store.find( Member, Member.mailing_list == self._mlist.fqdn_listname, Member.role == self.role, @@ -157,16 +158,18 @@ class AdministratorRoster(AbstractRoster): name = 'administrator' - def _query(self): - return config.db.store.find( + @dbconnection + def _query(self, store): + return store.find( Member, Member.mailing_list == self._mlist.fqdn_listname, Or(Member.role == MemberRole.owner, Member.role == MemberRole.moderator)) - def get_member(self, address): + @dbconnection + def get_member(self, store, address): """See `IRoster`.""" - results = config.db.store.find( + results = store.find( Member, Member.mailing_list == self._mlist.fqdn_listname, Or(Member.role == MemberRole.moderator, @@ -194,7 +197,8 @@ class DeliveryMemberRoster(AbstractRoster): # checking the delivery mode to a query parameter. return len(tuple(self.members)) - def _get_members(self, *delivery_modes): + @dbconnection + def _get_members(self, store, *delivery_modes): """The set of members for a mailing list, filter by delivery mode. :param delivery_modes: The modes to filter on. @@ -202,7 +206,7 @@ class DeliveryMemberRoster(AbstractRoster): :return: A generator of members. :rtype: generator """ - results = config.db.store.find( + results = store.find( Member, And(Member.mailing_list == self._mlist.fqdn_listname, Member.role == MemberRole.member)) @@ -244,25 +248,24 @@ class Subscribers(AbstractRoster): name = 'subscribers' - def _query(self): - return config.db.store.find( - Member, - mailing_list=self._mlist.fqdn_listname) + @dbconnection + def _query(self, store): + return store.find(Member, mailing_list=self._mlist.fqdn_listname) +@implementer(IRoster) class Memberships: """A roster of a single user's memberships.""" - implements(IRoster) - name = 'memberships' def __init__(self, user): self._user = user - def _query(self): - results = config.db.store.find( + @dbconnection + def _query(self, store): + results = store.find( Member, Or(Member.user_id == self._user.id, And(Address.user_id == self._user.id, @@ -291,9 +294,10 @@ class Memberships: for address in self._user.addresses: yield address - def get_member(self, address): + @dbconnection + def get_member(self, store, address): """See `IRoster`.""" - results = config.db.store.find( + results = store.find( Member, Member.address_id == Address.id, Address.user_id == self._user.id) diff --git a/src/mailman/model/tests/test_bounce.py b/src/mailman/model/tests/test_bounce.py index da2b661ea..377fab4cc 100644 --- a/src/mailman/model/tests/test_bounce.py +++ b/src/mailman/model/tests/test_bounce.py @@ -17,7 +17,7 @@ """Test bounce model objects.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -30,7 +30,7 @@ from datetime import datetime from zope.component import getUtility from mailman.app.lifecycle import create_list -from mailman.config import config +from mailman.database.transaction import transaction from mailman.interfaces.bounce import BounceContext, IBounceProcessor from mailman.testing.helpers import ( specialized_message_from_string as message_from_string) @@ -52,8 +52,9 @@ Message-Id: <first> """) def test_events_iterator(self): - self._processor.register(self._mlist, 'anne@example.com', self._msg) - config.db.commit() + with transaction(): + self._processor.register( + self._mlist, 'anne@example.com', self._msg) events = list(self._processor.events) self.assertEqual(len(events), 1) event = events[0] @@ -75,23 +76,25 @@ Message-Id: <first> self.assertEqual(event.processed, False) def test_unprocessed_events_iterator(self): - self._processor.register(self._mlist, 'anne@example.com', self._msg) - self._processor.register(self._mlist, 'bart@example.com', self._msg) - config.db.commit() + with transaction(): + self._processor.register( + self._mlist, 'anne@example.com', self._msg) + self._processor.register( + self._mlist, 'bart@example.com', self._msg) events = list(self._processor.events) self.assertEqual(len(events), 2) unprocessed = list(self._processor.unprocessed) # The unprocessed list will be exactly the same right now. self.assertEqual(len(unprocessed), 2) # Process one of the events. - events[0].processed = True - config.db.commit() + with transaction(): + events[0].processed = True # Now there will be only one unprocessed event. unprocessed = list(self._processor.unprocessed) self.assertEqual(len(unprocessed), 1) # Process the other event. - events[1].processed = True - config.db.commit() + with transaction(): + events[1].processed = True # Now there will be no unprocessed events. unprocessed = list(self._processor.unprocessed) self.assertEqual(len(unprocessed), 0) diff --git a/src/mailman/model/uid.py b/src/mailman/model/uid.py index c3564aa40..08eae8aff 100644 --- a/src/mailman/model/uid.py +++ b/src/mailman/model/uid.py @@ -17,7 +17,7 @@ """Unique IDs.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,8 +28,8 @@ __all__ = [ from storm.locals import Int from storm.properties import UUID -from mailman.config import config from mailman.database.model import Model +from mailman.database.transaction import dbconnection @@ -48,23 +48,29 @@ class UID(Model): id = Int(primary=True) uid = UUID() - def __init__(self, uid): + @dbconnection + def __init__(self, store, uid): super(UID, self).__init__() self.uid = uid - config.db.store.add(self) + store.add(self) def __repr__(self): return '<UID {0} at {1}>'.format(self.uid, id(self)) @staticmethod - def record(uid): + @dbconnection + # Note that the parameter order is deliberate reversed here. Normally, + # `store` is the first parameter after `self`, but since this is a + # staticmethod and there is no self, the decorator will see the uid in + # arg[0]. + def record(uid, store): """Record the uid in the database. :param uid: The unique id. :type uid: unicode :raises ValueError: if the id is not unique. """ - existing = config.db.store.find(UID, uid=uid) + existing = store.find(UID, uid=uid) if existing.count() != 0: raise ValueError(uid) return UID(uid) diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py index 9ca9b5aea..a723df44e 100644 --- a/src/mailman/model/user.py +++ b/src/mailman/model/user.py @@ -17,7 +17,7 @@ """Model for users.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,10 +28,10 @@ from storm.locals import ( DateTime, Int, RawStr, Reference, ReferenceSet, Unicode) from storm.properties import UUID from zope.event import notify -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config from mailman.database.model import Model +from mailman.database.transaction import dbconnection from mailman.interfaces.address import ( AddressAlreadyLinkedError, AddressNotLinkedError) from mailman.interfaces.user import ( @@ -47,11 +47,10 @@ uid_factory = UniqueIDFactory(context='users') +@implementer(IUser) class User(Model): """Mailman users.""" - implements(IUser) - id = Int(primary=True) display_name = Unicode() _password = RawStr(name='password') @@ -64,16 +63,17 @@ class User(Model): preferences_id = Int() preferences = Reference(preferences_id, 'Preferences.id') - def __init__(self, display_name=None, preferences=None): + @dbconnection + def __init__(self, store, display_name=None, preferences=None): super(User, self).__init__() self._created_on = date_factory.now() user_id = uid_factory.new_uid() - assert config.db.store.find(User, _user_id=user_id).count() == 0, ( + assert store.find(User, _user_id=user_id).count() == 0, ( 'Duplicate user id {0}'.format(user_id)) self._user_id = user_id self.display_name = ('' if display_name is None else display_name) self.preferences = preferences - config.db.store.add(self) + store.add(self) def __repr__(self): short_user_id = self.user_id.int @@ -135,18 +135,20 @@ class User(Model): """See `IUser`.""" self._preferred_address = None - def controls(self, email): + @dbconnection + def controls(self, store, email): """See `IUser`.""" - found = config.db.store.find(Address, email=email) + found = store.find(Address, email=email) if found.count() == 0: return False assert found.count() == 1, 'Unexpected count' return found[0].user is self - def register(self, email, display_name=None): + @dbconnection + def register(self, store, email, display_name=None): """See `IUser`.""" # First, see if the address already exists - address = config.db.store.find(Address, email=email).one() + address = store.find(Address, email=email).one() if address is None: if display_name is None: display_name = '' diff --git a/src/mailman/model/usermanager.py b/src/mailman/model/usermanager.py index c8a5c65a2..4c7daaa59 100644 --- a/src/mailman/model/usermanager.py +++ b/src/mailman/model/usermanager.py @@ -17,7 +17,7 @@ """A user manager.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,9 +25,9 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer -from mailman.config import config +from mailman.database.transaction import dbconnection from mailman.interfaces.address import ExistingAddressError from mailman.interfaces.usermanager import IUserManager from mailman.model.address import Address @@ -37,8 +37,9 @@ from mailman.model.user import User +@implementer(IUserManager) class UserManager: - implements(IUserManager) + """See `IUserManager`.""" def create_user(self, email=None, display_name=None): """See `IUserManager`.""" @@ -48,33 +49,38 @@ class UserManager: user.link(address) return user - def delete_user(self, user): + @dbconnection + def delete_user(self, store, user): """See `IUserManager`.""" - config.db.store.remove(user) + store.remove(user) - def get_user(self, email): + @dbconnection + def get_user(self, store, email): """See `IUserManager`.""" - addresses = config.db.store.find(Address, email=email.lower()) + addresses = store.find(Address, email=email.lower()) if addresses.count() == 0: return None return addresses.one().user - def get_user_by_id(self, user_id): + @dbconnection + def get_user_by_id(self, store, user_id): """See `IUserManager`.""" - users = config.db.store.find(User, _user_id=user_id) + users = store.find(User, _user_id=user_id) if users.count() == 0: return None return users.one() @property - def users(self): + @dbconnection + def users(self, store): """See `IUserManager`.""" - for user in config.db.store.find(User): + for user in store.find(User): yield user - def create_address(self, email, display_name=None): + @dbconnection + def create_address(self, store, email, display_name=None): """See `IUserManager`.""" - addresses = config.db.store.find(Address, email=email.lower()) + addresses = store.find(Address, email=email.lower()) if addresses.count() == 1: found = addresses[0] raise ExistingAddressError(found.original_email) @@ -85,32 +91,36 @@ class UserManager: # constructor will do the right thing. address = Address(email, display_name) address.preferences = Preferences() - config.db.store.add(address) + store.add(address) return address - def delete_address(self, address): + @dbconnection + def delete_address(self, store, address): """See `IUserManager`.""" # If there's a user controlling this address, it has to first be # unlinked before the address can be deleted. if address.user: address.user.unlink(address) - config.db.store.remove(address) + store.remove(address) - def get_address(self, email): + @dbconnection + def get_address(self, store, email): """See `IUserManager`.""" - addresses = config.db.store.find(Address, email=email.lower()) + addresses = store.find(Address, email=email.lower()) if addresses.count() == 0: return None return addresses.one() @property - def addresses(self): + @dbconnection + def addresses(self, store): """See `IUserManager`.""" - for address in config.db.store.find(Address): + for address in store.find(Address): yield address @property - def members(self): + @dbconnection + def members(self, store): """See `IUserManager.""" - for member in config.db.store.find(Member): + for member in store.find(Member): yield member diff --git a/src/mailman/mta/aliases.py b/src/mailman/mta/aliases.py index 573160d3c..e87f5880f 100644 --- a/src/mailman/mta/aliases.py +++ b/src/mailman/mta/aliases.py @@ -17,7 +17,7 @@ """Utility for generating all the aliases of a mailing list.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,7 +25,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.interfaces.mta import IMailTransportAgentAliases @@ -43,11 +43,10 @@ SUBDESTINATIONS = ( +@implementer(IMailTransportAgentAliases) class MailTransportAgentAliases: """Utility for generating all the aliases of a mailing list.""" - implements(IMailTransportAgentAliases) - def aliases(self, mlist): """See `IMailTransportAgentAliases`.""" # Always return diff --git a/src/mailman/mta/base.py b/src/mailman/mta/base.py index 8068eaec1..e11c37ff6 100644 --- a/src/mailman/mta/base.py +++ b/src/mailman/mta/base.py @@ -17,7 +17,7 @@ """Base delivery class.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -31,7 +31,7 @@ import socket import logging import smtplib -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.mta import IMailTransportAgentDelivery @@ -42,11 +42,10 @@ log = logging.getLogger('mailman.smtp') +@implementer(IMailTransportAgentDelivery) class BaseDelivery: """Base delivery class.""" - implements(IMailTransportAgentDelivery) - def __init__(self): """Create a basic deliverer.""" username = (config.mta.smtp_user if config.mta.smtp_user else None) diff --git a/src/mailman/mta/null.py b/src/mailman/mta/null.py index 6b2f76a1c..c94fa1015 100644 --- a/src/mailman/mta/null.py +++ b/src/mailman/mta/null.py @@ -20,7 +20,7 @@ Exim one example of an MTA that Just Works. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,16 +28,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer + from mailman.interfaces.mta import IMailTransportAgentLifecycle +@implementer(IMailTransportAgentLifecycle) class NullMTA: """Null MTA that just satisfies the interface.""" - implements(IMailTransportAgentLifecycle) - def create(self, mlist): """See `IMailTransportAgentLifecycle`.""" pass diff --git a/src/mailman/mta/postfix.py b/src/mailman/mta/postfix.py index 32bdb8268..c04e38f02 100644 --- a/src/mailman/mta/postfix.py +++ b/src/mailman/mta/postfix.py @@ -17,7 +17,7 @@ """Creation/deletion hooks for the Postfix MTA.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -31,7 +31,7 @@ import logging from flufl.lock import Lock from operator import attrgetter from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.interfaces.listmanager import IListManager @@ -55,11 +55,10 @@ class _FakeList: +@implementer(IMailTransportAgentLifecycle) class LMTP: """Connect Mailman to Postfix via LMTP.""" - implements(IMailTransportAgentLifecycle) - def create(self, mlist): """See `IMailTransportAgentLifecycle`.""" # We can ignore the mlist argument because for LMTP delivery, we just @@ -117,23 +116,24 @@ class LMTP: for list_name, mail_host in list_manager.name_components: mlist = _FakeList(list_name, mail_host) by_domain.setdefault(mlist.mail_host, []).append(mlist) - print >> fp, """\ + print("""\ # AUTOMATICALLY GENERATED BY MAILMAN ON {0} # # This file is generated by Mailman, and is kept in sync with the binary hash # file. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you know what you're # doing, and can keep the two files properly in sync. If you screw it up, # you're on your own. -""".format(now().replace(microsecond=0)) +""".format(now().replace(microsecond=0)), file=fp) sort_key = attrgetter('list_name') for domain in sorted(by_domain): - print >> fp, """\ -# Aliases which are visible only in the @{0} domain.""".format(domain) + print("""\ +# Aliases which are visible only in the @{0} domain.""".format(domain), + file=fp) for mlist in sorted(by_domain[domain], key=sort_key): utility = getUtility(IMailTransportAgentAliases) aliases = list(utility.aliases(mlist)) width = max(len(alias) for alias in aliases) + 3 - print >> fp, ALIASTMPL.format(aliases.pop(0), config, width) + print(ALIASTMPL.format(aliases.pop(0), config, width), file=fp) for alias in aliases: - print >> fp, ALIASTMPL.format(alias, config, width) - print >> fp + print(ALIASTMPL.format(alias, config, width), file=fp) + print(file=fp) diff --git a/src/mailman/rest/tests/test_addresses.py b/src/mailman/rest/tests/test_addresses.py index f4e7cab62..385b83912 100644 --- a/src/mailman/rest/tests/test_addresses.py +++ b/src/mailman/rest/tests/test_addresses.py @@ -17,10 +17,11 @@ """REST address tests.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ + 'TestAddresses', ] @@ -29,7 +30,7 @@ import unittest from urllib2 import HTTPError from mailman.app.lifecycle import create_list -from mailman.config import config +from mailman.database.transaction import transaction from mailman.testing.helpers import call_api from mailman.testing.layers import RESTLayer @@ -39,8 +40,8 @@ class TestAddresses(unittest.TestCase): layer = RESTLayer def setUp(self): - self._mlist = create_list('test@example.com') - config.db.commit() + with transaction(): + self._mlist = create_list('test@example.com') def test_membership_of_missing_address(self): # Try to get the memberships of a missing address. diff --git a/src/mailman/rest/tests/test_domains.py b/src/mailman/rest/tests/test_domains.py index 89cc34630..a86768481 100644 --- a/src/mailman/rest/tests/test_domains.py +++ b/src/mailman/rest/tests/test_domains.py @@ -17,10 +17,11 @@ """REST domain tests.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ + 'TestDomains', ] @@ -30,7 +31,7 @@ from urllib2 import HTTPError from zope.component import getUtility from mailman.app.lifecycle import create_list -from mailman.config import config +from mailman.database.transaction import transaction from mailman.interfaces.listmanager import IListManager from mailman.testing.helpers import call_api from mailman.testing.layers import RESTLayer @@ -41,8 +42,8 @@ class TestDomains(unittest.TestCase): layer = RESTLayer def setUp(self): - self._mlist = create_list('test@example.com') - config.db.commit() + with transaction(): + self._mlist = create_list('test@example.com') def test_bogus_endpoint_extension(self): # /domains/<domain>/lists/<anything> is not a valid endpoint. @@ -67,8 +68,8 @@ class TestDomains(unittest.TestCase): def test_lists_are_deleted_when_domain_is_deleted(self): # /domains/<domain> DELETE removes all associated mailing lists. - create_list('ant@example.com') - config.db.commit() + with transaction(): + create_list('ant@example.com') content, response = call_api( 'http://localhost:9001/3.0/domains/example.com', method='DELETE') self.assertEqual(response.status, 204) diff --git a/src/mailman/rest/tests/test_lists.py b/src/mailman/rest/tests/test_lists.py index b030a2e8d..cd0ebaf8e 100644 --- a/src/mailman/rest/tests/test_lists.py +++ b/src/mailman/rest/tests/test_lists.py @@ -32,7 +32,7 @@ from urllib2 import HTTPError from zope.component import getUtility from mailman.app.lifecycle import create_list -from mailman.config import config +from mailman.database.transaction import transaction from mailman.interfaces.usermanager import IUserManager from mailman.testing.helpers import call_api from mailman.testing.layers import RESTLayer @@ -96,8 +96,8 @@ class TestLists(unittest.TestCase): layer = RESTLayer def setUp(self): - self._mlist = create_list('test@example.com') - config.db.commit() + with transaction(): + self._mlist = create_list('test@example.com') self._usermanager = getUtility(IUserManager) def test_member_count_with_no_members(self): @@ -109,9 +109,9 @@ class TestLists(unittest.TestCase): def test_member_count_with_one_member(self): # Add a member to a list and check that the resource reflects this. - anne = self._usermanager.create_address('anne@example.com') - self._mlist.subscribe(anne) - config.db.commit() + with transaction(): + anne = self._usermanager.create_address('anne@example.com') + self._mlist.subscribe(anne) resource, response = call_api( 'http://localhost:9001/3.0/lists/test@example.com') self.assertEqual(response.status, 200) @@ -119,11 +119,11 @@ class TestLists(unittest.TestCase): def test_member_count_with_two_members(self): # Add two members to a list and check that the resource reflects this. - anne = self._usermanager.create_address('anne@example.com') - self._mlist.subscribe(anne) - bart = self._usermanager.create_address('bar@example.com') - self._mlist.subscribe(bart) - config.db.commit() + with transaction(): + anne = self._usermanager.create_address('anne@example.com') + self._mlist.subscribe(anne) + bart = self._usermanager.create_address('bar@example.com') + self._mlist.subscribe(bart) resource, response = call_api( 'http://localhost:9001/3.0/lists/test@example.com') self.assertEqual(response.status, 200) diff --git a/src/mailman/rest/tests/test_membership.py b/src/mailman/rest/tests/test_membership.py index 202e5f057..875f5e254 100644 --- a/src/mailman/rest/tests/test_membership.py +++ b/src/mailman/rest/tests/test_membership.py @@ -17,10 +17,11 @@ """REST membership tests.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ + 'TestMembership', ] @@ -31,6 +32,7 @@ from zope.component import getUtility from mailman.app.lifecycle import create_list from mailman.config import config +from mailman.database.transaction import transaction from mailman.interfaces.usermanager import IUserManager from mailman.testing.helpers import call_api from mailman.testing.layers import RESTLayer @@ -42,8 +44,8 @@ class TestMembership(unittest.TestCase): layer = RESTLayer def setUp(self): - self._mlist = create_list('test@example.com') - config.db.commit() + with transaction(): + self._mlist = create_list('test@example.com') self._usermanager = getUtility(IUserManager) def test_try_to_join_missing_list(self): @@ -85,9 +87,9 @@ class TestMembership(unittest.TestCase): raise AssertionError('Expected HTTPError') def test_try_to_leave_a_list_twice(self): - anne = self._usermanager.create_address('anne@example.com') - self._mlist.subscribe(anne) - config.db.commit() + with transaction(): + anne = self._usermanager.create_address('anne@example.com') + self._mlist.subscribe(anne) url = 'http://localhost:9001/3.0/members/1' content, response = call_api(url, method='DELETE') # For a successful DELETE, the response code is 204 and there is no @@ -104,9 +106,9 @@ class TestMembership(unittest.TestCase): raise AssertionError('Expected HTTPError') def test_try_to_join_a_list_twice(self): - anne = self._usermanager.create_address('anne@example.com') - self._mlist.subscribe(anne) - config.db.commit() + with transaction(): + anne = self._usermanager.create_address('anne@example.com') + self._mlist.subscribe(anne) try: # For Python 2.6. call_api('http://localhost:9001/3.0/members', { @@ -151,12 +153,12 @@ class TestMembership(unittest.TestCase): self.assertEqual(members[0].address.email, 'hugh/person@example.com') def test_join_as_user_with_preferred_address(self): - anne = self._usermanager.create_user('anne@example.com') - preferred = list(anne.addresses)[0] - preferred.verified_on = now() - anne.preferred_address = preferred - self._mlist.subscribe(anne) - config.db.commit() + with transaction(): + anne = self._usermanager.create_user('anne@example.com') + preferred = list(anne.addresses)[0] + preferred.verified_on = now() + anne.preferred_address = preferred + self._mlist.subscribe(anne) content, response = call_api('http://localhost:9001/3.0/members') self.assertEqual(response.status, 200) self.assertEqual(int(content['total_size']), 1) @@ -169,12 +171,12 @@ class TestMembership(unittest.TestCase): self.assertEqual(entry_0['fqdn_listname'], 'test@example.com') def test_member_changes_preferred_address(self): - anne = self._usermanager.create_user('anne@example.com') - preferred = list(anne.addresses)[0] - preferred.verified_on = now() - anne.preferred_address = preferred - self._mlist.subscribe(anne) - config.db.commit() + with transaction(): + anne = self._usermanager.create_user('anne@example.com') + preferred = list(anne.addresses)[0] + preferred.verified_on = now() + anne.preferred_address = preferred + self._mlist.subscribe(anne) # Take a look at Anne's current membership. content, response = call_api('http://localhost:9001/3.0/members') self.assertEqual(int(content['total_size']), 1) @@ -182,10 +184,10 @@ class TestMembership(unittest.TestCase): self.assertEqual(entry_0['address'], 'anne@example.com') # Anne registers a new address and makes it her preferred address. # There are no changes to her membership. - new_preferred = anne.register('aperson@example.com') - new_preferred.verified_on = now() - anne.preferred_address = new_preferred - config.db.commit() + with transaction(): + new_preferred = anne.register('aperson@example.com') + new_preferred.verified_on = now() + anne.preferred_address = new_preferred # Take another look at Anne's current membership. content, response = call_api('http://localhost:9001/3.0/members') self.assertEqual(int(content['total_size']), 1) @@ -214,9 +216,9 @@ class TestMembership(unittest.TestCase): def test_patch_member_bogus_attribute(self): # /members/<id> PATCH 'bogus' returns 400 - anne = self._usermanager.create_address('anne@example.com') - self._mlist.subscribe(anne) - config.db.commit() + with transaction(): + anne = self._usermanager.create_address('anne@example.com') + self._mlist.subscribe(anne) try: # For Python 2.6 call_api('http://localhost:9001/3.0/members/1', { diff --git a/src/mailman/rest/tests/test_moderation.py b/src/mailman/rest/tests/test_moderation.py index 79b0c8b80..dfcedef05 100644 --- a/src/mailman/rest/tests/test_moderation.py +++ b/src/mailman/rest/tests/test_moderation.py @@ -31,6 +31,7 @@ from urllib2 import HTTPError from mailman.app.lifecycle import create_list from mailman.app.moderator import hold_message, hold_subscription from mailman.config import config +from mailman.database.transaction import transaction from mailman.interfaces.member import DeliveryMode from mailman.testing.helpers import ( call_api, specialized_message_from_string as mfs) @@ -42,7 +43,8 @@ class TestModeration(unittest.TestCase): layer = RESTLayer def setUp(self): - self._mlist = create_list('ant@example.com') + with transaction(): + self._mlist = create_list('ant@example.com') self._msg = mfs("""\ From: anne@example.com To: ant@example.com @@ -51,7 +53,6 @@ Message-ID: <alpha> Something else. """) - config.db.commit() def test_not_found(self): # When a bogus mailing list is given, 404 should result. diff --git a/src/mailman/rest/tests/test_users.py b/src/mailman/rest/tests/test_users.py index 1630eb96a..301027885 100644 --- a/src/mailman/rest/tests/test_users.py +++ b/src/mailman/rest/tests/test_users.py @@ -21,6 +21,7 @@ from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ + 'TestUsers', ] @@ -29,7 +30,7 @@ import unittest from urllib2 import HTTPError from mailman.app.lifecycle import create_list -from mailman.config import config +from mailman.database.transaction import transaction from mailman.testing.helpers import call_api from mailman.testing.layers import RESTLayer @@ -39,8 +40,8 @@ class TestUsers(unittest.TestCase): layer = RESTLayer def setUp(self): - self._mlist = create_list('test@example.com') - config.db.commit() + with transaction(): + self._mlist = create_list('test@example.com') def test_delete_bogus_user(self): # Try to delete a user that does not exist. diff --git a/src/mailman/rules/administrivia.py b/src/mailman/rules/administrivia.py index 41c6edf30..4c49e4ff2 100644 --- a/src/mailman/rules/administrivia.py +++ b/src/mailman/rules/administrivia.py @@ -17,7 +17,7 @@ """The administrivia rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ from email.iterators import typed_subpart_iterator -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -53,9 +53,9 @@ EMAIL_COMMANDS = { +@implementer(IRule) class Administrivia: """The administrivia rule.""" - implements(IRule) name = 'administrivia' description = _('Catch mis-addressed email commands.') diff --git a/src/mailman/rules/any.py b/src/mailman/rules/any.py index b0d147bec..33c20394d 100644 --- a/src/mailman/rules/any.py +++ b/src/mailman/rules/any.py @@ -17,7 +17,7 @@ """Check if any previous rules have matched.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,16 +25,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +@implementer(IRule) class Any: """Look for any previous rule match.""" - implements(IRule) name = 'any' description = _('Look for any previous rule hit.') diff --git a/src/mailman/rules/approved.py b/src/mailman/rules/approved.py index 3e2b7bc83..1f4fc1369 100644 --- a/src/mailman/rules/approved.py +++ b/src/mailman/rules/approved.py @@ -29,7 +29,7 @@ import re from email.iterators import typed_subpart_iterator from flufl.password import verify -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule @@ -45,9 +45,9 @@ HEADERS = [ +@implementer(IRule) class Approved: """Look for moderator pre-approval.""" - implements(IRule) name = 'approved' description = _('The message has a matching Approve or Approved header.') @@ -119,7 +119,7 @@ class Approved: else: for header in HEADERS: del msg[header] - return (password is not missing and + return (password is not missing and verify(mlist.moderator_password, password)) diff --git a/src/mailman/rules/emergency.py b/src/mailman/rules/emergency.py index 7b2100875..7c11b0aa3 100644 --- a/src/mailman/rules/emergency.py +++ b/src/mailman/rules/emergency.py @@ -17,7 +17,7 @@ """The emergency hold rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,16 +25,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +@implementer(IRule) class Emergency: """The emergency hold rule.""" - implements(IRule) name = 'emergency' diff --git a/src/mailman/rules/implicit_dest.py b/src/mailman/rules/implicit_dest.py index 321a1775d..41d0403bc 100644 --- a/src/mailman/rules/implicit_dest.py +++ b/src/mailman/rules/implicit_dest.py @@ -17,7 +17,7 @@ """The implicit destination rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ import re from email.utils import getaddresses -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.mailinglist import IAcceptableAliasSet @@ -35,9 +35,9 @@ from mailman.interfaces.rules import IRule +@implementer(IRule) class ImplicitDestination: """The implicit destination rule.""" - implements(IRule) name = 'implicit-dest' description = _('Catch messages with implicit destination.') diff --git a/src/mailman/rules/loop.py b/src/mailman/rules/loop.py index 9e4aa0061..9282b1705 100644 --- a/src/mailman/rules/loop.py +++ b/src/mailman/rules/loop.py @@ -17,7 +17,7 @@ """Look for a posting loop.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,16 +25,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +@implementer(IRule) class Loop: """Look for a posting loop.""" - implements(IRule) name = 'loop' description = _('Look for a posting loop.') diff --git a/src/mailman/rules/max_recipients.py b/src/mailman/rules/max_recipients.py index 79396c72f..fd09114e2 100644 --- a/src/mailman/rules/max_recipients.py +++ b/src/mailman/rules/max_recipients.py @@ -17,7 +17,7 @@ """The maximum number of recipients rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,16 +26,16 @@ __all__ = [ from email.utils import getaddresses -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +@implementer(IRule) class MaximumRecipients: """The maximum number of recipients rule.""" - implements(IRule) name = 'max-recipients' description = _('Catch messages with too many explicit recipients.') diff --git a/src/mailman/rules/max_size.py b/src/mailman/rules/max_size.py index 420d63571..a67d87771 100644 --- a/src/mailman/rules/max_size.py +++ b/src/mailman/rules/max_size.py @@ -17,7 +17,7 @@ """The maximum message size rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,16 +25,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +@implementer(IRule) class MaximumSize: """The implicit destination rule.""" - implements(IRule) name = 'max-size' description = _('Catch messages that are bigger than a specified maximum.') diff --git a/src/mailman/rules/moderation.py b/src/mailman/rules/moderation.py index cb27d89d8..b2c777dc7 100644 --- a/src/mailman/rules/moderation.py +++ b/src/mailman/rules/moderation.py @@ -17,7 +17,7 @@ """Membership related rules.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -27,7 +27,7 @@ __all__ = [ from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.action import Action @@ -37,9 +37,9 @@ from mailman.interfaces.usermanager import IUserManager +@implementer(IRule) class MemberModeration: """The member moderation rule.""" - implements(IRule) name = 'member-moderation' description = _('Match messages sent by moderated members.') @@ -65,9 +65,9 @@ class MemberModeration: +@implementer(IRule) class NonmemberModeration: """The nonmember moderation rule.""" - implements(IRule) name = 'nonmember-moderation' description = _('Match messages sent by nonmembers.') diff --git a/src/mailman/rules/news_moderation.py b/src/mailman/rules/news_moderation.py index e43bca3b7..bd6eb6166 100644 --- a/src/mailman/rules/news_moderation.py +++ b/src/mailman/rules/news_moderation.py @@ -17,7 +17,7 @@ """The news moderation rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,7 +25,7 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.nntp import NewsgroupModeration @@ -33,9 +33,9 @@ from mailman.interfaces.rules import IRule +@implementer(IRule) class ModeratedNewsgroup: """The news moderation rule.""" - implements(IRule) name = 'news-moderation' description = _( diff --git a/src/mailman/rules/no_subject.py b/src/mailman/rules/no_subject.py index 1a2bab1d5..1fd0dfb8b 100644 --- a/src/mailman/rules/no_subject.py +++ b/src/mailman/rules/no_subject.py @@ -17,7 +17,7 @@ """The no-Subject header rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,16 +25,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +@implementer(IRule) class NoSubject: """The no-Subject rule.""" - implements(IRule) name = 'no-subject' description = _('Catch messages with no, or empty, Subject headers.') diff --git a/src/mailman/rules/suspicious.py b/src/mailman/rules/suspicious.py index ad1ab42cd..75fe0afab 100644 --- a/src/mailman/rules/suspicious.py +++ b/src/mailman/rules/suspicious.py @@ -17,7 +17,7 @@ """The historical 'suspicious header' rule.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ import re import logging -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule @@ -37,9 +37,9 @@ log = logging.getLogger('mailman.error') +@implementer(IRule) class SuspiciousHeader: """The historical 'suspicious header' rule.""" - implements(IRule) name = 'suspicious-header' description = _('Catch messages with suspicious headers.') diff --git a/src/mailman/rules/truth.py b/src/mailman/rules/truth.py index 20c152e4d..752618ced 100644 --- a/src/mailman/rules/truth.py +++ b/src/mailman/rules/truth.py @@ -17,7 +17,7 @@ """A rule which always matches.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -25,16 +25,16 @@ __all__ = [ ] -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +@implementer(IRule) class Truth: """Look for any previous rule match.""" - implements(IRule) name = 'truth' description = _('A rule which always matches.') diff --git a/src/mailman/runners/command.py b/src/mailman/runners/command.py index ac611ed3a..6501474a3 100644 --- a/src/mailman/runners/command.py +++ b/src/mailman/runners/command.py @@ -38,7 +38,7 @@ from email.errors import HeaderParseError from email.header import decode_header, make_header from email.iterators import typed_subpart_iterator from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ @@ -117,7 +117,7 @@ class CommandFinder: continue # Ensure that all the parts are unicodes. Since we only accept # ASCII commands and arguments, ignore anything else. - parts = [(part + parts = [(part if isinstance(part, unicode) else part.decode('ascii', 'ignore')) for part in parts] @@ -125,11 +125,10 @@ class CommandFinder: +@implementer(IEmailResults) class Results: """The email command results.""" - implements(IEmailResults) - def __init__(self, charset='us-ascii'): self._output = StringIO() self.charset = charset diff --git a/src/mailman/runners/incoming.py b/src/mailman/runners/incoming.py index d8db926c7..1e4ceaa65 100644 --- a/src/mailman/runners/incoming.py +++ b/src/mailman/runners/incoming.py @@ -26,7 +26,7 @@ prepared for delivery. Rejections, discards, and holds are processed immediately. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -36,9 +36,9 @@ __all__ = [ from zope.component import getUtility -from mailman.config import config from mailman.core.chains import process from mailman.core.runner import Runner +from mailman.database.transaction import transaction from mailman.interfaces.address import ExistingAddressError from mailman.interfaces.usermanager import IUserManager @@ -54,12 +54,12 @@ class IncomingRunner(Runner): # Ensure that the email addresses of the message's senders are known # to Mailman. This will be used in nonmember posting dispositions. user_manager = getUtility(IUserManager) - for sender in msg.senders: - try: - user_manager.create_address(sender) - except ExistingAddressError: - pass - config.db.commit() + with transaction(): + for sender in msg.senders: + try: + user_manager.create_address(sender) + except ExistingAddressError: + pass # Process the message through the mailing list's start chain. start_chain = (mlist.owner_chain if msgdata.get('to_owner', False) diff --git a/src/mailman/runners/lmtp.py b/src/mailman/runners/lmtp.py index 249ab94c1..61db6b848 100644 --- a/src/mailman/runners/lmtp.py +++ b/src/mailman/runners/lmtp.py @@ -15,6 +15,9 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. +# XXX This module needs to be refactored to avoid direct access to the +# config.db global. + """Mailman LMTP runner (server). Most mail servers can be configured to deliver local messages via 'LMTP'[1]. @@ -31,6 +34,14 @@ so that the peer mail server can provide better diagnostics. http://www.faqs.org/rfcs/rfc2033.html """ +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'LMTPRunner', + ] + + import email import smtpd import logging @@ -80,15 +91,15 @@ SUBADDRESS_QUEUES = dict( ) DASH = '-' -CRLF = '\r\n' -ERR_451 = '451 Requested action aborted: error in processing' -ERR_501 = '501 Message has defects' -ERR_502 = '502 Error: command HELO not implemented' -ERR_550 = '550 Requested action not taken: mailbox unavailable' -ERR_550_MID = '550 No Message-ID header provided' +CRLF = b'\r\n' +ERR_451 = b'451 Requested action aborted: error in processing' +ERR_501 = b'501 Message has defects' +ERR_502 = b'502 Error: command HELO not implemented' +ERR_550 = b'550 Requested action not taken: mailbox unavailable' +ERR_550_MID = b'550 No Message-ID header provided' # XXX Blech -smtpd.__version__ = 'Python LMTP runner 1.0' +smtpd.__version__ = b'Python LMTP runner 1.0' @@ -228,7 +239,7 @@ class LMTPRunner(Runner, smtpd.SMTPServer): config.switchboards[queue].enqueue(msg, msgdata) slog.debug('%s subaddress: %s, queue: %s', message_id, canonical_subaddress, queue) - status.append('250 Ok') + status.append(b'250 Ok') except Exception: slog.exception('Queue detection: %s', msg['message-id']) config.db.abort() diff --git a/src/mailman/runners/tests/test_archiver.py b/src/mailman/runners/tests/test_archiver.py index 6f5804cae..30cb9e461 100644 --- a/src/mailman/runners/tests/test_archiver.py +++ b/src/mailman/runners/tests/test_archiver.py @@ -29,7 +29,7 @@ import os import unittest from email import message_from_file -from zope.interface import implements +from zope.interface import implementer from mailman.app.lifecycle import create_list from mailman.config import config @@ -44,8 +44,8 @@ from mailman.utilities.datetime import RFC822_DATE_FMT, factory, now +@implementer(IArchiver) class DummyArchiver: - implements(IArchiver) name = 'dummy' @staticmethod @@ -193,7 +193,7 @@ First post! self.assertEqual(archived['x-original-date'], 'Mon, 01 Aug 2005 07:49:23 +0000') - @configuration('archiver.dummy', + @configuration('archiver.dummy', enable='yes', clobber_date='maybe', clobber_skew='1d') def test_clobber_date_maybe_when_insane(self): # The date is clobbered if it's farther off from now than its skew diff --git a/src/mailman/runners/tests/test_bounce.py b/src/mailman/runners/tests/test_bounce.py index 76eb65c1f..5c21000bf 100644 --- a/src/mailman/runners/tests/test_bounce.py +++ b/src/mailman/runners/tests/test_bounce.py @@ -17,17 +17,20 @@ """Test the bounce runner.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ + 'TestBounceRunner', + 'TestBounceRunnerBug876774', + 'TestStyle', ] import unittest from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from mailman.app.bounces import send_probe from mailman.app.lifecycle import create_list @@ -235,8 +238,9 @@ Message-Id: <third> # attributes. In particular, this will not set the bogus `bounce_processing` # attribute which the default style set (before LP: #876774 was fixed). +@implementer(IStyle) class TestStyle: - implements(IStyle) + """See `IStyle`.""" name = 'test' priority = 10 @@ -249,6 +253,7 @@ class TestStyle: styles.append(self) + class TestBounceRunnerBug876774(unittest.TestCase): """Test LP: #876774. diff --git a/src/mailman/runners/tests/test_confirm.py b/src/mailman/runners/tests/test_confirm.py index d2b24a2d1..62171979c 100644 --- a/src/mailman/runners/tests/test_confirm.py +++ b/src/mailman/runners/tests/test_confirm.py @@ -32,6 +32,7 @@ from zope.component import getUtility from mailman.app.lifecycle import create_list from mailman.config import config +from mailman.database.transaction import transaction from mailman.interfaces.registrar import IRegistrar from mailman.interfaces.usermanager import IUserManager from mailman.runners.command import CommandRunner @@ -50,14 +51,14 @@ class TestConfirm(unittest.TestCase): layer = ConfigLayer def setUp(self): - # Register a subscription requiring confirmation. registrar = getUtility(IRegistrar) - self._mlist = create_list('test@example.com') - self._mlist.send_welcome_message = False - self._token = registrar.register(self._mlist, 'anne@example.org') self._commandq = config.switchboards['command'] self._runner = make_testable_runner(CommandRunner, 'command') - config.db.commit() + with transaction(): + # Register a subscription requiring confirmation. + self._mlist = create_list('test@example.com') + self._mlist.send_welcome_message = False + self._token = registrar.register(self._mlist, 'anne@example.org') def test_confirm_with_re_prefix(self): subject = 'Re: confirm {0}'.format(self._token) diff --git a/src/mailman/runners/tests/test_lmtp.py b/src/mailman/runners/tests/test_lmtp.py index 87b69c7e4..46d4ed986 100644 --- a/src/mailman/runners/tests/test_lmtp.py +++ b/src/mailman/runners/tests/test_lmtp.py @@ -31,7 +31,7 @@ import unittest from datetime import datetime from mailman.app.lifecycle import create_list -from mailman.config import config +from mailman.database.transaction import transaction from mailman.testing.helpers import get_lmtp_client, get_queue_messages from mailman.testing.layers import LMTPLayer @@ -43,8 +43,8 @@ class TestLMTP(unittest.TestCase): layer = LMTPLayer def setUp(self): - self._mlist = create_list('test@example.com') - config.db.commit() + with transaction(): + self._mlist = create_list('test@example.com') self._lmtp = get_lmtp_client(quiet=True) self._lmtp.lhlo('remote.example.org') diff --git a/src/mailman/runners/tests/test_owner.py b/src/mailman/runners/tests/test_owner.py index 622bb2255..4ea5771be 100644 --- a/src/mailman/runners/tests/test_owner.py +++ b/src/mailman/runners/tests/test_owner.py @@ -37,6 +37,7 @@ from zope.component import getUtility from mailman.app.lifecycle import create_list from mailman.config import config +from mailman.database.transaction import transaction from mailman.interfaces.member import MemberRole from mailman.interfaces.usermanager import IUserManager from mailman.testing.helpers import ( @@ -59,17 +60,17 @@ class TestEmailToOwner(unittest.TestCase): self._mlist = create_list('test@example.com') # Add some owners, moderators, and members manager = getUtility(IUserManager) - anne = manager.create_address('anne@example.com') - bart = manager.create_address('bart@example.com') - cris = manager.create_address('cris@example.com') - dave = manager.create_address('dave@example.com') - self._mlist.subscribe(anne, MemberRole.member) - self._mlist.subscribe(anne, MemberRole.owner) - self._mlist.subscribe(bart, MemberRole.moderator) - self._mlist.subscribe(bart, MemberRole.owner) - self._mlist.subscribe(cris, MemberRole.moderator) - self._mlist.subscribe(dave, MemberRole.member) - config.db.commit() + with transaction(): + anne = manager.create_address('anne@example.com') + bart = manager.create_address('bart@example.com') + cris = manager.create_address('cris@example.com') + dave = manager.create_address('dave@example.com') + self._mlist.subscribe(anne, MemberRole.member) + self._mlist.subscribe(anne, MemberRole.owner) + self._mlist.subscribe(bart, MemberRole.moderator) + self._mlist.subscribe(bart, MemberRole.owner) + self._mlist.subscribe(cris, MemberRole.moderator) + self._mlist.subscribe(dave, MemberRole.member) self._inq = make_testable_runner(IncomingRunner, 'in') self._pipelineq = make_testable_runner(PipelineRunner, 'pipeline') self._outq = make_testable_runner(OutgoingRunner, 'out') diff --git a/src/mailman/runners/tests/test_pipeline.py b/src/mailman/runners/tests/test_pipeline.py index 8776bf844..3f8f62ab0 100644 --- a/src/mailman/runners/tests/test_pipeline.py +++ b/src/mailman/runners/tests/test_pipeline.py @@ -27,7 +27,7 @@ __all__ = [ import unittest -from zope.interface import implements +from zope.interface import implementer from mailman.app.lifecycle import create_list from mailman.config import config @@ -41,8 +41,10 @@ from mailman.testing.layers import ConfigLayer +@implementer(IHandler) class MyTestHandler: - implements(IHandler) + """See `IHandler`.""" + name = 'test handler' description = 'A test handler' @@ -54,8 +56,9 @@ class MyTestHandler: self._test.mark(self._marker) + +@implementer(IPipeline) class MyTestPipeline: - implements(IPipeline) name = 'test' description = 'a test pipeline' diff --git a/src/mailman/styles/default.py b/src/mailman/styles/default.py index 82deff026..6108831b7 100644 --- a/src/mailman/styles/default.py +++ b/src/mailman/styles/default.py @@ -17,7 +17,7 @@ """Application of list styles to new and existing lists.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -28,7 +28,7 @@ __all__ = [ # XXX Styles need to be reconciled with lazr.config. from datetime import timedelta -from zope.interface import implements +from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.action import Action, FilterAction @@ -41,11 +41,10 @@ from mailman.interfaces.styles import IStyle +@implementer(IStyle) class DefaultStyle: """The default (i.e. legacy) style.""" - implements(IStyle) - name = 'default' priority = 0 # the lowest priority style @@ -118,7 +117,7 @@ from: .*@uplinkpro.com mlist.default_nonmember_action = Action.hold # Archiver mlist.archive = True - mlist.archive_private = 0 + mlist.archive_private = False mlist.archive_volume_frequency = 1 mlist.emergency = False mlist.member_moderation_notice = '' diff --git a/src/mailman/styles/docs/styles.rst b/src/mailman/styles/docs/styles.rst index 90a02227b..8f589f10b 100644 --- a/src/mailman/styles/docs/styles.rst +++ b/src/mailman/styles/docs/styles.rst @@ -62,10 +62,10 @@ Registering styles New styles must implement the ``IStyle`` interface. - >>> from zope.interface import implements + >>> from zope.interface import implementer >>> from mailman.interfaces.styles import IStyle - >>> class TestStyle: - ... implements(IStyle) + >>> @implementer(IStyle) + ... class TestStyle: ... name = 'test' ... priority = 10 ... def apply(self, mailing_list): diff --git a/src/mailman/styles/manager.py b/src/mailman/styles/manager.py index f1d3f1dfb..8ec832f18 100644 --- a/src/mailman/styles/manager.py +++ b/src/mailman/styles/manager.py @@ -17,7 +17,7 @@ """Style manager.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,7 @@ __all__ = [ from operator import attrgetter -from zope.interface import implements +from zope.interface import implementer from zope.interface.verify import verifyObject from mailman.interfaces.styles import ( @@ -35,11 +35,10 @@ from mailman.utilities.modules import call_name +@implementer(IStyleManager) class StyleManager: """The built-in style manager.""" - implements(IStyleManager) - def __init__(self): """Install all styles from the configuration files.""" self._styles = {} diff --git a/src/mailman/styles/tests/test_styles.py b/src/mailman/styles/tests/test_styles.py index ce8b5064d..990ce541f 100644 --- a/src/mailman/styles/tests/test_styles.py +++ b/src/mailman/styles/tests/test_styles.py @@ -28,7 +28,7 @@ __all__ = [ import unittest from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from zope.interface.exceptions import DoesNotImplement from mailman.interfaces.styles import ( @@ -37,8 +37,8 @@ from mailman.testing.layers import ConfigLayer +@implementer(IStyle) class DummyStyle: - implements(IStyle) name = 'dummy' priority = 1 diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py index d9885cbac..4091c1cd6 100644 --- a/src/mailman/testing/helpers.py +++ b/src/mailman/testing/helpers.py @@ -17,7 +17,7 @@ """Various test helpers.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -66,6 +66,7 @@ from zope.component import getUtility from mailman.bin.master import Loop as Master from mailman.config import config +from mailman.database.transaction import transaction from mailman.email.message import Message from mailman.interfaces.member import MemberRole from mailman.interfaces.messages import IMessageStore @@ -239,13 +240,14 @@ def get_lmtp_client(quiet=False): # It's possible the process has started but is not yet accepting # connections. Wait a little while. lmtp = LMTP() + #lmtp.debuglevel = 1 until = datetime.datetime.now() + as_timedelta(config.devmode.wait) while datetime.datetime.now() < until: try: response = lmtp.connect( config.mta.lmtp_host, int(config.mta.lmtp_port)) if not quiet: - print response + print(response) return lmtp except socket.error as error: if error[0] == errno.ECONNREFUSED: @@ -427,19 +429,19 @@ def subscribe(mlist, first_name, role=MemberRole.member): user_manager = getUtility(IUserManager) email = '{0}person@example.com'.format(first_name[0].lower()) full_name = '{0} Person'.format(first_name) - person = user_manager.get_user(email) - if person is None: - address = user_manager.get_address(email) - if address is None: - person = user_manager.create_user(email, full_name) + with transaction(): + person = user_manager.get_user(email) + if person is None: + address = user_manager.get_address(email) + if address is None: + person = user_manager.create_user(email, full_name) + preferred_address = list(person.addresses)[0] + mlist.subscribe(preferred_address, role) + else: + mlist.subscribe(address, role) + else: preferred_address = list(person.addresses)[0] mlist.subscribe(preferred_address, role) - else: - mlist.subscribe(address, role) - else: - preferred_address = list(person.addresses)[0] - mlist.subscribe(preferred_address, role) - config.db.commit() @@ -467,9 +469,9 @@ def reset_the_world(): os.remove(os.path.join(dirpath, filename)) # Clear out messages in the message store. message_store = getUtility(IMessageStore) - for message in message_store.messages: - message_store.delete_message(message['message-id']) - config.db.commit() + with transaction(): + for message in message_store.messages: + message_store.delete_message(message['message-id']) # Reset the global style manager. getUtility(IStyleManager).populate() # Remove all dynamic header-match rules. diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 41ef86935..0faa1c8e4 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -25,7 +25,7 @@ # eventually get rid of the zope.test* dependencies and use something like # testresources or some such. -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -56,6 +56,7 @@ from mailman.config import config from mailman.core import initialize from mailman.core.initialize import INHIBIT_CONFIG_FILE from mailman.core.logging import get_handler +from mailman.database.transaction import transaction from mailman.interfaces.domain import IDomainManager from mailman.testing.helpers import ( TestableMaster, get_lmtp_client, reset_the_world) @@ -176,7 +177,7 @@ class ConfigLayer(MockAndMonkeyLayer): config_file = os.path.join(cls.var_dir, 'test.cfg') with open(config_file, 'w') as fp: fp.write(test_config) - print >> fp + print(file=fp) config.filename = config_file @classmethod @@ -189,10 +190,10 @@ class ConfigLayer(MockAndMonkeyLayer): @classmethod def testSetUp(cls): # Add an example domain. - getUtility(IDomainManager).add( - 'example.com', 'An example domain.', - 'http://lists.example.com', 'postmaster@example.com') - config.db.commit() + with transaction(): + getUtility(IDomainManager).add( + 'example.com', 'An example domain.', + 'http://lists.example.com', 'postmaster@example.com') @classmethod def testTearDown(cls): diff --git a/src/mailman/testing/mta.py b/src/mailman/testing/mta.py index 4699cb882..bba450352 100644 --- a/src/mailman/testing/mta.py +++ b/src/mailman/testing/mta.py @@ -17,7 +17,7 @@ """Fake MTA for testing purposes.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -31,7 +31,7 @@ from Queue import Empty, Queue from lazr.smtptest.controller import QueueController from lazr.smtptest.server import Channel, QueueServer -from zope.interface import implements +from zope.interface import implementer from mailman.interfaces.mta import IMailTransportAgentLifecycle @@ -40,11 +40,10 @@ log = logging.getLogger('lazr.smtptest') +@implementer(IMailTransportAgentLifecycle) class FakeMTA: """Fake MTA for testing purposes.""" - implements(IMailTransportAgentLifecycle) - def create(self, mlist): pass |
