summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/docs/bounces.rst2
-rw-r--r--src/mailman/chains/reject.py2
-rw-r--r--src/mailman/core/api.py9
-rw-r--r--src/mailman/core/chains.py11
-rw-r--r--src/mailman/core/constants.py8
-rw-r--r--src/mailman/core/errors.py110
-rw-r--r--src/mailman/core/i18n.py81
-rw-r--r--src/mailman/core/initialize.py18
-rw-r--r--src/mailman/core/logging.py23
-rw-r--r--src/mailman/core/pipelines.py37
-rw-r--r--src/mailman/core/rules.py7
-rw-r--r--src/mailman/core/runner.py12
-rw-r--r--src/mailman/core/switchboard.py13
-rw-r--r--src/mailman/core/system.py7
-rw-r--r--src/mailman/core/tests/test_pipelines.py40
-rw-r--r--src/mailman/core/tests/test_runner.py31
-rw-r--r--src/mailman/core/tests/test_switchboard.py5
-rw-r--r--src/mailman/handlers/member_recipients.py4
-rw-r--r--src/mailman/handlers/mime_delete.py6
-rw-r--r--src/mailman/handlers/tests/test_filter.py2
-rw-r--r--src/mailman/handlers/tests/test_mimedel.py14
-rw-r--r--src/mailman/interfaces/configuration.py2
-rw-r--r--src/mailman/interfaces/domain.py2
-rw-r--r--src/mailman/interfaces/errors.py2
-rw-r--r--src/mailman/interfaces/member.py2
-rw-r--r--src/mailman/interfaces/mta.py2
-rw-r--r--src/mailman/interfaces/pipeline.py36
-rw-r--r--src/mailman/rest/listconf.py5
-rw-r--r--src/mailman/rest/users.py5
-rw-r--r--src/mailman/rest/validator.py21
-rw-r--r--src/mailman/utilities/i18n.py2
-rw-r--r--src/mailman/utilities/importer.py2
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)