diff options
| author | Barry Warsaw | 2016-03-24 21:29:30 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2016-03-24 21:29:30 -0400 |
| commit | 5404f98d90410d69a744d9c0fb71a8a31f3a4a88 (patch) | |
| tree | beb5010e4d74ab0f8056419dc05058fc2bbd8cc6 /src | |
| parent | a0cf7d44cbf0527d8bac05f870208a85689da42f (diff) | |
| download | mailman-5404f98d90410d69a744d9c0fb71a8a31f3a4a88.tar.gz mailman-5404f98d90410d69a744d9c0fb71a8a31f3a4a88.tar.zst mailman-5404f98d90410d69a744d9c0fb71a8a31f3a4a88.zip | |
Diffstat (limited to 'src')
32 files changed, 156 insertions, 367 deletions
diff --git a/src/mailman/app/docs/bounces.rst b/src/mailman/app/docs/bounces.rst index 127a05ef6..99eae8b2c 100644 --- a/src/mailman/app/docs/bounces.rst +++ b/src/mailman/app/docs/bounces.rst @@ -66,7 +66,7 @@ An error message can be given when the message is bounced, and this will be included in the payload of the text/plain part. The error message must be passed in as an instance of a ``RejectMessage`` exception. - >>> from mailman.core.errors import RejectMessage + >>> from mailman.interfaces.pipeline import RejectMessage >>> error = RejectMessage("This wasn't very important after all.") >>> bounce_message(mlist, msg, error) >>> items = get_queue_messages('virgin') diff --git a/src/mailman/chains/reject.py b/src/mailman/chains/reject.py index 657e788af..f0926cd8c 100644 --- a/src/mailman/chains/reject.py +++ b/src/mailman/chains/reject.py @@ -22,9 +22,9 @@ import logging from mailman import public from mailman.app.bounces import bounce_message from mailman.chains.base import TerminalChainBase -from mailman.core.errors import RejectMessage from mailman.core.i18n import _ from mailman.interfaces.chain import RejectEvent +from mailman.interfaces.pipeline import RejectMessage from zope.event import notify diff --git a/src/mailman/core/api.py b/src/mailman/core/api.py index 39c108db2..b7841a9d8 100644 --- a/src/mailman/core/api.py +++ b/src/mailman/core/api.py @@ -17,19 +17,15 @@ """REST web service API contexts.""" -__all__ = [ - 'API30', - 'API31', - ] - - from lazr.config import as_boolean +from mailman import public from mailman.config import config from mailman.interfaces.api import IAPI from uuid import UUID from zope.interface import implementer +@public @implementer(IAPI) class API30: version = '3.0' @@ -58,6 +54,7 @@ class API30: return UUID(int=int(uuid)) +@public @implementer(IAPI) class API31: version = '3.1' diff --git a/src/mailman/core/chains.py b/src/mailman/core/chains.py index 6a98b2a33..3a503f193 100644 --- a/src/mailman/core/chains.py +++ b/src/mailman/core/chains.py @@ -17,12 +17,7 @@ """Application support for chain processing.""" -__all__ = [ - 'initialize', - 'process', - ] - - +from mailman import public from mailman.chains.base import Chain, TerminalChainBase from mailman.config import config from mailman.interfaces.chain import LinkAction, IChain @@ -30,6 +25,7 @@ from mailman.utilities.modules import find_components from zope.interface.verify import verifyObject +@public def process(mlist, msg, msgdata, start_chain='default-posting-chain'): """Process the message through a chain. @@ -85,13 +81,14 @@ def process(mlist, msg, msgdata, start_chain='default-posting-chain'): link.function(mlist, msg, msgdata) else: raise AssertionError( - 'Bad link action: {0}'.format(link.action)) + 'Bad link action: {}'.format(link.action)) else: # The rule did not match; keep going. if link.rule.record: misses.append(link.rule.name) +@public def initialize(): """Set up chains, both built-in and from the database.""" for chain_class in find_components('mailman.chains', IChain): diff --git a/src/mailman/core/constants.py b/src/mailman/core/constants.py index 3b6526f38..167ffef8b 100644 --- a/src/mailman/core/constants.py +++ b/src/mailman/core/constants.py @@ -17,11 +17,6 @@ """Various constants and enumerations.""" -__all__ = [ - 'system_preferences', - ] - - from mailman.config import config from mailman.interfaces.languages import ILanguageManager from mailman.interfaces.member import DeliveryMode, DeliveryStatus @@ -30,7 +25,6 @@ from zope.component import getUtility from zope.interface import implementer - @implementer(IPreferences) class SystemDefaultPreferences: """The default system preferences.""" @@ -48,5 +42,5 @@ class SystemDefaultPreferences: return getUtility(ILanguageManager)[config.mailman.default_language] - system_preferences = SystemDefaultPreferences() +__all__ = ['system_preferences'] diff --git a/src/mailman/core/errors.py b/src/mailman/core/errors.py deleted file mode 100644 index c293e81cb..000000000 --- a/src/mailman/core/errors.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (C) 1998-2016 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/>. - -"""Legacy Mailman exceptions. - -This module is largely obsolete, though not all exceptions in use have been -migrated to their proper location. There are still a number of Mailman 2.1 -exceptions floating about in here too. - -The right place for exceptions is in the interface module for their related -interfaces. -""" - - -__all__ = [ - 'DiscardMessage', - 'HandlerError', - 'HoldMessage', - 'LostHeldMessage', - 'RESTError', - 'ReadOnlyPATCHRequestError', - 'RejectMessage', - 'UnknownPATCHRequestError', - ] - - - -class MailmanError(Exception): - """Base class for all Mailman errors.""" - pass - - - -# Exceptions for admin request database -class LostHeldMessage(MailmanError): - """Held message was lost.""" - pass - - - -def _(s): - return s - - -# Exceptions for the Handler subsystem -class HandlerError(MailmanError): - """Base class for all handler errors.""" - - def __init__(self, message=None): - self.message = message - - def __str__(self): - return self.message - - -class HoldMessage(HandlerError): - """Base class for all message-being-held short circuits.""" - - # funky spelling is necessary to break import loops - reason = _('For some unknown reason') - - def reason_notice(self): - return self.reason - - # funky spelling is necessary to break import loops - rejection = _('Your message was rejected') - - def rejection_notice(self, mlist): - return self.rejection - - -class DiscardMessage(HandlerError): - """The message can be discarded with no further action""" - - -class RejectMessage(HandlerError): - """The message will be bounced back to the sender""" - - - -class RESTError(MailmanError): - """Base class for REST API errors.""" - - -class UnknownPATCHRequestError(RESTError): - """A PATCH request contained an unknown attribute.""" - - def __init__(self, attribute): - self.attribute = attribute - - -class ReadOnlyPATCHRequestError(RESTError): - """A PATCH request contained a read-only attribute.""" - - def __init__(self, attribute): - self.attribute = attribute diff --git a/src/mailman/core/i18n.py b/src/mailman/core/i18n.py index 28a231a97..ebe61894a 100644 --- a/src/mailman/core/i18n.py +++ b/src/mailman/core/i18n.py @@ -17,24 +17,23 @@ """Internationalization.""" -__all__ = [ - '_', - 'ctime', - 'initialize', - ] - - -import time import mailman.messages from flufl.i18n import PackageStrategy, registry from mailman.interfaces.configuration import ConfigurationUpdatedEvent +# We can't use @mailman.public here because of circular imports. +__all__ = [ + '_', + 'handle_ConfigurationUpdatedEvent', + 'initialize', + ] + + _ = None - def initialize(application=None): """Initialize the i18n subsystem. @@ -50,70 +49,6 @@ def initialize(application=None): _ = application._ - -def ctime(date): - """Translate a ctime. - - :param date: The date to translate. - :type date: str or time float - :return: The translated date. - :rtype: string - """ - # Don't make these module globals since we have to do runtime translation - # of the strings anyway. - daysofweek = [ - _('Mon'), _('Tue'), _('Wed'), _('Thu'), - _('Fri'), _('Sat'), _('Sun') - ] - months = [ - '', - _('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), - _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec') - ] - - tzname = _('Server Local Time') - if isinstance(date, str): - try: - year, mon, day, hh, mm, ss, wday, ydat, dst = time.strptime(date) - if dst in (0, 1): - tzname = time.tzname[dst] - else: - # MAS: No exception but dst = -1 so try - return ctime(time.mktime((year, mon, day, hh, mm, ss, wday, - ydat, dst))) - except (ValueError, AttributeError): - try: - wday, mon, day, hms, year = date.split() - hh, mm, ss = hms.split(':') - year = int(year) - day = int(day) - hh = int(hh) - mm = int(mm) - ss = int(ss) - except ValueError: - return date - else: - for i in range(0, 7): - wconst = (1999, 1, 1, 0, 0, 0, i, 1, 0) - if wday.lower() == time.strftime('%a', wconst).lower(): - wday = i - break - for i in range(1, 13): - mconst = (1999, i, 1, 0, 0, 0, 0, 1, 0) - if mon.lower() == time.strftime('%b', mconst).lower(): - mon = i - break - else: - year, mon, day, hh, mm, ss, wday, yday, dst = time.localtime(date) - if dst in (0, 1): - tzname = time.tzname[dst] - - wday = daysofweek[wday] - mon = months[mon] - return _('$wday $mon $day $hh:$mm:$ss $tzname $year') - - - def handle_ConfigurationUpdatedEvent(event): if isinstance(event, ConfigurationUpdatedEvent): _.default = event.config.mailman.default_language diff --git a/src/mailman/core/initialize.py b/src/mailman/core/initialize.py index bc6da9639..3c724aecd 100644 --- a/src/mailman/core/initialize.py +++ b/src/mailman/core/initialize.py @@ -24,20 +24,12 @@ line argument parsing, since some of the initialization behavior is controlled by the command line arguments. """ -__all__ = [ - 'initialize', - 'initialize_1', - 'initialize_2', - 'initialize_3', - 'INHIBIT_CONFIG_FILE', - ] - - import os import sys import mailman.config.config import mailman.core.logging +from mailman import public from mailman.interfaces.database import IDatabaseFactory from mailman.utilities.modules import call_name from pkg_resources import resource_string as resource_bytes @@ -49,9 +41,9 @@ from zope.configuration import xmlconfig # existing configuration file. Otherwise the existence of say a # ~/.mailman.cfg file can break tests. INHIBIT_CONFIG_FILE = object() +__all__ = ['INHIBIT_CONFIG_FILE'] - def search_for_configuration_file(): """Search the file system for a configuration file to use. @@ -89,12 +81,12 @@ def search_for_configuration_file(): return None - # These initialization calls are separated for the testing framework, which # needs to do some internal calculations after config file loading and log # initialization, but before database initialization. Generally all other # code will just call initialize(). +@public def initialize_1(config_path=None): """First initialization step. @@ -135,6 +127,7 @@ def initialize_1(config_path=None): mailman.config.config.push('extra testing config', extra_cfg) +@public def initialize_2(debug=False, propagate_logs=None, testing=False): """Second initialization step. @@ -174,6 +167,7 @@ def initialize_2(debug=False, propagate_logs=None, testing=False): initialize_commands() +@public def initialize_3(): """Third initialization step. @@ -185,7 +179,7 @@ def initialize_3(): call_name(config.mailman.post_hook) - +@public def initialize(config_path=None, propagate_logs=None): initialize_1(config_path) initialize_2(propagate_logs=propagate_logs) diff --git a/src/mailman/core/logging.py b/src/mailman/core/logging.py index 24aaa0ffa..298923d3f 100644 --- a/src/mailman/core/logging.py +++ b/src/mailman/core/logging.py @@ -17,25 +17,19 @@ """Logging initialization, using Python's standard logging package.""" -__all__ = [ - 'initialize', - 'reopen', - ] - - import os import sys import codecs import logging from lazr.config import as_boolean, as_log_level +from mailman import public from mailman.config import config _handlers = {} - # XXX I would love to simplify things and use Python's WatchedFileHandler, but # there are two problems. First, it's more difficult to handle the test # suite's need to reopen the file handler to a different path. @@ -50,7 +44,7 @@ class ReopenableFileHandler(logging.Handler): """A file handler that supports reopening.""" def __init__(self, name, filename): - logging.Handler.__init__(self) + super().__init__() self.name = name self.filename = filename self._stream = self._open() @@ -70,9 +64,9 @@ class ReopenableFileHandler(logging.Handler): try: msg = self.format(record) try: - stream.write('{0}'.format(msg)) + stream.write('{}'.format(msg)) except UnicodeError: - stream.write('{0}'.format(msg.encode('string-escape'))) + stream.write('{}'.format(msg.encode('string-escape'))) if msg[-1] != '\n': stream.write('\n') self.flush() @@ -83,7 +77,7 @@ class ReopenableFileHandler(logging.Handler): self.flush() self._stream.close() self._stream = None - logging.Handler.close(self) + super().close() def reopen(self, filename=None): """Reopen the output stream. @@ -98,7 +92,6 @@ class ReopenableFileHandler(logging.Handler): self._stream = self._open() - def _init_logger(propagate, sub_name, log, logger_config): # Get settings from log configuration file (or defaults). log_format = logger_config.format @@ -120,6 +113,7 @@ def _init_logger(propagate, sub_name, log, logger_config): log.addHandler(handler) +@public def initialize(propagate=None): """Initialize all logs. @@ -158,14 +152,15 @@ def initialize(propagate=None): log = logging.getLogger(logger_name) _init_logger(propagate, sub_name, log, logger_config) - + +@public def reopen(): """Re-open all log files.""" for handler in _handlers.values(): handler.reopen() - +@public def get_handler(sub_name): """Return the handler associated with a named logger. diff --git a/src/mailman/core/pipelines.py b/src/mailman/core/pipelines.py index a353e3ad8..5df21ed15 100644 --- a/src/mailman/core/pipelines.py +++ b/src/mailman/core/pipelines.py @@ -17,24 +17,15 @@ """Built-in pipelines.""" -__all__ = [ - 'BasePipeline', - 'OwnerPipeline', - 'PostingPipeline', - 'VirginPipeline', - 'initialize', - 'process', - ] - - import logging +from mailman import public from mailman.app.bounces import bounce_message from mailman.config import config -from mailman.core import errors from mailman.core.i18n import _ from mailman.interfaces.handler import IHandler -from mailman.interfaces.pipeline import IPipeline +from mailman.interfaces.pipeline import ( + DiscardMessage, IPipeline, RejectMessage) from mailman.utilities.modules import find_components from zope.interface import implementer from zope.interface.verify import verifyObject @@ -44,7 +35,7 @@ dlog = logging.getLogger('mailman.debug') vlog = logging.getLogger('mailman.vette') - +@public def process(mlist, msg, msgdata, pipeline_name='built-in'): """Process the message through the given pipeline. @@ -56,22 +47,22 @@ def process(mlist, msg, msgdata, pipeline_name='built-in'): message_id = msg.get('message-id', 'n/a') pipeline = config.pipelines[pipeline_name] for handler in pipeline: - dlog.debug('{0} pipeline {1} processing: {2}'.format( + dlog.debug('{} pipeline {} processing: {}'.format( message_id, pipeline_name, handler.name)) try: handler.process(mlist, msg, msgdata) - except errors.DiscardMessage as error: + except DiscardMessage as error: vlog.info( - '{0} discarded by "{1}" pipeline handler "{2}": {3}'.format( + '{} discarded by "{}" pipeline handler "{}": {}'.format( message_id, pipeline_name, handler.name, error.message)) - except errors.RejectMessage as error: + except RejectMessage as error: vlog.info( - '{0} rejected by "{1}" pipeline handler "{2}": {3}'.format( + '{} rejected by "{}" pipeline handler "{}": {}'.format( message_id, pipeline_name, handler.name, error.message)) bounce_message(mlist, msg, error) - +@public @implementer(IPipeline) class BasePipeline: """Base pipeline implementation.""" @@ -88,7 +79,7 @@ class BasePipeline: yield from self._handlers - +@public class OwnerPipeline(BasePipeline): """The built-in owner pipeline.""" @@ -101,6 +92,7 @@ class OwnerPipeline(BasePipeline): ) +@public class PostingPipeline(BasePipeline): """The built-in posting pipeline.""" @@ -127,6 +119,7 @@ class PostingPipeline(BasePipeline): ) +@public class VirginPipeline(BasePipeline): """The processing pipeline for virgin messages. @@ -141,7 +134,7 @@ class VirginPipeline(BasePipeline): ) - +@public def initialize(): """Initialize the pipelines.""" # Find all handlers in the registered plugins. @@ -149,7 +142,7 @@ def initialize(): handler = handler_class() verifyObject(IHandler, handler) assert handler.name not in config.handlers, ( - 'Duplicate handler "{0}" found in {1}'.format( + 'Duplicate handler "{}" found in {}'.format( handler.name, handler_class)) config.handlers[handler.name] = handler # Set up some pipelines. diff --git a/src/mailman/core/rules.py b/src/mailman/core/rules.py index b593214f8..f6ca87726 100644 --- a/src/mailman/core/rules.py +++ b/src/mailman/core/rules.py @@ -17,17 +17,14 @@ """Various rule helpers""" -__all__ = [ - 'initialize', - ] - - +from mailman import public from mailman.config import config from mailman.interfaces.rules import IRule from mailman.utilities.modules import find_components from zope.interface.verify import verifyObject +@public def initialize(): """Find and register all rules in all plugins.""" # Find rules in plugins. diff --git a/src/mailman/core/runner.py b/src/mailman/core/runner.py index 64b830131..4c9c59464 100644 --- a/src/mailman/core/runner.py +++ b/src/mailman/core/runner.py @@ -17,11 +17,6 @@ """The process runner base class.""" -__all__ = [ - 'Runner', - ] - - import time import signal import logging @@ -29,6 +24,7 @@ import traceback from io import StringIO from lazr.config import as_boolean, as_timedelta +from mailman import public from mailman.config import config from mailman.core.i18n import _ from mailman.core.logging import reopen @@ -47,7 +43,7 @@ elog = logging.getLogger('mailman.error') rlog = logging.getLogger('mailman.runner') - +@public @implementer(IRunner) class Runner: is_queue_runner = True @@ -74,7 +70,7 @@ class Runner: name, self.queue_directory, slice, numslices, True) else: self.queue_directory = None - self.switchboard= None + self.switchboard = None self.sleep_time = as_timedelta(section.sleep_time) # sleep_time is a timedelta; turn it into a float for time.sleep(). self.sleep_float = (86400 * self.sleep_time.days + @@ -86,7 +82,7 @@ class Runner: self.status = 0 def __repr__(self): - return '<{0} at {1:#x}>'.format(self.__class__.__name__, id(self)) + return '<{} at {:#x}>'.format(self.__class__.__name__, id(self)) def signal_handler(self, signum, frame): signame = { diff --git a/src/mailman/core/switchboard.py b/src/mailman/core/switchboard.py index ae85bf22e..122d6b85d 100644 --- a/src/mailman/core/switchboard.py +++ b/src/mailman/core/switchboard.py @@ -24,12 +24,6 @@ written. First, the message is written to the pickle, then the metadata dictionary is written. """ -__all__ = [ - 'Switchboard', - 'handle_ConfigurationUpdatedEvent', - ] - - import os import time import email @@ -37,6 +31,7 @@ import pickle import hashlib import logging +from mailman import public from mailman.config import config from mailman.email.message import Message from mailman.interfaces.configuration import ConfigurationUpdatedEvent @@ -60,7 +55,7 @@ MAX_BAK_COUNT = 3 elog = logging.getLogger('mailman.error') - +@public @implementer(ISwitchboard) class Switchboard: """See `ISwitchboard`.""" @@ -83,7 +78,7 @@ class Switchboard: :type recover: bool """ assert (numslices & (numslices - 1)) == 0, ( - 'Not a power of 2: {0}'.format(numslices)) + 'Not a power of 2: {}'.format(numslices)) self.name = name self.queue_directory = queue_directory # If configured to, create the directory if it doesn't yet exist. @@ -253,7 +248,7 @@ class Switchboard: os.rename(src, dst) - +@public def handle_ConfigurationUpdatedEvent(event): """Initialize the global switchboards for input/output.""" if not isinstance(event, ConfigurationUpdatedEvent): diff --git a/src/mailman/core/system.py b/src/mailman/core/system.py index f773a9f7f..c4489d926 100644 --- a/src/mailman/core/system.py +++ b/src/mailman/core/system.py @@ -17,11 +17,6 @@ """System information.""" -__all__ = [ - 'system', - ] - - import sys from mailman import version @@ -29,7 +24,6 @@ from mailman.interfaces.system import ISystem from zope.interface import implementer - @implementer(ISystem) class System: """See `ISystem`.""" @@ -46,3 +40,4 @@ class System: system = System() +__all__ = ['system'] diff --git a/src/mailman/core/tests/test_pipelines.py b/src/mailman/core/tests/test_pipelines.py index f36679347..98ce125a7 100644 --- a/src/mailman/core/tests/test_pipelines.py +++ b/src/mailman/core/tests/test_pipelines.py @@ -17,31 +17,25 @@ """Test the core modification pipelines.""" -__all__ = [ - 'TestOwnerPipeline', - 'TestPostingPipeline', - ] - - import unittest from mailman.app.lifecycle import create_list from mailman.config import config -from mailman.core.errors import DiscardMessage, RejectMessage from mailman.core.pipelines import process from mailman.interfaces.handler import IHandler from mailman.interfaces.member import MemberRole -from mailman.interfaces.pipeline import IPipeline +from mailman.interfaces.pipeline import ( + DiscardMessage, IPipeline, RejectMessage) from mailman.interfaces.usermanager import IUserManager from mailman.testing.helpers import ( - LogFileMark, digest_mbox, get_queue_messages, reset_the_world, + LogFileMark, digest_mbox, get_queue_messages, specialized_message_from_string as mfs) from mailman.testing.layers import ConfigLayer +from operator import delitem from zope.component import getUtility from zope.interface import implementer - @implementer(IHandler) class DiscardingHandler: name = 'discarding' @@ -76,7 +70,6 @@ class RejectingPipeline: yield RejectHandler() - class TestPostingPipeline(unittest.TestCase): """Test various aspects of the built-in postings pipeline.""" @@ -85,7 +78,9 @@ class TestPostingPipeline(unittest.TestCase): def setUp(self): self._mlist = create_list('test@example.com') config.pipelines['test-discarding'] = DiscardingPipeline() + self.addCleanup(delitem, config.pipelines, 'test-discarding') config.pipelines['test-rejecting'] = RejectingPipeline() + self.addCleanup(delitem, config.pipelines, 'test-rejecting') self._msg = mfs("""\ From: Anne Person <anne@example.org> To: test@example.com @@ -95,11 +90,6 @@ Message-ID: <ant> testing """) - def tearDown(self): - reset_the_world() - del config.pipelines['test-discarding'] - del config.pipelines['test-rejecting'] - def test_rfc2369_headers(self): # Ensure that RFC 2369 List-* headers are added. msgdata = {} @@ -129,9 +119,8 @@ testing '"rejecting": by test handler')) # In the rejection case, the original message will also be in the # virgin queue. - messages = get_queue_messages('virgin') - self.assertEqual(len(messages), 1) - self.assertEqual(str(messages[0].msg['subject']), 'a test') + items = get_queue_messages('virgin', expected_count=1) + self.assertEqual(str(items[0].msg['subject']), 'a test') def test_decorate_bulk(self): # Ensure that bulk postings get decorated with the footer. @@ -160,18 +149,14 @@ testing process(self._mlist, self._msg, {}, pipeline_name='default-posting-pipeline') for queue in ('archive', 'nntp'): - messages = get_queue_messages(queue) - self.assertEqual( - len(messages), 1, - 'Queue {} has {} messages'.format(queue, len(messages))) - payload = messages[0].msg.get_payload() + items = get_queue_messages(queue, expected_count=1) + payload = items[0].msg.get_payload() self.assertNotIn('Test mailing list', payload) self.assertEqual(len(digest_mbox(self._mlist)), 1) payload = digest_mbox(self._mlist)[0].get_payload() self.assertNotIn('Test mailing list', payload) - class TestOwnerPipeline(unittest.TestCase): """Test various aspects of the built-in owner pipeline.""" @@ -204,7 +189,6 @@ To: test-owner@example.com # outgoing queue. process(self._mlist, self._msg, {}, pipeline_name='default-owner-pipeline') - messages = get_queue_messages('out', sort_on='to') - self.assertEqual(len(messages), 1) - self.assertEqual(messages[0].msgdata['recipients'], + items = get_queue_messages('out', sort_on='to', expected_count=1) + self.assertEqual(items[0].msgdata['recipients'], set(('anne@example.com', 'bart@example.com'))) diff --git a/src/mailman/core/tests/test_runner.py b/src/mailman/core/tests/test_runner.py index e06357d7c..69911f95e 100644 --- a/src/mailman/core/tests/test_runner.py +++ b/src/mailman/core/tests/test_runner.py @@ -17,11 +17,6 @@ """Test some Runner base class behavior.""" -__all__ = [ - 'TestRunner', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -38,13 +33,11 @@ from mailman.testing.helpers import ( from mailman.testing.layers import ConfigLayer - class CrashingRunner(Runner): def _dispose(self, mlist, msg, msgdata): raise RuntimeError('borked') - class TestRunner(unittest.TestCase): """Test the Runner base class behavior.""" @@ -77,17 +70,16 @@ Message-ID: <ant> # message, and metadata. self.assertEqual(len(self._events), 1) event = self._events[0] - self.assertTrue(isinstance(event, RunnerCrashEvent)) + self.assertIsInstance(event, RunnerCrashEvent) self.assertEqual(event.mailing_list, self._mlist) self.assertEqual(event.message['message-id'], '<ant>') self.assertEqual(event.metadata['listid'], 'test.example.com') - self.assertTrue(isinstance(event.error, RuntimeError)) + self.assertIsInstance(event.error, RuntimeError) self.assertEqual(str(event.error), 'borked') - self.assertTrue(isinstance(event.runner, CrashingRunner)) + self.assertIsInstance(event.runner, CrashingRunner) # The message should also have ended up in the shunt queue. - shunted = get_queue_messages('shunt') - self.assertEqual(len(shunted), 1) - self.assertEqual(shunted[0].msg['message-id'], '<ant>') + items = get_queue_messages('shunt', expected_count=1) + self.assertEqual(items[0].msg['message-id'], '<ant>') def test_digest_messages(self): # In LP: #1130697, the digest runner creates MIME digests using the @@ -112,18 +104,17 @@ Message-ID: <ant> runner.run() error_text = error_log.read() self.assertEqual(len(error_text), 0, error_text) - self.assertEqual(len(get_queue_messages('shunt')), 0) - messages = get_queue_messages('out') - self.assertEqual(len(messages), 2) + get_queue_messages('shunt', expected_count=0) + items = get_queue_messages('out', expected_count=2) # Which one is the MIME digest? mime_digest = None - for bag in messages: - if bag.msg.get_content_type() == 'multipart/mixed': + for item in items: + if item.msg.get_content_type() == 'multipart/mixed': assert mime_digest is None, 'Found two MIME digests' - mime_digest = bag.msg + mime_digest = item.msg # The cook-headers handler ran. self.assertIn('x-mailman-version', mime_digest) self.assertEqual(mime_digest['precedence'], 'list') # The list's -request address is the original sender. - self.assertEqual(bag.msgdata['original_sender'], + self.assertEqual(item.msgdata['original_sender'], 'test-request@example.com') diff --git a/src/mailman/core/tests/test_switchboard.py b/src/mailman/core/tests/test_switchboard.py index b2e10fc3f..9389959b0 100644 --- a/src/mailman/core/tests/test_switchboard.py +++ b/src/mailman/core/tests/test_switchboard.py @@ -17,11 +17,6 @@ """Switchboard tests.""" -__all__ = [ - 'TestSwitchboard', - ] - - import unittest from mailman.config import config diff --git a/src/mailman/handlers/member_recipients.py b/src/mailman/handlers/member_recipients.py index f224b4f36..6a28af11a 100644 --- a/src/mailman/handlers/member_recipients.py +++ b/src/mailman/handlers/member_recipients.py @@ -29,10 +29,10 @@ __all__ = [ from mailman.config import config -from mailman.core import errors from mailman.core.i18n import _ from mailman.interfaces.handler import IHandler from mailman.interfaces.member import DeliveryStatus +from mailman.interfaces.pipeline import RejectMessage from mailman.utilities.string import wrap from zope.interface import implementer @@ -82,7 +82,7 @@ class MemberRecipients: Your urgent message to the $mlist.display_name mailing list was not authorized for delivery. The original message as received by Mailman is attached. """) - raise errors.RejectMessage(wrap(text)) + raise RejectMessage(wrap(text)) # Calculate the regular recipients of the message recipients = set(member.address.email for member in mlist.regular_members.members diff --git a/src/mailman/handlers/mime_delete.py b/src/mailman/handlers/mime_delete.py index 38495ebc9..a1e861bf8 100644 --- a/src/mailman/handlers/mime_delete.py +++ b/src/mailman/handlers/mime_delete.py @@ -42,11 +42,11 @@ from email.mime.text import MIMEText from itertools import count from lazr.config import as_boolean from mailman.config import config -from mailman.core import errors from mailman.core.i18n import _ from mailman.email.message import OwnerNotification from mailman.interfaces.action import FilterAction from mailman.interfaces.handler import IHandler +from mailman.interfaces.pipeline import DiscardMessage, RejectMessage from mailman.utilities.string import oneline from mailman.version import VERSION from string import Template @@ -60,7 +60,7 @@ log = logging.getLogger('mailman.error') def dispose(mlist, msg, msgdata, why): if mlist.filter_action is FilterAction.reject: # Bounce the message to the original author. - raise errors.RejectMessage(why) + raise RejectMessage(why) elif mlist.filter_action is FilterAction.forward: # Forward it on to the list moderators. text=_("""\ @@ -90,7 +90,7 @@ message. '{1} invalid FilterAction: {0}. Treating as discard'.format( mlist.fqdn_listname, mlist.filter_action.name)) # Most cases also discard the message - raise errors.DiscardMessage(why) + raise DiscardMessage(why) diff --git a/src/mailman/handlers/tests/test_filter.py b/src/mailman/handlers/tests/test_filter.py index 141bd6c8d..0933d8b00 100644 --- a/src/mailman/handlers/tests/test_filter.py +++ b/src/mailman/handlers/tests/test_filter.py @@ -26,8 +26,8 @@ import unittest from mailman.app.lifecycle import create_list from mailman.config import config -from mailman.core.errors import DiscardMessage from mailman.interfaces.mime import FilterAction +from mailman.interfaces.pipeline import DiscardMessage from mailman.testing.helpers import specialized_message_from_string as mfs from mailman.testing.layers import ConfigLayer diff --git a/src/mailman/handlers/tests/test_mimedel.py b/src/mailman/handlers/tests/test_mimedel.py index d8829d995..d5670f1bc 100644 --- a/src/mailman/handlers/tests/test_mimedel.py +++ b/src/mailman/handlers/tests/test_mimedel.py @@ -33,10 +33,10 @@ import unittest from contextlib import ExitStack, contextmanager from mailman.app.lifecycle import create_list from mailman.config import config -from mailman.core import errors from mailman.handlers import mime_delete from mailman.interfaces.action import FilterAction from mailman.interfaces.member import MemberRole +from mailman.interfaces.pipeline import DiscardMessage, RejectMessage from mailman.interfaces.usermanager import IUserManager from mailman.testing.helpers import ( LogFileMark, configuration, get_queue_messages, @@ -90,7 +90,7 @@ Message-ID: <ant> def test_dispose_discard(self): self._mlist.filter_action = FilterAction.discard - with self.assertRaises(errors.DiscardMessage) as cm: + with self.assertRaises(DiscardMessage) as cm: mime_delete.dispose(self._mlist, self._msg, {}, 'discarding') self.assertEqual(cm.exception.message, 'discarding') # There should be no messages in the 'bad' queue. @@ -98,7 +98,7 @@ Message-ID: <ant> def test_dispose_bounce(self): self._mlist.filter_action = FilterAction.reject - with self.assertRaises(errors.RejectMessage) as cm: + with self.assertRaises(RejectMessage) as cm: mime_delete.dispose(self._mlist, self._msg, {}, 'rejecting') self.assertEqual(cm.exception.message, 'rejecting') # There should be no messages in the 'bad' queue. @@ -114,7 +114,7 @@ Message-ID: <ant> self._mlist.subscribe(bart, MemberRole.moderator) # Now set the filter action and dispose the message. self._mlist.filter_action = FilterAction.forward - with self.assertRaises(errors.DiscardMessage) as cm: + with self.assertRaises(DiscardMessage) as cm: mime_delete.dispose(self._mlist, self._msg, {}, 'forwarding') self.assertEqual(cm.exception.message, 'forwarding') # There should now be a multipart message in the virgin queue destined @@ -156,7 +156,7 @@ message. # the site owner has indicated that filtered messages cannot be # preserved, then this is the same as discarding them. self._mlist.filter_action = FilterAction.preserve - with self.assertRaises(errors.DiscardMessage) as cm: + with self.assertRaises(DiscardMessage) as cm: mime_delete.dispose(self._mlist, self._msg, {}, 'not preserved') self.assertEqual(cm.exception.message, 'not preserved') # There should be no messages in the 'bad' queue. @@ -169,7 +169,7 @@ message. # preserved, then this is similar to discarding the message except # that a copy is preserved in the 'bad' queue. self._mlist.filter_action = FilterAction.preserve - with self.assertRaises(errors.DiscardMessage) as cm: + with self.assertRaises(DiscardMessage) as cm: mime_delete.dispose(self._mlist, self._msg, {}, 'preserved') self.assertEqual(cm.exception.message, 'preserved') # There should be no messages in the 'bad' queue. @@ -189,7 +189,7 @@ message. FilterAction.defer): self._mlist.filter_action = action mark = LogFileMark('mailman.error') - with self.assertRaises(errors.DiscardMessage) as cm: + with self.assertRaises(DiscardMessage) as cm: mime_delete.dispose(self._mlist, self._msg, {}, 'bad action') self.assertEqual(cm.exception.message, 'bad action') line = mark.readline()[:-1] diff --git a/src/mailman/interfaces/configuration.py b/src/mailman/interfaces/configuration.py index ba244d876..f68ba4785 100644 --- a/src/mailman/interfaces/configuration.py +++ b/src/mailman/interfaces/configuration.py @@ -24,7 +24,7 @@ __all__ = [ ] -from mailman.core.errors import MailmanError +from mailman.interfaces.errors import MailmanError from zope.interface import Interface diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py index 25aa54cc3..e978479df 100644 --- a/src/mailman/interfaces/domain.py +++ b/src/mailman/interfaces/domain.py @@ -28,7 +28,7 @@ __all__ = [ ] -from mailman.core.errors import MailmanError +from mailman.interfaces.errors import MailmanError from zope.interface import Interface, Attribute diff --git a/src/mailman/interfaces/errors.py b/src/mailman/interfaces/errors.py index 64194a70d..6045e4868 100644 --- a/src/mailman/interfaces/errors.py +++ b/src/mailman/interfaces/errors.py @@ -22,11 +22,11 @@ components. More specific exceptions will be located in the relevant interfaces. """ +# We can't use @mailman.public here because of circular imports. __all__ = [ 'MailmanError', ] - class MailmanError(Exception): """Base class for all Mailman exceptions.""" diff --git a/src/mailman/interfaces/member.py b/src/mailman/interfaces/member.py index f084391e2..91c2aab0a 100644 --- a/src/mailman/interfaces/member.py +++ b/src/mailman/interfaces/member.py @@ -34,7 +34,7 @@ __all__ = [ from enum import Enum -from mailman.core.errors import MailmanError +from mailman.interfaces.errors import MailmanError from zope.interface import Interface, Attribute diff --git a/src/mailman/interfaces/mta.py b/src/mailman/interfaces/mta.py index 5cf9c79c4..8c168c3a1 100644 --- a/src/mailman/interfaces/mta.py +++ b/src/mailman/interfaces/mta.py @@ -24,7 +24,7 @@ __all__ = [ ] -from mailman.core.errors import MailmanError +from mailman.interfaces.errors import MailmanError from zope.interface import Interface diff --git a/src/mailman/interfaces/pipeline.py b/src/mailman/interfaces/pipeline.py index b8830b003..5e39c2a3e 100644 --- a/src/mailman/interfaces/pipeline.py +++ b/src/mailman/interfaces/pipeline.py @@ -17,15 +17,41 @@ """Interface for describing pipelines.""" -__all__ = [ - 'IPipeline', - ] +from mailman import public +from zope.interface import Interface, Attribute -from zope.interface import Interface, Attribute +# For i18n extraction. +def _(s): + return s + + +# These are thrown but they aren't exceptions so don't inherit from +# mailman.interfaces.errors.MailmanError. Python requires that they inherit +# from BaseException. +@public +class DiscardMessage(BaseException): + """The message can be discarded with no further action""" + + def __init__(self, message=None): + self.message = message + + def __str__(self): + return self.message + + +@public +class RejectMessage(BaseException): + """The message will be bounced back to the sender""" + + def __init__(self, message=None): + self.message = message + + def __str__(self): + return self.message - +@public class IPipeline(Interface): """A pipeline of handlers.""" diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py index 6eb88e1fd..a6c15d428 100644 --- a/src/mailman/rest/listconf.py +++ b/src/mailman/rest/listconf.py @@ -24,8 +24,6 @@ __all__ = [ from lazr.config import as_boolean, as_timedelta from mailman.config import config -from mailman.core.errors import ( - ReadOnlyPATCHRequestError, UnknownPATCHRequestError) from mailman.interfaces.action import Action from mailman.interfaces.archiver import ArchivePolicy from mailman.interfaces.autorespond import ResponseAction @@ -35,7 +33,8 @@ from mailman.interfaces.mailinglist import ( from mailman.rest.helpers import ( GetterSetter, bad_request, etag, no_content, not_found, okay) from mailman.rest.validator import ( - PatchValidator, Validator, enum_validator, list_of_strings_validator) + PatchValidator, ReadOnlyPATCHRequestError, UnknownPATCHRequestError, + Validator, enum_validator, list_of_strings_validator) diff --git a/src/mailman/rest/users.py b/src/mailman/rest/users.py index 238ec4562..21ed26745 100644 --- a/src/mailman/rest/users.py +++ b/src/mailman/rest/users.py @@ -28,8 +28,6 @@ __all__ = [ from lazr.config import as_boolean from mailman.config import config -from mailman.core.errors import ( - ReadOnlyPATCHRequestError, UnknownPATCHRequestError) from mailman.interfaces.address import ExistingAddressError from mailman.interfaces.usermanager import IUserManager from mailman.rest.addresses import UserAddresses @@ -38,7 +36,8 @@ from mailman.rest.helpers import ( conflict, created, etag, forbidden, no_content, not_found, okay) from mailman.rest.preferences import Preferences from mailman.rest.validator import ( - PatchValidator, Validator, list_of_strings_validator) + PatchValidator, ReadOnlyPATCHRequestError, UnknownPATCHRequestError, + Validator, list_of_strings_validator) from passlib.utils import generate_password as generate from zope.component import getUtility diff --git a/src/mailman/rest/validator.py b/src/mailman/rest/validator.py index 6b708872a..748a63d5c 100644 --- a/src/mailman/rest/validator.py +++ b/src/mailman/rest/validator.py @@ -27,9 +27,8 @@ __all__ = [ ] -from mailman.core.errors import ( - ReadOnlyPATCHRequestError, UnknownPATCHRequestError) from mailman.interfaces.address import IEmailValidator +from mailman.interfaces.errors import MailmanError from mailman.interfaces.languages import ILanguageManager from zope.component import getUtility @@ -37,6 +36,24 @@ from zope.component import getUtility COMMASPACE = ', ' +class RESTError(MailmanError): + """Base class for REST API errors.""" + + +class UnknownPATCHRequestError(RESTError): + """A PATCH request contained an unknown attribute.""" + + def __init__(self, attribute): + self.attribute = attribute + + +class ReadOnlyPATCHRequestError(RESTError): + """A PATCH request contained a read-only attribute.""" + + def __init__(self, attribute): + self.attribute = attribute + + class enum_validator: """Convert an enum value name into an enum value.""" diff --git a/src/mailman/utilities/i18n.py b/src/mailman/utilities/i18n.py index 069bd8f52..3999af869 100644 --- a/src/mailman/utilities/i18n.py +++ b/src/mailman/utilities/i18n.py @@ -31,8 +31,8 @@ import sys from itertools import product from mailman.config import config from mailman.core.constants import system_preferences -from mailman.core.errors import MailmanError from mailman.core.i18n import _ +from mailman.interfaces.errors import MailmanError from mailman.utilities.string import expand, wrap as wrap_text from pkg_resources import resource_filename diff --git a/src/mailman/utilities/importer.py b/src/mailman/utilities/importer.py index 782841838..3d1a8b82f 100644 --- a/src/mailman/utilities/importer.py +++ b/src/mailman/utilities/importer.py @@ -31,7 +31,6 @@ import logging import datetime from mailman.config import config -from mailman.core.errors import MailmanError from mailman.handlers.decorate import decorate, decorate_template from mailman.interfaces.action import Action, FilterAction from mailman.interfaces.address import IEmailValidator @@ -40,6 +39,7 @@ from mailman.interfaces.autorespond import ResponseAction from mailman.interfaces.bans import IBanManager from mailman.interfaces.bounce import UnrecognizedBounceDisposition from mailman.interfaces.digests import DigestFrequency +from mailman.interfaces.errors import MailmanError from mailman.interfaces.languages import ILanguageManager from mailman.interfaces.mailinglist import ( IAcceptableAliasSet, IHeaderMatchList) |
