From 106f5ea8b75b0b2208f0e6d4a927afd159a16fa3 Mon Sep 17 00:00:00 2001 From: Karl-Aksel Puulmann Date: Fri, 5 Apr 2013 21:44:56 +0200 Subject: Add --sort flag to mailman conf subcommand (LP: #1162492) --- src/mailman/commands/cli_conf.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/mailman/commands/cli_conf.py b/src/mailman/commands/cli_conf.py index 1f8095d92..13979a591 100644 --- a/src/mailman/commands/cli_conf.py +++ b/src/mailman/commands/cli_conf.py @@ -65,10 +65,20 @@ class Conf: key-values pair from any section matching the given key will be displayed. """)) + command_parser.add_argument( + '-x', '--sort', + default=False, action='store_true', + help=_("Sort the output by sections and keys.")) def _get_value(self, section, key): return getattr(getattr(config, section), key) + def _sections(self, to_sort): + sections = config.schema._section_schemas + if to_sort: + sections = sorted(sections) + return sections + def _print_full_syntax(self, section, key, value, output): print('[{}] {}: {}'.format(section, key, value), file=output) @@ -78,8 +88,10 @@ class Conf: def _show_section_error(self, section): self.parser.error('No such section: {}'.format(section)) - def _print_values_for_section(self, section, output): + def _print_values_for_section(self, section, output, to_sort): current_section = getattr(config, section) + if to_sort: + current_section = sorted(current_section) for key in current_section: self._print_full_syntax(section, key, self._get_value(section, key), output) @@ -94,6 +106,7 @@ class Conf: # Process the command, ignoring the closing of the output file. section = args.section key = args.key + to_sort = args.sort # Case 1: Both section and key are given, so we can directly look up # the value. if section is not None and key is not None: @@ -111,7 +124,7 @@ class Conf: self._show_section_error(section) # Case 3: Section is not given, key is given. elif section is None and key is not None: - for current_section in config.schema._section_schemas: + for current_section in self._sections(to_sort): # We have to ensure that the current section actually exists # and that it contains the given key. if (self._section_exists(current_section) and @@ -124,12 +137,12 @@ class Conf: # Case 4: Neither section nor key are given, just display all the # sections and their corresponding key/value pairs. elif section is None and key is None: - for current_section in config.schema._section_schemas: + for current_section in self._sections(to_sort): # However, we have to make sure that the current sections and # key which are being looked up actually exist before trying # to print them. if self._section_exists(current_section): - self._print_values_for_section(current_section, output) + self._print_values_for_section(current_section, output, to_sort) def process(self, args): """See `ICLISubCommand`.""" -- cgit v1.3.1 From ee8a3f9b3212b72de4b96b608c31db4ef087f638 Mon Sep 17 00:00:00 2001 From: Karl-Aksel Puulmann Date: Sat, 6 Apr 2013 08:46:28 +0200 Subject: Fixed tests broken by previous commit --- src/mailman/commands/cli_conf.py | 2 +- src/mailman/commands/docs/conf.rst | 1 + src/mailman/commands/tests/test_conf.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mailman/commands/cli_conf.py b/src/mailman/commands/cli_conf.py index 13979a591..3c3143b1a 100644 --- a/src/mailman/commands/cli_conf.py +++ b/src/mailman/commands/cli_conf.py @@ -119,7 +119,7 @@ class Conf: # Case 2: Section is given, key is not given. elif section is not None and key is None: if self._section_exists(section): - self._print_values_for_section(section, output) + self._print_values_for_section(section, output, to_sort) else: self._show_section_error(section) # Case 3: Section is not given, key is given. diff --git a/src/mailman/commands/docs/conf.rst b/src/mailman/commands/docs/conf.rst index 6e458fb54..069bce553 100644 --- a/src/mailman/commands/docs/conf.rst +++ b/src/mailman/commands/docs/conf.rst @@ -14,6 +14,7 @@ a specific key-value pair, or several key-value pairs. ... key = None ... section = None ... output = None + ... sort = None >>> from mailman.commands.cli_conf import Conf >>> command = Conf() diff --git a/src/mailman/commands/tests/test_conf.py b/src/mailman/commands/tests/test_conf.py index bca7fe72f..758f26d56 100644 --- a/src/mailman/commands/tests/test_conf.py +++ b/src/mailman/commands/tests/test_conf.py @@ -40,6 +40,7 @@ class FakeArgs: section = None key = None output = None + sort = False class FakeParser: -- cgit v1.3.1 From 483f8861e2fb546f85191aa618152929328b175a Mon Sep 17 00:00:00 2001 From: Karl-Aksel Puulmann Date: Sat, 6 Apr 2013 09:13:47 +0200 Subject: Added an example to the docs --- src/mailman/commands/docs/conf.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mailman/commands/docs/conf.rst b/src/mailman/commands/docs/conf.rst index 069bce553..e3a8a488f 100644 --- a/src/mailman/commands/docs/conf.rst +++ b/src/mailman/commands/docs/conf.rst @@ -14,7 +14,7 @@ a specific key-value pair, or several key-value pairs. ... key = None ... section = None ... output = None - ... sort = None + ... sort = False >>> from mailman.commands.cli_conf import Conf >>> command = Conf() @@ -65,5 +65,12 @@ If you specify both a section and a key, you will get the corresponding value. >>> command.process(FakeArgs) noreply@example.com +You can also sort the output. The output will be first sorted by section, then by key. + + >>> FakeArgs.section = 'shell' + >>> FakeArgs.sort = True + [shell] banner: Welcome to the GNU Mailman shell + [shell] prompt: >>> + [shell] use_ipython: no .. _`Postfix command postconf(1)`: http://www.postfix.org/postconf.1.html -- cgit v1.3.1 From 735376674b98aa9b72a7e560c29dad2590d08316 Mon Sep 17 00:00:00 2001 From: Stephen J. Turnbull Date: Tue, 14 May 2013 13:04:58 +0900 Subject: Avoid implication that Python 3 support is coming soon. --- src/mailman/docs/INTRODUCTION.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/mailman/docs/INTRODUCTION.rst b/src/mailman/docs/INTRODUCTION.rst index 1ad71cf16..e7128e756 100644 --- a/src/mailman/docs/INTRODUCTION.rst +++ b/src/mailman/docs/INTRODUCTION.rst @@ -82,7 +82,7 @@ lists and archives, etc., are available at: Requirements ============ -Mailman 3.0 requires `Python 2.7`_ or newer. +Mailman 3.0 requires `Python 2.7`_. .. _`GNU Mailman`: http://www.list.org -- cgit v1.3.1 From 5103a8591d0368816bbad8a949269edc472f563a Mon Sep 17 00:00:00 2001 From: Stephen J. Turnbull Date: Tue, 14 May 2013 13:14:34 +0900 Subject: Some minor wording fixups. --- src/mailman/docs/START.rst | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/mailman/docs/START.rst b/src/mailman/docs/START.rst index 85b869c2d..9fffc3048 100644 --- a/src/mailman/docs/START.rst +++ b/src/mailman/docs/START.rst @@ -35,15 +35,18 @@ Requirements ============ Python 2.7 is required. It can either be the default 'python' on your -``$PATH`` or it can be accessible via the ``python2.7`` binary. -If your operating system does not include Python, see http://www.python.org -downloading and installing it from source. Python 3 is not yet supported. +``$PATH`` or it can be accessible via the ``python2.7`` binary. If +your operating system does not include Python, see http://www.python.org +for information about downloading installers (where available) and +installing it from source (when necessary or preferred). Python 3 is +not yet supported. In this documentation, a bare ``python`` refers to the Python executable used to invoke ``bootstrap.py``. -Mailman 3 is now based on the `zc.buildout`_ infrastructure, which greatly -simplifies testing Mailman. Buildout is not required for installation. +The Mailman 3 build is now based on the `zc.buildout`_ infrastructure, which +greatly simplifies testing Mailman. Buildout is not required for installation +of Mailman from a package. During the beta program, you may need some additional dependencies, such as a C compiler and the Python development headers and libraries. You will need an @@ -149,8 +152,9 @@ Try ``bin/mailman --help`` for more details. You can use the commands ``bin/mailman start`` to start the runner subprocess daemons, and of course ``bin/mailman stop`` to stop them. -Postorius is being developed as a separate, Django-based project. For now, -all configuration happens via the command line and REST API. +Postorius, a web UI for administration and subscriber settings, is being +developed as a separate, Django-based project. For now, the most flexible +means of configuration is via the command line and REST API. Mailman Web UI @@ -167,16 +171,16 @@ Postorius was prototyped at the `Pycon 2012 sprint`_, so it is "very alpha" as of Mailman 3 beta 1, and comes in several components. In particular, it requires a `Django`_ installation, and Bazaar checkouts of the `REST client module`_ and `Postorius`_ itself. Building it is fairly straightforward, -however, given Florian Fuchs' `Five Minute Guide` from his `blog post`_ on the +based on Florian Fuchs' `Five Minute Guide` from his `blog post`_ on the Mailman wiki. (Check the `blog post`_ for the most recent version!) The Archiver ------------ -In Mailman 3, the archivers are decoupled from the core engine. It is useful -to provide a simple, standard interface for third-party archiving tools and -services. For this reason, Mailman 3 defines a formal interface to insert +In Mailman 3, the archivers are decoupled from the core engine. Instead, +Mailman 3 provides a simple, standard interface for third-party archiving tools +and services. For this reason, Mailman 3 defines a formal interface to insert messages into any of a number of configured archivers, using whatever protocol is appropriate for that archiver. Summary, search, and retrieval of archived posts are handled by a separate application. -- cgit v1.3.1 From c249630798bba89a425067427e6cbfab6ceddc14 Mon Sep 17 00:00:00 2001 From: Stephen J. Turnbull Date: Tue, 14 May 2013 13:20:52 +0900 Subject: Touch up descriptions of MTAs. Fix one typo. --- src/mailman/docs/MTA.rst | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/mailman/docs/MTA.rst b/src/mailman/docs/MTA.rst index aa53981f8..6e7b4cf9f 100644 --- a/src/mailman/docs/MTA.rst +++ b/src/mailman/docs/MTA.rst @@ -9,27 +9,23 @@ upstream mail server do that. The preferred way to allow Mailman to accept incoming messages from your mail server is to use the `Local Mail Transfer Protocol`_ (LMTP_) interface. Most -open source mail server support LMTP for local delivery, and this is much more +open source mail servers support LMTP for local delivery. This is much more efficient than spawning a process just to do the delivery. -Your mail server should also accept `Simple Mail Transfer Protocol`_ (SMTP_) +Your mail server must also accept `Simple Mail Transfer Protocol`_ (SMTP_) connections from Mailman, for all outgoing messages. The specific instructions for hooking your mail server up to Mailman differs -depending on which mail server you're using. The following are instructions -for the popular open source mail servers. +depending on which mail server you're using. Following are instructions for +Postfix. We would really appreciate contributions of configurations for Exim +and Sendmail, and welcome information about other popular open source mail +servers. Note that Mailman provides lots of configuration variables that you can use to tweak performance for your operating environment. See the ``src/mailman/config/schema.cfg`` file for details. -Exim -==== - -Contributions are welcome! - - Postfix ======= @@ -131,6 +127,12 @@ the Postfix documentation at: .. _`mydestination`: http://www.postfix.org/postconf.5.html#mydestination +Exim +==== + +Contributions are welcome! + + Sendmail ======== -- cgit v1.3.1 From 36382dda0705d90360db55024926ee8f159758aa Mon Sep 17 00:00:00 2001 From: Stephen J. Turnbull Date: Tue, 14 May 2013 13:45:26 +0900 Subject: Improve diagrams. Some wording fixups. --- src/mailman/docs/8-miles-high.rst | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/mailman/docs/8-miles-high.rst b/src/mailman/docs/8-miles-high.rst index d068ebd73..85b186fc5 100644 --- a/src/mailman/docs/8-miles-high.rst +++ b/src/mailman/docs/8-miles-high.rst @@ -37,10 +37,12 @@ processing queues. node [shape=box, color=lightblue, style=filled]; msg [shape=ellipse, color=black, fillcolor=white]; lmtpd [label="LMTP\nSERVER"]; + rts [label="Return\nto Sender"]; msg -> MTA [label="SMTP"]; MTA -> lmtpd [label="LMTP"]; lmtpd -> MTA [label="reject"]; lmtpd -> IN -> PIPELINE [label=".pck"]; + IN -> rts; lmtpd -> BOUNCES [label=".pck"]; lmtpd -> COMMAND [label=".pck"]; } @@ -49,7 +51,9 @@ The `in` queue is processed by *filter chains* (explained below) to determine whether the post (or administrative request) will be processed. If not allowed, the message pickle is discarded, rejected (returned to sender), or held (saved for moderator approval -- not shown). Otherwise the message is -added to the `pipeline` (i.e. posting) queue. +added to the `pipeline` (i.e. posting) queue. (Note that rejecting at this +stage is *not* equivalent to rejecting during LMTP processing. This issue is +currently unresolved.) Each of the `command`, `bounce`, and `pipeline` queues is processed by a *pipeline of handlers* as in Mailman 2's pipeline. (Some functions such as @@ -60,7 +64,8 @@ Handlers may copy messages to other queues (*e.g.*, `archive`), and eventually posted messages for distribution to the list membership end up in the `out` queue for injection into the MTA. -The `virgin` queue is a special queue for messages created by Mailman. +The `virgin` queue (not depicted above) is a special queue for messages created +by Mailman. .. graphviz:: @@ -97,12 +102,13 @@ The default set of rules looks something like this: subgraph rules { rankdir=TB; node [shape=record]; - approved [label=" approved | { | }"]; - emergency [label=" emergency | { | }"]; - loop [label=" loop | { | }"]; - modmember [label=" member\nmoderated | { | }"]; - administrivia [group="0", label=" administrivia | "]; - maxsize [label=" max\ size | { no | }"]; + approved [label=" approved | { no | }"]; + emergency [label=" emergency | { no | }"]; + loop [label=" loop | { no | }"]; + modmember [label=" member\nmoderated | { no | }"]; + administrivia [group="0", + label=" administrivia | { no | }"]; + maxsize [label=" max\ size | { no | }"]; any [label=" any | { | }"]; truth [label=" truth | "]; @@ -114,6 +120,7 @@ The default set of rules looks something like this: DISCARD [shape=invhouse, color=black, style=solid]; MODERATION [color=wheat]; HOLD [color=wheat]; + action [color=wheat]; } { PIPELINE [shape=box, style=filled, color=cyan]; } @@ -130,7 +137,8 @@ The default set of rules looks something like this: modmember:no -> administrivia:in; modmember:yes -> MODERATION; - administrivia:always -> maxsize:in; + administrivia:no -> maxsize:in; + administrivia:yes -> action; maxsize:no -> any:in; maxsize:yes -> MODERATION; @@ -145,7 +153,7 @@ The default set of rules looks something like this: Configuration ============= -Uses `lazr.config`_, essentially an "ini"-style configuration format. +Mailman 3 uses `lazr.config`_, essentially an "ini"-style configuration format. Each Runner's configuration object knows whether it should be started when the Mailman daemon starts, and what queue the Runner manages. -- cgit v1.3.1 From 0120c079ed8c9a9b0b1bc5d8341ac2ec1a8638d3 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Tue, 18 Jun 2013 22:43:40 -0400 Subject: Switch to PEP 435 enums from flufl.enums. --- setup.py | 2 +- src/mailman/app/moderator.py | 2 +- src/mailman/app/subscriptions.py | 2 +- src/mailman/bin/master.py | 2 +- src/mailman/commands/cli_members.py | 4 ++-- src/mailman/commands/cli_status.py | 2 +- src/mailman/database/schema/mm_20120407000000.py | 6 +++--- src/mailman/database/types.py | 8 ++++---- src/mailman/interfaces/action.py | 2 +- src/mailman/interfaces/archiver.py | 2 +- src/mailman/interfaces/autorespond.py | 2 +- src/mailman/interfaces/bounce.py | 2 +- src/mailman/interfaces/chain.py | 2 +- src/mailman/interfaces/command.py | 2 +- src/mailman/interfaces/digests.py | 2 +- src/mailman/interfaces/mailinglist.py | 2 +- src/mailman/interfaces/member.py | 2 +- src/mailman/interfaces/mime.py | 2 +- src/mailman/interfaces/nntp.py | 2 +- src/mailman/interfaces/requests.py | 2 +- src/mailman/model/docs/autorespond.rst | 2 +- src/mailman/model/docs/membership.rst | 2 +- src/mailman/model/docs/users.rst | 3 +-- src/mailman/rest/addresses.py | 2 +- src/mailman/rest/helpers.py | 4 ++-- src/mailman/rest/lists.py | 4 ++-- src/mailman/rest/moderation.py | 4 ++-- src/mailman/rest/validator.py | 10 +++++++--- src/mailman/rules/docs/moderation.rst | 2 +- src/mailman/utilities/importer.py | 2 ++ 30 files changed, 46 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/setup.py b/setup.py index 3e032f46c..f3fc7faaa 100644 --- a/setup.py +++ b/setup.py @@ -93,8 +93,8 @@ case second `m'. Any other spelling is incorrect.""", 'console_scripts' : list(scripts), }, install_requires = [ + 'enum34', 'flufl.bounce', - 'flufl.enum', 'flufl.i18n', 'flufl.lock', 'httplib2', diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py index d8603366a..59fcb0976 100644 --- a/src/mailman/app/moderator.py +++ b/src/mailman/app/moderator.py @@ -246,7 +246,7 @@ def handle_subscription(mlist, id, action, comment=None): lang=getUtility(ILanguageManager)[data['language']]) elif action is Action.accept: key, data = requestdb.get_request(id) - delivery_mode = DeliveryMode(data['delivery_mode']) + delivery_mode = DeliveryMode[data['delivery_mode']] address = data['address'] display_name = data['display_name'] language = getUtility(ILanguageManager)[data['language']] diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py index 23e09ccc7..0995202b6 100644 --- a/src/mailman/app/subscriptions.py +++ b/src/mailman/app/subscriptions.py @@ -54,7 +54,7 @@ def _membership_sort_key(member): The members are sorted first by unique list id, then by subscribed email address, then by role. """ - return (member.list_id, member.address.email, int(member.role)) + return (member.list_id, member.address.email, member.role.value) diff --git a/src/mailman/bin/master.py b/src/mailman/bin/master.py index 9e1bbd6c5..65737ad75 100644 --- a/src/mailman/bin/master.py +++ b/src/mailman/bin/master.py @@ -34,7 +34,7 @@ import socket import logging from datetime import timedelta -from flufl.enum import Enum +from enum import Enum from flufl.lock import Lock, NotLockedError, TimeOutError from lazr.config import as_boolean diff --git a/src/mailman/commands/cli_members.py b/src/mailman/commands/cli_members.py index 7e4b5ec88..37df55cd4 100644 --- a/src/mailman/commands/cli_members.py +++ b/src/mailman/commands/cli_members.py @@ -130,7 +130,7 @@ class Members: DeliveryMode.mime_digests, DeliveryMode.summary_digests] elif args.digest is not None: - digest_types = [DeliveryMode(args.digest + '_digests')] + digest_types = [DeliveryMode[args.digest + '_digests']] else: # Don't filter on digest type. pass @@ -140,7 +140,7 @@ class Members: elif args.nomail == 'byadmin': status_types = [DeliveryStatus.by_moderator] elif args.nomail.startswith('by'): - status_types = [DeliveryStatus('by_' + args.nomail[2:])] + status_types = [DeliveryStatus['by_' + args.nomail[2:]]] elif args.nomail == 'enabled': status_types = [DeliveryStatus.enabled] elif args.nomail == 'unknown': diff --git a/src/mailman/commands/cli_status.py b/src/mailman/commands/cli_status.py index 1f4cc552a..8a5cf9bde 100644 --- a/src/mailman/commands/cli_status.py +++ b/src/mailman/commands/cli_status.py @@ -64,4 +64,4 @@ class Status: message = _('GNU Mailman is in an unexpected state ' '($hostname != $fqdn_name)') print(message) - return int(status) + return status.value diff --git a/src/mailman/database/schema/mm_20120407000000.py b/src/mailman/database/schema/mm_20120407000000.py index 8855df5af..0b1e51386 100644 --- a/src/mailman/database/schema/mm_20120407000000.py +++ b/src/mailman/database/schema/mm_20120407000000.py @@ -67,11 +67,11 @@ def upgrade(database, store, version, module_path): def archive_policy(archive, archive_private): """Convert archive and archive_private to archive_policy.""" if archive == 0: - return int(ArchivePolicy.never) + return ArchivePolicy.never.value elif archive_private == 1: - return int(ArchivePolicy.private) + return ArchivePolicy.private.value else: - return int(ArchivePolicy.public) + return ArchivePolicy.public.value diff --git a/src/mailman/database/types.py b/src/mailman/database/types.py index 42f64a640..86d504ca3 100644 --- a/src/mailman/database/types.py +++ b/src/mailman/database/types.py @@ -32,7 +32,7 @@ from storm.variables import Variable class _EnumVariable(Variable): - """Storm variable for supporting flufl.enum.Enum types. + """Storm variable for supporting enum types. To use this, make the database column a INTEGER. """ @@ -46,18 +46,18 @@ class _EnumVariable(Variable): return None if not from_db: return value - return self._enum[value] + return self._enum(value) def parse_get(self, value, to_db): if value is None: return None if not to_db: return value - return int(value) + return value.value class Enum(SimpleProperty): - """Custom Enum type for Storm supporting flufl.enum.Enums.""" + """Custom type for Storm supporting enums.""" variable_class = _EnumVariable diff --git a/src/mailman/interfaces/action.py b/src/mailman/interfaces/action.py index 287d253f9..22a65d5e1 100644 --- a/src/mailman/interfaces/action.py +++ b/src/mailman/interfaces/action.py @@ -24,7 +24,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum diff --git a/src/mailman/interfaces/archiver.py b/src/mailman/interfaces/archiver.py index 2182798ad..5f074503e 100644 --- a/src/mailman/interfaces/archiver.py +++ b/src/mailman/interfaces/archiver.py @@ -27,7 +27,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute diff --git a/src/mailman/interfaces/autorespond.py b/src/mailman/interfaces/autorespond.py index 7c66d984f..acad717f1 100644 --- a/src/mailman/interfaces/autorespond.py +++ b/src/mailman/interfaces/autorespond.py @@ -30,7 +30,7 @@ __all__ = [ from datetime import timedelta -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute ALWAYS_REPLY = timedelta() diff --git a/src/mailman/interfaces/bounce.py b/src/mailman/interfaces/bounce.py index 4719553c4..8e7266687 100644 --- a/src/mailman/interfaces/bounce.py +++ b/src/mailman/interfaces/bounce.py @@ -28,7 +28,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Attribute, Interface diff --git a/src/mailman/interfaces/chain.py b/src/mailman/interfaces/chain.py index b4fd98bb2..9fffc2760 100644 --- a/src/mailman/interfaces/chain.py +++ b/src/mailman/interfaces/chain.py @@ -35,7 +35,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute diff --git a/src/mailman/interfaces/command.py b/src/mailman/interfaces/command.py index 42a859f36..18a8024a8 100644 --- a/src/mailman/interfaces/command.py +++ b/src/mailman/interfaces/command.py @@ -28,7 +28,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute diff --git a/src/mailman/interfaces/digests.py b/src/mailman/interfaces/digests.py index c00758f7a..416e18f13 100644 --- a/src/mailman/interfaces/digests.py +++ b/src/mailman/interfaces/digests.py @@ -25,7 +25,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py index eb2a931f4..7beaf9c46 100644 --- a/src/mailman/interfaces/mailinglist.py +++ b/src/mailman/interfaces/mailinglist.py @@ -29,7 +29,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute from mailman.interfaces.member import MemberRole diff --git a/src/mailman/interfaces/member.py b/src/mailman/interfaces/member.py index ee609a0d9..b561a7571 100644 --- a/src/mailman/interfaces/member.py +++ b/src/mailman/interfaces/member.py @@ -36,7 +36,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute from mailman.core.errors import MailmanError diff --git a/src/mailman/interfaces/mime.py b/src/mailman/interfaces/mime.py index 629fc776b..549e6c3db 100644 --- a/src/mailman/interfaces/mime.py +++ b/src/mailman/interfaces/mime.py @@ -27,7 +27,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute diff --git a/src/mailman/interfaces/nntp.py b/src/mailman/interfaces/nntp.py index 2246fff6e..03be8f11d 100644 --- a/src/mailman/interfaces/nntp.py +++ b/src/mailman/interfaces/nntp.py @@ -25,7 +25,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum diff --git a/src/mailman/interfaces/requests.py b/src/mailman/interfaces/requests.py index 9f841a6a3..35de05100 100644 --- a/src/mailman/interfaces/requests.py +++ b/src/mailman/interfaces/requests.py @@ -30,7 +30,7 @@ __all__ = [ ] -from flufl.enum import Enum +from enum import Enum from zope.interface import Interface, Attribute diff --git a/src/mailman/model/docs/autorespond.rst b/src/mailman/model/docs/autorespond.rst index 3a9ad01b2..b2bf03ed9 100644 --- a/src/mailman/model/docs/autorespond.rst +++ b/src/mailman/model/docs/autorespond.rst @@ -90,7 +90,7 @@ You can also use the response set to get the date of the last response sent. >>> response.address >>> response.response_type - + >>> response.date_sent datetime.date(2005, 8, 1) diff --git a/src/mailman/model/docs/membership.rst b/src/mailman/model/docs/membership.rst index f257f25ce..eeba9b332 100644 --- a/src/mailman/model/docs/membership.rst +++ b/src/mailman/model/docs/membership.rst @@ -217,7 +217,7 @@ There is also a roster containing all the subscribers of a mailing list, regardless of their role. >>> def sortkey(member): - ... return (member.address.email, int(member.role)) + ... return (member.address.email, member.role.value) >>> for member in sorted(mlist.subscribers.members, key=sortkey): ... print member.address.email, member.role aperson@example.com MemberRole.member diff --git a/src/mailman/model/docs/users.rst b/src/mailman/model/docs/users.rst index 997f983b2..889fe46d1 100644 --- a/src/mailman/model/docs/users.rst +++ b/src/mailman/model/docs/users.rst @@ -332,8 +332,7 @@ membership role. >>> len(members) 4 >>> def sortkey(member): - ... return (member.address.email, member.mailing_list, - ... int(member.role)) + ... return member.address.email, member.mailing_list, member.role.value >>> for member in sorted(members, key=sortkey): ... print member.address.email, member.mailing_list.list_id, \ ... member.role diff --git a/src/mailman/rest/addresses.py b/src/mailman/rest/addresses.py index 2b0bf5890..2908fad57 100644 --- a/src/mailman/rest/addresses.py +++ b/src/mailman/rest/addresses.py @@ -178,7 +178,7 @@ class UserAddresses(_AddressBase): def membership_key(member): # Sort first by mailing list, then by address, then by role. - return member.list_id, member.address.email, int(member.role) + return member.list_id, member.address.email, member.role.value class AddressMemberships(MemberCollection): diff --git a/src/mailman/rest/helpers.py b/src/mailman/rest/helpers.py index 3a548cb10..7d911d42b 100644 --- a/src/mailman/rest/helpers.py +++ b/src/mailman/rest/helpers.py @@ -36,7 +36,7 @@ import hashlib from cStringIO import StringIO from datetime import datetime, timedelta -from flufl.enum import Enum +from enum import Enum from lazr.config import as_boolean from restish import http from restish.http import Response @@ -79,7 +79,7 @@ class ExtendedEncoder(json.JSONEncoder): seconds = obj.seconds + obj.microseconds / 1000000.0 return '{0}d{1}s'.format(obj.days, seconds) return '{0}d'.format(obj.days) - elif hasattr(obj, 'enum') and issubclass(obj.enum, Enum): + elif isinstance(obj, Enum): # It's up to the decoding validator to associate this name with # the right Enum class. return obj.name diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py index 2a3c72bfd..32e22a76b 100644 --- a/src/mailman/rest/lists.py +++ b/src/mailman/rest/lists.py @@ -57,7 +57,7 @@ def member_matcher(request, segments): return None try: role = MemberRole[segments[0]] - except ValueError: + except KeyError: # Not a valid role. return None # No more segments. @@ -76,7 +76,7 @@ def roster_matcher(request, segments): return None try: return (), dict(role=MemberRole[segments[1]]), () - except ValueError: + except KeyError: # Not a valid role. return None diff --git a/src/mailman/rest/moderation.py b/src/mailman/rest/moderation.py index c0dbe93b8..491807f38 100644 --- a/src/mailman/rest/moderation.py +++ b/src/mailman/rest/moderation.py @@ -60,7 +60,7 @@ class _ModerationBase: resource.update(data) # Check for a matching request type, and insert the type name into the # resource. - request_type = RequestType(resource.pop('_request_type')) + request_type = RequestType[resource.pop('_request_type')] if request_type not in expected_request_types: return None resource['type'] = request_type.name @@ -205,7 +205,7 @@ class MembershipChangeRequest(resource.Resource, _ModerationBase): return http.not_found() key, data = results try: - request_type = RequestType(data['_request_type']) + request_type = RequestType[data['_request_type']] except ValueError: return http.bad_request() if request_type is RequestType.subscription: diff --git a/src/mailman/rest/validator.py b/src/mailman/rest/validator.py index c59bc53d8..2d178226f 100644 --- a/src/mailman/rest/validator.py +++ b/src/mailman/rest/validator.py @@ -48,9 +48,13 @@ class enum_validator: self._enum_class = enum_class def __call__(self, enum_value): - # This will raise a ValueError if the enum value is unknown. Let that - # percolate up. - return self._enum_class[enum_value] + # This will raise a KeyError if the enum value is unknown. The + # Validator API requires turning this into a ValueError. + try: + return self._enum_class[enum_value] + except KeyError as exception: + # Retain the error message. + raise ValueError(exception.message) def subscriber_validator(subscriber): diff --git a/src/mailman/rules/docs/moderation.rst b/src/mailman/rules/docs/moderation.rst index eacc1cff3..3000f23a2 100644 --- a/src/mailman/rules/docs/moderation.rst +++ b/src/mailman/rules/docs/moderation.rst @@ -124,7 +124,7 @@ cperson is neither a member, nor a nonmember of the mailing list. :: >>> def memberkey(member): - ... return member.mailing_list, member.address.email, int(member.role) + ... return member.mailing_list, member.address.email, member.role.value >>> dump_list(mlist.members.members, key=memberkey) diff --git a/src/mailman/utilities/importer.py b/src/mailman/utilities/importer.py index 20941bfd6..f5aa8d10a 100644 --- a/src/mailman/utilities/importer.py +++ b/src/mailman/utilities/importer.py @@ -28,6 +28,7 @@ __all__ = [ import sys import datetime +from mailman.interfaces.action import FilterAction from mailman.interfaces.autorespond import ResponseAction from mailman.interfaces.digests import DigestFrequency from mailman.interfaces.mailinglist import Personalization, ReplyToMunging @@ -47,6 +48,7 @@ TYPES = dict( bounce_info_stale_after=seconds_to_delta, bounce_you_are_disabled_warnings_interval=seconds_to_delta, digest_volume_frequency=DigestFrequency, + filter_action=FilterAction, newsgroup_moderation=NewsgroupModeration, personalize=Personalization, reply_goes_to_list=ReplyToMunging, -- cgit v1.3.1 From bab445a705ca4257f89baed025dd613870b835fd Mon Sep 17 00:00:00 2001 From: Stephen J. Turnbull Date: Thu, 4 Jul 2013 02:59:23 +0900 Subject: Add 'Contact Us' section. - This makes HTML references to 'START.html#contact-us' useful. - Content is mostly the same, with addition of a reference to IRC. --- src/mailman/docs/START.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/mailman/docs/START.rst b/src/mailman/docs/START.rst index 9fffc3048..05154527f 100644 --- a/src/mailman/docs/START.rst +++ b/src/mailman/docs/START.rst @@ -25,10 +25,15 @@ search, and retrieval of archived messages to a separate application (a simple implementation is provided). The web interface (known as `Postorius`_) and archiver (known as `Hyperkitty`_) are in separate development. -Contributions are welcome. Please submit bug reports on the Mailman bug -tracker at https://bugs.launchpad.net/mailman though you will currently need -to have a login on Launchpad to do so. You can also send email to the -mailman-developers@python.org mailing list. + +Contact Us +========== + +Contributions of code, problem reports, and feature requests are welcome. +Please submit bug reports on the Mailman bug tracker at +https://bugs.launchpad.net/mailman (you need to have a login on Launchpad to +do so). You can also send email to the mailman-developers@python.org mailing +list, or ask on IRC channel #mailman on Freenode. Requirements -- cgit v1.3.1 From eabec04a40cd3e69690f2cc98f93d11efd575c8d Mon Sep 17 00:00:00 2001 From: Stephen J. Turnbull Date: Thu, 4 Jul 2013 03:09:15 +0900 Subject: Rewrite the MTA-independent part of MTA.rst. - Assume less knowledge of mail admin (but still fairly demanding). - Use 'MTA' instead of 'mail server' to distinguish 'software' from 'host'. - Use 'same host' configuration as example. (The configuration with lmtp_host == smtp_host == mail.example.com doesn't really make sense, since they will be equal only if they are both 'localhost'.) - Give explanations of each of the 6 parameters. - Provide a link where the confused can find help channels. --- src/mailman/docs/MTA.rst | 88 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/mailman/docs/MTA.rst b/src/mailman/docs/MTA.rst index 6e7b4cf9f..7165c30b9 100644 --- a/src/mailman/docs/MTA.rst +++ b/src/mailman/docs/MTA.rst @@ -2,27 +2,72 @@ Hooking up your mail server =========================== -Mailman needs to be hooked up to your mail server (a.k.a. *mail transport -agent* or *MTA*) both to accept incoming mail and to deliver outgoing mail. -Mailman itself never delivers messages to the end user; it lets its immediate -upstream mail server do that. - -The preferred way to allow Mailman to accept incoming messages from your mail -server is to use the `Local Mail Transfer Protocol`_ (LMTP_) interface. Most -open source mail servers support LMTP for local delivery. This is much more -efficient than spawning a process just to do the delivery. - -Your mail server must also accept `Simple Mail Transfer Protocol`_ (SMTP_) -connections from Mailman, for all outgoing messages. - -The specific instructions for hooking your mail server up to Mailman differs -depending on which mail server you're using. Following are instructions for -Postfix. We would really appreciate contributions of configurations for Exim -and Sendmail, and welcome information about other popular open source mail -servers. - -Note that Mailman provides lots of configuration variables that you can use to -tweak performance for your operating environment. See the +Mailman needs to communicate with your *MTA* (*mail transport agent* +or *mail server*, the software which handles sending mail across the +Internet), both to accept incoming mail and to deliver outgoing mail. +Mailman itself never delivers messages to the end user. It sends them +to its immediate upstream MTA, which delivers them. In the same way, +Mailman never receives mail directly. Mail from outside always comes +via the MTA. + +Mailman accepts incoming messages from the MTA using the `Local Mail +Transfer Protocol`_ (LMTP_) interface. Mailman can use other incoming +transports, but LMTP is much more efficient than spawning a process +just to do the delivery. Most open source MTAs support LMTP for local +delivery. If yours doesn't, and you need to use a different +interface, please ask on the `mailing list or on IRC`_. + +Mailman passes all outgoing messages to the MTA using the `Simple Mail +Transfer Protocol`_ (SMTP_). + +Cooperation between Mailman and the MTA requires some configuration of +both. MTA configuration differs for each of the available MTAs, and +there is a section for each one. Instructions for Postfix are given +below. We would really appreciate contributions of configurations for +Exim and Sendmail, and welcome information about other popular open +source mail servers. + +Configuring Mailman to communicate with the MTA is straightforward, +and basically the same for all MTAs. In your ``mailman.cfg`` file, +add (or edit) a section like the following:: + + [mta] + incoming: mailman.mta.postfix.LMTP + outgoing: mailman.mta.deliver.deliver + lmtp_host: 127.0.0.1 + lmtp_port: 8024 + smtp_host: localhost + smtp_port: 25 + +This configuration is for a system where Mailman and the MTA are on +the same host. + +The ``incoming`` and ``outgoing`` parameters identify the Python +objects used to communicate with the MTA. The ``deliver`` module used +in ``outgoing`` is pretty standard across all MTAs. The ``postfix`` +module in ``incoming`` is specific to Postfix. See the section for +your MTA below for details on these parameters. + +``lmtp_host`` and ``lmtp_port`` are parameters which are used by +Mailman, but also will be passed to the MTA to identify the Mailman +host. The "same host" case is special; some MTAs (including Postfix) +do not recognize "localhost", and need the numerical IP address. If +they are on different hosts, ``lmtp_host`` should be set to the domain +name or IP address of the Mailman host. ``lmtp_port`` is fairly +arbitrary (there is no standard port for LMTP). Use any port +convenient for your site. "8024" is as good as any, unless another +service is using it. + +``smtp_host`` and ``smtp_port`` are parameters used to identify the +MTA to Mailman. If the MTA and Mailman are on separate hosts, +``smtp_host`` should be set to the domain name or IP address of the +MTA host. ``smtp_port`` will almost always be 25, which is the +standard port for SMTP. (Some special site configurations set it to a +different port. If you need this, you probably already know that, +know why, and what to do, too!) + +Mailman also provides many other configuration variables that you can +use to tweak performance for your operating environment. See the ``src/mailman/config/schema.cfg`` file for details. @@ -139,6 +184,7 @@ Sendmail Contributions are welcome! +.. _`mailing list or on IRC`: START.html#contact-us .. _`Local Mail Transfer Protocol`: http://en.wikipedia.org/wiki/Local_Mail_Transfer_Protocol .. _LMTP: http://www.faqs.org/rfcs/rfc2033.html -- cgit v1.3.1 From 9b3268f2112c592ebb24dd8ef4e0802005b41e4e Mon Sep 17 00:00:00 2001 From: Sneha Priscilla Date: Fri, 9 Aug 2013 02:17:05 +0530 Subject: Adding a missing hide_address preference --- src/mailman/rest/preferences.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/mailman/rest/preferences.py b/src/mailman/rest/preferences.py index d392c0299..d2766446c 100644 --- a/src/mailman/rest/preferences.py +++ b/src/mailman/rest/preferences.py @@ -84,6 +84,7 @@ class Preferences(ReadOnlyPreferences): return http.not_found() kws = dict( acknowledge_posts=GetterSetter(as_boolean), + hide_address = GetterSetter(as_boolean), delivery_mode=GetterSetter(enum_validator(DeliveryMode)), delivery_status=GetterSetter(enum_validator(DeliveryStatus)), preferred_language=GetterSetter(language_validator), -- cgit v1.3.1 From 4be0e93b771e440dd9b59f418cddc4f59f98be42 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Sun, 25 Aug 2013 14:55:33 -0400 Subject: Switch to virtualenv and nose2 instead of zc.buildout and zope.testing. --- setup.py | 4 +-- src/mailman/__init__.py | 2 +- src/mailman/testing/__init__.py | 39 -------------------------- src/mailman/testing/layers.py | 2 +- src/mailman/testing/nose.py | 61 +++++++++++++++++++++++++++++++++++++++++ unittest.cfg | 10 +++++++ 6 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 src/mailman/testing/nose.py create mode 100644 unittest.cfg (limited to 'src') diff --git a/setup.py b/setup.py index 3e032f46c..63cea1187 100644 --- a/setup.py +++ b/setup.py @@ -101,14 +101,14 @@ case second `m'. Any other spelling is incorrect.""", 'lazr.config', 'lazr.smtptest', 'mock', + 'nose2', 'passlib', 'restish', 'storm', - 'zc.buildout', 'zope.component', 'zope.configuration', 'zope.event', 'zope.interface', - 'zope.testing<4', ], + test_suite = 'nose2.collector.collector', ) diff --git a/src/mailman/__init__.py b/src/mailman/__init__.py index 8690e77bd..87a2a2100 100644 --- a/src/mailman/__init__.py +++ b/src/mailman/__init__.py @@ -44,7 +44,7 @@ except ImportError: # # Do *not* do this if we're building the documentation. if 'build_sphinx' not in sys.argv: - if sys.argv[0].split(os.sep)[-1] == 'test': + if any('nose2' in arg for arg in sys.argv): from mailman.testing.i18n import initialize else: from mailman.core.i18n import initialize diff --git a/src/mailman/testing/__init__.py b/src/mailman/testing/__init__.py index e6a5047b6..e69de29bb 100644 --- a/src/mailman/testing/__init__.py +++ b/src/mailman/testing/__init__.py @@ -1,39 +0,0 @@ -# Copyright (C) 2011-2013 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 . - -"""Set up testing. - -This is used as an interface to buildout.cfg's [test] section. -zope.testrunner supports an initialization variable. It is set to import and -run the following test initialization method. -""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'initialize', - ] - - - -def initialize(root_directory): - """Initialize the test infrastructure.""" - from mailman.testing import layers - layers.MockAndMonkeyLayer.testing_mode = True - layers.ConfigLayer.enable_stderr(); - layers.ConfigLayer.set_root_directory(root_directory) diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index e47d5c9e0..6d150815f 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -154,7 +154,7 @@ class ConfigLayer(MockAndMonkeyLayer): continue logger_name = 'mailman.' + sub_name log = logging.getLogger(logger_name) - log.propagate = True + #log.propagate = True # Reopen the file to a new path that tests can get at. Instead of # using the configuration file path though, use a path that's # specific to the logger so that tests can find expected output diff --git a/src/mailman/testing/nose.py b/src/mailman/testing/nose.py new file mode 100644 index 000000000..c8a6a0798 --- /dev/null +++ b/src/mailman/testing/nose.py @@ -0,0 +1,61 @@ +# Copyright (C) 2013 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 . + +"""nose2 test infrastructure.""" + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'NosePlugin', + ] + + +import os +import re +import mailman + +from mailman.testing.layers import ConfigLayer, MockAndMonkeyLayer +from nose2.events import Plugin + + +TOPDIR = os.path.dirname(mailman.__file__) + + +class NosePlugin(Plugin): + configSection = 'mailman' + + def __init__(self): + self.patterns = [] + self.addArgument(self.patterns, 'P', 'pattern', + 'Add a test matching pattern') + + def startTestRun(self, event): + MockAndMonkeyLayer.testing_mode = True + ConfigLayer.enable_stderr() + + def getTestCaseNames(self, event): + if len(self.patterns) == 0: + # No filter patterns, so everything should be tested. + return + names = filter(event.isTestMethod, dir(event.testCase)) + for name in names: + for pattern in self.patterns: + if re.search(pattern, name): + break + else: + event.excludedNames.append(name) diff --git a/unittest.cfg b/unittest.cfg new file mode 100644 index 000000000..d639a8ae3 --- /dev/null +++ b/unittest.cfg @@ -0,0 +1,10 @@ +[unittest] +verbose = 2 +plugins = mailman.testing.nose + nose2.plugins.layers + +[mailman] +always-on = True + +[log-capture] +always-on = False -- cgit v1.3.1 From adf7634d62ad4a6c717078401b77e15dc5b2fcbf Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Sun, 25 Aug 2013 16:53:30 -0400 Subject: * Re-name doctests. * Fix executable location now that we're not using buildout. * Fix enum repr. --- src/mailman/app/docs/hooks.rst | 3 +- src/mailman/model/docs/autorespond.rst | 2 +- src/mailman/testing/documentation.py | 176 ++++++++++++++++++++++++ src/mailman/testing/nose.py | 42 +++++- src/mailman/tests/test_documentation.py | 233 -------------------------------- 5 files changed, 219 insertions(+), 237 deletions(-) create mode 100644 src/mailman/testing/documentation.py delete mode 100644 src/mailman/tests/test_documentation.py (limited to 'src') diff --git a/src/mailman/app/docs/hooks.rst b/src/mailman/app/docs/hooks.rst index 7e214f13f..a29c7ee10 100644 --- a/src/mailman/app/docs/hooks.rst +++ b/src/mailman/app/docs/hooks.rst @@ -52,8 +52,9 @@ script that will produce no output to force the hooks to run. >>> import subprocess >>> from mailman.testing.layers import ConfigLayer >>> def call(): + ... exe = os.path.join(os.path.dirname(sys.executable), 'mailman') ... proc = subprocess.Popen( - ... 'bin/mailman lists --domain ignore -q'.split(), + ... [exe, 'lists', '--domain', 'ignore', '-q'], ... cwd=ConfigLayer.root_directory, ... env=dict(MAILMAN_CONFIG_FILE=config_path, ... PYTHONPATH=config_directory), diff --git a/src/mailman/model/docs/autorespond.rst b/src/mailman/model/docs/autorespond.rst index 3a9ad01b2..df47fc2e7 100644 --- a/src/mailman/model/docs/autorespond.rst +++ b/src/mailman/model/docs/autorespond.rst @@ -90,7 +90,7 @@ You can also use the response set to get the date of the last response sent. >>> response.address >>> response.response_type - + >>> response.date_sent datetime.date(2005, 8, 1) diff --git a/src/mailman/testing/documentation.py b/src/mailman/testing/documentation.py new file mode 100644 index 000000000..b1cc36f91 --- /dev/null +++ b/src/mailman/testing/documentation.py @@ -0,0 +1,176 @@ +# Copyright (C) 2007-2013 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 . + +"""Harness for testing Mailman's documentation. + +Note that doctest extraction does not currently work for zip file +distributions. doctest discovery currently requires file system traversal. +""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'setup', + 'teardown' + ] + + +from inspect import isfunction, ismethod + +from mailman.app.lifecycle import create_list +from mailman.config import config +from mailman.testing.helpers import call_api, specialized_message_from_string +from mailman.testing.layers import SMTPLayer + + +DOT = '.' +COMMASPACE = ', ' + + + +def stop(): + """Call into pdb.set_trace()""" + # Do the import here so that you get the wacky special hacked pdb instead + # of Python's normal pdb. + import pdb + pdb.set_trace() + + +def dump_msgdata(msgdata, *additional_skips): + """Dump in a more readable way a message metadata dictionary.""" + if len(msgdata) == 0: + print '*Empty*' + return + skips = set(additional_skips) + # Some stuff we always want to skip, because their values will always be + # variable data. + skips.add('received_time') + longest = max(len(key) for key in msgdata if key not in skips) + for key in sorted(msgdata): + if key in skips: + continue + print '{0:{2}}: {1}'.format(key, msgdata[key], longest) + + +def dump_list(list_of_things, key=str): + """Print items in a string to get rid of stupid u'' prefixes.""" + # List of things may be a generator. + list_of_things = list(list_of_things) + if len(list_of_things) == 0: + print '*Empty*' + if key is not None: + list_of_things = sorted(list_of_things, key=key) + for item in list_of_things: + print item + + +def call_http(url, data=None, method=None, username=None, password=None): + """'Call a URL with a given HTTP method and return the resulting object. + + The object will have been JSON decoded. + + :param url: The url to open, read, and print. + :type url: string + :param data: Data to use to POST to a URL. + :type data: dict + :param method: Alternative HTTP method to use. + :type method: str + :param username: The HTTP Basic Auth user name. None means use the value + from the configuration. + :type username: str + :param password: The HTTP Basic Auth password. None means use the value + from the configuration. + :type username: str + :return: The decoded JSON data structure. + :raises HTTPError: when a non-2xx return code is received. + """ + content, response = call_api(url, data, method, username, password) + if content is None: + for header in sorted(response): + print '{0}: {1}'.format(header, response[header]) + return None + return content + + +def dump_json(url, data=None, method=None, username=None, password=None): + """Print the JSON dictionary read from a URL. + + :param url: The url to open, read, and print. + :type url: string + :param data: Data to use to POST to a URL. + :type data: dict + :param method: Alternative HTTP method to use. + :type method: str + :param username: The HTTP Basic Auth user name. None means use the value + from the configuration. + :type username: str + :param password: The HTTP Basic Auth password. None means use the value + from the configuration. + :type username: str + """ + results = call_http(url, data, method, username, password) + if results is None: + return + for key in sorted(results): + value = results[key] + if key == 'entries': + for i, entry in enumerate(value): + # entry is a dictionary. + print 'entry %d:' % i + for entry_key in sorted(entry): + print ' {0}: {1}'.format(entry_key, entry[entry_key]) + elif isinstance(value, list): + printable_value = COMMASPACE.join( + "'{0}'".format(s) for s in sorted(value)) + print '{0}: [{1}]'.format(key, printable_value) + else: + print '{0}: {1}'.format(key, value) + + + +def setup(testobj): + """Test setup.""" + # Make sure future statements in our doctests are the same as everywhere + # else. + testobj.globs['absolute_import'] = absolute_import + testobj.globs['unicode_literals'] = unicode_literals + # In general, I don't like adding convenience functions, since I think + # doctests should do the imports themselves. It makes for better + # documentation that way. However, a few are really useful, or help to + # hide some icky test implementation details. + testobj.globs['call_http'] = call_http + testobj.globs['config'] = config + testobj.globs['create_list'] = create_list + testobj.globs['dump_json'] = dump_json + testobj.globs['dump_msgdata'] = dump_msgdata + testobj.globs['dump_list'] = dump_list + testobj.globs['message_from_string'] = specialized_message_from_string + testobj.globs['smtpd'] = SMTPLayer.smtpd + testobj.globs['stop'] = stop + testobj.globs['transaction'] = config.db + # Add this so that cleanups can be automatically added by the doctest. + testobj.globs['cleanups'] = [] + + + +def teardown(testobj): + for cleanup in testobj.globs['cleanups']: + if isfunction(cleanup) or ismethod(cleanup): + cleanup() + else: + cleanup[0](*cleanup[1:]) diff --git a/src/mailman/testing/nose.py b/src/mailman/testing/nose.py index c8a6a0798..4255011d2 100644 --- a/src/mailman/testing/nose.py +++ b/src/mailman/testing/nose.py @@ -27,15 +27,20 @@ __all__ = [ import os import re +import doctest import mailman +import importlib -from mailman.testing.layers import ConfigLayer, MockAndMonkeyLayer +from mailman.testing.documentation import setup, teardown +from mailman.testing.layers import ConfigLayer, MockAndMonkeyLayer, SMTPLayer from nose2.events import Plugin - +DOT = '.' +FLAGS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF TOPDIR = os.path.dirname(mailman.__file__) + class NosePlugin(Plugin): configSection = 'mailman' @@ -59,3 +64,36 @@ class NosePlugin(Plugin): break else: event.excludedNames.append(name) + + def handleFile(self, event): + path = event.path[len(TOPDIR)+1:] + if len(self.patterns) > 0: + for pattern in self.patterns: + if re.search(pattern, path): + break + else: + # Skip this doctest. + return + base, ext = os.path.splitext(path) + if ext != '.rst': + return + # Look to see if the package defines a test layer, otherwise use the + # default layer. First turn the file system path into a dotted Python + # module path. + parent = os.path.dirname(path) + dotted = 'mailman.' + DOT.join(parent.split(os.path.sep)) + try: + module = importlib.import_module(dotted) + except ImportError: + layer = SMTPLayer + else: + layer = getattr(module, 'layer', SMTPLayer) + suite = doctest.DocFileSuite( + path, package='mailman', + optionflags=FLAGS, + setUp=setup, + tearDown=teardown) + # Flatten the suite, adding the layer flag on every TestCase. + for test in suite: + test.layer = layer + event.extraTests.append(test) diff --git a/src/mailman/tests/test_documentation.py b/src/mailman/tests/test_documentation.py deleted file mode 100644 index 37eb760ab..000000000 --- a/src/mailman/tests/test_documentation.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright (C) 2007-2013 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 . - -"""Harness for testing Mailman's documentation. - -Note that doctest extraction does not currently work for zip file -distributions. doctest discovery currently requires file system traversal. -""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'test_suite', - ] - - -import os -import sys -import doctest -import unittest - -from inspect import isfunction, ismethod - -import mailman - -from mailman.app.lifecycle import create_list -from mailman.config import config -from mailman.testing.helpers import ( - call_api, chdir, specialized_message_from_string) -from mailman.testing.layers import SMTPLayer - - -DOT = '.' -COMMASPACE = ', ' - - - -def stop(): - """Call into pdb.set_trace()""" - # Do the import here so that you get the wacky special hacked pdb instead - # of Python's normal pdb. - import pdb - pdb.set_trace() - - -def dump_msgdata(msgdata, *additional_skips): - """Dump in a more readable way a message metadata dictionary.""" - if len(msgdata) == 0: - print '*Empty*' - return - skips = set(additional_skips) - # Some stuff we always want to skip, because their values will always be - # variable data. - skips.add('received_time') - longest = max(len(key) for key in msgdata if key not in skips) - for key in sorted(msgdata): - if key in skips: - continue - print '{0:{2}}: {1}'.format(key, msgdata[key], longest) - - -def dump_list(list_of_things, key=str): - """Print items in a string to get rid of stupid u'' prefixes.""" - # List of things may be a generator. - list_of_things = list(list_of_things) - if len(list_of_things) == 0: - print '*Empty*' - if key is not None: - list_of_things = sorted(list_of_things, key=key) - for item in list_of_things: - print item - - -def call_http(url, data=None, method=None, username=None, password=None): - """'Call a URL with a given HTTP method and return the resulting object. - - The object will have been JSON decoded. - - :param url: The url to open, read, and print. - :type url: string - :param data: Data to use to POST to a URL. - :type data: dict - :param method: Alternative HTTP method to use. - :type method: str - :param username: The HTTP Basic Auth user name. None means use the value - from the configuration. - :type username: str - :param password: The HTTP Basic Auth password. None means use the value - from the configuration. - :type username: str - :return: The decoded JSON data structure. - :raises HTTPError: when a non-2xx return code is received. - """ - content, response = call_api(url, data, method, username, password) - if content is None: - for header in sorted(response): - print '{0}: {1}'.format(header, response[header]) - return None - return content - - -def dump_json(url, data=None, method=None, username=None, password=None): - """Print the JSON dictionary read from a URL. - - :param url: The url to open, read, and print. - :type url: string - :param data: Data to use to POST to a URL. - :type data: dict - :param method: Alternative HTTP method to use. - :type method: str - :param username: The HTTP Basic Auth user name. None means use the value - from the configuration. - :type username: str - :param password: The HTTP Basic Auth password. None means use the value - from the configuration. - :type username: str - """ - results = call_http(url, data, method, username, password) - if results is None: - return - for key in sorted(results): - value = results[key] - if key == 'entries': - for i, entry in enumerate(value): - # entry is a dictionary. - print 'entry %d:' % i - for entry_key in sorted(entry): - print ' {0}: {1}'.format(entry_key, entry[entry_key]) - elif isinstance(value, list): - printable_value = COMMASPACE.join( - "'{0}'".format(s) for s in sorted(value)) - print '{0}: [{1}]'.format(key, printable_value) - else: - print '{0}: {1}'.format(key, value) - - - -def setup(testobj): - """Test setup.""" - # Make sure future statements in our doctests are the same as everywhere - # else. - testobj.globs['absolute_import'] = absolute_import - testobj.globs['unicode_literals'] = unicode_literals - # In general, I don't like adding convenience functions, since I think - # doctests should do the imports themselves. It makes for better - # documentation that way. However, a few are really useful, or help to - # hide some icky test implementation details. - testobj.globs['call_http'] = call_http - testobj.globs['config'] = config - testobj.globs['create_list'] = create_list - testobj.globs['dump_json'] = dump_json - testobj.globs['dump_msgdata'] = dump_msgdata - testobj.globs['dump_list'] = dump_list - testobj.globs['message_from_string'] = specialized_message_from_string - testobj.globs['smtpd'] = SMTPLayer.smtpd - testobj.globs['stop'] = stop - testobj.globs['transaction'] = config.db - # Add this so that cleanups can be automatically added by the doctest. - testobj.globs['cleanups'] = [] - - - -def teardown(testobj): - for cleanup in testobj.globs['cleanups']: - if isfunction(cleanup) or ismethod(cleanup): - cleanup() - else: - cleanup[0](*cleanup[1:]) - - - -def test_suite(): - """Create test suites for all .rst documentation tests. - - .txt files are also tested, but .rst is highly preferred. - """ - suite = unittest.TestSuite() - topdir = os.path.dirname(mailman.__file__) - packages = [] - for dirpath, dirnames, filenames in os.walk(topdir): - if 'docs' in dirnames: - docsdir = os.path.join(dirpath, 'docs')[len(topdir)+1:] - packages.append(docsdir) - # Under higher verbosity settings, report all doctest errors, not just the - # first one. - flags = (doctest.ELLIPSIS | - doctest.NORMALIZE_WHITESPACE | - doctest.REPORT_NDIFF) - # Add all the doctests in all subpackages. - doctest_files = {} - with chdir(topdir): - for docsdir in packages: - # Look to see if the package defines a test layer, otherwise use - # SMTPLayer. - package_path = 'mailman.' + DOT.join(docsdir.split(os.sep)) - try: - __import__(package_path) - except ImportError: - layer = SMTPLayer - else: - layer = getattr(sys.modules[package_path], 'layer', SMTPLayer) - for filename in os.listdir(docsdir): - base, extension = os.path.splitext(filename) - if os.path.splitext(filename)[1] in ('.txt', '.rst'): - module_path = package_path + '.' + base - doctest_files[module_path] = ( - os.path.join(docsdir, filename), layer) - for module_path in sorted(doctest_files): - path, layer = doctest_files[module_path] - test = doctest.DocFileSuite( - path, - package='mailman', - optionflags=flags, - setUp=setup, - tearDown=teardown) - test.layer = layer - suite.addTest(test) - return suite -- cgit v1.3.1 From fc163fad1469f95837f9d97aacd0465689fbcec8 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 26 Aug 2013 21:47:10 -0400 Subject: Add enough __init__.py files to make these docs directories discoverable. --- src/mailman/archiving/docs/__init__.py | 0 src/mailman/commands/docs/__init__.py | 0 src/mailman/handlers/docs/__init__.py | 0 src/mailman/mta/docs/__init__.py | 0 src/mailman/rules/docs/__init__.py | 0 src/mailman/runners/docs/__init__.py | 0 src/mailman/testing/nose.py | 1 + 7 files changed, 1 insertion(+) create mode 100644 src/mailman/archiving/docs/__init__.py create mode 100644 src/mailman/commands/docs/__init__.py create mode 100644 src/mailman/handlers/docs/__init__.py create mode 100644 src/mailman/mta/docs/__init__.py create mode 100644 src/mailman/rules/docs/__init__.py create mode 100644 src/mailman/runners/docs/__init__.py (limited to 'src') diff --git a/src/mailman/archiving/docs/__init__.py b/src/mailman/archiving/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/mailman/commands/docs/__init__.py b/src/mailman/commands/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/mailman/handlers/docs/__init__.py b/src/mailman/handlers/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/mailman/mta/docs/__init__.py b/src/mailman/mta/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/mailman/rules/docs/__init__.py b/src/mailman/rules/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/mailman/runners/docs/__init__.py b/src/mailman/runners/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/mailman/testing/nose.py b/src/mailman/testing/nose.py index 4255011d2..96b92a0d7 100644 --- a/src/mailman/testing/nose.py +++ b/src/mailman/testing/nose.py @@ -45,6 +45,7 @@ class NosePlugin(Plugin): configSection = 'mailman' def __init__(self): + super(NosePlugin, self).__init__() self.patterns = [] self.addArgument(self.patterns, 'P', 'pattern', 'Add a test matching pattern') -- cgit v1.3.1 From 275748b9ca16ae16fdb0d6fc89dcdb4b2a0b2a35 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Tue, 27 Aug 2013 20:47:21 -0400 Subject: * Suppress the extra Doctest: lines. * Use DocFileTest directly. --- src/mailman/testing/nose.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/mailman/testing/nose.py b/src/mailman/testing/nose.py index 96b92a0d7..301f03aae 100644 --- a/src/mailman/testing/nose.py +++ b/src/mailman/testing/nose.py @@ -89,12 +89,12 @@ class NosePlugin(Plugin): layer = SMTPLayer else: layer = getattr(module, 'layer', SMTPLayer) - suite = doctest.DocFileSuite( + test = doctest.DocFileTest( path, package='mailman', optionflags=FLAGS, setUp=setup, tearDown=teardown) - # Flatten the suite, adding the layer flag on every TestCase. - for test in suite: - test.layer = layer - event.extraTests.append(test) + test.layer = layer + # Suppress the extra "Doctest: ..." line. + test.shortDescription = lambda: None + event.extraTests.append(test) -- cgit v1.3.1