Merge lp:~barry/mailman/py3 into lp:mailman

Proposed by Barry Warsaw
Status: Merged
Merged at revision: 7285
Proposed branch: lp:~barry/mailman/py3
Merge into: lp:mailman
Diff against target: 15621 lines (+2984/-3006) (has conflicts)
377 files modified
.bzrignore (+1/-0)
coverage.ini (+1/-1)
setup.py (+4/-5)
src/mailman/__init__.py (+0/-7)
src/mailman/app/bounces.py (+6/-9)
src/mailman/app/commands.py (+1/-5)
src/mailman/app/docs/hooks.rst (+3/-2)
src/mailman/app/docs/pipelines.rst (+8/-8)
src/mailman/app/docs/subscriptions.rst (+0/-7)
src/mailman/app/domain.py (+1/-5)
src/mailman/app/events.py (+1/-5)
src/mailman/app/inject.py (+7/-7)
src/mailman/app/lifecycle.py (+1/-5)
src/mailman/app/membership.py (+1/-5)
src/mailman/app/moderator.py (+4/-8)
src/mailman/app/notifications.py (+2/-7)
src/mailman/app/registrar.py (+3/-7)
src/mailman/app/replybot.py (+0/-3)
src/mailman/app/subscriptions.py (+4/-7)
src/mailman/app/templates.py (+20/-23)
src/mailman/app/tests/test_bounces.py (+4/-10)
src/mailman/app/tests/test_inject.py (+7/-8)
src/mailman/app/tests/test_lifecycle.py (+0/-3)
src/mailman/app/tests/test_membership.py (+1/-5)
src/mailman/app/tests/test_moderation.py (+1/-5)
src/mailman/app/tests/test_notifications.py (+2/-5)
src/mailman/app/tests/test_registration.py (+2/-6)
src/mailman/app/tests/test_subscriptions.py (+13/-5)
src/mailman/app/tests/test_templates.py (+12/-15)
src/mailman/archiving/mailarchive.py (+3/-8)
src/mailman/archiving/mhonarc.py (+3/-7)
src/mailman/archiving/prototype.py (+4/-8)
src/mailman/archiving/tests/test_prototype.py (+4/-8)
src/mailman/bin/export.py (+1/-1)
src/mailman/bin/gate_news.py (+1/-1)
src/mailman/bin/mailman.py (+9/-7)
src/mailman/bin/master.py (+11/-8)
src/mailman/bin/onebounce.py (+0/-3)
src/mailman/bin/runner.py (+0/-3)
src/mailman/bin/tests/test_master.py (+0/-3)
src/mailman/chains/accept.py (+1/-5)
src/mailman/chains/base.py (+1/-5)
src/mailman/chains/builtin.py (+1/-5)
src/mailman/chains/discard.py (+1/-4)
src/mailman/chains/headers.py (+2/-6)
src/mailman/chains/hold.py (+4/-8)
src/mailman/chains/moderation.py (+1/-5)
src/mailman/chains/owner.py (+1/-5)
src/mailman/chains/reject.py (+1/-5)
src/mailman/chains/tests/test_base.py (+0/-3)
src/mailman/chains/tests/test_headers.py (+0/-3)
src/mailman/chains/tests/test_hold.py (+1/-5)
src/mailman/chains/tests/test_owner.py (+1/-5)
src/mailman/commands/cli_aliases.py (+1/-5)
src/mailman/commands/cli_conf.py (+1/-5)
src/mailman/commands/cli_control.py (+8/-11)
src/mailman/commands/cli_help.py (+1/-5)
src/mailman/commands/cli_import.py (+4/-8)
src/mailman/commands/cli_info.py (+1/-5)
src/mailman/commands/cli_inject.py (+4/-8)
src/mailman/commands/cli_lists.py (+4/-8)
src/mailman/commands/cli_members.py (+4/-10)
src/mailman/commands/cli_qfile.py (+9/-11)
src/mailman/commands/cli_status.py (+1/-5)
src/mailman/commands/cli_unshunt.py (+2/-6)
src/mailman/commands/cli_version.py (+1/-5)
src/mailman/commands/cli_withlist.py (+3/-6)
src/mailman/commands/docs/echo.rst (+1/-1)
src/mailman/commands/docs/help.rst (+4/-4)
src/mailman/commands/docs/info.rst (+15/-14)
src/mailman/commands/docs/inject.rst (+5/-5)
src/mailman/commands/docs/members.rst (+2/-3)
src/mailman/commands/docs/membership.rst (+9/-9)
src/mailman/commands/docs/qfile.rst (+1/-6)
src/mailman/commands/docs/withlist.rst (+2/-2)
src/mailman/commands/eml_confirm.py (+4/-8)
src/mailman/commands/eml_echo.py (+1/-5)
src/mailman/commands/eml_end.py (+1/-5)
src/mailman/commands/eml_help.py (+1/-5)
src/mailman/commands/eml_membership.py (+3/-6)
src/mailman/commands/tests/test_conf.py (+1/-4)
src/mailman/commands/tests/test_confirm.py (+1/-5)
src/mailman/commands/tests/test_control.py (+1/-4)
src/mailman/commands/tests/test_create.py (+0/-3)
src/mailman/commands/tests/test_help.py (+5/-7)
src/mailman/config/__init__.py (+0/-3)
src/mailman/config/config.py (+55/-51)
src/mailman/config/mailman.cfg (+7/-3)
src/mailman/config/schema.cfg (+1/-1)
src/mailman/config/tests/test_archivers.py (+0/-3)
src/mailman/config/tests/test_configuration.py (+19/-26)
src/mailman/core/chains.py (+1/-5)
src/mailman/core/constants.py (+2/-6)
src/mailman/core/docs/runner.rst (+2/-2)
src/mailman/core/errors.py (+0/-3)
src/mailman/core/i18n.py (+3/-5)
src/mailman/core/initialize.py (+6/-10)
src/mailman/core/logging.py (+0/-3)
src/mailman/core/pipelines.py (+3/-6)
src/mailman/core/rules.py (+1/-5)
src/mailman/core/runner.py (+20/-14)
src/mailman/core/switchboard.py (+21/-23)
src/mailman/core/system.py (+1/-5)
src/mailman/core/tests/test_pipelines.py (+4/-10)
src/mailman/core/tests/test_runner.py (+2/-5)
src/mailman/database/alembic/__init__.py (+0/-3)
src/mailman/database/alembic/env.py (+1/-5)
src/mailman/database/alembic/versions/51b7f92bd06c_initial.py (+0/-3)
src/mailman/database/base.py (+4/-7)
src/mailman/database/factory.py (+3/-7)
src/mailman/database/model.py (+0/-3)
src/mailman/database/postgresql.py (+0/-3)
src/mailman/database/sqlite.py (+1/-4)
src/mailman/database/tests/test_factory.py (+4/-8)
src/mailman/database/transaction.py (+0/-4)
src/mailman/database/types.py (+3/-6)
src/mailman/docs/DEVELOP.rst (+4/-1)
src/mailman/docs/INTRODUCTION.rst (+2/-2)
src/mailman/docs/NEWS.rst (+30/-5)
src/mailman/docs/START.rst (+16/-17)
src/mailman/docs/STYLEGUIDE.rst (+11/-19)
src/mailman/docs/__init__.py (+0/-3)
src/mailman/email/message.py (+4/-8)
src/mailman/email/tests/test_message.py (+4/-7)
src/mailman/email/validate.py (+1/-5)
src/mailman/handlers/acknowledge.py (+3/-8)
src/mailman/handlers/after_delivery.py (+1/-5)
src/mailman/handlers/avoid_duplicates.py (+1/-5)
src/mailman/handlers/cleanse.py (+1/-5)
src/mailman/handlers/cleanse_dkim.py (+1/-5)
src/mailman/handlers/cook_headers.py (+5/-126)
src/mailman/handlers/decorate.py (+3/-7)
src/mailman/handlers/docs/acknowledge.rst (+4/-4)
src/mailman/handlers/docs/avoid-duplicates.rst (+6/-6)
src/mailman/handlers/docs/digests.rst (+4/-2)
src/mailman/handlers/docs/file-recips.rst (+3/-24)
src/mailman/handlers/docs/filtering.rst (+8/-14)
src/mailman/handlers/docs/nntp.rst (+1/-1)
src/mailman/handlers/docs/replybot.rst (+4/-4)
src/mailman/handlers/docs/rfc-2369.rst (+1/-1)
src/mailman/handlers/docs/subject-munging.rst (+49/-80)
src/mailman/handlers/docs/tagger.rst (+24/-24)
src/mailman/handlers/docs/to-outgoing.rst (+1/-1)
src/mailman/handlers/file_recipients.py (+1/-5)
src/mailman/handlers/member_recipients.py (+1/-5)
src/mailman/handlers/mime_delete.py (+3/-7)
src/mailman/handlers/owner_recipients.py (+1/-5)
src/mailman/handlers/replybot.py (+2/-6)
src/mailman/handlers/rfc_2369.py (+1/-5)
src/mailman/handlers/subject_prefix.py (+184/-0)
src/mailman/handlers/tagger.py (+4/-8)
src/mailman/handlers/tests/test_cook_headers.py (+2/-5)
src/mailman/handlers/tests/test_file_recips.py (+73/-0)
src/mailman/handlers/tests/test_filter.py (+57/-0)
src/mailman/handlers/tests/test_mimedel.py (+1/-5)
src/mailman/handlers/tests/test_recipients.py (+10/-21)
src/mailman/handlers/tests/test_subject_prefix.py (+129/-0)
src/mailman/handlers/tests/test_to_digest.py (+0/-3)
src/mailman/handlers/to_archive.py (+1/-5)
src/mailman/handlers/to_digest.py (+3/-7)
src/mailman/handlers/to_outgoing.py (+2/-7)
src/mailman/handlers/to_usenet.py (+3/-8)
src/mailman/interfaces/action.py (+0/-1)
src/mailman/interfaces/address.py (+1/-5)
src/mailman/interfaces/archiver.py (+0/-3)
src/mailman/interfaces/autorespond.py (+1/-3)
src/mailman/interfaces/bans.py (+0/-3)
src/mailman/interfaces/bounce.py (+0/-3)
src/mailman/interfaces/chain.py (+0/-3)
src/mailman/interfaces/command.py (+0/-3)
src/mailman/interfaces/configuration.py (+1/-5)
src/mailman/interfaces/database.py (+0/-3)
src/mailman/interfaces/digests.py (+0/-3)
src/mailman/interfaces/domain.py (+0/-3)
src/mailman/interfaces/errors.py (+0/-3)
src/mailman/interfaces/handler.py (+0/-3)
src/mailman/interfaces/languages.py (+0/-3)
src/mailman/interfaces/listmanager.py (+0/-3)
src/mailman/interfaces/mailinglist.py (+1/-5)
src/mailman/interfaces/member.py (+1/-5)
src/mailman/interfaces/messages.py (+0/-3)
src/mailman/interfaces/mime.py (+0/-3)
src/mailman/interfaces/mlistrequest.py (+0/-3)
src/mailman/interfaces/mta.py (+1/-5)
src/mailman/interfaces/nntp.py (+0/-3)
src/mailman/interfaces/pending.py (+0/-3)
src/mailman/interfaces/permissions.py (+0/-3)
src/mailman/interfaces/pipeline.py (+0/-4)
src/mailman/interfaces/preferences.py (+0/-3)
src/mailman/interfaces/registrar.py (+0/-3)
src/mailman/interfaces/requests.py (+0/-3)
src/mailman/interfaces/roster.py (+0/-3)
src/mailman/interfaces/rules.py (+0/-3)
src/mailman/interfaces/runner.py (+0/-3)
src/mailman/interfaces/styles.py (+1/-4)
src/mailman/interfaces/subscriptions.py (+1/-5)
src/mailman/interfaces/switchboard.py (+0/-3)
src/mailman/interfaces/system.py (+0/-3)
src/mailman/interfaces/templates.py (+0/-3)
src/mailman/interfaces/user.py (+1/-5)
src/mailman/interfaces/usermanager.py (+0/-3)
src/mailman/languages/language.py (+1/-5)
src/mailman/languages/manager.py (+2/-6)
src/mailman/model/address.py (+4/-8)
src/mailman/model/autorespond.py (+3/-7)
src/mailman/model/bans.py (+2/-6)
src/mailman/model/bounce.py (+2/-6)
src/mailman/model/digests.py (+3/-7)
src/mailman/model/docs/addresses.rst (+3/-17)
src/mailman/model/docs/domains.rst (+1/-13)
src/mailman/model/docs/languages.rst (+2/-2)
src/mailman/model/docs/listmanager.rst (+0/-16)
src/mailman/model/docs/mailinglist.rst (+10/-22)
src/mailman/model/docs/membership.rst (+5/-31)
src/mailman/model/docs/messagestore.rst (+4/-20)
src/mailman/model/docs/pending.rst (+5/-5)
src/mailman/model/docs/registration.rst (+5/-30)
src/mailman/model/docs/usermanager.rst (+1/-1)
src/mailman/model/docs/users.rst (+15/-62)
src/mailman/model/domain.py (+4/-8)
src/mailman/model/language.py (+2/-6)
src/mailman/model/listmanager.py (+2/-6)
src/mailman/model/mailinglist.py (+12/-14)
src/mailman/model/member.py (+5/-8)
src/mailman/model/message.py (+4/-7)
src/mailman/model/messagestore.py (+6/-15)
src/mailman/model/mime.py (+3/-7)
src/mailman/model/pending.py (+17/-35)
src/mailman/model/preferences.py (+3/-7)
src/mailman/model/requests.py (+12/-12)
src/mailman/model/roster.py (+2/-6)
src/mailman/model/tests/test_address.py (+22/-3)
src/mailman/model/tests/test_bounce.py (+2/-5)
src/mailman/model/tests/test_domain.py (+14/-8)
src/mailman/model/tests/test_listmanager.py (+20/-11)
src/mailman/model/tests/test_mailinglist.py (+41/-4)
src/mailman/model/tests/test_member.py (+0/-3)
src/mailman/model/tests/test_messagestore.py (+71/-0)
src/mailman/model/tests/test_registrar.py (+64/-0)
src/mailman/model/tests/test_requests.py (+0/-3)
src/mailman/model/tests/test_roster.py (+1/-5)
src/mailman/model/tests/test_uid.py (+1/-3)
src/mailman/model/tests/test_user.py (+39/-5)
src/mailman/model/uid.py (+1/-5)
src/mailman/model/user.py (+6/-10)
src/mailman/model/usermanager.py (+1/-5)
src/mailman/mta/aliases.py (+1/-5)
src/mailman/mta/base.py (+1/-5)
src/mailman/mta/bulk.py (+0/-4)
src/mailman/mta/connection.py (+0/-3)
src/mailman/mta/decorating.py (+0/-3)
src/mailman/mta/deliver.py (+0/-3)
src/mailman/mta/docs/authentication.rst (+1/-1)
src/mailman/mta/docs/bulk.rst (+6/-3)
src/mailman/mta/docs/connection.rst (+0/-24)
src/mailman/mta/exim4.py (+0/-3)
src/mailman/mta/null.py (+1/-5)
src/mailman/mta/personalized.py (+1/-5)
src/mailman/mta/postfix.py (+3/-7)
src/mailman/mta/tests/test_aliases.py (+1/-5)
src/mailman/mta/tests/test_connection.py (+51/-0)
src/mailman/mta/tests/test_delivery.py (+0/-3)
src/mailman/mta/verp.py (+0/-3)
src/mailman/options.py (+2/-6)
src/mailman/rest/addresses.py (+6/-7)
src/mailman/rest/docs/__init__.py (+0/-3)
src/mailman/rest/docs/addresses.rst (+4/-11)
src/mailman/rest/docs/basic.rst (+1/-4)
src/mailman/rest/docs/domains.rst (+0/-8)
src/mailman/rest/docs/helpers.rst (+9/-8)
src/mailman/rest/docs/membership.rst (+2/-2)
src/mailman/rest/docs/moderation.rst (+0/-7)
src/mailman/rest/docs/preferences.rst (+1/-1)
src/mailman/rest/docs/queues.rst (+174/-0)
src/mailman/rest/docs/systemconf.rst (+34/-0)
src/mailman/rest/docs/users.rst (+0/-30)
src/mailman/rest/domains.py (+6/-7)
src/mailman/rest/helpers.py (+5/-6)
src/mailman/rest/listconf.py (+19/-17)
src/mailman/rest/lists.py (+9/-12)
src/mailman/rest/members.py (+9/-11)
src/mailman/rest/moderation.py (+1/-4)
src/mailman/rest/preferences.py (+0/-3)
src/mailman/rest/queues.py (+129/-0)
src/mailman/rest/root.py (+20/-10)
src/mailman/rest/templates.py (+0/-3)
src/mailman/rest/tests/test_addresses.py (+11/-9)
src/mailman/rest/tests/test_domains.py (+14/-7)
src/mailman/rest/tests/test_listconf.py (+0/-3)
src/mailman/rest/tests/test_lists.py (+7/-11)
src/mailman/rest/tests/test_membership.py (+7/-11)
src/mailman/rest/tests/test_moderation.py (+19/-7)
src/mailman/rest/tests/test_paginate.py (+0/-3)
src/mailman/rest/tests/test_preferences.py (+2/-4)
src/mailman/rest/tests/test_queues.py (+107/-0)
src/mailman/rest/tests/test_root.py (+6/-8)
src/mailman/rest/tests/test_systemconf.py (+182/-0)
src/mailman/rest/tests/test_users.py (+60/-7)
src/mailman/rest/users.py (+14/-15)
src/mailman/rest/validator.py (+3/-7)
src/mailman/rest/wsgiapp.py (+1/-4)
src/mailman/rules/administrivia.py (+2/-6)
src/mailman/rules/any.py (+1/-5)
src/mailman/rules/approved.py (+2/-6)
src/mailman/rules/emergency.py (+1/-5)
src/mailman/rules/implicit_dest.py (+2/-5)
src/mailman/rules/loop.py (+1/-5)
src/mailman/rules/max_recipients.py (+1/-5)
src/mailman/rules/max_size.py (+1/-5)
src/mailman/rules/moderation.py (+2/-6)
src/mailman/rules/news_moderation.py (+1/-5)
src/mailman/rules/no_subject.py (+1/-5)
src/mailman/rules/suspicious.py (+2/-5)
src/mailman/rules/tests/test_approved.py (+9/-12)
src/mailman/rules/tests/test_moderation.py (+0/-3)
src/mailman/rules/truth.py (+1/-5)
src/mailman/runners/archive.py (+0/-4)
src/mailman/runners/bounce.py (+2/-3)
src/mailman/runners/command.py (+15/-19)
src/mailman/runners/digest.py (+14/-20)
src/mailman/runners/docs/command.rst (+12/-9)
src/mailman/runners/docs/digester.rst (+10/-208)
src/mailman/runners/docs/incoming.rst (+5/-5)
src/mailman/runners/docs/lmtp.rst (+14/-46)
src/mailman/runners/docs/nntp.rst (+1/-1)
src/mailman/runners/docs/outgoing.rst (+13/-13)
src/mailman/runners/incoming.py (+1/-5)
src/mailman/runners/lmtp.py (+18/-17)
src/mailman/runners/nntp.py (+6/-9)
src/mailman/runners/outgoing.py (+7/-3)
src/mailman/runners/pipeline.py (+5/-0)
src/mailman/runners/rest.py (+0/-3)
src/mailman/runners/retry.py (+0/-3)
src/mailman/runners/tests/test_archiver.py (+10/-15)
src/mailman/runners/tests/test_bounce.py (+5/-11)
src/mailman/runners/tests/test_confirm.py (+9/-14)
src/mailman/runners/tests/test_digest.py (+78/-5)
src/mailman/runners/tests/test_incoming.py (+3/-7)
src/mailman/runners/tests/test_join.py (+9/-13)
src/mailman/runners/tests/test_lmtp.py (+33/-7)
src/mailman/runners/tests/test_nntp.py (+17/-22)
src/mailman/runners/tests/test_outgoing.py (+29/-31)
src/mailman/runners/tests/test_owner.py (+4/-10)
src/mailman/runners/tests/test_pipeline.py (+4/-9)
src/mailman/runners/tests/test_rest.py (+0/-3)
src/mailman/runners/tests/test_retry.py (+2/-6)
src/mailman/runners/virgin.py (+5/-0)
src/mailman/styles/base.py (+0/-4)
src/mailman/styles/default.py (+1/-5)
src/mailman/styles/manager.py (+3/-7)
src/mailman/styles/tests/test_styles.py (+3/-7)
src/mailman/testing/documentation.py (+0/-9)
src/mailman/testing/helpers.py (+13/-14)
src/mailman/testing/i18n.py (+1/-5)
src/mailman/testing/layers.py (+9/-13)
src/mailman/testing/mta.py (+13/-18)
src/mailman/testing/nose.py (+7/-3)
src/mailman/tests/test_configfile.py (+5/-4)
src/mailman/utilities/datetime.py (+0/-4)
src/mailman/utilities/email.py (+5/-5)
src/mailman/utilities/filesystem.py (+0/-3)
src/mailman/utilities/i18n.py (+4/-6)
src/mailman/utilities/importer.py (+28/-23)
src/mailman/utilities/interact.py (+0/-9)
src/mailman/utilities/mailbox.py (+2/-5)
src/mailman/utilities/modules.py (+0/-3)
src/mailman/utilities/passwords.py (+1/-6)
src/mailman/utilities/string.py (+2/-6)
src/mailman/utilities/tests/test_email.py (+0/-3)
src/mailman/utilities/tests/test_import.py (+52/-43)
src/mailman/utilities/tests/test_passwords.py (+0/-3)
src/mailman/utilities/tests/test_templates.py (+9/-10)
src/mailman/utilities/tests/test_wrap.py (+1/-3)
src/mailman/utilities/uid.py (+0/-4)
src/mailman/version.py (+5/-0)
template.py (+0/-3)
tox.ini (+4/-4)
Text conflict in src/mailman/docs/NEWS.rst
Conflict adding file src/mailman/rest/docs/systemconf.rst.  Moved existing file to src/mailman/rest/docs/systemconf.rst.moved.
Conflict adding file src/mailman/rest/tests/test_systemconf.py.  Moved existing file to src/mailman/rest/tests/test_systemconf.py.moved.
Text conflict in src/mailman/version.py
To merge this branch: bzr merge lp:~barry/mailman/py3
Reviewer Review Type Date Requested Status
Mailman Coders Pending
Review via email: mp+245313@code.launchpad.net

Description of the change

Ports Mailman 3 core to Python 3.4

To post a comment you must log in.
lp:~barry/mailman/py3 updated
7315. By Barry Warsaw

Trunk merge.

7316. By Barry Warsaw

Update the setup.py, tox.ini, template.py and documentation for the switch to
Python 3.

7317. By Barry Warsaw

Remove huge amounts of now unnecessary file boilerplate.

7318. By Barry Warsaw

Update coverage.ini

7319. By Barry Warsaw

Deprecate <api>/system path in favor of <api>/system/versions.

7320. By Barry Warsaw

 * You can access the system configuration via the resource path
   ``/3.0/system/configuration/<section>``. This returns a dictionary with
   the keys being the section's variables and the values being their value
   from ``mailman.cfg`` as verbatim strings. You can get a list of all
   section names via ``/3.0/system/configuration`` which returns a dictionary
   containing the ``http_etag`` and the section names as a sorted list under
   the ``sections`` key. The system configuration resource is read-only.

7321. By Barry Warsaw

Prep for release.

7322. By Barry Warsaw

Documentation cleanup.

7323. By Barry Warsaw

Bump version.

7324. By Barry Warsaw

Fix the passing of certain essential environment variables down from the
`mailman` foreground command, through the `master` command and to the `runner`
command. Specifically, to ensure that all agree on $VAR_DIR, we ahve to set
MAILMAN_VAR_DIR in the environment that `runner` gets.

Also, so that all agree on the same configuration, always pass -C pointing
explicitly to the cfg file that the `mailman` command sees.

7325. By Barry Warsaw

Even if we're not loading a user-defined mailman.cfg file (i.e. the user ran
`mailman info` after first install and we're creating the var dir), we still
need to post-process so we get the UPPERCASE_DIR variables.

Fix an UnboundNameError in the error printing.

7326. By Barry Warsaw

Python 3.4 has no sys.exc_clear()

7327. By Barry Warsaw

Add $cfg_file as an expansion variable for relative paths in the mailman.cfg
file, and improve the error message when an expansion loop is found. With the
`[mailman]layout: dev` path setting, put $var_dir relative to the mailman.cfg
file.

7328. By Barry Warsaw

Add NEWS.

7329. By Barry Warsaw

Correct a comment.

7330. By Barry Warsaw

 * ``$cwd`` is now an additional substitution variable for the ``mailman.cfg``
   file's ``[paths.*]`` sections. A new ``[paths.here]`` section is added,
   which puts the ``var_dir`` in ``$cwd``. It is made the default layout.

 * You can now view the contents of, inject messages into, and delete messages
   from the various queue directories via the ``<api>/queues`` resource.

Also:

inject_message() and inject_text() now return the filebase of the file
injected into the queue directory.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2014-11-08 15:27:56 +0000
3+++ .bzrignore 2015-01-03 05:08:01 +0000
4@@ -22,3 +22,4 @@
5 .coverage
6 htmlcov
7 .tox
8+__pycache__
9
10=== modified file 'coverage.ini'
11--- coverage.ini 2014-11-16 21:28:05 +0000
12+++ coverage.ini 2015-01-03 05:08:01 +0000
13@@ -4,7 +4,7 @@
14 omit =
15 setup*
16 */showme.py
17- .tox/coverage/lib/python2.7/site-packages/*
18+ .tox/coverage/lib/python3.4/site-packages/*
19
20 [paths]
21 source =
22
23=== modified file 'setup.py'
24--- setup.py 2014-11-02 19:55:10 +0000
25+++ setup.py 2015-01-03 05:08:01 +0000
26@@ -15,17 +15,15 @@
27 # You should have received a copy of the GNU General Public License along with
28 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
29
30-# Do *not* import unicode_literals. This breaks setuptools.
31-from __future__ import absolute_import, print_function
32-
33 import re
34 import sys
35
36 from setuptools import setup, find_packages
37 from string import Template
38
39-if sys.hexversion < 0x20700f0:
40- print('Mailman requires at least Python 2.7')
41+
42+if sys.hexversion < 0x30400f0:
43+ print('Mailman requires at least Python 3.4')
44 sys.exit(1)
45
46
47@@ -105,6 +103,7 @@
48 'mock',
49 'nose2',
50 'passlib',
51+ 'six',
52 'sqlalchemy',
53 'zope.component',
54 'zope.configuration',
55
56=== modified file 'src/mailman/__init__.py'
57--- src/mailman/__init__.py 2014-11-11 15:59:21 +0000
58+++ src/mailman/__init__.py 2015-01-03 05:08:01 +0000
59@@ -17,13 +17,6 @@
60
61 """The `mailman` package."""
62
63-from __future__ import absolute_import, print_function, unicode_literals
64-
65-__metaclass__ = type
66-__all__ = [
67- ]
68-
69-
70 import sys
71
72
73
74=== modified file 'src/mailman/app/bounces.py'
75--- src/mailman/app/bounces.py 2014-12-09 01:38:26 +0000
76+++ src/mailman/app/bounces.py 2015-01-03 05:08:01 +0000
77@@ -17,9 +17,6 @@
78
79 """Application level bounce handling."""
80
81-from __future__ import absolute_import, print_function, unicode_literals
82-
83-__metaclass__ = type
84 __all__ = [
85 'ProbeVERP',
86 'StandardVERP',
87@@ -36,10 +33,6 @@
88 from email.mime.message import MIMEMessage
89 from email.mime.text import MIMEText
90 from email.utils import parseaddr
91-from string import Template
92-from zope.component import getUtility
93-from zope.interface import implementer
94-
95 from mailman.config import config
96 from mailman.core.i18n import _
97 from mailman.email.message import OwnerNotification, UserNotification
98@@ -50,6 +43,10 @@
99 from mailman.utilities.email import split_email
100 from mailman.utilities.i18n import make
101 from mailman.utilities.string import oneline
102+from string import Template
103+from zope.component import getUtility
104+from zope.interface import implementer
105+
106
107 log = logging.getLogger('mailman.config')
108 elog = logging.getLogger('mailman.error')
109@@ -71,8 +68,8 @@
110 :type error: Exception
111 """
112 # Bounce a message back to the sender, with an error message if provided
113- # in the exception argument.
114- if msg.sender is None:
115+ # in the exception argument. .sender might be None or the empty string.
116+ if not msg.sender:
117 # We can't bounce the message if we don't know who it's supposed to go
118 # to.
119 return
120
121=== modified file 'src/mailman/app/commands.py'
122--- src/mailman/app/commands.py 2014-04-28 15:23:35 +0000
123+++ src/mailman/app/commands.py 2015-01-03 05:08:01 +0000
124@@ -17,19 +17,15 @@
125
126 """Initialize the email commands."""
127
128-from __future__ import absolute_import, print_function, unicode_literals
129-
130-__metaclass__ = type
131 __all__ = [
132 'initialize',
133 ]
134
135
136-from zope.interface.verify import verifyObject
137-
138 from mailman.config import config
139 from mailman.interfaces.command import IEmailCommand
140 from mailman.utilities.modules import find_components
141+from zope.interface.verify import verifyObject
142
143
144
145
146
147=== modified file 'src/mailman/app/docs/hooks.rst'
148--- src/mailman/app/docs/hooks.rst 2014-11-09 12:52:58 +0000
149+++ src/mailman/app/docs/hooks.rst 2015-01-03 05:08:01 +0000
150@@ -18,12 +18,12 @@
151 ... counter = 1
152 ... def pre_hook():
153 ... global counter
154- ... print 'pre-hook:', counter
155+ ... print('pre-hook:', counter)
156 ... counter += 1
157 ...
158 ... def post_hook():
159 ... global counter
160- ... print 'post-hook:', counter
161+ ... print('post-hook:', counter)
162 ... counter += 1
163 ... """, file=fp)
164 >>> fp.close()
165@@ -61,6 +61,7 @@
166 ... proc = subprocess.Popen(
167 ... [exe, 'lists', '--domain', 'ignore', '-q'],
168 ... cwd=ConfigLayer.root_directory, env=env,
169+ ... universal_newlines=True,
170 ... stdout=subprocess.PIPE, stderr=subprocess.PIPE)
171 ... stdout, stderr = proc.communicate()
172 ... assert proc.returncode == 0, stderr
173
174=== modified file 'src/mailman/app/docs/pipelines.rst'
175--- src/mailman/app/docs/pipelines.rst 2014-11-08 15:14:00 +0000
176+++ src/mailman/app/docs/pipelines.rst 2015-01-03 05:08:01 +0000
177@@ -45,9 +45,9 @@
178 To: test@example.com
179 Message-ID: <first>
180 X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
181- Subject: [Test] My first post
182 X-Mailman-Version: ...
183 Precedence: list
184+ Subject: [Test] My first post
185 List-Id: <test.example.com>
186 Archived-At: http://lists.example.com/.../4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
187 List-Archive: <http://lists.example.com/archives/test@example.com>
188@@ -67,7 +67,7 @@
189 >>> dump_msgdata(msgdata)
190 original_sender : aperson@example.com
191 original_subject: My first post
192- recipients : set([])
193+ recipients : set()
194 stripped_subject: My first post
195
196 After pipeline processing, the message is now sitting in various other
197@@ -84,9 +84,9 @@
198 To: test@example.com
199 Message-ID: <first>
200 X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
201- Subject: [Test] My first post
202 X-Mailman-Version: ...
203 Precedence: list
204+ Subject: [Test] My first post
205 List-Id: <test.example.com>
206 ...
207 <BLANKLINE>
208@@ -97,7 +97,7 @@
209 _parsemsg : False
210 original_sender : aperson@example.com
211 original_subject: My first post
212- recipients : set([])
213+ recipients : set()
214 stripped_subject: My first post
215 version : 3
216
217@@ -121,9 +121,9 @@
218 To: test@example.com
219 Message-ID: <first>
220 X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
221- Subject: [Test] My first post
222 X-Mailman-Version: ...
223 Precedence: list
224+ Subject: [Test] My first post
225 List-Id: <test.example.com>
226 ...
227 <BLANKLINE>
228@@ -132,10 +132,10 @@
229
230 >>> dump_msgdata(messages[0].msgdata)
231 _parsemsg : False
232- listname : test@example.com
233+ listid : test.example.com
234 original_sender : aperson@example.com
235 original_subject: My first post
236- recipients : set([])
237+ recipients : set()
238 stripped_subject: My first post
239 version : 3
240
241@@ -152,9 +152,9 @@
242 To: test@example.com
243 Message-ID: <first>
244 X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
245- Subject: [Test] My first post
246 X-Mailman-Version: ...
247 Precedence: list
248+ Subject: [Test] My first post
249 List-Id: <test.example.com>
250 ...
251 <BLANKLINE>
252
253=== modified file 'src/mailman/app/docs/subscriptions.rst'
254--- src/mailman/app/docs/subscriptions.rst 2014-04-28 15:23:35 +0000
255+++ src/mailman/app/docs/subscriptions.rst 2015-01-03 05:08:01 +0000
256@@ -67,13 +67,6 @@
257 email address. However, the user must have a preferred email address.
258 ::
259
260- >>> service.join('test.example.com', bart.user.user_id,
261- ... role=MemberRole.owner)
262- Traceback (most recent call last):
263- ...
264- MissingPreferredAddressError: User must have a preferred address:
265- <User "Bart Person" (2) at ...>
266-
267 >>> from mailman.utilities.datetime import now
268 >>> address = list(bart.user.addresses)[0]
269 >>> address.verified_on = now()
270
271=== modified file 'src/mailman/app/domain.py'
272--- src/mailman/app/domain.py 2014-04-28 15:23:35 +0000
273+++ src/mailman/app/domain.py 2015-01-03 05:08:01 +0000
274@@ -17,18 +17,14 @@
275
276 """Application level domain support."""
277
278-from __future__ import absolute_import, print_function, unicode_literals
279-
280-__metaclass__ = type
281 __all__ = [
282 'handle_DomainDeletingEvent',
283 ]
284
285
286-from zope.component import getUtility
287-
288 from mailman.interfaces.domain import DomainDeletingEvent
289 from mailman.interfaces.listmanager import IListManager
290+from zope.component import getUtility
291
292
293
294
295
296=== modified file 'src/mailman/app/events.py'
297--- src/mailman/app/events.py 2014-04-28 15:23:35 +0000
298+++ src/mailman/app/events.py 2015-01-03 05:08:01 +0000
299@@ -17,22 +17,18 @@
300
301 """Global events."""
302
303-from __future__ import absolute_import, print_function, unicode_literals
304-
305-__metaclass__ = type
306 __all__ = [
307 'initialize',
308 ]
309
310
311-from zope import event
312-
313 from mailman.app import (
314 domain, membership, moderator, registrar, subscriptions)
315 from mailman.core import i18n, switchboard
316 from mailman.languages import manager as language_manager
317 from mailman.styles import manager as style_manager
318 from mailman.utilities import passwords
319+from zope import event
320
321
322
323
324
325=== modified file 'src/mailman/app/inject.py'
326--- src/mailman/app/inject.py 2014-01-01 14:59:42 +0000
327+++ src/mailman/app/inject.py 2015-01-03 05:08:01 +0000
328@@ -17,9 +17,6 @@
329
330 """Inject a message into a queue."""
331
332-from __future__ import absolute_import, print_function, unicode_literals
333-
334-__metaclass__ = type
335 __all__ = [
336 'inject_message',
337 'inject_text',
338@@ -28,7 +25,6 @@
339
340 from email import message_from_string
341 from email.utils import formatdate, make_msgid
342-
343 from mailman.config import config
344 from mailman.email.message import Message
345 from mailman.utilities.email import add_message_hash
346@@ -53,6 +49,8 @@
347 :type switchboard: string
348 :param kws: Additional values for the message metadata.
349 :type kws: dictionary
350+ :return: filebase of enqueued message
351+ :rtype: string
352 """
353 if switchboard is None:
354 switchboard = 'in'
355@@ -66,13 +64,13 @@
356 msg['Date'] = formatdate(localtime=True)
357 msg.original_size = len(msg.as_string())
358 msgdata = dict(
359- listname=mlist.fqdn_listname,
360+ listid=mlist.list_id,
361 original_size=msg.original_size,
362 )
363 msgdata.update(kws)
364 if recipients is not None:
365 msgdata['recipients'] = recipients
366- config.switchboards[switchboard].enqueue(msg, **msgdata)
367+ return config.switchboards[switchboard].enqueue(msg, **msgdata)
368
369
370
371
372@@ -95,6 +93,8 @@
373 :type switchboard: string
374 :param kws: Additional values for the message metadata.
375 :type kws: dictionary
376+ :return: filebase of enqueued message
377+ :rtype: string
378 """
379 message = message_from_string(text, Message)
380- inject_message(mlist, message, recipients, switchboard, **kws)
381+ return inject_message(mlist, message, recipients, switchboard, **kws)
382
383=== modified file 'src/mailman/app/lifecycle.py'
384--- src/mailman/app/lifecycle.py 2014-04-28 15:23:35 +0000
385+++ src/mailman/app/lifecycle.py 2015-01-03 05:08:01 +0000
386@@ -17,9 +17,6 @@
387
388 """Application level list creation."""
389
390-from __future__ import absolute_import, print_function, unicode_literals
391-
392-__metaclass__ = type
393 __all__ = [
394 'create_list',
395 'remove_list',
396@@ -31,8 +28,6 @@
397 import shutil
398 import logging
399
400-from zope.component import getUtility
401-
402 from mailman.config import config
403 from mailman.interfaces.address import IEmailValidator
404 from mailman.interfaces.domain import (
405@@ -42,6 +37,7 @@
406 from mailman.interfaces.styles import IStyleManager
407 from mailman.interfaces.usermanager import IUserManager
408 from mailman.utilities.modules import call_name
409+from zope.component import getUtility
410
411
412 log = logging.getLogger('mailman.error')
413
414=== modified file 'src/mailman/app/membership.py'
415--- src/mailman/app/membership.py 2014-04-15 14:03:39 +0000
416+++ src/mailman/app/membership.py 2015-01-03 05:08:01 +0000
417@@ -17,9 +17,6 @@
418
419 """Application support for membership management."""
420
421-from __future__ import absolute_import, print_function, unicode_literals
422-
423-__metaclass__ = type
424 __all__ = [
425 'add_member',
426 'delete_member',
427@@ -28,8 +25,6 @@
428
429
430 from email.utils import formataddr
431-from zope.component import getUtility
432-
433 from mailman.app.notifications import (
434 send_goodbye_message, send_welcome_message)
435 from mailman.config import config
436@@ -40,6 +35,7 @@
437 MemberRole, MembershipIsBannedError, NotAMemberError, SubscriptionEvent)
438 from mailman.interfaces.usermanager import IUserManager
439 from mailman.utilities.i18n import make
440+from zope.component import getUtility
441
442
443
444
445
446=== modified file 'src/mailman/app/moderator.py'
447--- src/mailman/app/moderator.py 2014-12-09 01:38:26 +0000
448+++ src/mailman/app/moderator.py 2015-01-03 05:08:01 +0000
449@@ -17,9 +17,6 @@
450
451 """Application support for moderators."""
452
453-from __future__ import absolute_import, print_function, unicode_literals
454-
455-__metaclass__ = type
456 __all__ = [
457 'handle_ListDeletingEvent',
458 'handle_message',
459@@ -35,8 +32,6 @@
460 import logging
461
462 from email.utils import formataddr, formatdate, getaddresses, make_msgid
463-from zope.component import getUtility
464-
465 from mailman.app.membership import add_member, delete_member
466 from mailman.app.notifications import send_admin_subscription_notice
467 from mailman.config import config
468@@ -51,6 +46,7 @@
469 from mailman.interfaces.requests import IListRequests, RequestType
470 from mailman.utilities.datetime import now
471 from mailman.utilities.i18n import make
472+from zope.component import getUtility
473
474
475 NL = '\n'
476@@ -86,14 +82,14 @@
477 # Message-ID header.
478 message_id = msg.get('message-id')
479 if message_id is None:
480- msg['Message-ID'] = message_id = make_msgid().decode('ascii')
481+ msg['Message-ID'] = message_id = make_msgid()
482 elif isinstance(message_id, bytes):
483 message_id = message_id.decode('ascii')
484 getUtility(IMessageStore).add(msg)
485 # Prepare the message metadata with some extra information needed only by
486 # the moderation interface.
487 msgdata['_mod_message_id'] = message_id
488- msgdata['_mod_fqdn_listname'] = mlist.fqdn_listname
489+ msgdata['_mod_listid'] = mlist.list_id
490 msgdata['_mod_sender'] = msg.sender
491 msgdata['_mod_subject'] = msg.get('subject', _('(no subject)'))
492 msgdata['_mod_reason'] = reason
493@@ -134,7 +130,7 @@
494 # Start by getting the message from the message store.
495 msg = message_store.get_message_by_id(message_id)
496 # Delete moderation-specific entries from the message metadata.
497- for key in msgdata.keys():
498+ for key in list(msgdata):
499 if key.startswith('_mod_'):
500 del msgdata[key]
501 # Add some metadata to indicate this message has now been approved.
502
503=== modified file 'src/mailman/app/notifications.py'
504--- src/mailman/app/notifications.py 2014-01-07 03:43:59 +0000
505+++ src/mailman/app/notifications.py 2015-01-03 05:08:01 +0000
506@@ -17,9 +17,6 @@
507
508 """Sending notifications."""
509
510-from __future__ import absolute_import, print_function, unicode_literals
511-
512-__metaclass__ = type
513 __all__ = [
514 'send_admin_subscription_notice',
515 'send_goodbye_message',
516@@ -31,9 +28,6 @@
517
518 from email.utils import formataddr
519 from lazr.config import as_boolean
520-from urllib2 import URLError
521-from zope.component import getUtility
522-
523 from mailman.config import config
524 from mailman.core.i18n import _
525 from mailman.email.message import OwnerNotification, UserNotification
526@@ -41,6 +35,8 @@
527 from mailman.interfaces.templates import ITemplateLoader
528 from mailman.utilities.i18n import make
529 from mailman.utilities.string import expand, wrap
530+from six.moves.urllib_error import URLError
531+from zope.component import getUtility
532
533
534 log = logging.getLogger('mailman.error')
535@@ -141,7 +137,6 @@
536 """
537 with _.using(mlist.preferred_language.code):
538 subject = _('$mlist.display_name subscription notification')
539- display_name = display_name.encode(language.charset, 'replace')
540 text = make('adminsubscribeack.txt',
541 mailing_list=mlist,
542 listname=mlist.display_name,
543
544=== modified file 'src/mailman/app/registrar.py'
545--- src/mailman/app/registrar.py 2014-01-07 03:43:59 +0000
546+++ src/mailman/app/registrar.py 2015-01-03 05:08:01 +0000
547@@ -17,9 +17,6 @@
548
549 """Implementation of the IUserRegistrar interface."""
550
551-from __future__ import absolute_import, print_function, unicode_literals
552-
553-__metaclass__ = type
554 __all__ = [
555 'Registrar',
556 'handle_ConfirmationNeededEvent',
557@@ -28,10 +25,6 @@
558
559 import logging
560
561-from zope.component import getUtility
562-from zope.event import notify
563-from zope.interface import implementer
564-
565 from mailman.core.i18n import _
566 from mailman.email.message import UserNotification
567 from mailman.interfaces.address import IEmailValidator
568@@ -42,6 +35,9 @@
569 from mailman.interfaces.templates import ITemplateLoader
570 from mailman.interfaces.usermanager import IUserManager
571 from mailman.utilities.datetime import now
572+from zope.component import getUtility
573+from zope.event import notify
574+from zope.interface import implementer
575
576
577 log = logging.getLogger('mailman.error')
578
579=== modified file 'src/mailman/app/replybot.py'
580--- src/mailman/app/replybot.py 2014-04-28 15:23:35 +0000
581+++ src/mailman/app/replybot.py 2015-01-03 05:08:01 +0000
582@@ -21,9 +21,6 @@
583 # mailing list. The reply governor should really apply site-wide per
584 # recipient (I think).
585
586-from __future__ import absolute_import, print_function, unicode_literals
587-
588-__metaclass__ = type
589 __all__ = [
590 'can_acknowledge',
591 ]
592
593=== modified file 'src/mailman/app/subscriptions.py'
594--- src/mailman/app/subscriptions.py 2014-09-22 18:47:02 +0000
595+++ src/mailman/app/subscriptions.py 2015-01-03 05:08:01 +0000
596@@ -15,11 +15,8 @@
597 # You should have received a copy of the GNU General Public License along with
598 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
599
600-"""Module stuff."""
601-
602-from __future__ import absolute_import, print_function, unicode_literals
603-
604-__metaclass__ = type
605+"""Handle subscriptions."""
606+
607 __all__ = [
608 'SubscriptionService',
609 'handle_ListDeletingEvent',
610@@ -108,7 +105,7 @@
611 # the parameter can either be an email address or a user id.
612 query = []
613 if subscriber is not None:
614- if isinstance(subscriber, basestring):
615+ if isinstance(subscriber, str):
616 # subscriber is an email address.
617 address = user_manager.get_address(subscriber)
618 user = user_manager.get_user(subscriber)
619@@ -148,7 +145,7 @@
620 if mlist is None:
621 raise NoSuchListError(list_id)
622 # Is the subscriber an email address or user id?
623- if isinstance(subscriber, basestring):
624+ if isinstance(subscriber, str):
625 if display_name is None:
626 display_name, at, domain = subscriber.partition('@')
627 # Because we want to keep the REST API simple, there is no
628
629=== modified file 'src/mailman/app/templates.py'
630--- src/mailman/app/templates.py 2014-04-14 16:14:13 +0000
631+++ src/mailman/app/templates.py 2015-01-03 05:08:01 +0000
632@@ -17,30 +17,27 @@
633
634 """Template loader."""
635
636-from __future__ import absolute_import, print_function, unicode_literals
637-
638-__metaclass__ = type
639 __all__ = [
640 'TemplateLoader',
641 ]
642
643
644-import urllib2
645-
646 from contextlib import closing
647-from urllib import addinfourl
648-from urlparse import urlparse
649-from zope.component import getUtility
650-from zope.interface import implementer
651-
652-from mailman.utilities.i18n import TemplateNotFoundError, find
653 from mailman.interfaces.languages import ILanguageManager
654 from mailman.interfaces.listmanager import IListManager
655 from mailman.interfaces.templates import ITemplateLoader
656+from mailman.utilities.i18n import TemplateNotFoundError, find
657+from six.moves.urllib_error import URLError
658+from six.moves.urllib_parse import urlparse
659+from six.moves.urllib_request import (
660+ BaseHandler, build_opener, install_opener, urlopen)
661+from six.moves.urllib_response import addinfourl
662+from zope.component import getUtility
663+from zope.interface import implementer
664
665
666
667
668-class MailmanHandler(urllib2.BaseHandler):
669+class MailmanHandler(BaseHandler):
670 # Handle internal mailman: URLs.
671 def mailman_open(self, req):
672 # Parse urls of the form:
673@@ -55,9 +52,9 @@
674 assert parsed.scheme == 'mailman'
675 # The path can contain one, two, or three components. Since no empty
676 # path components are legal, filter them out.
677- parts = filter(None, parsed.path.split('/'))
678+ parts = [p for p in parsed.path.split('/') if p]
679 if len(parts) == 0:
680- raise urllib2.URLError('No template specified')
681+ raise URLError('No template specified')
682 elif len(parts) == 1:
683 template = parts[0]
684 elif len(parts) == 2:
685@@ -69,25 +66,25 @@
686 language = getUtility(ILanguageManager).get(part0)
687 mlist = getUtility(IListManager).get(part0)
688 if language is None and mlist is None:
689- raise urllib2.URLError('Bad language or list name')
690+ raise URLError('Bad language or list name')
691 elif mlist is None:
692 code = language.code
693 elif len(parts) == 3:
694 fqdn_listname, code, template = parts
695 mlist = getUtility(IListManager).get(fqdn_listname)
696 if mlist is None:
697- raise urllib2.URLError('Missing list')
698+ raise URLError('Missing list')
699 language = getUtility(ILanguageManager).get(code)
700 if language is None:
701- raise urllib2.URLError('No such language')
702+ raise URLError('No such language')
703 code = language.code
704 else:
705- raise urllib2.URLError('No such file')
706+ raise URLError('No such file')
707 # Find the template, mutating any missing template exception.
708 try:
709 path, fp = find(template, mlist, code)
710 except TemplateNotFoundError:
711- raise urllib2.URLError('No such file')
712+ raise URLError('No such file')
713 return addinfourl(fp, {}, original_url)
714
715
716@@ -97,10 +94,10 @@
717 """Loader of templates, with caching and support for mailman:// URIs."""
718
719 def __init__(self):
720- opener = urllib2.build_opener(MailmanHandler())
721- urllib2.install_opener(opener)
722+ opener = build_opener(MailmanHandler())
723+ install_opener(opener)
724
725 def get(self, uri):
726 """See `ITemplateLoader`."""
727- with closing(urllib2.urlopen(uri)) as fp:
728- return fp.read().decode('utf-8')
729+ with closing(urlopen(uri)) as fp:
730+ return fp.read()
731
732=== modified file 'src/mailman/app/tests/test_bounces.py'
733--- src/mailman/app/tests/test_bounces.py 2014-01-07 03:43:59 +0000
734+++ src/mailman/app/tests/test_bounces.py 2015-01-03 05:08:01 +0000
735@@ -17,9 +17,6 @@
736
737 """Testing app.bounces functions."""
738
739-from __future__ import absolute_import, print_function, unicode_literals
740-
741-__metaclass__ = type
742 __all__ = [
743 'TestBounceMessage',
744 'TestMaybeForward',
745@@ -36,8 +33,6 @@
746 import tempfile
747 import unittest
748
749-from zope.component import getUtility
750-
751 from mailman.app.bounces import (
752 ProbeVERP, StandardVERP, bounce_message, maybe_forward, send_probe)
753 from mailman.app.lifecycle import create_list
754@@ -49,10 +44,9 @@
755 from mailman.interfaces.pending import IPendings
756 from mailman.interfaces.usermanager import IUserManager
757 from mailman.testing.helpers import (
758- LogFileMark,
759- get_queue_messages,
760- specialized_message_from_string as mfs)
761+ LogFileMark, get_queue_messages, specialized_message_from_string as mfs)
762 from mailman.testing.layers import ConfigLayer
763+from zope.component import getUtility
764
765
766
767
768@@ -334,7 +328,7 @@
769 send_probe(self._member, self._msg)
770 message = get_queue_messages('virgin')[0].msg
771 self.assertEqual(
772- message['Subject'],
773+ message['subject'].encode(),
774 '=?utf-8?q?ailing-may_ist-lay_Test_obe-pray_essage-may?=')
775
776 def test_probe_notice_with_member_nonenglish(self):
777@@ -533,7 +527,7 @@
778
779 def test_no_sender(self):
780 # The message won't be bounced if it has no discernible sender.
781- self._msg.sender = None
782+ del self._msg['from']
783 bounce_message(self._mlist, self._msg)
784 items = get_queue_messages('virgin')
785 # Nothing in the virgin queue means nothing's been bounced.
786
787=== modified file 'src/mailman/app/tests/test_inject.py'
788--- src/mailman/app/tests/test_inject.py 2014-01-01 14:59:42 +0000
789+++ src/mailman/app/tests/test_inject.py 2015-01-03 05:08:01 +0000
790@@ -17,10 +17,9 @@
791
792 """Testing app.inject functions."""
793
794-from __future__ import absolute_import, print_function, unicode_literals
795-
796-__metaclass__ = type
797 __all__ = [
798+ 'TestInjectMessage',
799+ 'TestInjectText',
800 ]
801
802
803@@ -64,7 +63,7 @@
804 self.assertEqual(len(items), 1)
805 self.assertMultiLineEqual(items[0].msg.as_string(),
806 self.msg.as_string())
807- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
808+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
809 self.assertEqual(items[0].msgdata['original_size'],
810 len(self.msg.as_string()))
811
812@@ -84,7 +83,7 @@
813 self.assertEqual(len(items), 1)
814 self.assertMultiLineEqual(items[0].msg.as_string(),
815 self.msg.as_string())
816- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
817+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
818 self.assertEqual(items[0].msgdata['original_size'],
819 len(self.msg.as_string()))
820
821@@ -144,7 +143,7 @@
822
823 def setUp(self):
824 self.mlist = create_list('test@example.com')
825- self.text = b"""\
826+ self.text = """\
827 From: bart@example.com
828 To: test@example.com
829 Subject: A test message
830@@ -171,7 +170,7 @@
831 # Delete that header because it is not in the original text.
832 del items[0].msg['x-message-id-hash']
833 self.assertMultiLineEqual(items[0].msg.as_string(), self.text)
834- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
835+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
836 self.assertEqual(items[0].msgdata['original_size'],
837 # Add back the X-Message-ID-Header which was in the
838 # message contributing to the original_size, but
839@@ -196,7 +195,7 @@
840 # Remove the X-Message-ID-Hash header which isn't in the original text.
841 del items[0].msg['x-message-id-hash']
842 self.assertMultiLineEqual(items[0].msg.as_string(), self.text)
843- self.assertEqual(items[0].msgdata['listname'], 'test@example.com')
844+ self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
845 self.assertEqual(items[0].msgdata['original_size'],
846 # Add back the X-Message-ID-Header which was in the
847 # message contributing to the original_size, but
848
849=== modified file 'src/mailman/app/tests/test_lifecycle.py'
850--- src/mailman/app/tests/test_lifecycle.py 2014-03-02 20:59:30 +0000
851+++ src/mailman/app/tests/test_lifecycle.py 2015-01-03 05:08:01 +0000
852@@ -17,9 +17,6 @@
853
854 """Test the high level list lifecycle API."""
855
856-from __future__ import absolute_import, print_function, unicode_literals
857-
858-__metaclass__ = type
859 __all__ = [
860 'TestLifecycle',
861 ]
862
863=== modified file 'src/mailman/app/tests/test_membership.py'
864--- src/mailman/app/tests/test_membership.py 2014-03-02 21:38:32 +0000
865+++ src/mailman/app/tests/test_membership.py 2015-01-03 05:08:01 +0000
866@@ -17,9 +17,6 @@
867
868 """Tests of application level membership functions."""
869
870-from __future__ import absolute_import, print_function, unicode_literals
871-
872-__metaclass__ = type
873 __all__ = [
874 'TestAddMember',
875 'TestAddMemberPassword',
876@@ -29,8 +26,6 @@
877
878 import unittest
879
880-from zope.component import getUtility
881-
882 from mailman.app.lifecycle import create_list
883 from mailman.app.membership import add_member, delete_member
884 from mailman.core.constants import system_preferences
885@@ -40,6 +35,7 @@
886 NotAMemberError)
887 from mailman.interfaces.usermanager import IUserManager
888 from mailman.testing.layers import ConfigLayer
889+from zope.component import getUtility
890
891
892
893
894
895=== modified file 'src/mailman/app/tests/test_moderation.py'
896--- src/mailman/app/tests/test_moderation.py 2014-01-01 14:59:42 +0000
897+++ src/mailman/app/tests/test_moderation.py 2015-01-03 05:08:01 +0000
898@@ -17,9 +17,6 @@
899
900 """Moderation tests."""
901
902-from __future__ import absolute_import, print_function, unicode_literals
903-
904-__metaclass__ = type
905 __all__ = [
906 'TestModeration',
907 ]
908@@ -27,8 +24,6 @@
909
910 import unittest
911
912-from zope.component import getUtility
913-
914 from mailman.app.lifecycle import create_list
915 from mailman.app.moderator import handle_message, hold_message
916 from mailman.interfaces.action import Action
917@@ -41,6 +36,7 @@
918 get_queue_messages, make_testable_runner, specialized_message_from_string)
919 from mailman.testing.layers import SMTPLayer
920 from mailman.utilities.datetime import now
921+from zope.component import getUtility
922
923
924
925
926
927=== modified file 'src/mailman/app/tests/test_notifications.py'
928--- src/mailman/app/tests/test_notifications.py 2014-01-07 03:43:59 +0000
929+++ src/mailman/app/tests/test_notifications.py 2015-01-03 05:08:01 +0000
930@@ -17,10 +17,8 @@
931
932 """Test notifications."""
933
934-from __future__ import absolute_import, print_function, unicode_literals
935-
936-__metaclass__ = type
937 __all__ = [
938+ 'TestNotifications',
939 ]
940
941
942@@ -29,8 +27,6 @@
943 import tempfile
944 import unittest
945
946-from zope.component import getUtility
947-
948 from mailman.app.lifecycle import create_list
949 from mailman.app.membership import add_member
950 from mailman.config import config
951@@ -38,6 +34,7 @@
952 from mailman.interfaces.member import DeliveryMode, MemberRole
953 from mailman.testing.helpers import get_queue_messages
954 from mailman.testing.layers import ConfigLayer
955+from zope.component import getUtility
956
957
958
959
960
961=== modified file 'src/mailman/app/tests/test_registration.py'
962--- src/mailman/app/tests/test_registration.py 2014-01-07 03:43:59 +0000
963+++ src/mailman/app/tests/test_registration.py 2015-01-03 05:08:01 +0000
964@@ -1,4 +1,4 @@
965-# Copyright (C) 2012 by the Free Software Foundation, Inc.
966+# Copyright (C) 2012-2014 by the Free Software Foundation, Inc.
967 #
968 # This file is part of GNU Mailman.
969 #
970@@ -17,9 +17,6 @@
971
972 """Test email address registration."""
973
974-from __future__ import absolute_import, print_function, unicode_literals
975-
976-__metaclass__ = type
977 __all__ = [
978 'TestEmailValidation',
979 'TestRegistration',
980@@ -28,14 +25,13 @@
981
982 import unittest
983
984-from zope.component import getUtility
985-
986 from mailman.app.lifecycle import create_list
987 from mailman.interfaces.address import InvalidEmailAddressError
988 from mailman.interfaces.pending import IPendings
989 from mailman.interfaces.registrar import ConfirmationNeededEvent, IRegistrar
990 from mailman.testing.helpers import event_subscribers
991 from mailman.testing.layers import ConfigLayer
992+from zope.component import getUtility
993
994
995
996
997
998=== modified file 'src/mailman/app/tests/test_subscriptions.py'
999--- src/mailman/app/tests/test_subscriptions.py 2014-01-01 14:59:42 +0000
1000+++ src/mailman/app/tests/test_subscriptions.py 2015-01-03 05:08:01 +0000
1001@@ -17,9 +17,6 @@
1002
1003 """Tests for the subscription service."""
1004
1005-from __future__ import absolute_import, print_function, unicode_literals
1006-
1007-__metaclass__ = type
1008 __all__ = [
1009 'TestJoin'
1010 ]
1011@@ -28,13 +25,13 @@
1012 import uuid
1013 import unittest
1014
1015-from zope.component import getUtility
1016-
1017 from mailman.app.lifecycle import create_list
1018 from mailman.interfaces.address import InvalidEmailAddressError
1019+from mailman.interfaces.member import MemberRole, MissingPreferredAddressError
1020 from mailman.interfaces.subscriptions import (
1021 MissingUserError, ISubscriptionService)
1022 from mailman.testing.layers import ConfigLayer
1023+from zope.component import getUtility
1024
1025
1026
1027
1028@@ -57,3 +54,14 @@
1029 with self.assertRaises(InvalidEmailAddressError) as cm:
1030 self._service.join('test.example.com', 'bogus')
1031 self.assertEqual(cm.exception.email, 'bogus')
1032+
1033+ def test_missing_preferred_address(self):
1034+ # A user cannot join a mailing list if they have no preferred address.
1035+ anne = self._service.join(
1036+ 'test.example.com', 'anne@example.com', 'Anne Person')
1037+ # Try to join Anne as a user with a different role. Her user has no
1038+ # preferred address, so this will fail.
1039+ self.assertRaises(MissingPreferredAddressError,
1040+ self._service.join,
1041+ 'test.example.com', anne.user.user_id,
1042+ role=MemberRole.owner)
1043
1044=== modified file 'src/mailman/app/tests/test_templates.py'
1045--- src/mailman/app/tests/test_templates.py 2014-04-14 16:14:13 +0000
1046+++ src/mailman/app/tests/test_templates.py 2015-01-03 05:08:01 +0000
1047@@ -17,27 +17,24 @@
1048
1049 """Test the template downloader API."""
1050
1051-from __future__ import absolute_import, print_function, unicode_literals
1052-
1053-__metaclass__ = type
1054 __all__ = [
1055 'TestTemplateLoader',
1056 ]
1057
1058
1059 import os
1060+import six
1061 import shutil
1062-import urllib2
1063 import tempfile
1064 import unittest
1065
1066-from zope.component import getUtility
1067-
1068 from mailman.app.lifecycle import create_list
1069 from mailman.config import config
1070 from mailman.interfaces.languages import ILanguageManager
1071 from mailman.interfaces.templates import ITemplateLoader
1072 from mailman.testing.layers import ConfigLayer
1073+from six.moves.urllib_error import URLError
1074+from zope.component import getUtility
1075
1076
1077
1078
1079@@ -98,32 +95,32 @@
1080 self.assertEqual(content, 'Test content')
1081
1082 def test_uri_not_found(self):
1083- with self.assertRaises(urllib2.URLError) as cm:
1084+ with self.assertRaises(URLError) as cm:
1085 self._loader.get('mailman:///missing.txt')
1086 self.assertEqual(cm.exception.reason, 'No such file')
1087
1088 def test_shorter_url_error(self):
1089- with self.assertRaises(urllib2.URLError) as cm:
1090+ with self.assertRaises(URLError) as cm:
1091 self._loader.get('mailman:///')
1092 self.assertEqual(cm.exception.reason, 'No template specified')
1093
1094 def test_short_url_error(self):
1095- with self.assertRaises(urllib2.URLError) as cm:
1096+ with self.assertRaises(URLError) as cm:
1097 self._loader.get('mailman://')
1098 self.assertEqual(cm.exception.reason, 'No template specified')
1099
1100 def test_bad_language(self):
1101- with self.assertRaises(urllib2.URLError) as cm:
1102+ with self.assertRaises(URLError) as cm:
1103 self._loader.get('mailman:///xx/demo.txt')
1104 self.assertEqual(cm.exception.reason, 'Bad language or list name')
1105
1106 def test_bad_mailing_list(self):
1107- with self.assertRaises(urllib2.URLError) as cm:
1108+ with self.assertRaises(URLError) as cm:
1109 self._loader.get('mailman:///missing@example.com/demo.txt')
1110 self.assertEqual(cm.exception.reason, 'Bad language or list name')
1111
1112 def test_too_many_path_components(self):
1113- with self.assertRaises(urllib2.URLError) as cm:
1114+ with self.assertRaises(URLError) as cm:
1115 self._loader.get('mailman:///missing@example.com/en/foo/demo.txt')
1116 self.assertEqual(cm.exception.reason, 'No such file')
1117
1118@@ -132,8 +129,8 @@
1119 test_text = b'\xe4\xb8\xad'
1120 path = os.path.join(self.var_dir, 'templates', 'site', 'it')
1121 os.makedirs(path)
1122- with open(os.path.join(path, 'demo.txt'), 'w') as fp:
1123- print(test_text, end='', file=fp)
1124+ with open(os.path.join(path, 'demo.txt'), 'wb') as fp:
1125+ fp.write(test_text)
1126 content = self._loader.get('mailman:///it/demo.txt')
1127- self.assertTrue(isinstance(content, unicode))
1128+ self.assertIsInstance(content, six.text_type)
1129 self.assertEqual(content, test_text.decode('utf-8'))
1130
1131=== modified file 'src/mailman/archiving/mailarchive.py'
1132--- src/mailman/archiving/mailarchive.py 2014-12-09 01:38:26 +0000
1133+++ src/mailman/archiving/mailarchive.py 2015-01-03 05:08:01 +0000
1134@@ -17,21 +17,16 @@
1135
1136 """The Mail-Archive.com archiver."""
1137
1138-from __future__ import absolute_import, print_function, unicode_literals
1139-
1140-__metaclass__ = type
1141 __all__ = [
1142 'MailArchive',
1143 ]
1144
1145
1146-from urllib import quote
1147-from urlparse import urljoin
1148-from zope.interface import implementer
1149-
1150 from mailman.config import config
1151 from mailman.config.config import external_configuration
1152 from mailman.interfaces.archiver import ArchivePolicy, IArchiver
1153+from six.moves.urllib_parse import quote, urljoin
1154+from zope.interface import implementer
1155
1156
1157
1158
1159@@ -77,5 +72,5 @@
1160 if mlist.archive_policy is ArchivePolicy.public:
1161 config.switchboards['out'].enqueue(
1162 msg,
1163- listname=mlist.fqdn_listname,
1164+ listid=mlist.list_id,
1165 recipients=[self.recipient])
1166
1167=== modified file 'src/mailman/archiving/mhonarc.py'
1168--- src/mailman/archiving/mhonarc.py 2014-12-09 01:38:26 +0000
1169+++ src/mailman/archiving/mhonarc.py 2015-01-03 05:08:01 +0000
1170@@ -17,9 +17,6 @@
1171
1172 """MHonArc archiver."""
1173
1174-from __future__ import absolute_import, print_function, unicode_literals
1175-
1176-__metaclass__ = type
1177 __all__ = [
1178 'MHonArc',
1179 ]
1180@@ -28,13 +25,12 @@
1181 import logging
1182 import subprocess
1183
1184-from urlparse import urljoin
1185-from zope.interface import implementer
1186-
1187 from mailman.config import config
1188 from mailman.config.config import external_configuration
1189 from mailman.interfaces.archiver import IArchiver
1190 from mailman.utilities.string import expand
1191+from six.moves.urllib_parse import urljoin
1192+from zope.interface import implementer
1193
1194
1195 log = logging.getLogger('mailman.archiver')
1196@@ -84,7 +80,7 @@
1197 command = expand(self.command, substitutions)
1198 proc = subprocess.Popen(
1199 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1200- shell=True)
1201+ universal_newlines=True, shell=True)
1202 stdout, stderr = proc.communicate(msg.as_string())
1203 if proc.returncode != 0:
1204 log.error('%s: mhonarc subprocess had non-zero exit code: %s' %
1205
1206=== modified file 'src/mailman/archiving/prototype.py'
1207--- src/mailman/archiving/prototype.py 2014-12-09 01:38:26 +0000
1208+++ src/mailman/archiving/prototype.py 2015-01-03 05:08:01 +0000
1209@@ -17,9 +17,6 @@
1210
1211 """Prototypical permalinking archiver."""
1212
1213-from __future__ import absolute_import, print_function, unicode_literals
1214-
1215-__metaclass__ = type
1216 __all__ = [
1217 'Prototype',
1218 ]
1219@@ -30,14 +27,13 @@
1220 import logging
1221
1222 from datetime import timedelta
1223+from flufl.lock import Lock, TimeOutError
1224 from mailbox import Maildir
1225-from urlparse import urljoin
1226-
1227-from flufl.lock import Lock, TimeOutError
1228-from zope.interface import implementer
1229-
1230 from mailman.config import config
1231 from mailman.interfaces.archiver import IArchiver
1232+from six.moves.urllib_parse import urljoin
1233+from zope.interface import implementer
1234+
1235
1236 log = logging.getLogger('mailman.error')
1237
1238
1239=== modified file 'src/mailman/archiving/tests/test_prototype.py'
1240--- src/mailman/archiving/tests/test_prototype.py 2014-01-01 14:59:42 +0000
1241+++ src/mailman/archiving/tests/test_prototype.py 2015-01-03 05:08:01 +0000
1242@@ -17,9 +17,6 @@
1243
1244 """Test the prototype archiver."""
1245
1246-from __future__ import absolute_import, print_function, unicode_literals
1247-
1248-__metaclass__ = type
1249 __all__ = [
1250 'TestPrototypeArchiver',
1251 ]
1252@@ -33,7 +30,6 @@
1253
1254 from email import message_from_file
1255 from flufl.lock import Lock
1256-
1257 from mailman.app.lifecycle import create_list
1258 from mailman.archiving.prototype import Prototype
1259 from mailman.config import config
1260@@ -89,13 +85,13 @@
1261 def _find(self, path):
1262 all_filenames = set()
1263 for dirpath, dirnames, filenames in os.walk(path):
1264- if not isinstance(dirpath, unicode):
1265- dirpath = unicode(dirpath)
1266+ if isinstance(dirpath, bytes):
1267+ dirpath = dirpath.decode('utf-8')
1268 all_filenames.add(dirpath)
1269 for filename in filenames:
1270 new_filename = filename
1271- if not isinstance(filename, unicode):
1272- new_filename = unicode(filename)
1273+ if isinstance(filename, bytes):
1274+ new_filename = filename.decode('utf-8')
1275 all_filenames.add(os.path.join(dirpath, new_filename))
1276 return all_filenames
1277
1278
1279=== modified file 'src/mailman/bin/export.py'
1280--- src/mailman/bin/export.py 2014-01-01 14:59:42 +0000
1281+++ src/mailman/bin/export.py 2015-01-03 05:08:01 +0000
1282@@ -134,7 +134,7 @@
1283 print >> self._fp, '<%s%s/>' % (_name, attrs)
1284 else:
1285 # The value might contain angle brackets.
1286- value = escape(unicode(_value))
1287+ value = escape(_value.decode('utf-8'))
1288 print >> self._fp, '<%s%s>%s</%s>' % (_name, attrs, value, _name)
1289
1290 def _do_list_categories(self, mlist, k, subcat=None):
1291
1292=== modified file 'src/mailman/bin/gate_news.py'
1293--- src/mailman/bin/gate_news.py 2014-11-20 01:29:44 +0000
1294+++ src/mailman/bin/gate_news.py 2015-01-03 05:08:01 +0000
1295@@ -149,7 +149,7 @@
1296 # Post the message to the locked list
1297 inq = Switchboard(config.INQUEUE_DIR)
1298 inq.enqueue(msg,
1299- listname=mlist.internal_name(),
1300+ listid=mlist.list_id,
1301 fromusenet=True)
1302 log.info('posted to list %s: %7d', listname, num)
1303 except nntplib.NNTPError as e:
1304
1305=== modified file 'src/mailman/bin/mailman.py'
1306--- src/mailman/bin/mailman.py 2014-01-01 14:59:42 +0000
1307+++ src/mailman/bin/mailman.py 2015-01-03 05:08:01 +0000
1308@@ -17,9 +17,6 @@
1309
1310 """The 'mailman' command dispatcher."""
1311
1312-from __future__ import absolute_import, print_function, unicode_literals
1313-
1314-__metaclass__ = type
1315 __all__ = [
1316 'main',
1317 ]
1318@@ -28,13 +25,13 @@
1319 import os
1320 import argparse
1321
1322-from zope.interface.verify import verifyObject
1323-
1324+from functools import cmp_to_key
1325 from mailman.core.i18n import _
1326 from mailman.core.initialize import initialize
1327 from mailman.interfaces.command import ICLISubCommand
1328 from mailman.utilities.modules import find_components
1329 from mailman.version import MAILMAN_VERSION_FULL
1330+from zope.interface.verify import verifyObject
1331
1332
1333
1334
1335@@ -77,9 +74,14 @@
1336 return -1
1337 elif other.name == 'help':
1338 return 1
1339+ elif command.name < other.name:
1340+ return -1
1341+ elif command.name == other.name:
1342+ return 0
1343 else:
1344- return cmp(command.name, other.name)
1345- subcommands.sort(cmp=sort_function)
1346+ assert command.name > other.name
1347+ return 1
1348+ subcommands.sort(key=cmp_to_key(sort_function))
1349 for command in subcommands:
1350 command_parser = subparser.add_parser(
1351 command.name, help=_(command.__doc__))
1352
1353=== modified file 'src/mailman/bin/master.py'
1354--- src/mailman/bin/master.py 2014-11-20 01:29:44 +0000
1355+++ src/mailman/bin/master.py 2015-01-03 05:08:01 +0000
1356@@ -17,9 +17,6 @@
1357
1358 """Master subprocess watcher."""
1359
1360-from __future__ import absolute_import, print_function, unicode_literals
1361-
1362-__metaclass__ = type
1363 __all__ = [
1364 'Loop',
1365 'main',
1366@@ -37,7 +34,6 @@
1367 from enum import Enum
1368 from flufl.lock import Lock, NotLockedError, TimeOutError
1369 from lazr.config import as_boolean
1370-
1371 from mailman.config import config
1372 from mailman.core.i18n import _
1373 from mailman.core.logging import reopen
1374@@ -357,7 +353,7 @@
1375 # Set the environment variable which tells the runner that it's
1376 # running under bin/master control. This subtly changes the error
1377 # behavior of bin/runner.
1378- os.environ['MAILMAN_UNDER_MASTER_CONTROL'] = '1'
1379+ env = {'MAILMAN_UNDER_MASTER_CONTROL': '1'}
1380 # Craft the command line arguments for the exec() call.
1381 rswitch = '--runner=' + spec
1382 # Wherever master lives, so too must live the runner script.
1383@@ -365,14 +361,21 @@
1384 # config.PYTHON, which is the absolute path to the Python interpreter,
1385 # must be given as argv[0] due to Python's library search algorithm.
1386 args = [sys.executable, sys.executable, exe, rswitch]
1387- if self._config_file is not None:
1388- args.extend(['-C', self._config_file])
1389+ # Always pass the explicit path to the configuration file to the
1390+ # sub-runners. This avoids any debate about which cfg file is used.
1391+ config_file = (config.filename if self._config_file is None
1392+ else self._config_file)
1393+ args.extend(['-C', config_file])
1394 log = logging.getLogger('mailman.runner')
1395 log.debug('starting: %s', args)
1396+ # We must pass this environment variable through if it's set,
1397+ # otherwise runner processes will not have the correct VAR_DIR.
1398+ var_dir = os.environ.get('MAILMAN_VAR_DIR')
1399+ if var_dir is not None:
1400+ env['MAILMAN_VAR_DIR'] = var_dir
1401 # For the testing framework, if this environment variable is set, pass
1402 # it on to the subprocess.
1403 coverage_env = os.environ.get('COVERAGE_PROCESS_START')
1404- env = dict()
1405 if coverage_env is not None:
1406 env['COVERAGE_PROCESS_START'] = coverage_env
1407 args.append(env)
1408
1409=== modified file 'src/mailman/bin/onebounce.py'
1410--- src/mailman/bin/onebounce.py 2014-04-28 15:23:35 +0000
1411+++ src/mailman/bin/onebounce.py 2015-01-03 05:08:01 +0000
1412@@ -18,9 +18,6 @@
1413 """Test bounce detection on message files."""
1414
1415
1416-from __future__ import absolute_import, print_function, unicode_literals
1417-
1418-__metaclass__ = type
1419 __all__ = [
1420 'main',
1421 ]
1422
1423=== modified file 'src/mailman/bin/runner.py'
1424--- src/mailman/bin/runner.py 2014-11-16 21:28:05 +0000
1425+++ src/mailman/bin/runner.py 2015-01-03 05:08:01 +0000
1426@@ -17,9 +17,6 @@
1427
1428 """The runner process."""
1429
1430-from __future__ import absolute_import, print_function, unicode_literals
1431-
1432-__metaclass__ = type
1433 __all__ = [
1434 'main',
1435 ]
1436
1437=== modified file 'src/mailman/bin/tests/test_master.py'
1438--- src/mailman/bin/tests/test_master.py 2014-11-01 16:49:15 +0000
1439+++ src/mailman/bin/tests/test_master.py 2015-01-03 05:08:01 +0000
1440@@ -17,9 +17,6 @@
1441
1442 """Test master watcher utilities."""
1443
1444-from __future__ import absolute_import, print_function, unicode_literals
1445-
1446-__metaclass__ = type
1447 __all__ = [
1448 'TestMasterLock',
1449 ]
1450
1451=== modified file 'src/mailman/chains/accept.py'
1452--- src/mailman/chains/accept.py 2014-04-28 15:23:35 +0000
1453+++ src/mailman/chains/accept.py 2015-01-03 05:08:01 +0000
1454@@ -17,9 +17,6 @@
1455
1456 """The terminal 'accept' chain."""
1457
1458-from __future__ import absolute_import, print_function, unicode_literals
1459-
1460-__metaclass__ = type
1461 __all__ = [
1462 'AcceptChain',
1463 ]
1464@@ -27,12 +24,11 @@
1465
1466 import logging
1467
1468-from zope.event import notify
1469-
1470 from mailman.chains.base import TerminalChainBase
1471 from mailman.config import config
1472 from mailman.core.i18n import _
1473 from mailman.interfaces.chain import AcceptEvent
1474+from zope.event import notify
1475
1476
1477 log = logging.getLogger('mailman.vette')
1478
1479=== modified file 'src/mailman/chains/base.py'
1480--- src/mailman/chains/base.py 2014-11-11 15:59:21 +0000
1481+++ src/mailman/chains/base.py 2015-01-03 05:08:01 +0000
1482@@ -17,9 +17,6 @@
1483
1484 """Base class for terminal chains."""
1485
1486-from __future__ import absolute_import, print_function, unicode_literals
1487-
1488-__metaclass__ = type
1489 __all__ = [
1490 'Chain',
1491 'Link',
1492@@ -27,11 +24,10 @@
1493 ]
1494
1495
1496-from zope.interface import implementer
1497-
1498 from mailman.config import config
1499 from mailman.interfaces.chain import (
1500 IChain, IChainIterator, IChainLink, IMutableChain, LinkAction)
1501+from zope.interface import implementer
1502
1503
1504
1505
1506
1507=== modified file 'src/mailman/chains/builtin.py'
1508--- src/mailman/chains/builtin.py 2014-01-01 14:59:42 +0000
1509+++ src/mailman/chains/builtin.py 2015-01-03 05:08:01 +0000
1510@@ -17,9 +17,6 @@
1511
1512 """The default built-in starting chain."""
1513
1514-from __future__ import absolute_import, print_function, unicode_literals
1515-
1516-__metaclass__ = type
1517 __all__ = [
1518 'BuiltInChain',
1519 ]
1520@@ -27,12 +24,11 @@
1521
1522 import logging
1523
1524-from zope.interface import implementer
1525-
1526 from mailman.chains.base import Link
1527 from mailman.config import config
1528 from mailman.core.i18n import _
1529 from mailman.interfaces.chain import IChain, LinkAction
1530+from zope.interface import implementer
1531
1532
1533 log = logging.getLogger('mailman.vette')
1534
1535=== modified file 'src/mailman/chains/discard.py'
1536--- src/mailman/chains/discard.py 2014-04-28 15:23:35 +0000
1537+++ src/mailman/chains/discard.py 2015-01-03 05:08:01 +0000
1538@@ -17,20 +17,17 @@
1539
1540 """The terminal 'discard' chain."""
1541
1542-from __future__ import absolute_import, print_function, unicode_literals
1543-
1544-__metaclass__ = type
1545 __all__ = [
1546 'DiscardChain',
1547 ]
1548
1549
1550 import logging
1551-from zope.event import notify
1552
1553 from mailman.chains.base import TerminalChainBase
1554 from mailman.core.i18n import _
1555 from mailman.interfaces.chain import DiscardEvent
1556+from zope.event import notify
1557
1558
1559 log = logging.getLogger('mailman.vette')
1560
1561=== modified file 'src/mailman/chains/headers.py'
1562--- src/mailman/chains/headers.py 2014-01-01 14:59:42 +0000
1563+++ src/mailman/chains/headers.py 2015-01-03 05:08:01 +0000
1564@@ -17,9 +17,6 @@
1565
1566 """The header-matching chain."""
1567
1568-from __future__ import absolute_import, print_function, unicode_literals
1569-
1570-__metaclass__ = type
1571 __all__ = [
1572 'HeaderMatchChain',
1573 ]
1574@@ -28,13 +25,12 @@
1575 import re
1576 import logging
1577
1578-from zope.interface import implementer
1579-
1580 from mailman.chains.base import Chain, Link
1581 from mailman.config import config
1582 from mailman.core.i18n import _
1583 from mailman.interfaces.chain import LinkAction
1584 from mailman.interfaces.rules import IRule
1585+from zope.interface import implementer
1586
1587
1588 log = logging.getLogger('mailman.error')
1589@@ -122,7 +118,7 @@
1590 """See `IMutableChain`."""
1591 # Remove all dynamically created rules. Use the keys so we can mutate
1592 # the dictionary inside the loop.
1593- for rule_name in config.rules.keys():
1594+ for rule_name in list(config.rules):
1595 if rule_name.startswith('header-match-'):
1596 del config.rules[rule_name]
1597 self._extended_links = []
1598
1599=== modified file 'src/mailman/chains/hold.py'
1600--- src/mailman/chains/hold.py 2014-01-01 14:59:42 +0000
1601+++ src/mailman/chains/hold.py 2015-01-03 05:08:01 +0000
1602@@ -17,9 +17,6 @@
1603
1604 """The terminal 'hold' chain."""
1605
1606-from __future__ import absolute_import, print_function, unicode_literals
1607-
1608-__metaclass__ = type
1609 __all__ = [
1610 'HoldChain',
1611 ]
1612@@ -30,10 +27,6 @@
1613 from email.mime.message import MIMEMessage
1614 from email.mime.text import MIMEText
1615 from email.utils import formatdate, make_msgid
1616-from zope.component import getUtility
1617-from zope.event import notify
1618-from zope.interface import implementer
1619-
1620 from mailman.app.moderator import hold_message
1621 from mailman.app.replybot import can_acknowledge
1622 from mailman.chains.base import TerminalChainBase
1623@@ -47,6 +40,9 @@
1624 from mailman.interfaces.usermanager import IUserManager
1625 from mailman.utilities.i18n import make
1626 from mailman.utilities.string import oneline, wrap
1627+from zope.component import getUtility
1628+from zope.event import notify
1629+from zope.interface import implementer
1630
1631
1632 log = logging.getLogger('mailman.vette')
1633@@ -157,7 +153,7 @@
1634 if original_subject is None:
1635 original_subject = _('(no subject)')
1636 else:
1637- original_subject = oneline(original_subject, charset)
1638+ original_subject = oneline(original_subject, in_unicode=True)
1639 substitutions = dict(
1640 listname = mlist.fqdn_listname,
1641 subject = original_subject,
1642
1643=== modified file 'src/mailman/chains/moderation.py'
1644--- src/mailman/chains/moderation.py 2014-01-01 14:59:42 +0000
1645+++ src/mailman/chains/moderation.py 2015-01-03 05:08:01 +0000
1646@@ -34,21 +34,17 @@
1647 members, while `hold` is the default for nonmembers.
1648 """
1649
1650-from __future__ import absolute_import, print_function, unicode_literals
1651-
1652-__metaclass__ = type
1653 __all__ = [
1654 'ModerationChain',
1655 ]
1656
1657
1658-from zope.interface import implementer
1659-
1660 from mailman.chains.base import Link
1661 from mailman.config import config
1662 from mailman.core.i18n import _
1663 from mailman.interfaces.action import Action
1664 from mailman.interfaces.chain import IChain, LinkAction
1665+from zope.interface import implementer
1666
1667
1668
1669
1670
1671=== modified file 'src/mailman/chains/owner.py'
1672--- src/mailman/chains/owner.py 2014-01-01 14:59:42 +0000
1673+++ src/mailman/chains/owner.py 2015-01-03 05:08:01 +0000
1674@@ -17,9 +17,6 @@
1675
1676 """The standard -owner posting chain."""
1677
1678-from __future__ import absolute_import, print_function, unicode_literals
1679-
1680-__metaclass__ = type
1681 __all__ = [
1682 'BuiltInOwnerChain',
1683 ]
1684@@ -27,12 +24,11 @@
1685
1686 import logging
1687
1688-from zope.event import notify
1689-
1690 from mailman.chains.base import TerminalChainBase
1691 from mailman.config import config
1692 from mailman.core.i18n import _
1693 from mailman.interfaces.chain import AcceptOwnerEvent
1694+from zope.event import notify
1695
1696
1697 log = logging.getLogger('mailman.vette')
1698
1699=== modified file 'src/mailman/chains/reject.py'
1700--- src/mailman/chains/reject.py 2014-04-28 15:23:35 +0000
1701+++ src/mailman/chains/reject.py 2015-01-03 05:08:01 +0000
1702@@ -17,9 +17,6 @@
1703
1704 """The terminal 'reject' chain."""
1705
1706-from __future__ import absolute_import, print_function, unicode_literals
1707-
1708-__metaclass__ = type
1709 __all__ = [
1710 'RejectChain',
1711 ]
1712@@ -27,12 +24,11 @@
1713
1714 import logging
1715
1716-from zope.event import notify
1717-
1718 from mailman.app.bounces import bounce_message
1719 from mailman.chains.base import TerminalChainBase
1720 from mailman.core.i18n import _
1721 from mailman.interfaces.chain import RejectEvent
1722+from zope.event import notify
1723
1724
1725 log = logging.getLogger('mailman.vette')
1726
1727=== modified file 'src/mailman/chains/tests/test_base.py'
1728--- src/mailman/chains/tests/test_base.py 2014-11-11 15:59:21 +0000
1729+++ src/mailman/chains/tests/test_base.py 2015-01-03 05:08:01 +0000
1730@@ -17,9 +17,6 @@
1731
1732 """Test the base chain stuff."""
1733
1734-from __future__ import absolute_import, print_function, unicode_literals
1735-
1736-__metaclass__ = type
1737 __all__ = [
1738 'TestMiscellaneous',
1739 ]
1740
1741=== modified file 'src/mailman/chains/tests/test_headers.py'
1742--- src/mailman/chains/tests/test_headers.py 2014-01-01 14:59:42 +0000
1743+++ src/mailman/chains/tests/test_headers.py 2015-01-03 05:08:01 +0000
1744@@ -17,9 +17,6 @@
1745
1746 """Test the header chain."""
1747
1748-from __future__ import absolute_import, print_function, unicode_literals
1749-
1750-__metaclass__ = type
1751 __all__ = [
1752 'TestHeaderChain',
1753 ]
1754
1755=== modified file 'src/mailman/chains/tests/test_hold.py'
1756--- src/mailman/chains/tests/test_hold.py 2014-04-28 15:23:35 +0000
1757+++ src/mailman/chains/tests/test_hold.py 2015-01-03 05:08:01 +0000
1758@@ -17,9 +17,6 @@
1759
1760 """Additional tests for the hold chain."""
1761
1762-from __future__ import absolute_import, print_function, unicode_literals
1763-
1764-__metaclass__ = type
1765 __all__ = [
1766 'TestAutorespond',
1767 ]
1768@@ -27,14 +24,13 @@
1769
1770 import unittest
1771
1772-from zope.component import getUtility
1773-
1774 from mailman.app.lifecycle import create_list
1775 from mailman.chains.hold import autorespond_to_sender
1776 from mailman.interfaces.autorespond import IAutoResponseSet, Response
1777 from mailman.interfaces.usermanager import IUserManager
1778 from mailman.testing.helpers import configuration, get_queue_messages
1779 from mailman.testing.layers import ConfigLayer
1780+from zope.component import getUtility
1781
1782
1783
1784
1785
1786=== modified file 'src/mailman/chains/tests/test_owner.py'
1787--- src/mailman/chains/tests/test_owner.py 2014-01-01 14:59:42 +0000
1788+++ src/mailman/chains/tests/test_owner.py 2015-01-03 05:08:01 +0000
1789@@ -17,9 +17,6 @@
1790
1791 """Test the owner chain."""
1792
1793-from __future__ import absolute_import, print_function, unicode_literals
1794-
1795-__metaclass__ = type
1796 __all__ = [
1797 'TestOwnerChain',
1798 ]
1799@@ -32,8 +29,7 @@
1800 from mailman.core.chains import process
1801 from mailman.interfaces.chain import AcceptOwnerEvent
1802 from mailman.testing.helpers import (
1803- event_subscribers,
1804- get_queue_messages,
1805+ event_subscribers, get_queue_messages,
1806 specialized_message_from_string as mfs)
1807 from mailman.testing.layers import ConfigLayer
1808
1809
1810=== modified file 'src/mailman/commands/cli_aliases.py'
1811--- src/mailman/commands/cli_aliases.py 2014-01-01 14:59:42 +0000
1812+++ src/mailman/commands/cli_aliases.py 2015-01-03 05:08:01 +0000
1813@@ -17,20 +17,16 @@
1814
1815 """Generate Mailman alias files for your MTA."""
1816
1817-from __future__ import absolute_import, print_function, unicode_literals
1818-
1819-__metaclass__ = type
1820 __all__ = [
1821 'Aliases',
1822 ]
1823
1824
1825-from zope.interface import implementer
1826-
1827 from mailman.config import config
1828 from mailman.core.i18n import _
1829 from mailman.interfaces.command import ICLISubCommand
1830 from mailman.utilities.modules import call_name
1831+from zope.interface import implementer
1832
1833
1834
1835
1836
1837=== modified file 'src/mailman/commands/cli_conf.py'
1838--- src/mailman/commands/cli_conf.py 2014-11-08 00:31:21 +0000
1839+++ src/mailman/commands/cli_conf.py 2015-01-03 05:08:01 +0000
1840@@ -17,9 +17,6 @@
1841
1842 """Print the mailman configuration."""
1843
1844-from __future__ import absolute_import, print_function, unicode_literals
1845-
1846-__metaclass__ = type
1847 __all__ = [
1848 'Conf'
1849 ]
1850@@ -29,11 +26,10 @@
1851
1852 from contextlib import closing
1853 from lazr.config._config import Section
1854-from zope.interface import implementer
1855-
1856 from mailman.config import config
1857 from mailman.core.i18n import _
1858 from mailman.interfaces.command import ICLISubCommand
1859+from zope.interface import implementer
1860
1861
1862
1863
1864
1865=== modified file 'src/mailman/commands/cli_control.py'
1866--- src/mailman/commands/cli_control.py 2014-01-01 14:59:42 +0000
1867+++ src/mailman/commands/cli_control.py 2015-01-03 05:08:01 +0000
1868@@ -15,11 +15,8 @@
1869 # You should have received a copy of the GNU General Public License along with
1870 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
1871
1872-"""Module stuff."""
1873-
1874-from __future__ import absolute_import, print_function, unicode_literals
1875-
1876-__metaclass__ = type
1877+"""Start/stop/reopen/restart commands."""
1878+
1879 __all__ = [
1880 'Reopen',
1881 'Restart',
1882@@ -34,12 +31,11 @@
1883 import signal
1884 import logging
1885
1886-from zope.interface import implementer
1887-
1888 from mailman.bin.master import WatcherState, master_state
1889 from mailman.config import config
1890 from mailman.core.i18n import _
1891 from mailman.interfaces.command import ICLISubCommand
1892+from zope.interface import implementer
1893
1894
1895 qlog = logging.getLogger('mailman.runner')
1896@@ -124,8 +120,8 @@
1897 # subprocesses to calculate their path to the $VAR_DIR. Before we
1898 # chdir() though, calculate the absolute path to the configuration
1899 # file.
1900- config_path = (os.path.abspath(args.config)
1901- if args.config else None)
1902+ config_path = (config.filename if args.config is None
1903+ else os.path.abspath(args.config))
1904 os.environ['MAILMAN_VAR_DIR'] = config.VAR_DIR
1905 os.chdir(config.VAR_DIR)
1906 # Exec the master watcher.
1907@@ -135,8 +131,9 @@
1908 ]
1909 if args.force:
1910 execl_args.append('--force')
1911- if config_path:
1912- execl_args.extend(['-C', config_path])
1913+ # Always pass the config file path to the master projects, so there's
1914+ # no confusion about which cfg is being used.
1915+ execl_args.extend(['-C', config_path])
1916 qlog.debug('starting: %s', execl_args)
1917 os.execl(*execl_args)
1918 # We should never get here.
1919
1920=== modified file 'src/mailman/commands/cli_help.py'
1921--- src/mailman/commands/cli_help.py 2014-01-01 14:59:42 +0000
1922+++ src/mailman/commands/cli_help.py 2015-01-03 05:08:01 +0000
1923@@ -17,18 +17,14 @@
1924
1925 """The 'help' subcommand."""
1926
1927-from __future__ import absolute_import, print_function, unicode_literals
1928-
1929-__metaclass__ = type
1930 __all__ = [
1931 'Help',
1932 ]
1933
1934
1935+from mailman.interfaces.command import ICLISubCommand
1936 from zope.interface import implementer
1937
1938-from mailman.interfaces.command import ICLISubCommand
1939-
1940
1941
1942
1943 @implementer(ICLISubCommand)
1944
1945=== modified file 'src/mailman/commands/cli_import.py'
1946--- src/mailman/commands/cli_import.py 2014-04-14 16:14:13 +0000
1947+++ src/mailman/commands/cli_import.py 2015-01-03 05:08:01 +0000
1948@@ -17,25 +17,21 @@
1949
1950 """Importing list data into Mailman 3."""
1951
1952-from __future__ import absolute_import, print_function, unicode_literals
1953-
1954-__metaclass__ = type
1955 __all__ = [
1956 'Import21',
1957 ]
1958
1959
1960 import sys
1961-import cPickle
1962-
1963-from zope.component import getUtility
1964-from zope.interface import implementer
1965
1966 from mailman.core.i18n import _
1967 from mailman.database.transaction import transactional
1968 from mailman.interfaces.command import ICLISubCommand
1969 from mailman.interfaces.listmanager import IListManager
1970 from mailman.utilities.importer import import_config_pck, Import21Error
1971+from six.moves import cPickle
1972+from zope.component import getUtility
1973+from zope.interface import implementer
1974
1975
1976
1977
1978@@ -78,7 +74,7 @@
1979 assert len(args.pickle_file) == 1, (
1980 'Unexpected positional arguments: %s' % args.pickle_file)
1981 filename = args.pickle_file[0]
1982- with open(filename) as fp:
1983+ with open(filename, 'rb') as fp:
1984 while True:
1985 try:
1986 config_dict = cPickle.load(fp)
1987
1988=== modified file 'src/mailman/commands/cli_info.py'
1989--- src/mailman/commands/cli_info.py 2014-01-01 14:59:42 +0000
1990+++ src/mailman/commands/cli_info.py 2015-01-03 05:08:01 +0000
1991@@ -17,9 +17,6 @@
1992
1993 """Information about this Mailman instance."""
1994
1995-from __future__ import absolute_import, print_function, unicode_literals
1996-
1997-__metaclass__ = type
1998 __all__ = [
1999 'Info'
2000 ]
2001@@ -28,13 +25,12 @@
2002 import sys
2003
2004 from lazr.config import as_boolean
2005-from zope.interface import implementer
2006-
2007 from mailman.config import config
2008 from mailman.core.i18n import _
2009 from mailman.interfaces.command import ICLISubCommand
2010 from mailman.rest.helpers import path_to
2011 from mailman.version import MAILMAN_VERSION_FULL
2012+from zope.interface import implementer
2013
2014
2015
2016
2017
2018=== modified file 'src/mailman/commands/cli_inject.py'
2019--- src/mailman/commands/cli_inject.py 2014-01-01 14:59:42 +0000
2020+++ src/mailman/commands/cli_inject.py 2015-01-03 05:08:01 +0000
2021@@ -17,9 +17,6 @@
2022
2023 """bin/mailman inject"""
2024
2025-from __future__ import absolute_import, print_function, unicode_literals
2026-
2027-__metaclass__ = type
2028 __all__ = [
2029 'Inject',
2030 ]
2031@@ -27,14 +24,13 @@
2032
2033 import sys
2034
2035-from zope.component import getUtility
2036-from zope.interface import implementer
2037-
2038 from mailman.app.inject import inject_text
2039 from mailman.config import config
2040 from mailman.core.i18n import _
2041 from mailman.interfaces.command import ICLISubCommand
2042 from mailman.interfaces.listmanager import IListManager
2043+from zope.component import getUtility
2044+from zope.interface import implementer
2045
2046
2047
2048
2049@@ -49,7 +45,7 @@
2050 self.parser = parser
2051 command_parser.add_argument(
2052 '-q', '--queue',
2053- type=unicode, help=_("""
2054+ help=_("""
2055 The name of the queue to inject the message to. QUEUE must be one
2056 of the directories inside the qfiles directory. If omitted, the
2057 incoming queue is used."""))
2058@@ -59,7 +55,7 @@
2059 help=_('Show a list of all available queue names and exit.'))
2060 command_parser.add_argument(
2061 '-f', '--filename',
2062- type=unicode, help=_("""
2063+ help=_("""
2064 Name of file containing the message to inject. If not given, or
2065 '-' (without the quotes) standard input is used."""))
2066 # Required positional argument.
2067
2068=== modified file 'src/mailman/commands/cli_lists.py'
2069--- src/mailman/commands/cli_lists.py 2014-11-20 01:29:44 +0000
2070+++ src/mailman/commands/cli_lists.py 2015-01-03 05:08:01 +0000
2071@@ -17,9 +17,6 @@
2072
2073 """The 'lists' subcommand."""
2074
2075-from __future__ import absolute_import, print_function, unicode_literals
2076-
2077-__metaclass__ = type
2078 __all__ = [
2079 'Create',
2080 'Lists',
2081@@ -27,9 +24,6 @@
2082 ]
2083
2084
2085-from zope.component import getUtility
2086-from zope.interface import implementer
2087-
2088 from mailman.app.lifecycle import create_list, remove_list
2089 from mailman.core.constants import system_preferences
2090 from mailman.core.i18n import _
2091@@ -43,6 +37,8 @@
2092 from mailman.interfaces.languages import ILanguageManager
2093 from mailman.interfaces.listmanager import IListManager, ListAlreadyExistsError
2094 from mailman.utilities.i18n import make
2095+from zope.component import getUtility
2096+from zope.interface import implementer
2097
2098
2099 COMMASPACE = ', '
2100@@ -135,12 +131,12 @@
2101 self.parser = parser
2102 command_parser.add_argument(
2103 '--language',
2104- type=unicode, metavar='CODE', help=_("""\
2105+ metavar='CODE', help=_("""\
2106 Set the list's preferred language to CODE, which must be a
2107 registered two letter language code."""))
2108 command_parser.add_argument(
2109 '-o', '--owner',
2110- type=unicode, action='append', default=[],
2111+ action='append', default=[],
2112 dest='owners', metavar='OWNER', help=_("""\
2113 Specify a listowner email address. If the address is not
2114 currently registered with Mailman, the address is registered and
2115
2116=== modified file 'src/mailman/commands/cli_members.py'
2117--- src/mailman/commands/cli_members.py 2014-01-01 14:59:42 +0000
2118+++ src/mailman/commands/cli_members.py 2015-01-03 05:08:01 +0000
2119@@ -17,9 +17,6 @@
2120
2121 """The 'members' subcommand."""
2122
2123-from __future__ import absolute_import, print_function, unicode_literals
2124-
2125-__metaclass__ = type
2126 __all__ = [
2127 'Members',
2128 ]
2129@@ -29,11 +26,6 @@
2130 import codecs
2131
2132 from email.utils import formataddr, parseaddr
2133-from operator import attrgetter
2134-from passlib.utils import generate_password as generate
2135-from zope.component import getUtility
2136-from zope.interface import implementer
2137-
2138 from mailman.app.membership import add_member
2139 from mailman.config import config
2140 from mailman.core.i18n import _
2141@@ -42,6 +34,10 @@
2142 from mailman.interfaces.listmanager import IListManager
2143 from mailman.interfaces.member import (
2144 AlreadySubscribedError, DeliveryMode, DeliveryStatus)
2145+from operator import attrgetter
2146+from passlib.utils import generate_password as generate
2147+from zope.component import getUtility
2148+from zope.interface import implementer
2149
2150
2151
2152
2153@@ -197,8 +193,6 @@
2154 continue
2155 # Parse the line and ensure that the values are unicodes.
2156 display_name, email = parseaddr(line)
2157- display_name = display_name.decode(fp.encoding)
2158- email = email.decode(fp.encoding)
2159 # Give the user a default, user-friendly password.
2160 password = generate(int(config.passwords.password_length))
2161 try:
2162
2163=== modified file 'src/mailman/commands/cli_qfile.py'
2164--- src/mailman/commands/cli_qfile.py 2014-01-01 14:59:42 +0000
2165+++ src/mailman/commands/cli_qfile.py 2015-01-03 05:08:01 +0000
2166@@ -17,24 +17,22 @@
2167
2168 """Getting information out of a qfile."""
2169
2170-from __future__ import absolute_import, print_function, unicode_literals
2171-
2172-__metaclass__ = type
2173 __all__ = [
2174 'QFile',
2175 ]
2176
2177
2178-import cPickle
2179-
2180-from pprint import PrettyPrinter
2181-from zope.interface import implementer
2182+import six
2183
2184 from mailman.core.i18n import _
2185 from mailman.interfaces.command import ICLISubCommand
2186 from mailman.utilities.interact import interact
2187-
2188-
2189+from pprint import PrettyPrinter
2190+from six.moves import cPickle
2191+from zope.interface import implementer
2192+
2193+
2194+# This is deliberately called 'm' for use with --interactive.
2195 m = []
2196
2197
2198@@ -71,7 +69,7 @@
2199 """See `ICLISubCommand`."""
2200 printer = PrettyPrinter(indent=4)
2201 assert len(args.qfile) == 1, 'Wrong number of positional arguments'
2202- with open(args.qfile[0]) as fp:
2203+ with open(args.qfile[0], 'rb') as fp:
2204 while True:
2205 try:
2206 m.append(cPickle.load(fp))
2207@@ -82,7 +80,7 @@
2208 for i, obj in enumerate(m):
2209 count = i + 1
2210 print(_('<----- start object $count ----->'))
2211- if isinstance(obj, basestring):
2212+ if isinstance(obj, six.string_types):
2213 print(obj)
2214 else:
2215 printer.pprint(obj)
2216
2217=== modified file 'src/mailman/commands/cli_status.py'
2218--- src/mailman/commands/cli_status.py 2014-01-01 14:59:42 +0000
2219+++ src/mailman/commands/cli_status.py 2015-01-03 05:08:01 +0000
2220@@ -17,9 +17,6 @@
2221
2222 """bin/mailman status."""
2223
2224-from __future__ import absolute_import, print_function, unicode_literals
2225-
2226-__metaclass__ = type
2227 __all__ = [
2228 'Status',
2229 ]
2230@@ -27,11 +24,10 @@
2231
2232 import socket
2233
2234-from zope.interface import implementer
2235-
2236 from mailman.bin.master import WatcherState, master_state
2237 from mailman.core.i18n import _
2238 from mailman.interfaces.command import ICLISubCommand
2239+from zope.interface import implementer
2240
2241
2242
2243
2244
2245=== modified file 'src/mailman/commands/cli_unshunt.py'
2246--- src/mailman/commands/cli_unshunt.py 2014-01-01 14:59:42 +0000
2247+++ src/mailman/commands/cli_unshunt.py 2015-01-03 05:08:01 +0000
2248@@ -17,9 +17,6 @@
2249
2250 """The 'unshunt' command."""
2251
2252-from __future__ import absolute_import, print_function, unicode_literals
2253-
2254-__metaclass__ = type
2255 __all__ = [
2256 'Unshunt',
2257 ]
2258@@ -27,11 +24,10 @@
2259
2260 import sys
2261
2262-from zope.interface import implementer
2263-
2264 from mailman.config import config
2265 from mailman.core.i18n import _
2266 from mailman.interfaces.command import ICLISubCommand
2267+from zope.interface import implementer
2268
2269
2270
2271
2272@@ -62,7 +58,7 @@
2273 which_queue = msgdata.get('whichq', 'in')
2274 if not args.discard:
2275 config.switchboards[which_queue].enqueue(msg, msgdata)
2276- except Exception as error:
2277+ except Exception:
2278 print(_('Cannot unshunt message $filebase, skipping:\n$error'),
2279 file=sys.stderr)
2280 else:
2281
2282=== modified file 'src/mailman/commands/cli_version.py'
2283--- src/mailman/commands/cli_version.py 2014-01-01 14:59:42 +0000
2284+++ src/mailman/commands/cli_version.py 2015-01-03 05:08:01 +0000
2285@@ -17,18 +17,14 @@
2286
2287 """The Mailman version."""
2288
2289-from __future__ import absolute_import, print_function, unicode_literals
2290-
2291-__metaclass__ = type
2292 __all__ = [
2293 'Version',
2294 ]
2295
2296
2297-from zope.interface import implementer
2298-
2299 from mailman.interfaces.command import ICLISubCommand
2300 from mailman.version import MAILMAN_VERSION_FULL
2301+from zope.interface import implementer
2302
2303
2304
2305
2306
2307=== modified file 'src/mailman/commands/cli_withlist.py'
2308--- src/mailman/commands/cli_withlist.py 2014-01-01 14:59:42 +0000
2309+++ src/mailman/commands/cli_withlist.py 2015-01-03 05:08:01 +0000
2310@@ -17,9 +17,6 @@
2311
2312 """bin/mailman withlist"""
2313
2314-from __future__ import absolute_import, print_function, unicode_literals
2315-
2316-__metaclass__ = type
2317 __all__ = [
2318 'Shell',
2319 'Withlist',
2320@@ -30,15 +27,15 @@
2321 import sys
2322
2323 from lazr.config import as_boolean
2324-from zope.component import getUtility
2325-from zope.interface import implementer
2326-
2327 from mailman.config import config
2328 from mailman.core.i18n import _
2329 from mailman.interfaces.command import ICLISubCommand
2330 from mailman.interfaces.listmanager import IListManager
2331 from mailman.utilities.interact import DEFAULT_BANNER, interact
2332 from mailman.utilities.modules import call_name
2333+from zope.component import getUtility
2334+from zope.interface import implementer
2335+
2336
2337 # Global holding onto the open mailing list.
2338 m = None
2339
2340=== modified file 'src/mailman/commands/docs/echo.rst'
2341--- src/mailman/commands/docs/echo.rst 2014-04-28 15:23:35 +0000
2342+++ src/mailman/commands/docs/echo.rst 2015-01-03 05:08:01 +0000
2343@@ -24,7 +24,7 @@
2344 >>> from mailman.email.message import Message
2345 >>> print(command.process(mlist, Message(), {}, ('foo', 'bar'), results))
2346 ContinueProcessing.yes
2347- >>> print(unicode(results))
2348+ >>> print(str(results))
2349 The results of your email command are provided below.
2350 <BLANKLINE>
2351 echo foo bar
2352
2353=== modified file 'src/mailman/commands/docs/help.rst'
2354--- src/mailman/commands/docs/help.rst 2014-04-28 15:23:35 +0000
2355+++ src/mailman/commands/docs/help.rst 2015-01-03 05:08:01 +0000
2356@@ -25,7 +25,7 @@
2357 >>> from mailman.email.message import Message
2358 >>> print(help.process(mlist, Message(), {}, (), results))
2359 ContinueProcessing.yes
2360- >>> print(unicode(results))
2361+ >>> print(results)
2362 The results of your email command are provided below.
2363 <BLANKLINE>
2364 confirm - Confirm a subscription request.
2365@@ -44,19 +44,19 @@
2366 >>> results = Results()
2367 >>> print(help.process(mlist, Message(), {}, ('help',), results))
2368 ContinueProcessing.yes
2369- >>> print(unicode(results))
2370+ >>> print(results)
2371 The results of your email command are provided below.
2372 <BLANKLINE>
2373 help [command]
2374 Get help about available email commands.
2375 <BLANKLINE>
2376-
2377+
2378 Some commands have even more detailed help.
2379
2380 >>> results = Results()
2381 >>> print(help.process(mlist, Message(), {}, ('join',), results))
2382 ContinueProcessing.yes
2383- >>> print(unicode(results))
2384+ >>> print(results)
2385 The results of your email command are provided below.
2386 <BLANKLINE>
2387 join [digest=<no|mime|plain>]
2388
2389=== modified file 'src/mailman/commands/docs/info.rst'
2390--- src/mailman/commands/docs/info.rst 2014-04-28 15:23:35 +0000
2391+++ src/mailman/commands/docs/info.rst 2015-01-03 05:08:01 +0000
2392@@ -62,20 +62,21 @@
2393 Python ...
2394 ...
2395 File system paths:
2396- ARCHIVE_DIR = /var/lib/mailman/archives
2397- BIN_DIR = /sbin
2398- DATA_DIR = /var/lib/mailman/data
2399- ETC_DIR = /etc
2400- EXT_DIR = /etc/mailman.d
2401- LIST_DATA_DIR = /var/lib/mailman/lists
2402- LOCK_DIR = /var/lock/mailman
2403- LOCK_FILE = /var/lock/mailman/master.lck
2404- LOG_DIR = /var/log/mailman
2405- MESSAGES_DIR = /var/lib/mailman/messages
2406- PID_FILE = /var/run/mailman/master.pid
2407- QUEUE_DIR = /var/spool/mailman
2408- TEMPLATE_DIR = .../mailman/templates
2409- VAR_DIR = /var/lib/mailman
2410+ ARCHIVE_DIR = /var/lib/mailman/archives
2411+ BIN_DIR = /sbin
2412+ CFG_FILE = .../test.cfg
2413+ DATA_DIR = /var/lib/mailman/data
2414+ ETC_DIR = /etc
2415+ EXT_DIR = /etc/mailman.d
2416+ LIST_DATA_DIR = /var/lib/mailman/lists
2417+ LOCK_DIR = /var/lock/mailman
2418+ LOCK_FILE = /var/lock/mailman/master.lck
2419+ LOG_DIR = /var/log/mailman
2420+ MESSAGES_DIR = /var/lib/mailman/messages
2421+ PID_FILE = /var/run/mailman/master.pid
2422+ QUEUE_DIR = /var/spool/mailman
2423+ TEMPLATE_DIR = .../mailman/templates
2424+ VAR_DIR = /var/lib/mailman
2425
2426
2427 .. _`Filesystem Hierarchy Standard`: http://www.pathname.com/fhs/
2428
2429=== modified file 'src/mailman/commands/docs/inject.rst'
2430--- src/mailman/commands/docs/inject.rst 2014-04-28 15:23:35 +0000
2431+++ src/mailman/commands/docs/inject.rst 2015-01-03 05:08:01 +0000
2432@@ -94,7 +94,7 @@
2433
2434 >>> dump_msgdata(items[0].msgdata)
2435 _parsemsg : False
2436- listname : test@example.com
2437+ listid : test.example.com
2438 original_size: 203
2439 version : 3
2440
2441@@ -122,7 +122,7 @@
2442
2443 >>> dump_msgdata(items[0].msgdata)
2444 _parsemsg : False
2445- listname : test@example.com
2446+ listid : test.example.com
2447 original_size: 203
2448 version : 3
2449
2450@@ -133,7 +133,7 @@
2451 The message text can also be provided on standard input.
2452 ::
2453
2454- >>> from StringIO import StringIO
2455+ >>> from six import StringIO
2456
2457 # Remember: we've got unicode literals turned on.
2458 >>> standard_in = StringIO(str("""\
2459@@ -167,7 +167,7 @@
2460
2461 >>> dump_msgdata(items[0].msgdata)
2462 _parsemsg : False
2463- listname : test@example.com
2464+ listid : test.example.com
2465 original_size: 211
2466 version : 3
2467
2468@@ -195,7 +195,7 @@
2469 _parsemsg : False
2470 bar : two
2471 foo : one
2472- listname : test@example.com
2473+ listid : test.example.com
2474 original_size: 203
2475 version : 3
2476
2477
2478=== modified file 'src/mailman/commands/docs/members.rst'
2479--- src/mailman/commands/docs/members.rst 2014-04-28 15:23:35 +0000
2480+++ src/mailman/commands/docs/members.rst 2015-01-03 05:08:01 +0000
2481@@ -229,15 +229,14 @@
2482 taken from standard input.
2483 ::
2484
2485- >>> from StringIO import StringIO
2486+ >>> from six import StringIO
2487 >>> fp = StringIO()
2488- >>> fp.encoding = 'us-ascii'
2489 >>> for address in ('dperson@example.com',
2490 ... 'Elly Person <eperson@example.com>',
2491 ... 'fperson@example.com (Fred Person)',
2492 ... ):
2493 ... print(address, file=fp)
2494- >>> fp.seek(0)
2495+ >>> filepos = fp.seek(0)
2496 >>> import sys
2497 >>> sys.stdin = fp
2498
2499
2500=== modified file 'src/mailman/commands/docs/membership.rst'
2501--- src/mailman/commands/docs/membership.rst 2014-04-28 15:23:35 +0000
2502+++ src/mailman/commands/docs/membership.rst 2015-01-03 05:08:01 +0000
2503@@ -45,7 +45,7 @@
2504 >>> from mailman.email.message import Message
2505 >>> print(join.process(mlist, Message(), {}, (), results))
2506 ContinueProcessing.no
2507- >>> print(unicode(results))
2508+ >>> print(results)
2509 The results of your email command are provided below.
2510 <BLANKLINE>
2511 join: No valid address found to subscribe
2512@@ -60,7 +60,7 @@
2513 >>> results = Results()
2514 >>> print(subscribe.process(mlist, Message(), {}, (), results))
2515 ContinueProcessing.no
2516- >>> print(unicode(results))
2517+ >>> print(results)
2518 The results of your email command are provided below.
2519 <BLANKLINE>
2520 subscribe: No valid address found to subscribe
2521@@ -79,7 +79,7 @@
2522 >>> results = Results()
2523 >>> print(join.process(mlist, msg, {}, (), results))
2524 ContinueProcessing.yes
2525- >>> print(unicode(results))
2526+ >>> print(results)
2527 The results of your email command are provided below.
2528 <BLANKLINE>
2529 Confirmation email sent to Anne Person <anne@example.com>
2530@@ -150,7 +150,7 @@
2531 >>> results = Results()
2532 >>> print(confirm.process(mlist, msg, {}, (token,), results))
2533 ContinueProcessing.yes
2534- >>> print(unicode(results))
2535+ >>> print(results)
2536 The results of your email command are provided below.
2537 <BLANKLINE>
2538 Confirmed
2539@@ -208,7 +208,7 @@
2540 >>> results = Results()
2541 >>> print(confirm.process(mlist_2, msg, {}, (token,), results))
2542 ContinueProcessing.yes
2543- >>> print(unicode(results))
2544+ >>> print(results)
2545 The results of your email command are provided below.
2546 <BLANKLINE>
2547 Confirmed
2548@@ -241,7 +241,7 @@
2549 >>> results = Results()
2550 >>> print(leave.process(mlist_2, msg, {}, (), results))
2551 ContinueProcessing.yes
2552- >>> print(unicode(results))
2553+ >>> print(results)
2554 The results of your email command are provided below.
2555 <BLANKLINE>
2556 Anne Person <anne@example.com> left baker@example.com
2557@@ -278,7 +278,7 @@
2558 >>> print(leave.process(mlist, msg, {}, (), results))
2559 ContinueProcessing.no
2560
2561- >>> print(unicode(results))
2562+ >>> print(results)
2563 The results of your email command are provided below.
2564 <BLANKLINE>
2565 Invalid or unverified email address: anne.person@example.org
2566@@ -299,7 +299,7 @@
2567 >>> print(leave.process(mlist, msg, {}, (), results))
2568 ContinueProcessing.yes
2569
2570- >>> print(unicode(results))
2571+ >>> print(results)
2572 The results of your email command are provided below.
2573 <BLANKLINE>
2574 Anne Person <anne.person@example.org> left alpha@example.com
2575@@ -354,7 +354,7 @@
2576 >>> print(confirm.process(mlist, msg, {}, (token,), results))
2577 ContinueProcessing.yes
2578
2579- >>> print(unicode(results))
2580+ >>> print(results)
2581 The results of your email command are provided below.
2582 <BLANKLINE>
2583 Confirmed
2584
2585=== modified file 'src/mailman/commands/docs/qfile.rst'
2586--- src/mailman/commands/docs/qfile.rst 2012-03-26 12:04:00 +0000
2587+++ src/mailman/commands/docs/qfile.rst 2015-01-03 05:08:01 +0000
2588@@ -47,7 +47,6 @@
2589 >>> command.process(FakeArgs)
2590 [----- start pickle -----]
2591 <----- start object 1 ----->
2592- From nobody ...
2593 From: aperson@example.com
2594 To: test@example.com
2595 Subject: Uh oh
2596@@ -55,11 +54,7 @@
2597 I borkeded Mailman.
2598 <BLANKLINE>
2599 <----- start object 2 ----->
2600- { u'_parsemsg': False,
2601- 'bad': u'yes',
2602- 'bar': u'baz',
2603- 'foo': 7,
2604- u'version': 3}
2605+ {'_parsemsg': False, 'bad': 'yes', 'bar': 'baz', 'foo': 7, 'version': 3}
2606 [----- end pickle -----]
2607
2608 Maybe we don't want to print the contents of the file though, in case we want
2609
2610=== modified file 'src/mailman/commands/docs/withlist.rst'
2611--- src/mailman/commands/docs/withlist.rst 2014-10-31 03:12:00 +0000
2612+++ src/mailman/commands/docs/withlist.rst 2015-01-03 05:08:01 +0000
2613@@ -52,10 +52,10 @@
2614 >>> with open(os.path.join(config.VAR_DIR, 'showme.py'), 'w') as fp:
2615 ... print("""\
2616 ... def showme(mailing_list):
2617- ... print "The list's name is", mailing_list.fqdn_listname
2618+ ... print("The list's name is", mailing_list.fqdn_listname)
2619 ...
2620 ... def displayname(mailing_list):
2621- ... print "The list's display name is", mailing_list.display_name
2622+ ... print("The list's display name is", mailing_list.display_name)
2623 ... """, file=fp)
2624
2625 If the name of the function is the same as the module, then you only need to
2626
2627=== modified file 'src/mailman/commands/eml_confirm.py'
2628--- src/mailman/commands/eml_confirm.py 2014-01-01 14:59:42 +0000
2629+++ src/mailman/commands/eml_confirm.py 2015-01-03 05:08:01 +0000
2630@@ -15,22 +15,18 @@
2631 # You should have received a copy of the GNU General Public License along with
2632 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
2633
2634-"""Module stuff."""
2635-
2636-from __future__ import absolute_import, print_function, unicode_literals
2637-
2638-__metaclass__ = type
2639+"""The 'confirm' email command."""
2640+
2641 __all__ = [
2642 'Confirm',
2643 ]
2644
2645
2646-from zope.component import getUtility
2647-from zope.interface import implementer
2648-
2649 from mailman.core.i18n import _
2650 from mailman.interfaces.command import ContinueProcessing, IEmailCommand
2651 from mailman.interfaces.registrar import IRegistrar
2652+from zope.component import getUtility
2653+from zope.interface import implementer
2654
2655
2656
2657
2658
2659=== modified file 'src/mailman/commands/eml_echo.py'
2660--- src/mailman/commands/eml_echo.py 2014-01-01 14:59:42 +0000
2661+++ src/mailman/commands/eml_echo.py 2015-01-03 05:08:01 +0000
2662@@ -17,18 +17,14 @@
2663
2664 """The email command 'echo'."""
2665
2666-from __future__ import absolute_import, print_function, unicode_literals
2667-
2668-__metaclass__ = type
2669 __all__ = [
2670 'Echo',
2671 ]
2672
2673
2674-from zope.interface import implementer
2675-
2676 from mailman.core.i18n import _
2677 from mailman.interfaces.command import ContinueProcessing, IEmailCommand
2678+from zope.interface import implementer
2679
2680
2681 SPACE = ' '
2682
2683=== modified file 'src/mailman/commands/eml_end.py'
2684--- src/mailman/commands/eml_end.py 2014-01-01 14:59:42 +0000
2685+++ src/mailman/commands/eml_end.py 2015-01-03 05:08:01 +0000
2686@@ -17,19 +17,15 @@
2687
2688 """The email commands 'end' and 'stop'."""
2689
2690-from __future__ import absolute_import, print_function, unicode_literals
2691-
2692-__metaclass__ = type
2693 __all__ = [
2694 'End',
2695 'Stop',
2696 ]
2697
2698
2699-from zope.interface import implementer
2700-
2701 from mailman.core.i18n import _
2702 from mailman.interfaces.command import ContinueProcessing, IEmailCommand
2703+from zope.interface import implementer
2704
2705
2706
2707
2708
2709=== modified file 'src/mailman/commands/eml_help.py'
2710--- src/mailman/commands/eml_help.py 2014-01-01 14:59:42 +0000
2711+++ src/mailman/commands/eml_help.py 2015-01-03 05:08:01 +0000
2712@@ -17,20 +17,16 @@
2713
2714 """The email command 'help'."""
2715
2716-from __future__ import absolute_import, print_function, unicode_literals
2717-
2718-__metaclass__ = type
2719 __all__ = [
2720 'Help',
2721 ]
2722
2723
2724-from zope.interface import implementer
2725-
2726 from mailman.config import config
2727 from mailman.core.i18n import _
2728 from mailman.interfaces.command import ContinueProcessing, IEmailCommand
2729 from mailman.utilities.string import wrap
2730+from zope.interface import implementer
2731
2732
2733 SPACE = ' '
2734
2735=== modified file 'src/mailman/commands/eml_membership.py'
2736--- src/mailman/commands/eml_membership.py 2014-12-09 01:38:26 +0000
2737+++ src/mailman/commands/eml_membership.py 2015-01-03 05:08:01 +0000
2738@@ -17,9 +17,6 @@
2739
2740 """The email commands 'join' and 'subscribe'."""
2741
2742-from __future__ import absolute_import, print_function, unicode_literals
2743-
2744-__metaclass__ = type
2745 __all__ = [
2746 'Join',
2747 'Subscribe',
2748@@ -29,15 +26,14 @@
2749
2750
2751 from email.utils import formataddr, parseaddr
2752-from zope.component import getUtility
2753-from zope.interface import implementer
2754-
2755 from mailman.core.i18n import _
2756 from mailman.interfaces.command import ContinueProcessing, IEmailCommand
2757 from mailman.interfaces.member import DeliveryMode, MemberRole
2758 from mailman.interfaces.registrar import IRegistrar
2759 from mailman.interfaces.subscriptions import ISubscriptionService
2760 from mailman.interfaces.usermanager import IUserManager
2761+from zope.component import getUtility
2762+from zope.interface import implementer
2763
2764
2765
2766
2767@@ -182,6 +178,7 @@
2768 return ContinueProcessing.yes
2769
2770
2771+
2772
2773 class Unsubscribe(Leave):
2774 """The email 'unsubscribe' command (an alias for 'leave')."""
2775
2776
2777=== modified file 'src/mailman/commands/tests/test_conf.py'
2778--- src/mailman/commands/tests/test_conf.py 2014-01-01 14:59:42 +0000
2779+++ src/mailman/commands/tests/test_conf.py 2015-01-03 05:08:01 +0000
2780@@ -17,9 +17,6 @@
2781
2782 """Test the conf subcommand."""
2783
2784-from __future__ import absolute_import, print_function, unicode_literals
2785-
2786-__metaclass__ = type
2787 __all__ = [
2788 'TestConf',
2789 ]
2790@@ -31,9 +28,9 @@
2791 import tempfile
2792 import unittest
2793
2794-from StringIO import StringIO
2795 from mailman.commands.cli_conf import Conf
2796 from mailman.testing.layers import ConfigLayer
2797+from six import StringIO
2798
2799
2800
2801
2802
2803=== modified file 'src/mailman/commands/tests/test_confirm.py'
2804--- src/mailman/commands/tests/test_confirm.py 2014-01-07 03:43:59 +0000
2805+++ src/mailman/commands/tests/test_confirm.py 2015-01-03 05:08:01 +0000
2806@@ -17,9 +17,6 @@
2807
2808 """Test the `confirm` command."""
2809
2810-from __future__ import absolute_import, print_function, unicode_literals
2811-
2812-__metaclass__ = type
2813 __all__ = [
2814 'TestConfirm',
2815 ]
2816@@ -27,8 +24,6 @@
2817
2818 import unittest
2819
2820-from zope.component import getUtility
2821-
2822 from mailman.app.lifecycle import create_list
2823 from mailman.commands.eml_confirm import Confirm
2824 from mailman.email.message import Message
2825@@ -37,6 +32,7 @@
2826 from mailman.runners.command import Results
2827 from mailman.testing.helpers import get_queue_messages, reset_the_world
2828 from mailman.testing.layers import ConfigLayer
2829+from zope.component import getUtility
2830
2831
2832
2833
2834
2835=== modified file 'src/mailman/commands/tests/test_control.py'
2836--- src/mailman/commands/tests/test_control.py 2014-04-28 15:23:35 +0000
2837+++ src/mailman/commands/tests/test_control.py 2015-01-03 05:08:01 +0000
2838@@ -17,9 +17,6 @@
2839
2840 """Test some additional corner cases for starting/stopping."""
2841
2842-from __future__ import absolute_import, print_function, unicode_literals
2843-
2844-__metaclass__ = type
2845 __all__ = [
2846 'TestStart',
2847 'find_master',
2848@@ -37,11 +34,11 @@
2849 import unittest
2850
2851 from datetime import timedelta, datetime
2852-
2853 from mailman.commands.cli_control import Start, kill_watcher
2854 from mailman.config import config
2855 from mailman.testing.layers import ConfigLayer
2856
2857+
2858 SEP = '|'
2859
2860
2861
2862=== modified file 'src/mailman/commands/tests/test_create.py'
2863--- src/mailman/commands/tests/test_create.py 2014-04-28 15:23:35 +0000
2864+++ src/mailman/commands/tests/test_create.py 2015-01-03 05:08:01 +0000
2865@@ -17,9 +17,6 @@
2866
2867 """Test `bin/mailman create`."""
2868
2869-from __future__ import absolute_import, print_function, unicode_literals
2870-
2871-__metaclass__ = type
2872 __all__ = [
2873 'TestCreate',
2874 ]
2875
2876=== modified file 'src/mailman/commands/tests/test_help.py'
2877--- src/mailman/commands/tests/test_help.py 2014-01-01 14:59:42 +0000
2878+++ src/mailman/commands/tests/test_help.py 2015-01-03 05:08:01 +0000
2879@@ -17,10 +17,8 @@
2880
2881 """Additional tests for the `help` email command."""
2882
2883-from __future__ import absolute_import, print_function, unicode_literals
2884-
2885-__metaclass__ = type
2886 __all__ = [
2887+ 'TestHelp',
2888 ]
2889
2890
2891@@ -47,11 +45,11 @@
2892 def test_too_many_arguments(self):
2893 # Error message when too many help arguments are given.
2894 results = Results()
2895- status = self._help.process(self._mlist, Message(), {},
2896+ status = self._help.process(self._mlist, Message(), {},
2897 ('more', 'than', 'one'),
2898 results)
2899 self.assertEqual(status, ContinueProcessing.no)
2900- self.assertEqual(unicode(results), """\
2901+ self.assertEqual(str(results), """\
2902 The results of your email command are provided below.
2903
2904 help: too many arguments: more than one
2905@@ -60,10 +58,10 @@
2906 def test_no_such_command(self):
2907 # Error message when asking for help on an existent command.
2908 results = Results()
2909- status = self._help.process(self._mlist, Message(), {},
2910+ status = self._help.process(self._mlist, Message(), {},
2911 ('doesnotexist',), results)
2912 self.assertEqual(status, ContinueProcessing.no)
2913- self.assertEqual(unicode(results), """\
2914+ self.assertEqual(str(results), """\
2915 The results of your email command are provided below.
2916
2917 help: no such command: doesnotexist
2918
2919=== modified file 'src/mailman/config/__init__.py'
2920--- src/mailman/config/__init__.py 2014-04-28 15:23:35 +0000
2921+++ src/mailman/config/__init__.py 2015-01-03 05:08:01 +0000
2922@@ -17,9 +17,6 @@
2923
2924 """Mailman configuration package."""
2925
2926-from __future__ import absolute_import, print_function, unicode_literals
2927-
2928-__metaclass__ = type
2929 __all__ = [
2930 'config',
2931 ]
2932
2933=== modified file 'src/mailman/config/config.py'
2934--- src/mailman/config/config.py 2014-12-29 22:44:58 +0000
2935+++ src/mailman/config/config.py 2015-01-03 05:08:01 +0000
2936@@ -17,9 +17,6 @@
2937
2938 """Configuration file loading and management."""
2939
2940-from __future__ import absolute_import, print_function, unicode_literals
2941-
2942-__metaclass__ = type
2943 __all__ = [
2944 'Configuration',
2945 'external_configuration',
2946@@ -29,27 +26,28 @@
2947
2948 import os
2949 import sys
2950+import mailman.templates
2951
2952-from ConfigParser import SafeConfigParser
2953 from flufl.lock import Lock
2954 from lazr.config import ConfigSchema, as_boolean
2955-from pkg_resources import resource_stream, resource_string
2956-from string import Template
2957-from zope.component import getUtility
2958-from zope.event import notify
2959-from zope.interface import implementer
2960-
2961-import mailman.templates
2962-
2963 from mailman import version
2964 from mailman.interfaces.configuration import (
2965 ConfigurationUpdatedEvent, IConfiguration, MissingConfigurationFileError)
2966 from mailman.interfaces.languages import ILanguageManager
2967 from mailman.utilities.filesystem import makedirs
2968 from mailman.utilities.modules import call_name, expand_path
2969+from pkg_resources import resource_filename, resource_string as resource_bytes
2970+from six.moves.configparser import ConfigParser, RawConfigParser
2971+from string import Template
2972+from unittest.mock import patch
2973+from zope.component import getUtility
2974+from zope.event import notify
2975+from zope.interface import implementer
2976
2977
2978 SPACE = ' '
2979+SPACERS = '\n'
2980+
2981
2982 MAILMAN_CFG_TEMPLATE = """\
2983 # AUTOMATICALLY GENERATED BY MAILMAN ON {}
2984@@ -66,6 +64,11 @@
2985 # enabled: yes
2986 # recipient: your.address@your.domain"""
2987
2988+class _NonStrictRawConfigParser(RawConfigParser):
2989+ def __init__(self, *args, **kws):
2990+ kws['strict'] = False
2991+ super().__init__(*args, **kws)
2992+
2993
2994
2995
2996 @implementer(IConfiguration)
2997@@ -102,30 +105,29 @@
2998
2999 def load(self, filename=None):
3000 """Load the configuration from the schema and config files."""
3001- schema_file = config_file = None
3002- try:
3003- schema_file = resource_stream('mailman.config', 'schema.cfg')
3004- schema = ConfigSchema('schema.cfg', schema_file)
3005- # If a configuration file was given, load it now too. First, load
3006- # the absolute minimum default configuration, then if a
3007- # configuration filename was given by the user, push it.
3008- config_file = resource_stream('mailman.config', 'mailman.cfg')
3009- self._config = schema.loadFile(config_file, 'mailman.cfg')
3010- if filename is not None:
3011- self.filename = filename
3012- with open(filename) as user_config:
3013- self._config.push(filename, user_config.read())
3014- finally:
3015- if schema_file:
3016- schema_file.close()
3017- if config_file:
3018- config_file.close()
3019- self._post_process()
3020+ schema_file = resource_filename('mailman.config', 'schema.cfg')
3021+ schema = ConfigSchema(schema_file)
3022+ # If a configuration file was given, load it now too. First, load
3023+ # the absolute minimum default configuration, then if a
3024+ # configuration filename was given by the user, push it.
3025+ config_file = resource_filename('mailman.config', 'mailman.cfg')
3026+ self._config = schema.load(config_file)
3027+ if filename is None:
3028+ self._post_process()
3029+ else:
3030+ self.filename = filename
3031+ with open(filename, 'r', encoding='utf-8') as user_config:
3032+ self.push(filename, user_config.read())
3033
3034 def push(self, config_name, config_string):
3035 """Push a new configuration onto the stack."""
3036 self._clear()
3037- self._config.push(config_name, config_string)
3038+ # In Python 3, the RawConfigParser() must be created with
3039+ # strict=False, otherwise we'll get a DuplicateSectionError.
3040+ # See https://bugs.launchpad.net/lazr.config/+bug/1397779
3041+ with patch('lazr.config._config.RawConfigParser',
3042+ _NonStrictRawConfigParser):
3043+ self._config.push(config_name, config_string)
3044 self._post_process()
3045
3046 def pop(self, config_name):
3047@@ -164,6 +166,7 @@
3048 # path is relative.
3049 var_dir = os.environ.get('MAILMAN_VAR_DIR', category.var_dir)
3050 substitutions = dict(
3051+ cwd = os.getcwd(),
3052 argv = bin_dir,
3053 # Directories.
3054 bin_dir = category.bin_dir,
3055@@ -185,26 +188,32 @@
3056 lock_file = category.lock_file,
3057 pid_file = category.pid_file,
3058 )
3059+ # Add the path to the .cfg file, if one was given on the command line.
3060+ if self.filename is not None:
3061+ substitutions['cfg_file'] = self.filename
3062 # Now, perform substitutions recursively until there are no more
3063 # variables with $-vars in them, or until substitutions are not
3064 # helping any more.
3065 last_dollar_count = 0
3066 while True:
3067+ expandables = []
3068 # Mutate the dictionary during iteration.
3069- dollar_count = 0
3070- for key in substitutions.keys():
3071+ for key in substitutions:
3072 raw_value = substitutions[key]
3073 value = Template(raw_value).safe_substitute(substitutions)
3074 if '$' in value:
3075 # Still more work to do.
3076- dollar_count += 1
3077+ expandables.append((key, value))
3078 substitutions[key] = value
3079- if dollar_count == 0:
3080+ if len(expandables) == 0:
3081 break
3082- if dollar_count == last_dollar_count:
3083- print('Path expansion infloop detected', file=sys.stderr)
3084+ if len(expandables) == last_dollar_count:
3085+ print('Path expansion infloop detected:\n',
3086+ SPACERS.join('\t{}: {}'.format(key, value)
3087+ for key, value in sorted(expandables)),
3088+ file=sys.stderr)
3089 sys.exit(1)
3090- last_dollar_count = dollar_count
3091+ last_dollar_count = len(expandables)
3092 # Ensure that all paths are normalized and made absolute. Handle the
3093 # few special cases first. Most of these are due to backward
3094 # compatibility.
3095@@ -269,7 +278,7 @@
3096
3097
3098
3099
3100-def load_external(path, encoding=None):
3101+def load_external(path):
3102 """Load the configuration file named by path.
3103
3104 :param path: A string naming the location of the external configuration
3105@@ -278,21 +287,16 @@
3106 value must name a ``.cfg`` file located within Python's import path,
3107 however the trailing ``.cfg`` suffix is implied (don't provide it
3108 here).
3109- :param encoding: The encoding to apply to the data read from path. If
3110- None, then bytes will be returned.
3111- :return: A unicode string or bytes, depending on ``encoding``.
3112+ :return: The contents of the configuration file.
3113+ :rtype: str
3114 """
3115 # Is the context coming from a file system or Python path?
3116 if path.startswith('python:'):
3117 resource_path = path[7:]
3118 package, dot, resource = resource_path.rpartition('.')
3119- config_string = resource_string(package, resource + '.cfg')
3120- else:
3121- with open(path, 'rb') as fp:
3122- config_string = fp.read()
3123- if encoding is None:
3124- return config_string
3125- return config_string.decode(encoding)
3126+ return resource_bytes(package, resource + '.cfg').decode('utf-8')
3127+ with open(path, 'r', encoding='utf-8') as fp:
3128+ return fp.read()
3129
3130
3131 def external_configuration(path):
3132@@ -308,7 +312,7 @@
3133 """
3134 # Is the context coming from a file system or Python path?
3135 cfg_path = expand_path(path)
3136- parser = SafeConfigParser()
3137+ parser = ConfigParser()
3138 files = parser.read(cfg_path)
3139 if files != [cfg_path]:
3140 raise MissingConfigurationFileError(path)
3141
3142=== modified file 'src/mailman/config/mailman.cfg'
3143--- src/mailman/config/mailman.cfg 2014-03-02 22:59:30 +0000
3144+++ src/mailman/config/mailman.cfg 2015-01-03 05:08:01 +0000
3145@@ -23,9 +23,13 @@
3146 # /var/tmp/mailman
3147
3148 [paths.dev]
3149-# Convenient development layout where everything is put in the current
3150-# directory.
3151-var_dir: var
3152+# Convenient development layout where everything is put in a directory above
3153+# where the mailman.cfg file lives.
3154+var_dir: $cfg_file/../..
3155+
3156+[paths.here]
3157+# Layout where the var directory is put in the current working directory.
3158+var_dir: $cwd/var
3159
3160 [paths.fhs]
3161 # Filesystem Hiearchy Standard 2.3
3162
3163=== modified file 'src/mailman/config/schema.cfg'
3164--- src/mailman/config/schema.cfg 2014-11-02 19:55:10 +0000
3165+++ src/mailman/config/schema.cfg 2015-01-03 05:08:01 +0000
3166@@ -59,7 +59,7 @@
3167 post_hook:
3168
3169 # Which paths.* file system layout to use.
3170-layout: dev
3171+layout: here
3172
3173 # Can MIME filtered messages be preserved by list owners?
3174 filtered_messages_are_preservable: no
3175
3176=== modified file 'src/mailman/config/tests/test_archivers.py'
3177--- src/mailman/config/tests/test_archivers.py 2014-01-01 14:59:42 +0000
3178+++ src/mailman/config/tests/test_archivers.py 2015-01-03 05:08:01 +0000
3179@@ -17,9 +17,6 @@
3180
3181 """Site-wide archiver configuration tests."""
3182
3183-from __future__ import absolute_import, print_function, unicode_literals
3184-
3185-__metaclass__ = type
3186 __all__ = [
3187 'TestArchivers',
3188 ]
3189
3190=== modified file 'src/mailman/config/tests/test_configuration.py'
3191--- src/mailman/config/tests/test_configuration.py 2014-11-09 12:52:58 +0000
3192+++ src/mailman/config/tests/test_configuration.py 2015-01-03 05:08:01 +0000
3193@@ -17,9 +17,6 @@
3194
3195 """Test the system-wide global configuration."""
3196
3197-from __future__ import absolute_import, print_function, unicode_literals
3198-
3199-__metaclass__ = type
3200 __all__ = [
3201 'TestConfiguration',
3202 'TestConfigurationErrors',
3203@@ -32,6 +29,7 @@
3204 import tempfile
3205 import unittest
3206
3207+from contextlib import ExitStack
3208 from mailman.config.config import (
3209 Configuration, external_configuration, load_external)
3210 from mailman.interfaces.configuration import (
3211@@ -65,26 +63,13 @@
3212 class TestExternal(unittest.TestCase):
3213 """Test external configuration file loading APIs."""
3214
3215- def test_load_external_by_filename_as_bytes(self):
3216+ def test_load_external_by_filename(self):
3217 filename = resource_filename('mailman.config', 'postfix.cfg')
3218 contents = load_external(filename)
3219- self.assertIsInstance(contents, bytes)
3220- self.assertEqual(contents[:9], b'[postfix]')
3221+ self.assertEqual(contents[:9], '[postfix]')
3222
3223- def test_load_external_by_path_as_bytes(self):
3224+ def test_load_external_by_path(self):
3225 contents = load_external('python:mailman.config.postfix')
3226- self.assertIsInstance(contents, bytes)
3227- self.assertEqual(contents[:9], b'[postfix]')
3228-
3229- def test_load_external_by_filename_as_string(self):
3230- filename = resource_filename('mailman.config', 'postfix.cfg')
3231- contents = load_external(filename, encoding='utf-8')
3232- self.assertIsInstance(contents, unicode)
3233- self.assertEqual(contents[:9], '[postfix]')
3234-
3235- def test_load_external_by_path_as_string(self):
3236- contents = load_external('python:mailman.config.postfix', 'utf-8')
3237- self.assertIsInstance(contents, unicode)
3238 self.assertEqual(contents[:9], '[postfix]')
3239
3240 def test_external_configuration_by_filename(self):
3241@@ -121,24 +106,32 @@
3242 # Use a fake sys.exit() function that records that it was called, and
3243 # that prevents further processing.
3244 config = Configuration()
3245- # Suppress warning messages in the test output.
3246- with self.assertRaises(SystemExit) as cm, mock.patch('sys.stderr'):
3247+ # Suppress warning messages in the test output. Also, make sure that
3248+ # the config.load() call doesn't break global state.
3249+ with ExitStack() as resources:
3250+ resources.enter_context(mock.patch('sys.stderr'))
3251+ resources.enter_context(mock.patch.object(config, '_clear'))
3252+ cm = resources.enter_context(self.assertRaises(SystemExit))
3253 config.load(filename)
3254 self.assertEqual(cm.exception.args, (1,))
3255
3256 def test_path_expansion_infloop(self):
3257- # A path expansion never completes because it references a
3258- # non-existent substitution variable.
3259+ # A path expansion never completes because it references a non-existent
3260+ # substitution variable.
3261 fd, filename = tempfile.mkstemp()
3262 self.addCleanup(os.remove, filename)
3263 os.close(fd)
3264 with open(filename, 'w') as fp:
3265 print("""\
3266-[paths.dev]
3267+[paths.here]
3268 log_dir: $nopath/log_dir
3269 """, file=fp)
3270 config = Configuration()
3271- # Suppress warning messages in the test output.
3272- with self.assertRaises(SystemExit) as cm, mock.patch('sys.stderr'):
3273+ # Suppress warning messages in the test output. Also, make sure that
3274+ # the config.load() call doesn't break global state.
3275+ with ExitStack() as resources:
3276+ resources.enter_context(mock.patch('sys.stderr'))
3277+ resources.enter_context(mock.patch.object(config, '_clear'))
3278+ cm = resources.enter_context(self.assertRaises(SystemExit))
3279 config.load(filename)
3280 self.assertEqual(cm.exception.args, (1,))
3281
3282=== modified file 'src/mailman/core/chains.py'
3283--- src/mailman/core/chains.py 2014-04-28 15:23:35 +0000
3284+++ src/mailman/core/chains.py 2015-01-03 05:08:01 +0000
3285@@ -17,21 +17,17 @@
3286
3287 """Application support for chain processing."""
3288
3289-from __future__ import absolute_import, print_function, unicode_literals
3290-
3291-__metaclass__ = type
3292 __all__ = [
3293 'initialize',
3294 'process',
3295 ]
3296
3297
3298-from zope.interface.verify import verifyObject
3299-
3300 from mailman.chains.base import Chain, TerminalChainBase
3301 from mailman.config import config
3302 from mailman.interfaces.chain import LinkAction, IChain
3303 from mailman.utilities.modules import find_components
3304+from zope.interface.verify import verifyObject
3305
3306
3307
3308
3309
3310=== modified file 'src/mailman/core/constants.py'
3311--- src/mailman/core/constants.py 2014-01-01 14:59:42 +0000
3312+++ src/mailman/core/constants.py 2015-01-03 05:08:01 +0000
3313@@ -17,21 +17,17 @@
3314
3315 """Various constants and enumerations."""
3316
3317-from __future__ import absolute_import, print_function, unicode_literals
3318-
3319-__metaclass__ = type
3320 __all__ = [
3321 'system_preferences',
3322 ]
3323
3324
3325-from zope.component import getUtility
3326-from zope.interface import implementer
3327-
3328 from mailman.config import config
3329 from mailman.interfaces.languages import ILanguageManager
3330 from mailman.interfaces.member import DeliveryMode, DeliveryStatus
3331 from mailman.interfaces.preferences import IPreferences
3332+from zope.component import getUtility
3333+from zope.interface import implementer
3334
3335
3336
3337
3338
3339=== modified file 'src/mailman/core/docs/runner.rst'
3340--- src/mailman/core/docs/runner.rst 2014-11-01 03:35:02 +0000
3341+++ src/mailman/core/docs/runner.rst 2015-01-03 05:08:01 +0000
3342@@ -55,7 +55,7 @@
3343 ... A test message.
3344 ... """)
3345 >>> switchboard = config.switchboards['test']
3346- >>> filebase = switchboard.enqueue(msg, listname=mlist.fqdn_listname,
3347+ >>> filebase = switchboard.enqueue(msg, listid=mlist.list_id,
3348 ... foo='yes', bar='no')
3349 >>> runner.run()
3350 >>> print(runner.msg.as_string())
3351@@ -69,7 +69,7 @@
3352 bar : no
3353 foo : yes
3354 lang : en
3355- listname : test@example.com
3356+ listid : test.example.com
3357 version : 3
3358
3359 XXX More of the Runner API should be tested.
3360
3361=== modified file 'src/mailman/core/errors.py'
3362--- src/mailman/core/errors.py 2014-04-28 15:23:35 +0000
3363+++ src/mailman/core/errors.py 2015-01-03 05:08:01 +0000
3364@@ -26,9 +26,6 @@
3365 """
3366
3367
3368-from __future__ import absolute_import, print_function, unicode_literals
3369-
3370-__metaclass__ = type
3371 __all__ = [
3372 'AlreadyReceivingDigests',
3373 'AlreadyReceivingRegularDeliveries',
3374
3375=== modified file 'src/mailman/core/i18n.py'
3376--- src/mailman/core/i18n.py 2014-04-28 15:23:35 +0000
3377+++ src/mailman/core/i18n.py 2015-01-03 05:08:01 +0000
3378@@ -17,9 +17,6 @@
3379
3380 """Internationalization."""
3381
3382-from __future__ import absolute_import, print_function, unicode_literals
3383-
3384-__metaclass__ = type
3385 __all__ = [
3386 '_',
3387 'ctime',
3388@@ -28,11 +25,12 @@
3389
3390
3391 import time
3392+import mailman.messages
3393+
3394 from flufl.i18n import PackageStrategy, registry
3395-
3396-import mailman.messages
3397 from mailman.interfaces.configuration import ConfigurationUpdatedEvent
3398
3399+
3400 _ = None
3401
3402
3403
3404=== modified file 'src/mailman/core/initialize.py'
3405--- src/mailman/core/initialize.py 2014-11-09 12:52:58 +0000
3406+++ src/mailman/core/initialize.py 2015-01-03 05:08:01 +0000
3407@@ -24,9 +24,6 @@
3408 by the command line arguments.
3409 """
3410
3411-from __future__ import absolute_import, print_function, unicode_literals
3412-
3413-__metaclass__ = type
3414 __all__ = [
3415 'initialize',
3416 'initialize_1',
3417@@ -38,16 +35,15 @@
3418
3419 import os
3420 import sys
3421-
3422-from pkg_resources import resource_string
3423-from zope.component import getUtility
3424-from zope.configuration import xmlconfig
3425-
3426 import mailman.config.config
3427 import mailman.core.logging
3428
3429 from mailman.interfaces.database import IDatabaseFactory
3430 from mailman.utilities.modules import call_name
3431+from pkg_resources import resource_string as resource_bytes
3432+from zope.component import getUtility
3433+from zope.configuration import xmlconfig
3434+
3435
3436 # The test infrastructure uses this to prevent the search and loading of any
3437 # existing configuration file. Otherwise the existence of say a
3438@@ -109,8 +105,8 @@
3439 :param config_path: The path to the configuration file.
3440 :type config_path: string
3441 """
3442- zcml = resource_string('mailman.config', 'configure.zcml')
3443- xmlconfig.string(zcml)
3444+ zcml = resource_bytes('mailman.config', 'configure.zcml')
3445+ xmlconfig.string(zcml.decode('utf-8'))
3446 # By default, set the umask so that only owner and group can read and
3447 # write our files. Specifically we must have g+rw and we probably want
3448 # o-rwx although I think in most cases it doesn't hurt if other can read
3449
3450=== modified file 'src/mailman/core/logging.py'
3451--- src/mailman/core/logging.py 2014-12-29 22:50:07 +0000
3452+++ src/mailman/core/logging.py 2015-01-03 05:08:01 +0000
3453@@ -17,9 +17,6 @@
3454
3455 """Logging initialization, using Python's standard logging package."""
3456
3457-from __future__ import absolute_import, print_function, unicode_literals
3458-
3459-__metaclass__ = type
3460 __all__ = [
3461 'initialize',
3462 'reopen',
3463
3464=== modified file 'src/mailman/core/pipelines.py'
3465--- src/mailman/core/pipelines.py 2014-01-01 14:59:42 +0000
3466+++ src/mailman/core/pipelines.py 2015-01-03 05:08:01 +0000
3467@@ -17,9 +17,6 @@
3468
3469 """Built-in pipelines."""
3470
3471-from __future__ import absolute_import, print_function, unicode_literals
3472-
3473-__metaclass__ = type
3474 __all__ = [
3475 'BasePipeline',
3476 'OwnerPipeline',
3477@@ -32,9 +29,6 @@
3478
3479 import logging
3480
3481-from zope.interface import implementer
3482-from zope.interface.verify import verifyObject
3483-
3484 from mailman.app.bounces import bounce_message
3485 from mailman.config import config
3486 from mailman.core import errors
3487@@ -42,6 +36,8 @@
3488 from mailman.interfaces.handler import IHandler
3489 from mailman.interfaces.pipeline import IPipeline
3490 from mailman.utilities.modules import find_components
3491+from zope.interface import implementer
3492+from zope.interface.verify import verifyObject
3493
3494
3495 dlog = logging.getLogger('mailman.debug')
3496@@ -120,6 +116,7 @@
3497 'cleanse',
3498 'cleanse-dkim',
3499 'cook-headers',
3500+ 'subject-prefix',
3501 'rfc-2369',
3502 'to-archive',
3503 'to-digest',
3504
3505=== modified file 'src/mailman/core/rules.py'
3506--- src/mailman/core/rules.py 2014-04-28 15:23:35 +0000
3507+++ src/mailman/core/rules.py 2015-01-03 05:08:01 +0000
3508@@ -17,19 +17,15 @@
3509
3510 """Various rule helpers"""
3511
3512-from __future__ import absolute_import, print_function, unicode_literals
3513-
3514-__metaclass__ = type
3515 __all__ = [
3516 'initialize',
3517 ]
3518
3519
3520-from zope.interface.verify import verifyObject
3521-
3522 from mailman.config import config
3523 from mailman.interfaces.rules import IRule
3524 from mailman.utilities.modules import find_components
3525+from zope.interface.verify import verifyObject
3526
3527
3528
3529
3530
3531=== modified file 'src/mailman/core/runner.py'
3532--- src/mailman/core/runner.py 2014-12-09 11:25:45 +0000
3533+++ src/mailman/core/runner.py 2015-01-03 05:08:01 +0000
3534@@ -17,9 +17,6 @@
3535
3536 """The process runner base class."""
3537
3538-from __future__ import absolute_import, print_function, unicode_literals
3539-
3540-__metaclass__ = type
3541 __all__ = [
3542 'Runner',
3543 ]
3544@@ -30,12 +27,7 @@
3545 import logging
3546 import traceback
3547
3548-from cStringIO import StringIO
3549 from lazr.config import as_boolean, as_timedelta
3550-from zope.component import getUtility
3551-from zope.event import notify
3552-from zope.interface import implementer
3553-
3554 from mailman.config import config
3555 from mailman.core.i18n import _
3556 from mailman.core.logging import reopen
3557@@ -44,6 +36,10 @@
3558 from mailman.interfaces.listmanager import IListManager
3559 from mailman.interfaces.runner import IRunner, RunnerCrashEvent
3560 from mailman.utilities.string import expand
3561+from six.moves import cStringIO as StringIO
3562+from zope.component import getUtility
3563+from zope.event import notify
3564+from zope.interface import implementer
3565
3566
3567 dlog = logging.getLogger('mailman.debug')
3568@@ -218,16 +214,26 @@
3569 # them out of our sight.
3570 #
3571 # Find out which mailing list this message is destined for.
3572+ mlist = None
3573 missing = object()
3574- listname = msgdata.get('listname', missing)
3575- mlist = (None
3576- if listname is missing
3577- else getUtility(IListManager).get(unicode(listname)))
3578+ # First try to dig out the target list by id. If there's no list-id
3579+ # in the metadata, fall back to the fqdn list name for backward
3580+ # compatibility.
3581+ list_manager = getUtility(IListManager)
3582+ list_id = msgdata.get('listid', missing)
3583+ fqdn_listname = None
3584+ if list_id is missing:
3585+ fqdn_listname = msgdata.get('listname', missing)
3586+ # XXX Deprecate.
3587+ if fqdn_listname is not missing:
3588+ mlist = list_manager.get(fqdn_listname)
3589+ else:
3590+ mlist = list_manager.get_by_list_id(list_id)
3591 if mlist is None:
3592+ identifier = (list_id if list_id is not None else fqdn_listname)
3593 elog.error(
3594 '%s runner "%s" shunting message for missing list: %s',
3595- msg['message-id'], self.name,
3596- ('n/a' if listname is missing else listname))
3597+ msg['message-id'], self.name, identifier)
3598 config.switchboards['shunt'].enqueue(msg, msgdata)
3599 return
3600 # Now process this message. We also want to set up the language
3601
3602=== modified file 'src/mailman/core/switchboard.py'
3603--- src/mailman/core/switchboard.py 2014-11-20 01:29:44 +0000
3604+++ src/mailman/core/switchboard.py 2015-01-03 05:08:01 +0000
3605@@ -24,9 +24,6 @@
3606 dictionary is written.
3607 """
3608
3609-from __future__ import absolute_import, print_function, unicode_literals
3610-
3611-__metaclass__ = type
3612 __all__ = [
3613 'Switchboard',
3614 'handle_ConfigurationUpdatedEvent',
3615@@ -37,22 +34,22 @@
3616 import time
3617 import email
3618 import pickle
3619-import cPickle
3620 import hashlib
3621 import logging
3622
3623-from zope.interface import implementer
3624-
3625 from mailman.config import config
3626 from mailman.email.message import Message
3627 from mailman.interfaces.configuration import ConfigurationUpdatedEvent
3628 from mailman.interfaces.switchboard import ISwitchboard
3629 from mailman.utilities.filesystem import makedirs
3630 from mailman.utilities.string import expand
3631-
3632-
3633-# 20 bytes of all bits set, maximum hashlib.sha.digest() value.
3634-shamax = 0xffffffffffffffffffffffffffffffffffffffffL
3635+from six.moves import cPickle
3636+from zope.interface import implementer
3637+
3638+
3639+# 20 bytes of all bits set, maximum hashlib.sha.digest() value. We do it this
3640+# way for Python 2/3 compatibility.
3641+shamax = int('0xffffffffffffffffffffffffffffffffffffffff', 16)
3642 # Small increment to add to time in case two entries have the same time. This
3643 # prevents skipping one of two entries with the same time until the next pass.
3644 DELTA = .0001
3645@@ -92,7 +89,7 @@
3646 self.queue_directory = queue_directory
3647 # If configured to, create the directory if it doesn't yet exist.
3648 if config.create_paths:
3649- makedirs(self.queue_directory, 0770)
3650+ makedirs(self.queue_directory, 0o770)
3651 # Fast track for no slices
3652 self._lower = None
3653 self._upper = None
3654@@ -112,37 +109,37 @@
3655 # of parallel runner processes.
3656 data = _metadata.copy()
3657 data.update(_kws)
3658- listname = data.get('listname', '--nolist--')
3659+ list_id = data.get('listid', '--nolist--')
3660 # Get some data for the input to the sha hash.
3661- now = time.time()
3662+ now = repr(time.time())
3663 if data.get('_plaintext'):
3664 protocol = 0
3665 msgsave = cPickle.dumps(str(_msg), protocol)
3666 else:
3667 protocol = pickle.HIGHEST_PROTOCOL
3668 msgsave = cPickle.dumps(_msg, protocol)
3669- # listname is unicode but the input to the hash function must be an
3670- # 8-bit string (eventually, a bytes object).
3671- hashfood = msgsave + listname.encode('utf-8') + repr(now)
3672+ # The list-id field is a string but the input to the hash function must
3673+ # be bytes.
3674+ hashfood = msgsave + list_id.encode('utf-8') + now.encode('utf-8')
3675 # Encode the current time into the file name for FIFO sorting. The
3676 # file name consists of two parts separated by a '+': the received
3677 # time for this message (i.e. when it first showed up on this system)
3678 # and the sha hex digest.
3679- filebase = repr(now) + '+' + hashlib.sha1(hashfood).hexdigest()
3680+ filebase = now + '+' + hashlib.sha1(hashfood).hexdigest()
3681 filename = os.path.join(self.queue_directory, filebase + '.pck')
3682 tmpfile = filename + '.tmp'
3683 # Always add the metadata schema version number
3684 data['version'] = config.QFILE_SCHEMA_VERSION
3685 # Filter out volatile entries. Use .keys() so that we can mutate the
3686 # dictionary during the iteration.
3687- for k in data.keys():
3688+ for k in list(data):
3689 if k.startswith('_'):
3690 del data[k]
3691 # We have to tell the dequeue() method whether to parse the message
3692 # object or not.
3693 data['_parsemsg'] = (protocol == 0)
3694 # Write to the pickle file the message object and metadata.
3695- with open(tmpfile, 'w') as fp:
3696+ with open(tmpfile, 'wb') as fp:
3697 fp.write(msgsave)
3698 cPickle.dump(data, fp, protocol)
3699 fp.flush()
3700@@ -156,7 +153,7 @@
3701 filename = os.path.join(self.queue_directory, filebase + '.pck')
3702 backfile = os.path.join(self.queue_directory, filebase + '.bak')
3703 # Read the message object and metadata.
3704- with open(filename) as fp:
3705+ with open(filename, 'rb') as fp:
3706 # Move the file to the backup file name for processing. If this
3707 # process crashes uncleanly the .bak file will be used to
3708 # re-instate the .pck file in order to try again.
3709@@ -207,13 +204,13 @@
3710 # Throw out any files which don't match our bitrange. BAW: test
3711 # performance and end-cases of this algorithm. MAS: both
3712 # comparisons need to be <= to get complete range.
3713- if lower is None or (lower <= long(digest, 16) <= upper):
3714+ if lower is None or (lower <= int(digest, 16) <= upper):
3715 key = float(when)
3716 while key in times:
3717 key += DELTA
3718 times[key] = filebase
3719 # FIFO sort
3720- return [times[key] for key in sorted(times)]
3721+ return [times[k] for k in sorted(times)]
3722
3723 def recover_backup_files(self):
3724 """See `ISwitchboard`."""
3725@@ -228,7 +225,8 @@
3726 dst = os.path.join(self.queue_directory, filebase + '.pck')
3727 with open(src, 'rb+') as fp:
3728 try:
3729- msg = cPickle.load(fp)
3730+ # Throw away the message object.
3731+ cPickle.load(fp)
3732 data_pos = fp.tell()
3733 data = cPickle.load(fp)
3734 except Exception as error:
3735
3736=== modified file 'src/mailman/core/system.py'
3737--- src/mailman/core/system.py 2014-01-01 14:59:42 +0000
3738+++ src/mailman/core/system.py 2015-01-03 05:08:01 +0000
3739@@ -17,9 +17,6 @@
3740
3741 """System information."""
3742
3743-from __future__ import absolute_import, print_function, unicode_literals
3744-
3745-__metaclass__ = type
3746 __all__ = [
3747 'system',
3748 ]
3749@@ -27,10 +24,9 @@
3750
3751 import sys
3752
3753-from zope.interface import implementer
3754-
3755 from mailman import version
3756 from mailman.interfaces.system import ISystem
3757+from zope.interface import implementer
3758
3759
3760
3761
3762
3763=== modified file 'src/mailman/core/tests/test_pipelines.py'
3764--- src/mailman/core/tests/test_pipelines.py 2014-01-01 14:59:42 +0000
3765+++ src/mailman/core/tests/test_pipelines.py 2015-01-03 05:08:01 +0000
3766@@ -17,9 +17,6 @@
3767
3768 """Test the core modification pipelines."""
3769
3770-from __future__ import absolute_import, print_function, unicode_literals
3771-
3772-__metaclass__ = type
3773 __all__ = [
3774 'TestOwnerPipeline',
3775 'TestPostingPipeline',
3776@@ -28,9 +25,6 @@
3777
3778 import unittest
3779
3780-from zope.component import getUtility
3781-from zope.interface import implementer
3782-
3783 from mailman.app.lifecycle import create_list
3784 from mailman.config import config
3785 from mailman.core.errors import DiscardMessage, RejectMessage
3786@@ -40,11 +34,11 @@
3787 from mailman.interfaces.pipeline import IPipeline
3788 from mailman.interfaces.usermanager import IUserManager
3789 from mailman.testing.helpers import (
3790- LogFileMark,
3791- get_queue_messages,
3792- reset_the_world,
3793+ LogFileMark, get_queue_messages, reset_the_world,
3794 specialized_message_from_string as mfs)
3795 from mailman.testing.layers import ConfigLayer
3796+from zope.component import getUtility
3797+from zope.interface import implementer
3798
3799
3800
3801
3802@@ -175,5 +169,5 @@
3803 pipeline_name='default-owner-pipeline')
3804 messages = get_queue_messages('out', sort_on='to')
3805 self.assertEqual(len(messages), 1)
3806- self.assertEqual(messages[0].msgdata['recipients'],
3807+ self.assertEqual(messages[0].msgdata['recipients'],
3808 set(('anne@example.com', 'bart@example.com')))
3809
3810=== modified file 'src/mailman/core/tests/test_runner.py'
3811--- src/mailman/core/tests/test_runner.py 2014-12-11 02:49:39 +0000
3812+++ src/mailman/core/tests/test_runner.py 2015-01-03 05:08:01 +0000
3813@@ -17,9 +17,6 @@
3814
3815 """Test some Runner base class behavior."""
3816
3817-from __future__ import absolute_import, print_function, unicode_literals
3818-
3819-__metaclass__ = type
3820 __all__ = [
3821 'TestRunner',
3822 ]
3823@@ -70,7 +67,7 @@
3824 Message-ID: <ant>
3825
3826 """)
3827- config.switchboards['in'].enqueue(msg, listname='test@example.com')
3828+ config.switchboards['in'].enqueue(msg, listid='test.example.com')
3829 with event_subscribers(self._got_event):
3830 runner.run()
3831 # We should now have exactly one event, which will contain the
3832@@ -81,7 +78,7 @@
3833 self.assertTrue(isinstance(event, RunnerCrashEvent))
3834 self.assertEqual(event.mailing_list, self._mlist)
3835 self.assertEqual(event.message['message-id'], '<ant>')
3836- self.assertEqual(event.metadata['listname'], 'test@example.com')
3837+ self.assertEqual(event.metadata['listid'], 'test.example.com')
3838 self.assertTrue(isinstance(event.error, RuntimeError))
3839 self.assertEqual(str(event.error), 'borked')
3840 self.assertTrue(isinstance(event.runner, CrashingRunner))
3841
3842=== modified file 'src/mailman/database/alembic/__init__.py'
3843--- src/mailman/database/alembic/__init__.py 2014-10-13 21:03:25 +0000
3844+++ src/mailman/database/alembic/__init__.py 2015-01-03 05:08:01 +0000
3845@@ -17,9 +17,6 @@
3846
3847 """Alembic configuration initization."""
3848
3849-from __future__ import absolute_import, print_function, unicode_literals
3850-
3851-__metaclass__ = type
3852 __all__ = [
3853 'alembic_cfg',
3854 ]
3855
3856=== modified file 'src/mailman/database/alembic/env.py'
3857--- src/mailman/database/alembic/env.py 2014-10-13 14:19:01 +0000
3858+++ src/mailman/database/alembic/env.py 2015-01-03 05:08:01 +0000
3859@@ -17,9 +17,6 @@
3860
3861 """Alembic migration environment."""
3862
3863-from __future__ import absolute_import, print_function, unicode_literals
3864-
3865-__metaclass__ = type
3866 __all__ = [
3867 'run_migrations_offline',
3868 'run_migrations_online',
3869@@ -28,11 +25,10 @@
3870
3871 from alembic import context
3872 from contextlib import closing
3873-from sqlalchemy import create_engine
3874-
3875 from mailman.config import config
3876 from mailman.database.model import Model
3877 from mailman.utilities.string import expand
3878+from sqlalchemy import create_engine
3879
3880
3881
3882
3883
3884=== modified file 'src/mailman/database/alembic/versions/51b7f92bd06c_initial.py'
3885--- src/mailman/database/alembic/versions/51b7f92bd06c_initial.py 2014-10-13 19:24:24 +0000
3886+++ src/mailman/database/alembic/versions/51b7f92bd06c_initial.py 2015-01-03 05:08:01 +0000
3887@@ -29,9 +29,6 @@
3888 Create Date: 2014-10-10 09:53:35.624472
3889 """
3890
3891-from __future__ import absolute_import, print_function, unicode_literals
3892-
3893-__metaclass__ = type
3894 __all__ = [
3895 'downgrade',
3896 'upgrade',
3897
3898=== modified file 'src/mailman/database/base.py'
3899--- src/mailman/database/base.py 2014-11-01 17:46:36 +0000
3900+++ src/mailman/database/base.py 2015-01-03 05:08:01 +0000
3901@@ -15,9 +15,8 @@
3902 # You should have received a copy of the GNU General Public License along with
3903 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
3904
3905-from __future__ import absolute_import, print_function, unicode_literals
3906+"""Common database support."""
3907
3908-__metaclass__ = type
3909 __all__ = [
3910 'SABaseDatabase',
3911 ]
3912@@ -25,17 +24,15 @@
3913
3914 import logging
3915
3916+from mailman.config import config
3917+from mailman.interfaces.database import IDatabase
3918+from mailman.utilities.string import expand
3919 from sqlalchemy import create_engine
3920 from sqlalchemy.orm import sessionmaker
3921 from zope.interface import implementer
3922
3923-from mailman.config import config
3924-from mailman.interfaces.database import IDatabase
3925-from mailman.utilities.string import expand
3926-
3927
3928 log = logging.getLogger('mailman.database')
3929-NL = '\n'
3930
3931
3932
3933
3934
3935=== modified file 'src/mailman/database/factory.py'
3936--- src/mailman/database/factory.py 2014-10-13 19:24:24 +0000
3937+++ src/mailman/database/factory.py 2015-01-03 05:08:01 +0000
3938@@ -17,9 +17,6 @@
3939
3940 """Database factory."""
3941
3942-from __future__ import absolute_import, print_function, unicode_literals
3943-
3944-__metaclass__ = type
3945 __all__ = [
3946 'DatabaseFactory',
3947 'DatabaseTestingFactory',
3948@@ -33,16 +30,15 @@
3949 from alembic.migration import MigrationContext
3950 from alembic.script import ScriptDirectory
3951 from flufl.lock import Lock
3952-from sqlalchemy import MetaData
3953-from zope.interface import implementer
3954-from zope.interface.verify import verifyObject
3955-
3956 from mailman.config import config
3957 from mailman.database.alembic import alembic_cfg
3958 from mailman.database.model import Model
3959 from mailman.interfaces.database import (
3960 DatabaseError, IDatabase, IDatabaseFactory)
3961 from mailman.utilities.modules import call_name
3962+from sqlalchemy import MetaData
3963+from zope.interface import implementer
3964+from zope.interface.verify import verifyObject
3965
3966
3967 LAST_STORM_SCHEMA_VERSION = '20130406000000'
3968
3969=== modified file 'src/mailman/database/model.py'
3970--- src/mailman/database/model.py 2014-11-01 16:49:15 +0000
3971+++ src/mailman/database/model.py 2015-01-03 05:08:01 +0000
3972@@ -17,9 +17,6 @@
3973
3974 """Base class for all database classes."""
3975
3976-from __future__ import absolute_import, print_function, unicode_literals
3977-
3978-__metaclass__ = type
3979 __all__ = [
3980 'Model',
3981 ]
3982
3983=== modified file 'src/mailman/database/postgresql.py'
3984--- src/mailman/database/postgresql.py 2014-09-28 00:17:05 +0000
3985+++ src/mailman/database/postgresql.py 2015-01-03 05:08:01 +0000
3986@@ -17,9 +17,6 @@
3987
3988 """PostgreSQL database support."""
3989
3990-from __future__ import absolute_import, print_function, unicode_literals
3991-
3992-__metaclass__ = type
3993 __all__ = [
3994 'PostgreSQLDatabase',
3995 ]
3996
3997=== modified file 'src/mailman/database/sqlite.py'
3998--- src/mailman/database/sqlite.py 2014-09-23 13:09:45 +0000
3999+++ src/mailman/database/sqlite.py 2015-01-03 05:08:01 +0000
4000@@ -17,9 +17,6 @@
4001
4002 """SQLite database support."""
4003
4004-from __future__ import absolute_import, print_function, unicode_literals
4005-
4006-__metaclass__ = type
4007 __all__ = [
4008 'SQLiteDatabase',
4009 ]
4010@@ -28,7 +25,7 @@
4011 import os
4012
4013 from mailman.database.base import SABaseDatabase
4014-from urlparse import urlparse
4015+from six.moves.urllib_parse import urlparse
4016
4017
4018
4019
4020
4021=== modified file 'src/mailman/database/tests/test_factory.py'
4022--- src/mailman/database/tests/test_factory.py 2014-11-29 17:46:49 +0000
4023+++ src/mailman/database/tests/test_factory.py 2015-01-03 05:08:01 +0000
4024@@ -17,9 +17,6 @@
4025
4026 """Test database schema migrations"""
4027
4028-from __future__ import absolute_import, print_function, unicode_literals
4029-
4030-__metaclass__ = type
4031 __all__ = [
4032 'TestSchemaManager',
4033 ]
4034@@ -28,17 +25,16 @@
4035 import unittest
4036 import alembic.command
4037
4038-from mock import patch
4039-from sqlalchemy import MetaData, Table, Column, Integer, Unicode
4040-from sqlalchemy.exc import ProgrammingError, OperationalError
4041-from sqlalchemy.schema import Index
4042-
4043 from mailman.config import config
4044 from mailman.database.alembic import alembic_cfg
4045 from mailman.database.factory import LAST_STORM_SCHEMA_VERSION, SchemaManager
4046 from mailman.database.model import Model
4047 from mailman.interfaces.database import DatabaseError
4048 from mailman.testing.layers import ConfigLayer
4049+from mock import patch
4050+from sqlalchemy import MetaData, Table, Column, Integer, Unicode
4051+from sqlalchemy.exc import ProgrammingError, OperationalError
4052+from sqlalchemy.schema import Index
4053
4054
4055
4056
4057
4058=== modified file 'src/mailman/database/transaction.py'
4059--- src/mailman/database/transaction.py 2014-01-01 14:59:42 +0000
4060+++ src/mailman/database/transaction.py 2015-01-03 05:08:01 +0000
4061@@ -17,9 +17,6 @@
4062
4063 """Transactional support."""
4064
4065-from __future__ import absolute_import, print_function, unicode_literals
4066-
4067-__metaclass__ = type
4068 __all__ = [
4069 'dbconnection',
4070 'transaction',
4071@@ -28,7 +25,6 @@
4072
4073
4074 from contextlib import contextmanager
4075-
4076 from mailman.config import config
4077
4078
4079
4080=== modified file 'src/mailman/database/types.py'
4081--- src/mailman/database/types.py 2014-09-28 00:17:05 +0000
4082+++ src/mailman/database/types.py 2015-01-03 05:08:01 +0000
4083@@ -15,17 +15,14 @@
4084 # You should have received a copy of the GNU General Public License along with
4085 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
4086
4087-"""Storm type conversions."""
4088-
4089-
4090-from __future__ import absolute_import, print_function, unicode_literals
4091-
4092-__metaclass__ = type
4093+"""Database type conversions."""
4094+
4095 __all__ = [
4096 'Enum',
4097 'UUID',
4098 ]
4099
4100+
4101 import uuid
4102
4103 from sqlalchemy import Integer
4104
4105=== modified file 'src/mailman/docs/DEVELOP.rst'
4106--- src/mailman/docs/DEVELOP.rst 2012-12-30 23:24:26 +0000
4107+++ src/mailman/docs/DEVELOP.rst 2015-01-03 05:08:01 +0000
4108@@ -3,7 +3,9 @@
4109 ==================
4110
4111 The following documentation is generated from the internal developer
4112-documentation. This documentation is also used by the test suite.
4113+documentation. This documentation is also used by the test suite. Another
4114+good source of architectural information is available in the chapter written
4115+by Barry Warsaw for the `Architecture of Open Source Applications`_.
4116
4117 For now, this will have to suffice as an overview of the Mailman system.
4118
4119@@ -154,3 +156,4 @@
4120
4121
4122 .. _`Python pickles`: http://docs.python.org/2/library/pickle.html
4123+.. _`Architecture of Open Source Applications`: http://www.aosabook.org/en/mailman.html
4124
4125=== modified file 'src/mailman/docs/INTRODUCTION.rst'
4126--- src/mailman/docs/INTRODUCTION.rst 2014-01-01 14:59:42 +0000
4127+++ src/mailman/docs/INTRODUCTION.rst 2015-01-03 05:08:01 +0000
4128@@ -82,7 +82,7 @@
4129 Requirements
4130 ============
4131
4132-Mailman 3.0 requires `Python 2.7`_.
4133+Mailman 3 requires `Python 3.4`_ or newer.
4134
4135
4136 .. _`GNU Mailman`: http://www.list.org
4137@@ -90,4 +90,4 @@
4138 .. _`Getting Started`: START.html
4139 .. _Python: http://www.python.org
4140 .. _FAQ: http://wiki.list.org/display/DOC/Frequently+Asked+Questions
4141-.. _`Python 2.7`: http://www.python.org/download/releases/2.7.3/
4142+.. _`Python 3.4`: https://www.python.org/downloads/release/python-342/
4143
4144=== modified file 'src/mailman/docs/NEWS.rst'
4145--- src/mailman/docs/NEWS.rst 2014-12-30 19:43:56 +0000
4146+++ src/mailman/docs/NEWS.rst 2015-01-03 05:08:01 +0000
4147@@ -8,11 +8,35 @@
4148 Here is a history of user visible changes to Mailman.
4149
4150
4151-3.0 beta 6 -- "Show Don't Tell"
4152-===============================
4153-(2015-XX-XX)
4154-
4155-
4156+<<<<<<< TREE
4157+3.0 beta 6 -- "Show Don't Tell"
4158+===============================
4159+(2015-XX-XX)
4160+
4161+
4162+=======
4163+3.0 beta 6 -- "Show Don't Tell"
4164+===============================
4165+(2015-XX-XX)
4166+
4167+Configuration
4168+-------------
4169+ * When specifying a file system path in the [paths.*] section, $cfg_file can
4170+ be used to expand into the path of the ``-C`` option if given. In the
4171+ default ``[paths.dev]`` section, ``$var_dir`` is now specified relative to
4172+ ``$cfg_file`` so that it won't accidentally be relative to the current
4173+ working directory, if ``-C`` is given.
4174+ * ``$cwd`` is now an additional substitution variable for the ``mailman.cfg``
4175+ file's ``[paths.*]`` sections. A new ``[paths.here]`` section is added,
4176+ which puts the ``var_dir`` in ``$cwd``. It is made the default layout.
4177+
4178+REST
4179+----
4180+ * You can now view the contents of, inject messages into, and delete messages
4181+ from the various queue directories via the ``<api>/queues`` resource.
4182+
4183+
4184+>>>>>>> MERGE-SOURCE
4185 3.0 beta 5 -- "Carve Away The Stone"
4186 ====================================
4187 (2014-12-29)
4188@@ -49,6 +73,7 @@
4189
4190 Development
4191 -----------
4192+ * Python 3.4 is now the minimum requirement.
4193 * You no longer have to create a virtual environment separately when running
4194 the test suite. Just use `tox`.
4195 * You no longer have to edit `src/mailman/testing/testing.cfg` to run the
4196
4197=== modified file 'src/mailman/docs/START.rst'
4198--- src/mailman/docs/START.rst 2014-11-09 12:52:58 +0000
4199+++ src/mailman/docs/START.rst 2015-01-03 05:08:01 +0000
4200@@ -39,12 +39,11 @@
4201 Requirements
4202 ============
4203
4204-Python 2.7 is required. It can either be the default 'python' on your
4205-``$PATH`` or it can be accessible via the ``python2.7`` binary. If
4206-your operating system does not include Python, see http://www.python.org
4207-for information about downloading installers (where available) and
4208-installing it from source (when necessary or preferred). Python 3 is
4209-not yet supported.
4210+Python 3.4 or newer is required. It can either be the default 'python3' on
4211+your ``$PATH`` or it can be accessible via the ``python3.4`` binary. If your
4212+operating system does not include Python, see http://www.python.org for
4213+information about downloading installers (where available) and installing it
4214+from source (when necessary or preferred). Python 2 is not supported.
4215
4216 You may need some additional dependencies, which are either available from
4217 your OS vendor, or can be downloaded automatically from the `Python
4218@@ -80,9 +79,9 @@
4219 You do have access to the virtualenv, and you can use this to run individual
4220 tests, e.g.::
4221
4222- $ .tox/py27/bin/python -m nose2 -vv -P user
4223+ $ .tox/py34/bin/python -m nose2 -vv -P user
4224
4225-Use `.tox/py27/bin/python -m nose2 --help` for more options.
4226+Use `.tox/py34/bin/python -m nose2 --help` for more options.
4227
4228 If you want to run the full test suite against the PostgreSQL database, set
4229 the database up as described in :doc:`DATABASE`, then create a `postgres.cfg`
4230@@ -112,23 +111,23 @@
4231
4232 First, create a virtual environment. By default ``virtualenv`` uses the
4233 ``python`` executable it finds first on your ``$PATH``. Make sure this is
4234-Python 2.7 (just start the interactive interpreter and check the version in
4235+Python 3.4 (just start the interactive interpreter and check the version in
4236 the startup banner). The directory you install the virtualenv into is up to
4237-you, but for purposes of this document, we'll install it into ``/tmp/py27``::
4238-
4239- % virtualenv --system-site-packages /tmp/py27
4240-
4241-If your default Python is not version 2.7, use the ``--python`` option to
4242+you, but for purposes of this document, we'll install it into ``/tmp/mm3``::
4243+
4244+ % virtualenv -p python3 --system-site-packages /tmp/mm3
4245+
4246+If your default Python is not version 3.4, use the ``--python`` option to
4247 specify the Python executable. You can use the command name if this version
4248 is on your ``PATH``::
4249
4250- % virtualenv --system-site-packages --python=python2.7 /tmp/py27
4251+ % virtualenv --system-site-packages --python=python3.4 /tmp/mm3
4252
4253-or you may specify the full path to any Python 2.7 executable.
4254+or you may specify the full path to any Python 3.4 executable.
4255
4256 Now, activate the virtual environment and set it up for development::
4257
4258- % source /tmp/py27/bin/activate
4259+ % source /tmp/mm3/bin/activate
4260 % python setup.py develop
4261
4262 Sit back and have some Kombucha while you wait for everything to download and
4263
4264=== modified file 'src/mailman/docs/STYLEGUIDE.rst'
4265--- src/mailman/docs/STYLEGUIDE.rst 2014-01-01 14:59:42 +0000
4266+++ src/mailman/docs/STYLEGUIDE.rst 2015-01-03 05:08:01 +0000
4267@@ -15,33 +15,25 @@
4268 This document contains a style guide for Python programming, as used in GNU
4269 Mailman. `PEP 8`_ is the basis for this style guide so it's recommendations
4270 should be followed except for the differences outlined here. This document
4271-assumes the use of Python 2.7, but not (yet) Python 3.
4272+assumes the use of Python 3.
4273
4274-* After file comments (e.g. license block), add a ``__metaclass__`` definition
4275- so that all classes will be new-style. Following that, add an ``__all__``
4276- section that names, one-per-line, all the public names exported by this
4277- module. You should enable absolute imports and unicode literals. See the
4278+* After file comments (e.g. license block), add an ``__all__`` section that
4279+ names, one-per-line, all the public names exported by this module. See the
4280 `GNU Mailman Python template`_ as an example.
4281
4282 * Imports are always put at the top of the file, just after any module
4283 comments and docstrings, and before module globals and constants, but after
4284- any ``__future__`` imports, or ``__metaclass__`` and ``__all__``
4285- definitions.
4286+ any ``__all__`` definitions.
4287
4288 Imports should be grouped, with the order being:
4289
4290- 1. non-from imports for standard and third party libraries
4291- 2. non-from imports from the application
4292- 3. from-imports from the standard and third party libraries
4293- 4. from-imports from the application
4294-
4295- From-imports should follow non-from imports. Dotted imports should follow
4296- non-dotted imports. Non-dotted imports should be grouped by increasing
4297- length, while dotted imports should be grouped alphabetically.
4298-
4299-* In general, there should be one class per module. Keep files small, but
4300- it's okay to group related code together. List everything exported from the
4301- module in the ``__all__``.
4302+ 1. non-from imports, grouped from shorted module name to longest module
4303+ name, with ties being broken by alphabetical order.
4304+ 3. from-imports grouped alphabetically.
4305+
4306+* In general, there should be one class per module. This is not a
4307+ hard-and-fast rule. Keep files small, but it's okay to group related code
4308+ together. List everything exported from the module in the ``__all__``.
4309
4310 * Right hanging comments are discouraged, in favor of preceding comments.
4311 E.g. bad::
4312
4313=== modified file 'src/mailman/docs/__init__.py'
4314--- src/mailman/docs/__init__.py 2014-04-28 15:23:35 +0000
4315+++ src/mailman/docs/__init__.py 2015-01-03 05:08:01 +0000
4316@@ -17,9 +17,6 @@
4317
4318 """General Mailman doc tests."""
4319
4320-from __future__ import absolute_import, print_function, unicode_literals
4321-
4322-__metaclass__ = type
4323 __all__ = [
4324 'layer',
4325 ]
4326
4327=== modified file 'src/mailman/email/message.py'
4328--- src/mailman/email/message.py 2014-12-11 02:49:39 +0000
4329+++ src/mailman/email/message.py 2015-01-03 05:08:01 +0000
4330@@ -23,9 +23,6 @@
4331 attributes.
4332 """
4333
4334-from __future__ import absolute_import, print_function, unicode_literals
4335-
4336-__metaclass__ = type
4337 __all__ = [
4338 'Message',
4339 'MultipartDigestMessage',
4340@@ -40,7 +37,6 @@
4341
4342 from email.header import Header
4343 from email.mime.multipart import MIMEMultipart
4344-
4345 from mailman.config import config
4346
4347
4348@@ -149,8 +145,8 @@
4349 subject = ('(no subject)' if subject is None else subject)
4350 if text is not None:
4351 self.set_payload(text.encode(charset), charset)
4352- self['Subject'] = Header(subject.encode(charset), charset,
4353- header_name='Subject', errors='replace')
4354+ self['Subject'] = Header(
4355+ subject, charset, header_name='Subject', errors='replace')
4356 self['From'] = sender
4357 if isinstance(recipients, (list, set, tuple)):
4358 self['To'] = COMMASPACE.join(recipients)
4359@@ -198,7 +194,7 @@
4360 reduced_list_headers=True,
4361 )
4362 if mlist is not None:
4363- enqueue_kws['listname'] = mlist.fqdn_listname
4364+ enqueue_kws['listid'] = mlist.list_id
4365 enqueue_kws.update(_kws)
4366 virginq.enqueue(self, **enqueue_kws)
4367
4368@@ -227,7 +223,7 @@
4369 virginq = config.switchboards['virgin']
4370 # The message metadata better have a `recip' attribute
4371 virginq.enqueue(self,
4372- listname=mlist.fqdn_listname,
4373+ listid=mlist.list_id,
4374 recipients=self.recipients,
4375 nodecorate=True,
4376 reduced_list_headers=True,
4377
4378=== modified file 'src/mailman/email/tests/test_message.py'
4379--- src/mailman/email/tests/test_message.py 2014-12-09 01:38:26 +0000
4380+++ src/mailman/email/tests/test_message.py 2015-01-03 05:08:01 +0000
4381@@ -17,9 +17,6 @@
4382
4383 """Test the message API."""
4384
4385-from __future__ import absolute_import, print_function, unicode_literals
4386-
4387-__metaclass__ = type
4388 __all__ = [
4389 'TestMessage',
4390 'TestMessageSubclass',
4391@@ -27,8 +24,8 @@
4392
4393
4394 import unittest
4395+
4396 from email.parser import FeedParser
4397-
4398 from mailman.app.lifecycle import create_list
4399 from mailman.email.message import Message, UserNotification
4400 from mailman.testing.helpers import get_queue_messages
4401@@ -66,7 +63,7 @@
4402 class TestMessageSubclass(unittest.TestCase):
4403 def test_i18n_filenames(self):
4404 parser = FeedParser(_factory=Message)
4405- parser.feed(b"""\
4406+ parser.feed("""\
4407 Message-ID: <blah@example.com>
4408 Content-Type: multipart/mixed; boundary="------------050607040206050605060208"
4409
4410@@ -88,6 +85,6 @@
4411 attachment = msg.get_payload(1)
4412 try:
4413 filename = attachment.get_filename()
4414- except TypeError as e:
4415- self.fail(e)
4416+ except TypeError as error:
4417+ self.fail(error)
4418 self.assertEqual(filename, u'd\xe9jeuner.txt')
4419
4420=== modified file 'src/mailman/email/validate.py'
4421--- src/mailman/email/validate.py 2014-01-01 14:59:42 +0000
4422+++ src/mailman/email/validate.py 2015-01-03 05:08:01 +0000
4423@@ -17,9 +17,6 @@
4424
4425 """Email address validation."""
4426
4427-from __future__ import absolute_import, print_function, unicode_literals
4428-
4429-__metaclass__ = type
4430 __all__ = [
4431 'Validator',
4432 ]
4433@@ -27,11 +24,10 @@
4434
4435 import re
4436
4437-from zope.interface import implementer
4438-
4439 from mailman.interfaces.address import (
4440 IEmailValidator, InvalidEmailAddressError)
4441 from mailman.utilities.email import split_email
4442+from zope.interface import implementer
4443
4444
4445 # What other characters should be disallowed?
4446
4447=== modified file 'src/mailman/handlers/acknowledge.py'
4448--- src/mailman/handlers/acknowledge.py 2014-01-01 14:59:42 +0000
4449+++ src/mailman/handlers/acknowledge.py 2015-01-03 05:08:01 +0000
4450@@ -20,23 +20,19 @@
4451 This only happens if the sender has set their AcknowledgePosts attribute.
4452 """
4453
4454-from __future__ import absolute_import, print_function, unicode_literals
4455-
4456-__metaclass__ = type
4457 __all__ = [
4458 'Acknowledge',
4459 ]
4460
4461
4462-from zope.component import getUtility
4463-from zope.interface import implementer
4464-
4465 from mailman.core.i18n import _
4466 from mailman.email.message import UserNotification
4467 from mailman.interfaces.handler import IHandler
4468 from mailman.interfaces.languages import ILanguageManager
4469 from mailman.utilities.i18n import make
4470 from mailman.utilities.string import oneline
4471+from zope.component import getUtility
4472+from zope.interface import implementer
4473
4474
4475
4476
4477@@ -67,14 +63,13 @@
4478 language = (language_manager[msgdata['lang']]
4479 if 'lang' in msgdata
4480 else member.preferred_language)
4481- charset = language_manager[language.code].charset
4482 # Now get the acknowledgement template.
4483 display_name = mlist.display_name
4484 text = make('postack.txt',
4485 mailing_list=mlist,
4486 language=language.code,
4487 wrap=False,
4488- subject=oneline(original_subject, charset),
4489+ subject=oneline(original_subject, in_unicode=True),
4490 list_name=mlist.list_name,
4491 display_name=display_name,
4492 listinfo_url=mlist.script_url('listinfo'),
4493
4494=== modified file 'src/mailman/handlers/after_delivery.py'
4495--- src/mailman/handlers/after_delivery.py 2014-01-01 14:59:42 +0000
4496+++ src/mailman/handlers/after_delivery.py 2015-01-03 05:08:01 +0000
4497@@ -17,19 +17,15 @@
4498
4499 """Perform some bookkeeping after a successful post."""
4500
4501-from __future__ import absolute_import, print_function, unicode_literals
4502-
4503-__metaclass__ = type
4504 __all__ = [
4505 'AfterDelivery',
4506 ]
4507
4508
4509-from zope.interface import implementer
4510-
4511 from mailman.core.i18n import _
4512 from mailman.interfaces.handler import IHandler
4513 from mailman.utilities.datetime import now
4514+from zope.interface import implementer
4515
4516
4517
4518
4519
4520=== modified file 'src/mailman/handlers/avoid_duplicates.py'
4521--- src/mailman/handlers/avoid_duplicates.py 2014-01-01 14:59:42 +0000
4522+++ src/mailman/handlers/avoid_duplicates.py 2015-01-03 05:08:01 +0000
4523@@ -23,19 +23,15 @@
4524 warning header, or pass it through, depending on the user's preferences.
4525 """
4526
4527-from __future__ import absolute_import, print_function, unicode_literals
4528-
4529-__metaclass__ = type
4530 __all__ = [
4531 'AvoidDuplicates',
4532 ]
4533
4534
4535 from email.utils import getaddresses, formataddr
4536-from zope.interface import implementer
4537-
4538 from mailman.core.i18n import _
4539 from mailman.interfaces.handler import IHandler
4540+from zope.interface import implementer
4541
4542
4543 COMMASPACE = ', '
4544
4545=== modified file 'src/mailman/handlers/cleanse.py'
4546--- src/mailman/handlers/cleanse.py 2014-01-01 14:59:42 +0000
4547+++ src/mailman/handlers/cleanse.py 2015-01-03 05:08:01 +0000
4548@@ -17,9 +17,6 @@
4549
4550 """Cleanse certain headers from all messages."""
4551
4552-from __future__ import absolute_import, print_function, unicode_literals
4553-
4554-__metaclass__ = type
4555 __all__ = [
4556 'Cleanse',
4557 ]
4558@@ -28,11 +25,10 @@
4559 import logging
4560
4561 from email.utils import formataddr
4562-from zope.interface import implementer
4563-
4564 from mailman.core.i18n import _
4565 from mailman.handlers.cook_headers import uheader
4566 from mailman.interfaces.handler import IHandler
4567+from zope.interface import implementer
4568
4569
4570 log = logging.getLogger('mailman.post')
4571
4572=== modified file 'src/mailman/handlers/cleanse_dkim.py'
4573--- src/mailman/handlers/cleanse_dkim.py 2014-01-01 14:59:42 +0000
4574+++ src/mailman/handlers/cleanse_dkim.py 2015-01-03 05:08:01 +0000
4575@@ -25,20 +25,16 @@
4576 originating at the Mailman server for the outgoing message.
4577 """
4578
4579-from __future__ import absolute_import, print_function, unicode_literals
4580-
4581-__metaclass__ = type
4582 __all__ = [
4583 'CleanseDKIM',
4584 ]
4585
4586
4587 from lazr.config import as_boolean
4588-from zope.interface import implementer
4589-
4590 from mailman.config import config
4591 from mailman.core.i18n import _
4592 from mailman.interfaces.handler import IHandler
4593+from zope.interface import implementer
4594
4595
4596
4597
4598
4599=== modified file 'src/mailman/handlers/cook_headers.py'
4600--- src/mailman/handlers/cook_headers.py 2014-12-09 11:25:45 +0000
4601+++ src/mailman/handlers/cook_headers.py 2015-01-03 05:08:01 +0000
4602@@ -17,9 +17,6 @@
4603
4604 """Cook a message's headers."""
4605
4606-from __future__ import absolute_import, print_function, unicode_literals
4607-
4608-__metaclass__ = type
4609 __all__ = [
4610 'CookHeaders',
4611 ]
4612@@ -27,21 +24,18 @@
4613
4614 import re
4615
4616-from email.errors import HeaderParseError
4617-from email.header import Header, decode_header, make_header
4618+from email.header import Header
4619 from email.utils import parseaddr, formataddr, getaddresses
4620-from zope.interface import implementer
4621-
4622 from mailman.core.i18n import _
4623 from mailman.interfaces.handler import IHandler
4624 from mailman.interfaces.mailinglist import Personalization, ReplyToMunging
4625 from mailman.version import VERSION
4626+from zope.interface import implementer
4627
4628
4629 COMMASPACE = ', '
4630 MAXLINELEN = 78
4631-
4632-nonascii = re.compile('[^\s!-~]')
4633+NONASCII = re.compile('[^\s!-~]')
4634
4635
4636
4637
4638@@ -54,12 +48,12 @@
4639 specified.
4640 """
4641 charset = mlist.preferred_language.charset
4642- if nonascii.search(s):
4643+ if NONASCII.search(s):
4644 # use list charset but ...
4645 if charset == 'us-ascii':
4646 charset = 'iso-8859-1'
4647 else:
4648- # there is no nonascii so ...
4649+ # there is no non-ascii so ...
4650 charset = 'us-ascii'
4651 return Header(s, charset, maxlinelen, header_name, continuation_ws)
4652
4653@@ -78,13 +72,6 @@
4654 msgdata['original_sender'] = msg.sender
4655 # VirginRunner sets _fasttrack for internally crafted messages.
4656 fasttrack = msgdata.get('_fasttrack')
4657- if not msgdata.get('isdigest') and not fasttrack:
4658- try:
4659- prefix_subject(mlist, msg, msgdata)
4660- except (UnicodeError, ValueError):
4661- # TK: Sometimes subject header is not MIME encoded for 8bit
4662- # simply abort prefixing.
4663- pass
4664 # Add Precedence: and other useful headers. None of these are standard
4665 # and finding information on some of them are fairly difficult. Some are
4666 # just common practice, and we'll add more here as they become necessary.
4667@@ -171,114 +158,6 @@
4668
4669
4670
4671
4672-def prefix_subject(mlist, msg, msgdata):
4673- """Maybe add a subject prefix.
4674-
4675- Add the subject prefix unless the message is a digest or is being fast
4676- tracked (e.g. internally crafted, delivered to a single user such as the
4677- list admin).
4678- """
4679- if not mlist.subject_prefix.strip():
4680- return
4681- prefix = mlist.subject_prefix
4682- subject = msg.get('subject', '')
4683- # Try to figure out what the continuation_ws is for the header
4684- if isinstance(subject, Header):
4685- lines = str(subject).splitlines()
4686- else:
4687- lines = subject.splitlines()
4688- ws = '\t'
4689- if len(lines) > 1 and lines[1] and lines[1][0] in ' \t':
4690- ws = lines[1][0]
4691- msgdata['original_subject'] = subject
4692- # The subject may be multilingual but we take the first charset as major
4693- # one and try to decode. If it is decodable, returned subject is in one
4694- # line and cset is properly set. If fail, subject is mime-encoded and
4695- # cset is set as us-ascii. See detail for ch_oneline() (CookHeaders one
4696- # line function).
4697- subject, cset = ch_oneline(subject)
4698- # TK: Python interpreter has evolved to be strict on ascii charset code
4699- # range. It is safe to use unicode string when manupilating header
4700- # contents with re module. It would be best to return unicode in
4701- # ch_oneline() but here is temporary solution.
4702- subject = unicode(subject, cset)
4703- # If the subject_prefix contains '%d', it is replaced with the
4704- # mailing list sequential number. Sequential number format allows
4705- # '%d' or '%05d' like pattern.
4706- prefix_pattern = re.escape(prefix)
4707- # unescape '%' :-<
4708- prefix_pattern = '%'.join(prefix_pattern.split(r'\%'))
4709- p = re.compile('%\d*d')
4710- if p.search(prefix, 1):
4711- # prefix have number, so we should search prefix w/number in subject.
4712- # Also, force new style.
4713- prefix_pattern = p.sub(r'\s*\d+\s*', prefix_pattern)
4714- subject = re.sub(prefix_pattern, '', subject)
4715- rematch = re.match('((RE|AW|SV|VS)(\[\d+\])?:\s*)+', subject, re.I)
4716- if rematch:
4717- subject = subject[rematch.end():]
4718- recolon = 'Re:'
4719- else:
4720- recolon = ''
4721- # At this point, subject may become null if someone post mail with
4722- # subject: [subject prefix]
4723- if subject.strip() == '':
4724- subject = _('(no subject)')
4725- cset = mlist.preferred_language.charset
4726- # and substitute %d in prefix with post_id
4727- try:
4728- prefix = prefix % mlist.post_id
4729- except TypeError:
4730- pass
4731- # Get the header as a Header instance, with proper unicode conversion
4732- if not recolon:
4733- h = uheader(mlist, prefix, 'Subject', continuation_ws=ws)
4734- else:
4735- h = uheader(mlist, prefix, 'Subject', continuation_ws=ws)
4736- h.append(recolon)
4737- # TK: Subject is concatenated and unicode string.
4738- subject = subject.encode(cset, 'replace')
4739- h.append(subject, cset)
4740- del msg['subject']
4741- msg['Subject'] = h
4742- ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws)
4743- ss.append(subject, cset)
4744- msgdata['stripped_subject'] = ss
4745-
4746-
4747-
4748
4749-def ch_oneline(headerstr):
4750- # Decode header string in one line and convert into single charset.
4751- # Return (string, cset) tuple as check for failure.
4752- try:
4753- d = decode_header(headerstr)
4754- # At this point, we should rstrip() every string because some
4755- # MUA deliberately add trailing spaces when composing return
4756- # message.
4757- d = [(s.rstrip(), c) for (s, c) in d]
4758- # Find all charsets in the original header. We use 'utf-8' rather
4759- # than using the first charset (in mailman 2.1.x) if multiple
4760- # charsets are used.
4761- csets = []
4762- for (s, c) in d:
4763- if c and c not in csets:
4764- csets.append(c)
4765- if len(csets) == 0:
4766- cset = 'us-ascii'
4767- elif len(csets) == 1:
4768- cset = csets[0]
4769- else:
4770- cset = 'utf-8'
4771- h = make_header(d)
4772- ustr = unicode(h)
4773- oneline = ''.join(ustr.splitlines())
4774- return oneline.encode(cset, 'replace'), cset
4775- except (LookupError, UnicodeError, ValueError, HeaderParseError):
4776- # possibly charset problem. return with undecoded string in one line.
4777- return ''.join(headerstr.splitlines()), 'us-ascii'
4778-
4779-
4780-
4781
4782 @implementer(IHandler)
4783 class CookHeaders:
4784 """Modify message headers."""
4785
4786=== modified file 'src/mailman/handlers/decorate.py'
4787--- src/mailman/handlers/decorate.py 2014-04-14 16:14:13 +0000
4788+++ src/mailman/handlers/decorate.py 2015-01-03 05:08:01 +0000
4789@@ -17,9 +17,6 @@
4790
4791 """Decorate a message by sticking the header and footer around it."""
4792
4793-from __future__ import absolute_import, print_function, unicode_literals
4794-
4795-__metaclass__ = type
4796 __all__ = [
4797 'Decorate',
4798 'decorate',
4799@@ -31,15 +28,14 @@
4800 import logging
4801
4802 from email.mime.text import MIMEText
4803-from urllib2 import URLError
4804-from zope.component import getUtility
4805-from zope.interface import implementer
4806-
4807 from mailman.core.i18n import _
4808 from mailman.email.message import Message
4809 from mailman.interfaces.handler import IHandler
4810 from mailman.interfaces.templates import ITemplateLoader
4811 from mailman.utilities.string import expand
4812+from six.moves.urllib_error import URLError
4813+from zope.component import getUtility
4814+from zope.interface import implementer
4815
4816
4817 log = logging.getLogger('mailman.error')
4818
4819=== modified file 'src/mailman/handlers/docs/acknowledge.rst'
4820--- src/mailman/handlers/docs/acknowledge.rst 2014-04-28 15:23:35 +0000
4821+++ src/mailman/handlers/docs/acknowledge.rst 2015-01-03 05:08:01 +0000
4822@@ -113,9 +113,9 @@
4823 1
4824 >>> dump_msgdata(messages[0].msgdata)
4825 _parsemsg : False
4826- listname : test@example.com
4827+ listid : test.example.com
4828 nodecorate : True
4829- recipients : set([u'aperson@example.com'])
4830+ recipients : {'aperson@example.com'}
4831 reduced_list_headers: True
4832 ...
4833 >>> print(messages[0].msg.as_string())
4834@@ -150,9 +150,9 @@
4835 1
4836 >>> dump_msgdata(messages[0].msgdata)
4837 _parsemsg : False
4838- listname : test@example.com
4839+ listid : test.example.com
4840 nodecorate : True
4841- recipients : set([u'aperson@example.com'])
4842+ recipients : {'aperson@example.com'}
4843 reduced_list_headers: True
4844 ...
4845 >>> print(messages[0].msg.as_string())
4846
4847=== modified file 'src/mailman/handlers/docs/avoid-duplicates.rst'
4848--- src/mailman/handlers/docs/avoid-duplicates.rst 2014-04-28 15:23:35 +0000
4849+++ src/mailman/handlers/docs/avoid-duplicates.rst 2015-01-03 05:08:01 +0000
4850@@ -71,7 +71,7 @@
4851 >>> msgdata = recips.copy()
4852 >>> handler.process(mlist, msg, msgdata)
4853 >>> sorted(msgdata['recipients'])
4854- [u'aperson@example.com', u'bperson@example.com']
4855+ ['aperson@example.com', 'bperson@example.com']
4856 >>> print(msg.as_string())
4857 From: Claire Person <cperson@example.com>
4858 <BLANKLINE>
4859@@ -89,7 +89,7 @@
4860 >>> msgdata = recips.copy()
4861 >>> handler.process(mlist, msg, msgdata)
4862 >>> sorted(msgdata['recipients'])
4863- [u'bperson@example.com']
4864+ ['bperson@example.com']
4865 >>> print(msg.as_string())
4866 From: Claire Person <cperson@example.com>
4867 CC: aperson@example.com
4868@@ -109,7 +109,7 @@
4869 >>> msgdata = recips.copy()
4870 >>> handler.process(mlist, msg, msgdata)
4871 >>> sorted(msgdata['recipients'])
4872- [u'aperson@example.com', u'bperson@example.com']
4873+ ['aperson@example.com', 'bperson@example.com']
4874 >>> print(msg.as_string())
4875 From: Claire Person <cperson@example.com>
4876 CC: bperson@example.com
4877@@ -128,7 +128,7 @@
4878 >>> msgdata = recips.copy()
4879 >>> handler.process(mlist, msg, msgdata)
4880 >>> sorted(msgdata['recipients'])
4881- [u'bperson@example.com']
4882+ ['bperson@example.com']
4883 >>> print(msg.as_string())
4884 From: Claire Person <cperson@example.com>
4885 To: aperson@example.com
4886@@ -147,7 +147,7 @@
4887 >>> msgdata = recips.copy()
4888 >>> handler.process(mlist, msg, msgdata)
4889 >>> sorted(msgdata['recipients'])
4890- [u'bperson@example.com']
4891+ ['bperson@example.com']
4892 >>> print(msg.as_string())
4893 From: Claire Person <cperson@example.com>
4894 Resent-To: aperson@example.com
4895@@ -166,7 +166,7 @@
4896 >>> msgdata = recips.copy()
4897 >>> handler.process(mlist, msg, msgdata)
4898 >>> sorted(msgdata['recipients'])
4899- [u'bperson@example.com']
4900+ ['bperson@example.com']
4901 >>> print(msg.as_string())
4902 From: Claire Person <cperson@example.com>
4903 Resent-Cc: aperson@example.com
4904
4905=== modified file 'src/mailman/handlers/docs/digests.rst'
4906--- src/mailman/handlers/docs/digests.rst 2014-04-28 15:23:35 +0000
4907+++ src/mailman/handlers/docs/digests.rst 2015-01-03 05:08:01 +0000
4908@@ -82,11 +82,13 @@
4909 >>> mlist.digest_size_threshold = 1
4910 >>> mlist.volume = 2
4911 >>> mlist.next_digest_number = 10
4912+ >>> digest_path = os.path.join(mlist.data_path, 'digest.mmdf')
4913 >>> size = 0
4914 >>> for msg in message_factory:
4915 ... process(mlist, msg, {})
4916- ... size += len(str(msg))
4917- ... if size >= mlist.digest_size_threshold * 1024:
4918+ ... # When the digest reaches the proper size, it is renamed. So we
4919+ ... # can break out of this list when the file disappears.
4920+ ... if not os.path.exists(digest_path):
4921 ... break
4922
4923 >>> sum(1 for msg in digest_mbox(mlist))
4924
4925=== modified file 'src/mailman/handlers/docs/file-recips.rst'
4926--- src/mailman/handlers/docs/file-recips.rst 2014-04-28 15:23:35 +0000
4927+++ src/mailman/handlers/docs/file-recips.rst 2015-01-03 05:08:01 +0000
4928@@ -34,26 +34,6 @@
4929 recipients: 7
4930
4931
4932-Missing file
4933-============
4934-
4935-The include file must live inside the list's data directory, under the name
4936-``members.txt``. If the file doesn't exist, the list of recipients will be
4937-empty.
4938-
4939- >>> import os
4940- >>> file_path = os.path.join(mlist.data_path, 'members.txt')
4941- >>> open(file_path)
4942- Traceback (most recent call last):
4943- ...
4944- IOError: [Errno ...]
4945- No such file or directory: u'.../_xtest@example.com/members.txt'
4946- >>> msgdata = {}
4947- >>> handler.process(mlist, msg, msgdata)
4948- >>> dump_list(msgdata['recipients'])
4949- *Empty*
4950-
4951-
4952 Existing file
4953 =============
4954
4955@@ -61,16 +41,15 @@
4956 addresses are returned as the set of recipients.
4957 ::
4958
4959- >>> fp = open(file_path, 'w')
4960- >>> try:
4961+ >>> import os
4962+ >>> file_path = os.path.join(mlist.data_path, 'members.txt')
4963+ >>> with open(file_path, 'w', encoding='utf-8') as fp:
4964 ... print('bperson@example.com', file=fp)
4965 ... print('cperson@example.com', file=fp)
4966 ... print('dperson@example.com', file=fp)
4967 ... print('eperson@example.com', file=fp)
4968 ... print('fperson@example.com', file=fp)
4969 ... print('gperson@example.com', file=fp)
4970- ... finally:
4971- ... fp.close()
4972
4973 >>> msgdata = {}
4974 >>> handler.process(mlist, msg, msgdata)
4975
4976=== modified file 'src/mailman/handlers/docs/filtering.rst'
4977--- src/mailman/handlers/docs/filtering.rst 2014-04-28 15:23:35 +0000
4978+++ src/mailman/handlers/docs/filtering.rst 2015-01-03 05:08:01 +0000
4979@@ -26,6 +26,8 @@
4980 A simple filtering setting will just search the content types of the messages
4981 parts, discarding all parts with a matching MIME type. If the message's outer
4982 content type matches the filter, the entire message will be discarded.
4983+However, if we turn off content filtering altogether, then the handler
4984+short-circuits.
4985 ::
4986
4987 >>> from mailman.interfaces.mime import FilterAction
4988@@ -42,14 +44,6 @@
4989 ... """)
4990
4991 >>> process = config.handlers['mime-delete'].process
4992- >>> process(mlist, msg, {})
4993- Traceback (most recent call last):
4994- ...
4995- DiscardMessage: The message's content type was explicitly disallowed
4996-
4997-However, if we turn off content filtering altogether, then the handler
4998-short-circuits.
4999-
5000 >>> mlist.filter_content = False
The diff has been truncated for viewing.