diff options
| author | Barry Warsaw | 2012-04-22 17:33:33 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2012-04-22 17:33:33 -0400 |
| commit | 4488631dff02731ff03f2fef01ee27bbd944812b (patch) | |
| tree | a4064e30019764ffac7f5d65893b49779cb6918b | |
| parent | 7da6bb2bdba138877e53a70d224b253af14c65bc (diff) | |
| parent | cd3bab07fe2de591fee0dd27e9d47076a7e7db0f (diff) | |
| download | mailman-4488631dff02731ff03f2fef01ee27bbd944812b.tar.gz mailman-4488631dff02731ff03f2fef01ee27bbd944812b.tar.zst mailman-4488631dff02731ff03f2fef01ee27bbd944812b.zip | |
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): |
