summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/app/subscriptions.py14
-rw-r--r--src/mailman/archiving/tests/test_prototype.py5
-rw-r--r--src/mailman/bin/set_members.py189
-rw-r--r--src/mailman/commands/cli_import.py12
-rw-r--r--src/mailman/commands/cli_lists.py26
-rw-r--r--src/mailman/commands/cli_members.py5
-rw-r--r--src/mailman/database/transaction.py52
-rw-r--r--src/mailman/interfaces/database.py7
-rw-r--r--src/mailman/model/autorespond.py21
-rw-r--r--src/mailman/model/bans.py37
-rw-r--r--src/mailman/model/bounce.py20
-rw-r--r--src/mailman/model/domain.py40
-rw-r--r--src/mailman/model/listmanager.py49
-rw-r--r--src/mailman/model/member.py11
-rw-r--r--src/mailman/model/message.py10
-rw-r--r--src/mailman/model/messagestore.py33
-rw-r--r--src/mailman/model/pending.py18
-rw-r--r--src/mailman/model/requests.py43
-rw-r--r--src/mailman/model/roster.py46
-rw-r--r--src/mailman/model/tests/test_bounce.py25
-rw-r--r--src/mailman/model/uid.py18
-rw-r--r--src/mailman/model/user.py21
-rw-r--r--src/mailman/model/usermanager.py51
-rw-r--r--src/mailman/rest/tests/test_addresses.py9
-rw-r--r--src/mailman/rest/tests/test_domains.py13
-rw-r--r--src/mailman/rest/tests/test_lists.py22
-rw-r--r--src/mailman/rest/tests/test_membership.py58
-rw-r--r--src/mailman/rest/tests/test_moderation.py5
-rw-r--r--src/mailman/rest/tests/test_users.py7
-rw-r--r--src/mailman/runners/incoming.py16
-rw-r--r--src/mailman/runners/lmtp.py27
-rw-r--r--src/mailman/runners/tests/test_confirm.py11
-rw-r--r--src/mailman/runners/tests/test_lmtp.py6
-rw-r--r--src/mailman/runners/tests/test_owner.py23
-rw-r--r--src/mailman/testing/helpers.py34
-rw-r--r--src/mailman/testing/layers.py13
36 files changed, 460 insertions, 537 deletions
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index 60f8cdebe..5852a7483 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__ = [
@@ -36,6 +36,7 @@ from zope.interface import implements
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)
@@ -90,9 +91,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 +103,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 +141,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/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/commands/cli_import.py b/src/mailman/commands/cli_import.py
index b703f3ffd..716a8ede1 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__ = [
@@ -31,8 +31,8 @@ import cPickle
from zope.component import getUtility
from zope.interface import implements
-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
@@ -59,6 +59,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 +91,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_lists.py b/src/mailman/commands/cli_lists.py
index af6afe22d..17d4bc375 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__ = [
@@ -31,9 +31,9 @@ from zope.component import getUtility
from zope.interface import implements
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)
@@ -98,11 +98,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,8 +120,8 @@ 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))
@@ -214,13 +214,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,
@@ -262,11 +262,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 +278,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..17312e8ec 100644
--- a/src/mailman/commands/cli_members.py
+++ b/src/mailman/commands/cli_members.py
@@ -37,6 +37,7 @@ from zope.interface import implements
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 (
@@ -177,6 +178,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 +209,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/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/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/model/autorespond.py b/src/mailman/model/autorespond.py
index 7b42205b4..9e0fbd042 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__ = [
@@ -29,8 +29,8 @@ __all__ = [
from storm.locals import And, Date, Desc, Int, Reference
from zope.interface import implements
-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)
@@ -66,27 +66,30 @@ class AutoResponseSet:
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..89addd8c7 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__ = [
@@ -30,8 +30,8 @@ import re
from storm.locals import Int, Unicode
from zope.interface import implements
-from mailman.config import config
from mailman.database.model import Model
+from mailman.database.transaction import dbconnection
from mailman.interfaces.bans import IBan, IBanManager
@@ -53,43 +53,43 @@ class Ban(Model):
class BanManager:
implements(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 +97,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..b957a2243 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__ = [
@@ -29,8 +29,8 @@ __all__ = [
from storm.locals import Bool, Int, DateTime, Unicode
from zope.interface import implements
-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)
@@ -62,21 +62,23 @@ class BounceEvent(Model):
class BounceProcessor:
implements(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/domain.py b/src/mailman/model/domain.py
index 49c935740..e93335328 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__ = [
@@ -31,8 +31,8 @@ from storm.locals import Int, Unicode
from zope.event import notify
from zope.interface import implements
-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)
@@ -90,9 +90,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:
@@ -119,7 +120,9 @@ class DomainManager:
implements(IDomainManager)
- def add(self, mail_host,
+ @dbconnection
+ def add(self, store,
+ mail_host,
description=None,
base_url=None,
contact_address=None):
@@ -131,20 +134,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 +164,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/listmanager.py b/src/mailman/model/listmanager.py
index 0ea87a082..8d845b36f 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__ = [
@@ -28,7 +28,7 @@ __all__ = [
from zope.event import notify
from zope.interface import implements
-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,
@@ -43,13 +43,14 @@ class ListManager:
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 +58,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/member.py b/src/mailman/model/member.py
index ae83fb388..5a71eb8be 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__ = [
@@ -29,9 +29,9 @@ from storm.properties import UUID
from zope.component import getUtility
from zope.interface import implements
-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
@@ -176,7 +176,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..67c4caf79 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__ = [
@@ -28,8 +27,8 @@ __all__ = [
from storm.locals import AutoReload, Int, RawStr, Unicode
from zope.interface import implements
-from mailman.config import config
from mailman.database.model import Model
+from mailman.database.transaction import dbconnection
from mailman.interfaces.messages import IMessage
@@ -45,9 +44,10 @@ class Message(Model):
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..df2205ff8 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__ = [
@@ -34,6 +33,7 @@ import cPickle as pickle
from zope.interface import implements
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
@@ -49,7 +49,8 @@ EMPTYSTRING = ''
class MessageStore:
implements(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 +58,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 +104,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/pending.py b/src/mailman/model/pending.py
index 557361c6f..39845e0bf 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__ = [
@@ -37,6 +37,7 @@ 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
@@ -86,7 +87,8 @@ class Pendings:
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 +106,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 +131,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 +160,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/requests.py b/src/mailman/model/requests.py
index 4a3efa67f..030c630b9 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__ = [
@@ -29,8 +29,8 @@ from storm.locals import AutoReload, Int, RawStr, Reference, Unicode
from zope.component import getUtility
from zope.interface import implements
-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
@@ -49,30 +49,33 @@ class ListRequests:
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 +90,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 +108,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..84ed12930 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__ = [
@@ -40,7 +40,7 @@ __all__ = [
from storm.expr import And, Or
from zope.interface import implements
-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
@@ -64,8 +64,9 @@ class AbstractRoster:
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 +102,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 +159,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 +198,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 +207,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,10 +249,9 @@ 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)
@@ -261,8 +265,9 @@ class 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 +296,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..15ed9170f 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__ = [
@@ -30,8 +30,8 @@ from storm.properties import UUID
from zope.event import notify
from zope.interface import implements
-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 (
@@ -64,16 +64,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 +136,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..2c4158b90 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__ = [
@@ -27,7 +27,7 @@ __all__ = [
from zope.interface import implements
-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
@@ -48,33 +48,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 +90,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/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/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_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/testing/helpers.py b/src/mailman/testing/helpers.py
index 3648a6710..62e3b9d2a 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__ = [
@@ -64,6 +64,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
@@ -237,13 +238,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:
@@ -397,19 +399,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()
@@ -437,9 +439,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):