summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2012-04-22 17:33:33 -0400
committerBarry Warsaw2012-04-22 17:33:33 -0400
commit4488631dff02731ff03f2fef01ee27bbd944812b (patch)
treea4064e30019764ffac7f5d65893b49779cb6918b /src
parent7da6bb2bdba138877e53a70d224b253af14c65bc (diff)
parentcd3bab07fe2de591fee0dd27e9d47076a7e7db0f (diff)
downloadmailman-4488631dff02731ff03f2fef01ee27bbd944812b.tar.gz
mailman-4488631dff02731ff03f2fef01ee27bbd944812b.tar.zst
mailman-4488631dff02731ff03f2fef01ee27bbd944812b.zip
Several non-functional improvements to the code base.
Reduce the explicit use of the config.db global by introducing two new helpers: - A new transaction() context manager which will commit the transaction on successful exit, otherwise it will abort the transaction - A new dbconnection decorator which calls the decorated method with the Storm store object as the first argument (after self). This can be used instead of config.db.store. By reducing the explicit use of this global, we have a better chance of refactoring it away in the future. Still TODO: runner.py and lmtp.py. Be explicit about the `store` attribute on the IDatabase interface. More consistent use of __future__ imports. Remove an obsolete command line script.
Diffstat (limited to 'src')
-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):