diff -Nru mailmanclient-3.1.1/conf.py mailmanclient-3.2.0/conf.py --- mailmanclient-3.1.1/conf.py 2017-06-05 23:34:05.000000000 +0000 +++ mailmanclient-3.2.0/conf.py 2018-02-25 20:14:59.000000000 +0000 @@ -72,7 +72,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', '.tox', 'eggs'] +exclude_patterns = ['_build', '.tox', 'eggs', '.pc'] # The reST default role (used for this markup: `text`) to use for all # documents. diff -Nru mailmanclient-3.1.1/conftest.py mailmanclient-3.2.0/conftest.py --- mailmanclient-3.1.1/conftest.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/conftest.py 2018-02-25 20:14:59.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2017 The Free Software Foundation, Inc. +# Copyright (C) 2017-2018 by the Free Software Foundation, Inc. # # This file is part of mailman.client. # diff -Nru mailmanclient-3.1.1/debian/changelog mailmanclient-3.2.0/debian/changelog --- mailmanclient-3.1.1/debian/changelog 2018-03-16 20:55:03.000000000 +0000 +++ mailmanclient-3.2.0/debian/changelog 2018-10-05 18:37:42.000000000 +0000 @@ -1,3 +1,43 @@ +mailmanclient (3.2.0-2~ubuntu18.04.1) bionic; urgency=medium + + * Source backport to bionic + + -- Michael Jeanson Fri, 05 Oct 2018 14:37:42 -0400 + +mailmanclient (3.2.0-2) unstable; urgency=medium + + * Drop Python 2.7 support, mailmanclient 3.2 requires Python 3.5 or + higher. See https://gitlab.com/mailman/mailmanclient/issues/40 for + further information. (Closes: #909209) + + -- Jonas Meurer Thu, 20 Sep 2018 19:04:19 +0200 + +mailmanclient (3.2.0-1) unstable; urgency=medium + + [ Jonas Meurer ] + * New upstream release 3.2.0 + * Drop obsolete patch d/patches/0001_sphinx_ignore_pc.patch + * Drop obsolete patch d/patches/0003_fix_doc_errors.patch + * d/upstream/signing-key.asc: + - Change the key to 541EA0448453394FF77A0ECC9D9B2BA061D0A67C + (Abhilash Raj ) since latest upstream + tarball got signed by this key. + * d/source/local-options: + - Remove obsolete file + * d/control: + - Bump standards-version to 4.2.1, no changes required + + [ Pierre-Elliott Bécue ] + * d/control: + - Remove ancient X-Python-Version field + - Move sphinx deps to python3 + * d/p/0001: + - Update authorship reference to match gbp expectations + * d/p: + - Add patch 0002 to compile documentation properly + + -- Jonas Meurer Mon, 17 Sep 2018 12:45:44 +0200 + mailmanclient (3.1.1-5) unstable; urgency=medium [ Jonas Meurer ] diff -Nru mailmanclient-3.1.1/debian/control mailmanclient-3.2.0/debian/control --- mailmanclient-3.1.1/debian/control 2018-03-16 16:07:29.000000000 +0000 +++ mailmanclient-3.2.0/debian/control 2018-09-20 17:04:19.000000000 +0000 @@ -4,38 +4,19 @@ Jonas Meurer Section: python Priority: optional -Build-Depends: debhelper (>= 11), +Build-Depends: debhelper (>= 11~), dh-python, - python-all, - python-httplib2, - python-six, - python-setuptools, - python-sphinx, - python-sphinx-rtd-theme, python3-all, python3-httplib2, python3-six, python3-setuptools, -Standards-Version: 4.1.3 + python3-sphinx, + python3-sphinx-rtd-theme, +Standards-Version: 4.2.1 Homepage: https://gitlab.com/mailman/mailmanclient Vcs-Browser: https://salsa.debian.org/mailman-team/mailmanclient Vcs-Git: https://salsa.debian.org/mailman-team/mailmanclient.git Testsuite: autopkgtest-pkg-python -X-Python-Version: >= 2.6 - -Package: python-mailmanclient -Architecture: all -Depends: python-httplib2, - python-six, - ${misc:Depends}, - ${python:Depends} -Suggests: mailman3, python-mailmanclient-doc -Description: Python bindings for Mailman3 REST API (Python 2 version) - Mailmanclient provides the official REST API to interact with Mailman3 server. - This library is required by the official web interfaces for archive browsing - and administration of a server. - . - This package contains the Python 2 version of the library. Package: python3-mailmanclient Architecture: all diff -Nru mailmanclient-3.1.1/debian/patches/0001_README_remove_embedded_images.patch mailmanclient-3.2.0/debian/patches/0001_README_remove_embedded_images.patch --- mailmanclient-3.1.1/debian/patches/0001_README_remove_embedded_images.patch 1970-01-01 00:00:00.000000000 +0000 +++ mailmanclient-3.2.0/debian/patches/0001_README_remove_embedded_images.patch 2018-09-20 17:04:19.000000000 +0000 @@ -0,0 +1,31 @@ +From: Jonas Meurer +Date: Mon, 9 Oct 2017 12:00:00 +0200 +Subject: _README_remove_embedded_images + +--- + README.rst | 12 ------------ + 1 file changed, 12 deletions(-) + +diff --git a/README.rst b/README.rst +index d7d1286..db33ed6 100644 +--- a/README.rst ++++ b/README.rst +@@ -18,18 +18,6 @@ + Mailman Client + ============== + +-.. image:: https://gitlab.com/mailman/mailmanclient/badges/master/build.svg +- :target: https://gitlab.com/mailman/mailmanclient/commits/master +- +-.. image:: https://readthedocs.org/projects/mailmanclient/badge +- :target: https://mailmanclient.readthedocs.io +- +-.. image:: http://img.shields.io/pypi/v/mailmanclient.svg +- :target: https://pypi.python.org/pypi/mailmanclient +- +-.. image:: http://img.shields.io/pypi/dm/mailmanclient.svg +- :target: https://pypi.python.org/pypi/mailmanclient +- + The ``mailmanclient`` library provides official Python bindings for the GNU + Mailman 3 REST API. + diff -Nru mailmanclient-3.1.1/debian/patches/0001_sphinx_ignore_pc.patch mailmanclient-3.2.0/debian/patches/0001_sphinx_ignore_pc.patch --- mailmanclient-3.1.1/debian/patches/0001_sphinx_ignore_pc.patch 2018-01-06 20:39:12.000000000 +0000 +++ mailmanclient-3.2.0/debian/patches/0001_sphinx_ignore_pc.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -Description: Make sphinx-build ignore .pc directory - The quilt patch management tool might create the '.pc' directory during - the package build process. In case that '.rst' files are patched by - quilt, they end up in '.pc' as well. A sphinxdoc recusively scans the - sources for '.rst' files, this causes unwanted behaviour. This patch - adds '.pc' to the list of excluded patterns. -Forwarded: https://gitlab.com/mailman/mailmanclient/issues/29 -Author: Jonas Meurer -Last-Update: 2017-10-09 - ---- - conf.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - ---- a/conf.py -+++ b/conf.py -@@ -72,7 +72,7 @@ release = _version - - # List of patterns, relative to source directory, that match files and - # directories to ignore when looking for source files. --exclude_patterns = ['_build', '.tox', 'eggs'] -+exclude_patterns = ['_build', '.tox', 'eggs', '.pc'] - - # The reST default role (used for this markup: `text`) to use for all - # documents. diff -Nru mailmanclient-3.1.1/debian/patches/0002-Put-src-as-APP_ROOT.patch mailmanclient-3.2.0/debian/patches/0002-Put-src-as-APP_ROOT.patch --- mailmanclient-3.1.1/debian/patches/0002-Put-src-as-APP_ROOT.patch 1970-01-01 00:00:00.000000000 +0000 +++ mailmanclient-3.2.0/debian/patches/0002-Put-src-as-APP_ROOT.patch 2018-09-20 17:04:19.000000000 +0000 @@ -0,0 +1,23 @@ +From: =?utf-8?q?Pierre-Elliott_B=C3=A9cue?= +Date: Tue, 24 Jul 2018 18:46:55 +0200 +Subject: Put src as APP_ROOT + + * The doc compilation is done using "import mailmanclient", but + mailmanclient is in 'src'. Changing this. +--- + conf.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/conf.py b/conf.py +index 2965985..4dfccee 100644 +--- a/conf.py ++++ b/conf.py +@@ -16,7 +16,7 @@ import sys + import os + + # import the source code directory into Python Path for use with Auto Module +-APP_ROOT = os.path.dirname(__file__) ++APP_ROOT = os.path.join(os.path.dirname(__file__), 'src') + sys.path.insert(0, APP_ROOT) + + # If extensions (or modules to document with autodoc) are in another directory, diff -Nru mailmanclient-3.1.1/debian/patches/0002_README_remove_embedded_images.patch mailmanclient-3.2.0/debian/patches/0002_README_remove_embedded_images.patch --- mailmanclient-3.1.1/debian/patches/0002_README_remove_embedded_images.patch 2018-01-06 20:39:12.000000000 +0000 +++ mailmanclient-3.2.0/debian/patches/0002_README_remove_embedded_images.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -Description: Remove embedded remote images from README.rst - The documentation files shipped with Debian packages should not embed - remote images, especially not from sources that are known to track - users (like shields.io). - . -Forwarded: not-needed -Author: Jonas Meurer -Last-Update: 2017-10-09 - ---- - README.rst | 12 ------------ - 1 file changed, 12 deletions(-) - ---- a/README.rst -+++ b/README.rst -@@ -18,18 +18,6 @@ - Mailman Client - ============== - --.. image:: https://gitlab.com/mailman/mailmanclient/badges/master/build.svg -- :target: https://gitlab.com/mailman/mailmanclient/commits/master -- --.. image:: https://readthedocs.org/projects/mailmanclient/badge -- :target: https://mailmanclient.readthedocs.io -- --.. image:: http://img.shields.io/pypi/v/mailmanclient.svg -- :target: https://pypi.python.org/pypi/mailmanclient -- --.. image:: http://img.shields.io/pypi/dm/mailmanclient.svg -- :target: https://pypi.python.org/pypi/mailmanclient -- - The ``mailmanclient`` library provides official Python bindings for the GNU - Mailman 3 REST API. - diff -Nru mailmanclient-3.1.1/debian/patches/0003_fix_doc_errors.patch mailmanclient-3.2.0/debian/patches/0003_fix_doc_errors.patch --- mailmanclient-3.1.1/debian/patches/0003_fix_doc_errors.patch 2018-01-06 20:39:12.000000000 +0000 +++ mailmanclient-3.2.0/debian/patches/0003_fix_doc_errors.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -Description: Fixe sphinx doc config and add a missing import - The documentation with sphinx compiles badly because the APP_ROOT - specified in conf.py is erroneous. Furthermore, there is a missing - import in mailmanclient for the documentation to compile. - . -Forwarded: not-needed -Author: Pierre-Elliott Bécue -Last-Update: 2018-01-05 - ---- - conf.py | 2 +- - src/mailmanclient/__init__.py | 1 + - 2 files changed, 2 insertions(+), 1 deletion(-) - ---- a/conf.py -+++ b/conf.py -@@ -16,7 +16,7 @@ - import os - - # import the source code directory into Python Path for use with Auto Module --APP_ROOT = os.path.dirname(__file__) -+APP_ROOT = os.path.join(os.path.dirname(__file__), 'src') - sys.path.insert(0, APP_ROOT) - - # If extensions (or modules to document with autodoc) are in another directory, ---- a/src/mailmanclient/__init__.py -+++ b/src/mailmanclient/__init__.py -@@ -28,6 +28,7 @@ - from mailmanclient.restobjects.address import Address, Addresses - from mailmanclient.restobjects.ban import Bans, BannedAddress - from mailmanclient.restobjects.configuration import Configuration -+from mailmanclient.restobjects.domain import Domain - from mailmanclient.restobjects.header_match import HeaderMatch, HeaderMatches - from mailmanclient.restobjects.held_message import HeldMessage - from mailmanclient.restobjects.archivers import ListArchivers -@@ -47,6 +48,7 @@ - 'BannedAddress', - 'Client', - 'Configuration', -+ 'Domain', - 'HeaderMatch', - 'HeaderMatches', - 'HeldMessage', diff -Nru mailmanclient-3.1.1/debian/patches/series mailmanclient-3.2.0/debian/patches/series --- mailmanclient-3.1.1/debian/patches/series 2018-01-06 20:39:12.000000000 +0000 +++ mailmanclient-3.2.0/debian/patches/series 2018-09-20 17:04:19.000000000 +0000 @@ -1,3 +1,2 @@ -0001_sphinx_ignore_pc.patch -0002_README_remove_embedded_images.patch -0003_fix_doc_errors.patch +0001_README_remove_embedded_images.patch +0002-Put-src-as-APP_ROOT.patch diff -Nru mailmanclient-3.1.1/debian/python-mailmanclient-doc.doc-base mailmanclient-3.2.0/debian/python-mailmanclient-doc.doc-base --- mailmanclient-3.1.1/debian/python-mailmanclient-doc.doc-base 2018-01-06 20:39:12.000000000 +0000 +++ mailmanclient-3.2.0/debian/python-mailmanclient-doc.doc-base 2018-09-20 17:04:19.000000000 +0000 @@ -4,6 +4,6 @@ Section: Programming/Python Format: HTML -Index: /usr/share/doc/python-mailmanclient/html/README.html -Files: /usr/share/doc/python-mailmanclient/html/*.html +Index: /usr/share/doc/python-mailmanclient-doc/html/README.html +Files: /usr/share/doc/python-mailmanclient-doc/html/*.html diff -Nru mailmanclient-3.1.1/debian/README.source mailmanclient-3.2.0/debian/README.source --- mailmanclient-3.1.1/debian/README.source 2018-01-06 20:39:12.000000000 +0000 +++ mailmanclient-3.2.0/debian/README.source 2018-09-20 17:04:19.000000000 +0000 @@ -1,17 +1,3 @@ -W: mailmanclient source: new-package-should-not-package-python2-module python-mailmanclient - - mailmanclient serves as a library to both postorius and hyperkitty packages, - that provide frontend for mailman3 management. Upstream is currently working - on python3 compatibility of hyperkitty/django-mailman3/postorius[1-3]. In - the meantime, this package will provide the python2 bindings along with its - python3 bindings. - - [1] https://gitlab.com/mailman/django-mailman3/merge_requests/20 - [2] https://gitlab.com/mailman/postorius/merge_requests/255 - [3] https://gitlab.com/mailman/hyperkitty/merge_requests/67 - - -- Pierre-Elliott Bécue Mon, 11 Dec 2017 10:21:30 +0100 - General maintenance This package is maintained in Git and uses the git-buildpackage workflow. diff -Nru mailmanclient-3.1.1/debian/rules mailmanclient-3.2.0/debian/rules --- mailmanclient-3.1.1/debian/rules 2018-03-16 20:55:03.000000000 +0000 +++ mailmanclient-3.2.0/debian/rules 2018-09-20 17:04:19.000000000 +0000 @@ -2,7 +2,7 @@ export PYBUILD_NAME=mailmanclient %: - dh $@ --with python2,python3,sphinxdoc --buildsystem=pybuild + dh $@ --with python3,sphinxdoc --buildsystem=pybuild # Tests disabled as they require a running mailman3 server override_dh_auto_test: diff -Nru mailmanclient-3.1.1/debian/upstream/signing-key.asc mailmanclient-3.2.0/debian/upstream/signing-key.asc --- mailmanclient-3.1.1/debian/upstream/signing-key.asc 2018-01-06 20:39:12.000000000 +0000 +++ mailmanclient-3.2.0/debian/upstream/signing-key.asc 2018-09-20 17:04:19.000000000 +0000 @@ -1,635 +1,64 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 -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 -YpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0 -dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNo -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRl -c2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNl -IC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29s -b3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAA -ACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAA -AAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2 -LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQ -zxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAA -AAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAA -BAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt -AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADl -AOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8 -AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJB -AksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4 -A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRj -BHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXF -BdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdh -B3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6 -CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrFCtwK8wsLCyILOQtR -C2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2p -DcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBD -EGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMj -E0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJ -FmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3 -Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1w -HZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1 -IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH -JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpo -KpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9a -L5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSe -NNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2 -OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAj -QGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZn -RqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0C -TUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2 -VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtF -W5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLw -Y0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3 -a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZzAXNd -c7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh -fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH -hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7O -jzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4 -mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG -o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24 -ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR -uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRR -xM7FS8XIxkbGw8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA5 -0LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK -3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG -6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt -9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23////hAIxFeGlmAABNTQAq -AAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAA -AAEAAgAAh2kABAAAAAEAAABaAAAAAAAAHzkAAABvAAAfOQAAAG8AA6ABAAMAAAAB -AAEAAKACAAQAAAABAAAAyKADAAQAAAABAAABOgAAAAD/2wBDAAIBAQIBAQICAQIC -AgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkNDg0M -DgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL -CwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAE6AMgDASIAAhEBAxEB/8QA -HwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA -AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRol -JicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG -h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ -2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQF -BgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEI -FEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RV -VldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmq -srO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oA -DAMBAAIRAxEAPwD4FVMHmnqnORSquGH+cVIoyK9JGAiqO4p4QHtQq8c/lT1HHajY -A8v0HNP8sEZoRQQByM+lSKME4xQA0RfN84zShc9BzTlGf89ad65xmkIaIgD0pRGO -ijn09aGlRdxdgAvUnoarDWYUmUCTYxI2g4BPuc4wPes6laFNe87G1OjOb91XLBTA -478UGMA4NUbe+e7kDu0Vvao2GcyK2enICk/lxUGoeImj3C3GIwoIlYZXOcEcc/gP -xrn/ALQo9zb6jV7GoEwcN9TTNgY4Iwf5Vxt54/lV90uEO7aSnzLj25xW1o2tNKu+ -6mSWRhwAcAcD/P8AOtIYqE/hM5YaUdzX8njI/nTDHn8Kjt9Wt7l9sEobBwwXnn3I -qcFSSD2610KSezMXFoiMeaZ5WB9asbQ2MdTTGXA54q7isQvEOo4pjRgD61ZeEhsN -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 -a92fhLo= -=+bl/ +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 +e4i0uh4IcTw= +=wsKs -----END PGP PUBLIC KEY BLOCK----- diff -Nru mailmanclient-3.1.1/PKG-INFO mailmanclient-3.2.0/PKG-INFO --- mailmanclient-3.1.1/PKG-INFO 2017-10-08 04:53:49.000000000 +0000 +++ mailmanclient-3.2.0/PKG-INFO 2018-07-11 03:19:02.000000000 +0000 @@ -1,12 +1,111 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: mailmanclient -Version: 3.1.1 -Summary: mailmanclient -- python library for Mailman REST API +Version: 3.2.0 +Summary: mailmanclient -- Python bindings for Mailman REST API Home-page: https://www.list.org/ -Author: Barry Warsaw -Author-email: barry@list.org +Maintainer: Barry Warsaw +Maintainer-email: barry@list.org License: LGPLv3 Description: .. + This file is part of mailmanclient. + + mailmanclient is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + mailmanclient is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with mailman.client. If not, see . + + + ============== + Mailman Client + ============== + + .. image:: https://gitlab.com/mailman/mailmanclient/badges/master/build.svg + :target: https://gitlab.com/mailman/mailmanclient/commits/master + + .. image:: https://readthedocs.org/projects/mailmanclient/badge + :target: https://mailmanclient.readthedocs.io + + .. image:: http://img.shields.io/pypi/v/mailmanclient.svg + :target: https://pypi.python.org/pypi/mailmanclient + + .. image:: http://img.shields.io/pypi/dm/mailmanclient.svg + :target: https://pypi.python.org/pypi/mailmanclient + + The ``mailmanclient`` library provides official Python bindings for the GNU + Mailman 3 REST API. + + + Requirements + ============ + + ``mailmanclient`` requires Python 2.7 or newer. + + + Documentation + ============= + + A `simple guide`_ to using the library is available within this package, in + the form of doctests. The manual is also available online at: + + http://mailmanclient.readthedocs.org/en/latest/ + + + Project details + =============== + + The project home page is: + + https://gitlab.com/mailman/mailmanclient + + You should report bugs at: + + https://gitlab.com/mailman/mailmanclient/issues + + You can download the latest version of the package either from the `Cheese Shop`_: + + http://pypi.python.org/pypi/mailmanclient + + or from the GitLab page above. Of course you can also just install it with + ``pip`` from the command line:: + + $ pip install mailmanclient + + You can grab the latest development copy of the code using Git, from the Gitlab + home page above. If you have Git installed, you can grab your own branch of + the code like this:: + + $ git clone https://gitlab.com/mailman/mailmanclient.git + + You may contact the developers via mailman-developers@python.org + + + Acknowledgements + ================ + + Many thanks to Florian Fuchs for his contribution of an initial REST + client. Also thanks to all the contributors of Mailman Client who have + contributed code, raised issues or devoted their time in any capacity! + + + .. toctree:: + :maxdepth: 2 + :caption: Table of Contents + + src/mailmanclient/docs/NEWS.rst + src/mailmanclient/docs/using.rst + src/mailmanclient/docs/apiref.rst + src/mailmanclient/docs/testing.rst + + .. _`simple guide`: https://mailmanclient.readthedocs.io/en/latest/using.html + .. _`Cheese Shop`: https://pypi.python.org/pypi/mailmanclient + Platform: UNKNOWN Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Classifier: Operating System :: POSIX diff -Nru mailmanclient-3.1.1/setup.cfg mailmanclient-3.2.0/setup.cfg --- mailmanclient-3.1.1/setup.cfg 2017-10-08 04:53:49.000000000 +0000 +++ mailmanclient-3.2.0/setup.cfg 2018-07-11 03:19:02.000000000 +0000 @@ -7,5 +7,4 @@ [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -Nru mailmanclient-3.1.1/setup_helpers.py mailmanclient-3.2.0/setup_helpers.py --- mailmanclient-3.1.1/setup_helpers.py 2017-10-04 04:03:59.000000000 +0000 +++ mailmanclient-3.2.0/setup_helpers.py 2018-02-25 20:14:59.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009-2017 The Free Software Foundation, Inc. +# Copyright (C) 2009-2018 by the Free Software Foundation, Inc. # # This file is part of mailman.client # diff -Nru mailmanclient-3.1.1/setup.py mailmanclient-3.2.0/setup.py --- mailmanclient-3.1.1/setup.py 2017-10-04 04:03:59.000000000 +0000 +++ mailmanclient-3.2.0/setup.py 2018-05-19 00:59:48.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailman.client. # @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with mailman.client. If not, see . -from setup_helpers import description, get_version, require_python +from setup_helpers import get_version, require_python from setuptools import setup, find_packages @@ -27,8 +27,8 @@ name='mailmanclient', version=__version__, packages=find_packages('src'), - description='mailmanclient -- python library for Mailman REST API', - long_description=description('README.rst'), + description='mailmanclient -- Python bindings for Mailman REST API', + long_description=open('README.rst').read(), package_dir={'': 'src'}, include_package_data=True, maintainer='Barry Warsaw', diff -Nru mailmanclient-3.1.1/src/mailmanclient/_client.py mailmanclient-3.2.0/src/mailmanclient/_client.py --- mailmanclient-3.1.1/src/mailmanclient/_client.py 2017-10-08 04:31:31.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/_client.py 2018-02-25 20:14:59.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2017 The Free Software Foundation, Inc. +# Copyright (C) 2017-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # diff -Nru mailmanclient-3.1.1/src/mailmanclient/client.py mailmanclient-3.2.0/src/mailmanclient/client.py --- mailmanclient-3.1.1/src/mailmanclient/client.py 2017-08-16 10:03:37.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/client.py 2018-07-11 03:12:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -30,7 +30,9 @@ from mailmanclient.restobjects.member import Member from mailmanclient.restobjects.preferences import Preferences from mailmanclient.restobjects.queue import Queue +from mailmanclient.restobjects.styles import Styles from mailmanclient.restobjects.user import User +from mailmanclient.restobjects.templates import Template, TemplateList from mailmanclient.restbase.connection import Connection from mailmanclient.restbase.page import Page @@ -45,16 +47,17 @@ # class Client: - """Access the Mailman REST API root.""" + """Access the Mailman REST API root. + + :param baseurl: The base url to access the Mailman 3 REST API. + :param name: The Basic Auth user name. If given, the `password` must + also be given. + :param password: The Basic Auth password. If given the `name` must + also be given. + """ def __init__(self, baseurl, name=None, password=None): """Initialize client access to the REST API. - - :param baseurl: The base url to access the Mailman 3 REST API. - :param name: The Basic Auth user name. If given, the `password` must - also be given. - :param password: The Basic Auth password. If given the `name` must - also be given. """ self._connection = Connection(baseurl, name, password) @@ -64,30 +67,48 @@ @property def system(self): + """ + Get the basic system information. + """ return self._connection.call('system/versions')[1] @property def preferences(self): + """ + Get all default system Preferences. + """ return Preferences(self._connection, 'system/preferences') @property def configuration(self): + """ + Get the system configuration. + """ response, content = self._connection.call('system/configuration') return {section: Configuration( self._connection, section) for section in content['sections']} @property def pipelines(self): + """ + Get a list of all Pipelines. + """ response, content = self._connection.call('system/pipelines') return content @property def chains(self): + """ + Get a list of all the Chains. + """ response, content = self._connection.call('system/chains') return content @property def queues(self): + """ + Get a list of all Queues. + """ response, content = self._connection.call('queues') queues = {} for entry in content['entries']: @@ -96,10 +117,23 @@ return queues @property + def styles(self): + return Styles(self._connection, 'lists/styles') + + @property def lists(self): + """ + Get a list of all MailingLists. + """ return self.get_lists() def get_lists(self, advertised=None): + """Get a list of all the MailingLists. + + :param advertised: If marked True, returns all MailingLists including + the ones that aren't advertised. + :type advertised: Bool + """ url = 'lists' if advertised: url += '?advertised=true' @@ -110,6 +144,14 @@ for entry in content['entries']] def get_list_page(self, count=50, page=1, advertised=None): + """ + Get a list of all MailingList with pagination. + + :param count: Number of entries per-page (defaults to 50). + :param page: The page number to return (defaults to 1). + :param advertised: If marked True, returns all MailingLists including + the ones that aren't advertised. + """ url = 'lists' if advertised: url += '?advertised=true' @@ -117,6 +159,9 @@ @property def domains(self): + """ + Get a list of all Domains. + """ response, content = self._connection.call('domains') if 'entries' not in content: return [] @@ -126,6 +171,9 @@ @property def members(self): + """ + Get a list of all the Members. + """ response, content = self._connection.call('members') if 'entries' not in content: return [] @@ -133,6 +181,13 @@ for entry in content['entries']] def get_member(self, fqdn_listname, subscriber_address): + """ + Get the Member object for a given MailingList and Subsciber's Email + Address. + + :param fqdn_listname: Fully qualified address for the MailingList. + :param subscriber_address: Email Address for the subscriber. + """ return self.get_list(fqdn_listname).get_member(subscriber_address) def get_member_page(self, count=50, page=1): @@ -140,6 +195,9 @@ @property def users(self): + """ + Get all the users. + """ response, content = self._connection.call('users') if 'entries' not in content: return [] @@ -148,10 +206,26 @@ key=itemgetter('self_link'))] def get_user_page(self, count=50, page=1): + """ + Get all the users with pagination. + + :param count: Number of entries per-page (defaults to 50). + :param page: The page number to return (defaults to 1). + """ return Page(self._connection, 'users', User, count, page) def create_domain(self, mail_host, base_url=MISSING, - description=None, owner=None): + description=None, owner=None, alias_domain=None): + """ + Create a new Domain. + + :param mail_host: The Mail host for the new domain. If you want + "foo@bar.com" as the address for your MailingList, + use "bar.com" here. + :param description: A brief description for this Domain. + :param owner: Email address for the owner of this list. + :param alias_domain: Alias domain. + """ if base_url is not MISSING: warnings.warn( 'The `base_url` parameter in the `create_domain()` method is ' @@ -162,15 +236,24 @@ data['description'] = description if owner is not None: data['owner'] = owner + if alias_domain is not None: + data['alias_domain'] = alias_domain response, content = self._connection.call('domains', data) return Domain(self._connection, response['location']) def delete_domain(self, mail_host): + """ + Delete a Domain. + + :param mail_host: The Mail host for the domain you want to delete. + """ response, content = self._connection.call( 'domains/{0}'.format(mail_host), None, 'DELETE') def get_domain(self, mail_host, web_host=MISSING): - """Get domain by its mail_host or its web_host.""" + """ + Get Domain by its mail_host. + """ if web_host is not MISSING: warnings.warn( 'The `web_host` parameter in the `get_domain()` method is ' @@ -181,6 +264,13 @@ return Domain(self._connection, content['self_link']) def create_user(self, email, password, display_name=''): + """ + Create a new User. + + :param email: Email address for the new user. + :param password: Password for the new user. + :param display_name: An optional name for the new user. + """ response, content = self._connection.call( 'users', dict(email=email, password=password, @@ -188,27 +278,100 @@ return User(self._connection, response['location']) def get_user(self, address): + """ + Given an Email Address, return the User it belongs to. + + :param address: Email Address of the User. + """ response, content = self._connection.call( 'users/{0}'.format(address)) return User(self._connection, content['self_link'], content) def get_address(self, address): + """ + Given an Email Address, return the Address object. + + :param address: Email address. + """ response, content = self._connection.call( 'addresses/{0}'.format(address)) return Address(self._connection, content['self_link'], content) def get_list(self, fqdn_listname): + """ + Get a MailingList object. + + :param fqdn_listname: Fully qualified name of the MailingList. + """ response, content = self._connection.call( 'lists/{0}'.format(fqdn_listname)) return MailingList(self._connection, content['self_link'], content) def delete_list(self, fqdn_listname): + """ + Delete a MailingList. + + :param fqdn_listname: Fully qualified name of the MailingList. + """ response, content = self._connection.call( 'lists/{0}'.format(fqdn_listname), None, 'DELETE') @property def bans(self): + """ + Get a list of all the bans. + """ return Bans(self._connection, 'bans', mlist=None) def get_bans_page(self, count=50, page=1): + """ + Get a list of all the bans with pagination. + + :param count: Number of entries per-page (defaults to 50). + :param page: The page number to return (defaults to 1). + """ return Page(self._connection, 'bans', BannedAddress, count, page) + + @property + def templates(self): + """Get all site-context templates. + """ + return TemplateList(self._connection, 'uris') + + def get_templates_page(self, count=25, page=1): + """Get paginated site-context templates. + """ + return Page(self._connection, 'uris', Template, count, page) + + def set_template(self, template_name, url, username=None, password=None): + """Set template in site-context. + """ + data = {template_name: url} + if username is not None and password is not None: + data['username'] = username + data['password'] = password + return self._connection.call('uris', data, 'PATCH')[1] + + def find_lists(self, subscriber, role=None, count=50, page=1): + """ + Given a subscriber and a role, return all the list they are subscribed + to with given role. + + If no role is specified all the related mailing lists are returned + without duplicates, even though there can potentially be multiple + memberships of a user in a single mailing list. + + :param subscriber: The address of the subscriber. + :type subscriber: str + :param role: owner, moderator or subscriber + :type role: str + """ + url = 'lists/find' + data = dict(subscriber=subscriber, count=count, page=page) + if role is not None: + data['role'] = role + response, content = self._connection.call(url, data) + if 'entries' not in content: + return [] + return [MailingList(self._connection, entry['self_link'], entry) + for entry in content['entries']] diff -Nru mailmanclient-3.1.1/src/mailmanclient/constants.py mailmanclient-3.2.0/src/mailmanclient/constants.py --- mailmanclient-3.1.1/src/mailmanclient/constants.py 2017-10-08 04:51:20.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/constants.py 2018-07-11 03:14:44.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -15,7 +15,7 @@ # along with mailmanclient. If not, see . -__version__ = '3.1.1' +__version__ = '3.2.0' DEFAULT_PAGE_ITEM_COUNT = 50 MISSING = object() diff -Nru mailmanclient-3.1.1/src/mailmanclient/docs/apiref.rst mailmanclient-3.2.0/src/mailmanclient/docs/apiref.rst --- mailmanclient-3.1.1/src/mailmanclient/docs/apiref.rst 2017-05-25 05:07:45.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/docs/apiref.rst 2018-02-25 20:14:59.000000000 +0000 @@ -8,62 +8,62 @@ :private-members: :inherited-members: -.. autoclass:: mailmanclient._client.Domain +.. autoclass:: mailmanclient.Domain :members: :undoc-members: -.. autoclass:: mailmanclient._client.MailingList +.. autoclass:: mailmanclient.MailingList :members: :undoc-members: -.. autoclass:: mailmanclient._client.ListArchivers +.. autoclass:: mailmanclient.ListArchivers :members: :undoc-members: -.. autoclass:: mailmanclient._client.Bans +.. autoclass:: mailmanclient.Bans :members: :undoc-members: -.. autoclass:: mailmanclient._client.BannedAddress +.. autoclass:: mailmanclient.BannedAddress :members: :undoc-members: -.. autoclass:: mailmanclient._client.HeaderMatches +.. autoclass:: mailmanclient.HeaderMatches :members: :undoc-members: -.. autoclass:: mailmanclient._client.HeaderMatch +.. autoclass:: mailmanclient.HeaderMatch :members: :undoc-members: -.. autoclass:: mailmanclient._client.Member +.. autoclass:: mailmanclient.Member :members: :undoc-members: -.. autoclass:: mailmanclient._client.User +.. autoclass:: mailmanclient.User :members: :undoc-members: -.. autoclass:: mailmanclient._client.Addresses +.. autoclass:: mailmanclient.Addresses :members: :undoc-members: -.. autoclass:: mailmanclient._client.Address +.. autoclass:: mailmanclient.Address :members: :undoc-members: -.. autoclass:: mailmanclient._client.HeldMessage +.. autoclass:: mailmanclient.HeldMessage :members: :undoc-members: -.. autoclass:: mailmanclient._client.Preferences +.. autoclass:: mailmanclient.Preferences :members: :undoc-members: -.. autoclass:: mailmanclient._client.Settings +.. autoclass:: mailmanclient.Settings :members: :undoc-members: -.. autoclass:: mailmanclient._client.Queue +.. autoclass:: mailmanclient.Queue :members: :undoc-members: diff -Nru mailmanclient-3.1.1/src/mailmanclient/docs/conftest.py mailmanclient-3.2.0/src/mailmanclient/docs/conftest.py --- mailmanclient-3.1.1/src/mailmanclient/docs/conftest.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/docs/conftest.py 2018-02-25 20:14:59.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2017 The Free Software Foundation, Inc. +# Copyright (C) 2017-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # diff -Nru mailmanclient-3.1.1/src/mailmanclient/docs/NEWS.rst mailmanclient-3.2.0/src/mailmanclient/docs/NEWS.rst --- mailmanclient-3.1.1/src/mailmanclient/docs/NEWS.rst 2017-10-08 04:49:22.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/docs/NEWS.rst 2018-07-11 03:16:34.000000000 +0000 @@ -3,6 +3,35 @@ ======================= +3.2.0 (2018-07-10) +================== + +Changes +------- + +* Add '.pc' (patch directory) to list of ignored patterns when building the + documentation with Sphinx. +* `Mailinglist.add_owner` and `Mailinglist.add_moderator` now accept an + additional `display_name` argument that allows associating display names with + these memberships. +* Add a new API ``Client.find_lists`` which allows filtering mailing lists + related to a subscriber. It optionally allows a role, which filters the lists + that the address is subscribed to with that role. + +Backwards Incompatible Changes +------------------------------- + +* `MailingList.owners` and `MailingList.moderators` now returns a list of + `Member` objects instead of a list of emails. + + + +Backwards Incompatible Changes +------------------------------- + +* `Domain.owners` now returns a list of `User` objects instead of just a dictionary of + JSON response. (!63) + 3.1.1 (2017-10-07) ================== diff -Nru mailmanclient-3.1.1/src/mailmanclient/docs/using.rst mailmanclient-3.2.0/src/mailmanclient/docs/using.rst --- mailmanclient-3.1.1/src/mailmanclient/docs/using.rst 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/docs/using.rst 2018-05-19 00:59:48.000000000 +0000 @@ -43,39 +43,50 @@ is returned. >>> example_dot_com = client.create_domain('example.com') - >>> example_dot_com - >>> print(example_dot_com.description) None >>> print(example_dot_com.mail_host) example.com + >>> print(example_dot_com.alias_domain) + None + +A domain can have an alias_domain attribute to help with some unusual Postfix +configurations. + + >>> example_dot_edu = client.create_domain('example.edu', + ... alias_domain='x.example.edu') + >>> print(example_dot_edu.mail_host) + example.edu + >>> print(example_dot_edu.alias_domain) + x.example.edu You can also get an existing domain independently using its mail host. >>> example = client.get_domain('example.com') - >>> example - + >>> print(example.mail_host) + example.com After creating a few more domains, we can print the list of all domains. - >>> client.create_domain('example.net') - + >>> example_net = client.create_domain('example.net') >>> example_org = client.create_domain('example.org') - >>> print(example_org) - - >>> for mail_host in client.domains: - ... print(mail_host) - - - + >>> print(example_org.mail_host) + example.org + >>> for domain in client.domains: + ... print(domain.mail_host) + example.com + example.edu + example.net + example.org Also, domain can be deleted. >>> example_org.delete() - >>> for mail_host in client.domains: - ... print(mail_host) - - + >>> for domain in client.domains: + ... print(domain.mail_host) + example.com + example.edu + example.net Mailing lists @@ -84,8 +95,6 @@ Once you have a domain, you can create mailing lists in that domain. >>> test_one = example.create_list('test-1') - >>> test_one - >>> print(test_one.fqdn_listname) test-1@example.com >>> print(test_one.mail_host) @@ -95,29 +104,48 @@ >>> print(test_one.display_name) Test-1 +You can create a mailing list with a specific list style. + + >>> test_two = example.create_list('test-announce', style_name='legacy-announce') + >>> print(test_two.fqdn_listname) + test-announce@example.com + +You can retrieve a list of known mailing list styles along with the default +one. + + >>> styles = client.styles + >>> from operator import itemgetter + >>> for style in sorted(styles['styles'], key=itemgetter('name')): + ... print('{0}: {1}'.format(style['name'], style['description'])) + legacy-announce: Announce only mailing list style. + legacy-default: Ordinary discussion mailing list style. + >>> print(styles['default']) + legacy-default + You can also retrieve the mailing list after the fact. >>> my_list = client.get_list('test-1@example.com') - >>> my_list - + >>> print(my_list.fqdn_listname) + test-1@example.com And you can print all the known mailing lists. :: - >>> example.create_list('test-2') - + >>> print(example.create_list('test-2').fqdn_listname) + test-2@example.com >>> domain = client.get_domain('example.net') - >>> domain.create_list('test-3') - - >>> example.create_list('test-3') - + >>> print(domain.create_list('test-3').fqdn_listname) + test-3@example.net + >>> print(example.create_list('test-3').fqdn_listname) + test-3@example.com >>> for mlist in client.lists: - ... print(mlist) - - - - + ... print(mlist.fqdn_listname) + test-1@example.com + test-2@example.com + test-3@example.com + test-3@example.net + test-announce@example.com You can also select advertised lists only. :: @@ -125,10 +153,11 @@ >>> my_list.settings['advertised'] = False >>> my_list.settings.save() >>> for mlist in client.get_lists(advertised=True): - ... print(mlist) - - - + ... print(mlist.fqdn_listname) + test-2@example.com + test-3@example.com + test-3@example.net + test-announce@example.com List results can be retrieved as pages: @@ -138,46 +167,48 @@ >>> len(page) 2 >>> page.total_size - 4 + 5 >>> for m_list in page: - ... print(m_list) - - + ... print(m_list.fqdn_listname) + test-1@example.com + test-2@example.com >>> page = page.next >>> page.nr 2 >>> for m_list in page: - ... print(m_list) - - + ... print(m_list.fqdn_listname) + test-3@example.com + test-3@example.net Pages can also use the advertised filter: >>> page = client.get_list_page(count=2, page=1, advertised=True) >>> for m_list in page: - ... print(m_list) - - + ... print(m_list.fqdn_listname) + test-2@example.com + test-3@example.com If you only want to know all lists for a specific domain, use the domain object. >>> for mlist in example.lists: - ... print(mlist) - - - + ... print(mlist.fqdn_listname) + test-1@example.com + test-2@example.com + test-3@example.com + test-announce@example.com It is also possible to display only advertised lists when using the domain. >>> for mlist in example.get_lists(advertised=True): - ... print(mlist) - - + ... print(mlist.fqdn_listname) + test-2@example.com + test-3@example.com + test-announce@example.com >>> for mlist in example.get_list_page(count=2, page=1, advertised=True): - ... print(mlist) - - + ... print(mlist.fqdn_listname) + test-2@example.com + test-3@example.com You can use a list instance to delete the list. @@ -189,10 +220,10 @@ >>> client.delete_list('test-3@example.com') >>> for mlist in client.lists: - ... print(mlist) - - - + ... print(mlist.fqdn_listname) + test-1@example.com + test-2@example.com + test-announce@example.com Membership ========== @@ -245,42 +276,50 @@ approval and should only be used if the subscription is initiated by a moderator or admin. - >>> test_one.subscribe('anna@example.com', 'Anna', - ... pre_verified=True, - ... pre_confirmed=True) - - - >>> test_one.subscribe('bill@example.com', 'Bill', - ... pre_verified=True, - ... pre_confirmed=True) - - - >>> test_two.subscribe('anna@example.com', - ... pre_verified=True, - ... pre_confirmed=True) - - - >>> test_two.subscribe('cris@example.com', 'Cris', - ... pre_verified=True, - ... pre_confirmed=True) - + >>> print(test_one.subscribe('anna@example.com', 'Anna', + ... pre_verified=True, + ... pre_confirmed=True)) + Member "anna@example.com" on "test-1.example.com" + + >>> print(test_one.subscribe('bill@example.com', 'Bill', + ... pre_verified=True, + ... pre_confirmed=True)) + Member "bill@example.com" on "test-1.example.com" + + >>> print(test_two.subscribe('anna@example.com', + ... pre_verified=True, + ... pre_confirmed=True)) + Member "anna@example.com" on "test-2.example.com" + + >>> print(test_two.subscribe('cris@example.com', 'Cris', + ... pre_verified=True, + ... pre_confirmed=True)) + Member "cris@example.com" on "test-2.example.com" We can retrieve all known memberships. These are sorted first by mailing list name, then by email address. >>> for member in client.members: ... print(member) - - - - + Member "anna@example.com" on "test-1.example.com" + Member "bill@example.com" on "test-1.example.com" + Member "anna@example.com" on "test-2.example.com" + Member "cris@example.com" on "test-2.example.com" We can also view the memberships for a single mailing list. >>> for member in test_one.members: ... print(member) - - + Member "anna@example.com" on "test-1.example.com" + Member "bill@example.com" on "test-1.example.com" + +Membership may have a name associated, this depends on whether the member ``Address`` +or ``User`` has a ``display_name`` attribute. + + >>> for member in test_one.members: + ... print(member.display_name) + Anna + Bill Membership lists can be paginated, to recieve only a part of the result. @@ -291,16 +330,16 @@ 4 >>> for member in page: ... print(member) - - + Member "anna@example.com" on "test-1.example.com" + Member "bill@example.com" on "test-1.example.com" >>> page = page.next >>> page.nr 2 >>> for member in page: ... print(member) - - + Member "anna@example.com" on "test-2.example.com" + Member "cris@example.com" on "test-2.example.com" >>> page = test_one.get_member_page(count=1, page=1) >>> page.nr @@ -309,7 +348,7 @@ 2 >>> for member in page: ... print(member) - + Member "anna@example.com" on "test-1.example.com" >>> page = page.next >>> page.nr 2 @@ -317,20 +356,22 @@ 2 >>> for member in page: ... print(member) - + Member "bill@example.com" on "test-1.example.com" We can get a single membership too. >>> cris_test_two = test_two.get_member('cris@example.com') - >>> cris_test_two - + >>> print(cris_test_two) + Member "cris@example.com" on "test-2.example.com" >>> print(cris_test_two.role) member + >>> print(cris_test_two.display_name) + Cris A membership can also be retrieved without instantiating the list object first: - >>> client.get_member('test-2@example.com', 'cris@example.com') - + >>> print(client.get_member('test-2@example.com', 'cris@example.com')) + Member "cris@example.com" on "test-2.example.com" A membership has preferences. @@ -352,8 +393,9 @@ The membership object's ``user`` attribute will return a User object: - >>> cris_test_two.user - + >>> cris_u = cris_test_two.user + >>> print(cris_u.display_name, cris_u.user_id) + Cris ... If you use an address which is not a member of test_two `ValueError` is raised: @@ -370,17 +412,17 @@ >>> test_one.unsubscribe('anna@example.com') >>> for member in client.members: ... print(member) - - - + Member "bill@example.com" on "test-1.example.com" + Member "anna@example.com" on "test-2.example.com" + Member "cris@example.com" on "test-2.example.com" A little later, Cris decides to unsubscribe from the Test Two mailing list. >>> cris_test_two.unsubscribe() >>> for member in client.members: ... print(member) - - + Member "bill@example.com" on "test-1.example.com" + Member "anna@example.com" on "test-2.example.com" If you try to unsubscribe an address which is not a member address `ValueError` is raised: @@ -416,10 +458,12 @@ access the clients user property. >>> for user in client.users: - ... print(user) - - - + ... print(user.display_name) + Unverified + Unconfirmed + Anna + Bill + Cris The list of users can also be paginated: @@ -430,11 +474,11 @@ 5 >>> for user in page: - ... print(user) - - - - + ... print(user.display_name) + Unverified + Unconfirmed + Anna + Bill You can get the next or previous pages without calling ``get_userpage`` again. @@ -443,19 +487,19 @@ 2 >>> for user in page: - ... print(user) - + ... print(user.display_name) + Cris >>> page = page.previous >>> page.nr 1 >>> for user in page: - ... print(user) - - - - + ... print(user.display_name) + Unverified + Unconfirmed + Anna + Bill A single user can be retrieved using their email address. @@ -475,7 +519,7 @@ Multiple addresses can be assigned to a user record: - >>> cris.add_address('cris.person@example.org') + >>> print(cris.add_address('cris.person@example.org')) cris.person@example.org >>> print(client.get_address('cris.person@example.org')) cris.person@example.org @@ -487,10 +531,11 @@ Trying to add an existing address will raise an error: - >>> client.create_user(email='dana@example.org', - ... password='somepass', - ... display_name='Dana') - + >>> dana = client.create_user(email='dana@example.org', + ... password='somepass', + ... display_name='Dana') + >>> print(dana.display_name) + Dana >>> cris.add_address('dana@example.org') # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... @@ -498,7 +543,7 @@ This can be overridden by using the ``absorb_existing`` flag: - >>> cris.add_address('dana@example.org', absorb_existing=True) + >>> print(cris.add_address('dana@example.org', absorb_existing=True)) dana@example.org The user Chris will then be merged with Dana, acquiring all its subscriptions @@ -550,10 +595,11 @@ Users can be added using ``create_user``. The display_name is optional: - >>> client.create_user(email='ler@primus.org', - ... password='somepass', - ... display_name='Ler') - + >>> ler = client.create_user(email='ler@primus.org', + ... password='somepass', + ... display_name='Ler') + >>> print(ler.display_name) + Ler >>> ler = client.get_user('ler@primus.org') >>> print(ler.password) $... @@ -588,7 +634,7 @@ >>> bill = client.get_user('bill@example.com') >>> for subscription in bill.subscriptions: ... print(subscription) - + Member "bill@example.com" on "test-1.example.com" If all you need are the list ids of all mailing lists a user is subscribed to, you can use the ``subscription_list_ids`` property. @@ -606,7 +652,7 @@ >>> settings = test_one.settings >>> len(settings) - 56 + 57 >>> for attr in sorted(settings): ... print(attr + ': ' + str(settings[attr])) @@ -695,6 +741,7 @@ default-owner-chain default-posting-chain discard + dmarc header-match hold moderation @@ -711,45 +758,48 @@ >>> test_one.moderators [] -Owners can be added via the ``add_owner`` method: +Owners can be added via the ``add_owner`` method and they can have an optional +``display_name`` associated like other ``members``: - >>> test_one.add_owner('foo@example.com') + >>> test_one.add_owner('foo@example.com', display_name='Foo') >>> for owner in test_one.owners: - ... print(owner) + ... print(owner.email) foo@example.com The owner of the list not automatically added as a member: - >>> test_one.members - [] + >>> for m in test_one.members: + ... print(m) + Member "bill@example.com" on "test-1.example.com" Moderators can be added similarly: - >>> test_one.add_moderator('bar@example.com') + >>> test_one.add_moderator('bar@example.com', display_name='Bar') >>> for moderator in test_one.moderators: - ... print(moderator) + ... print(moderator.email) bar@example.com Moderators are also not automatically added as members: - >>> test_one.members - [] + >>> for m in test_one.members: + ... print(m) + Member "bill@example.com" on "test-1.example.com" Members and owners/moderators are separate entries in in the general members list: - >>> test_one.subscribe('bar@example.com', 'Bar', - ... pre_verified=True, - ... pre_confirmed=True) - + >>> print(test_one.subscribe('bar@example.com', 'Bar', + ... pre_verified=True, + ... pre_confirmed=True)) + Member "bar@example.com" on "test-1.example.com" >>> for member in client.members: - ... print('%s: %s' %(member, member.role)) - : owner - : moderator - : member - : member - : member + ... print('%s: %s' % (member, member.role)) + Member "foo@example.com" on "test-1.example.com": owner + Member "bar@example.com" on "test-1.example.com": moderator + Member "bar@example.com" on "test-1.example.com": member + Member "bill@example.com" on "test-1.example.com": member + Member "anna@example.com" on "test-2.example.com": member Both owners and moderators can be removed: @@ -949,18 +999,22 @@ anna@example.com >>> 'anna@example.com' in client.bans True - >>> client.bans.add('bill@example.com') + >>> print(client.bans.add('bill@example.com')) + bill@example.com + >>> for addr in list(client.bans): + ... print(addr) + anna@example.com bill@example.com - >>> print(list(client.bans)) - [anna@example.com, bill@example.com] The list of banned addresses can be paginated using the ``get_bans_page()`` method:: - >>> print(list(client.get_bans_page(count=1, page=1))) - [anna@example.com] - >>> print(list(client.get_bans_page(count=1, page=2))) - [bill@example.com] + >>> for addr in list(client.get_bans_page(count=1, page=1)): + ... print(addr) + anna@example.com + >>> for addr in list(client.get_bans_page(count=1, page=2)): + ... print(addr) + bill@example.com You can use the ``delete()`` method on a banned address to unban it, or the ``remove()`` method on the ban list:: @@ -968,8 +1022,9 @@ >>> banned_anna.delete() >>> 'anna@example.com' in client.bans False - >>> print(list(client.bans)) - [bill@example.com] + >>> for addr in list(client.bans): + ... print(addr) + bill@example.com >>> client.bans.remove('bill@example.com') >>> 'bill@example.com' in client.bans False @@ -984,14 +1039,18 @@ >>> banned_anna = test_one.bans.add('anna@example.com') >>> 'anna@example.com' in test_one.bans True - >>> test_one.bans.add('bill@example.com') + >>> print(test_one.bans.add('bill@example.com')) + bill@example.com + >>> for addr in list(test_one.bans): + ... print(addr) + anna@example.com + bill@example.com + >>> for addr in list(test_one.get_bans_page(count=1, page=1)): + ... print(addr) + anna@example.com + >>> for addr in list(test_one.get_bans_page(count=1, page=2)): + ... print(addr) bill@example.com - >>> print(list(test_one.bans)) - [anna@example.com, bill@example.com] - >>> print(list(test_one.get_bans_page(count=1, page=1))) - [anna@example.com] - >>> print(list(test_one.get_bans_page(count=1, page=2))) - [bill@example.com] >>> banned_anna.delete() >>> 'anna@example.com' in test_one.bans False @@ -1008,7 +1067,7 @@ >>> archivers = test_one.archivers >>> print(archivers) - + Archivers on test-1.example.com The activation status of each available archiver can be accessed like a key in a dictionary. @@ -1051,7 +1110,7 @@ >>> header_matches = test_one.header_matches >>> print(header_matches) - + Header matches for "test-1.example.com" >>> len(header_matches) 0 @@ -1062,14 +1121,15 @@ - the action to take when the header matches the pattern. This can be ``'accept'``, ``'discard'``, ``'reject'``, or ``'hold'``. - >>> header_matches.add('Subject', '^test: ', 'discard') - + >>> print(header_matches.add('Subject', '^test: ', 'discard')) + Header match on "subject" >>> print(header_matches) - + Header matches for "test-1.example.com" >>> len(header_matches) 1 - >>> print(list(header_matches)) - [] + >>> for hm in list(header_matches): + ... print(hm) + Header match on "subject" You can delete a header match by deleting it from the ``header_matches`` collection. @@ -1082,8 +1142,8 @@ that the collection will not automatically be updated. Get a new collection from the list's ``header_matches`` attribute to see the change. - >>> header_matches.add('Subject', '^test: ', 'discard') - + >>> print(header_matches.add('Subject', '^test: ', 'discard')) + Header match on "subject" >>> header_matches[0].delete() >>> len(header_matches) # not automatically updated 1 @@ -1099,95 +1159,97 @@ >>> cfg = client.configuration >>> for key in sorted(cfg): - ... print(cfg[key]) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ... print(cfg[key].name) + antispam + archiver.mail_archive + archiver.master + archiver.mhonarc + archiver.prototype + bounces + database + devmode + digests + dmarc + language.ar + language.ast + language.ca + language.cs + language.da + language.de + language.el + language.en + language.es + language.et + language.eu + language.fi + language.fr + language.gl + language.he + language.hr + language.hu + language.ia + language.it + language.ja + language.ko + language.lt + language.nl + language.no + language.pl + language.pt + language.pt_BR + language.ro + language.ru + language.sk + language.sl + language.sr + language.sv + language.tr + language.uk + language.vi + language.zh_CN + language.zh_TW + logging.archiver + logging.bounce + logging.config + logging.database + logging.debug + logging.error + logging.fromusenet + logging.http + logging.locks + logging.mischief + logging.plugins + logging.root + logging.runner + logging.smtp + logging.subscribe + logging.vette + mailman + mta + nntp + passwords + paths.dev + paths.fhs + paths.here + paths.local + plugin.master + runner.archive + runner.bad + runner.bounces + runner.command + runner.digest + runner.in + runner.lmtp + runner.nntp + runner.out + runner.pipeline + runner.rest + runner.retry + runner.shunt + runner.virgin + shell + styles + webservice Each configuration object is a dictionary and you can iterate over them: diff -Nru mailmanclient-3.1.1/src/mailmanclient/__init__.py mailmanclient-3.2.0/src/mailmanclient/__init__.py --- mailmanclient-3.1.1/src/mailmanclient/__init__.py 2017-10-08 04:31:31.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/__init__.py 2018-02-25 20:14:59.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -28,6 +28,7 @@ from mailmanclient.restobjects.address import Address, Addresses from mailmanclient.restobjects.ban import Bans, BannedAddress from mailmanclient.restobjects.configuration import Configuration +from mailmanclient.restobjects.domain import Domain from mailmanclient.restobjects.header_match import HeaderMatch, HeaderMatches from mailmanclient.restobjects.held_message import HeldMessage from mailmanclient.restobjects.archivers import ListArchivers @@ -47,6 +48,7 @@ 'BannedAddress', 'Client', 'Configuration', + 'Domain' 'HeaderMatch', 'HeaderMatches', 'HeldMessage', diff -Nru mailmanclient-3.1.1/src/mailmanclient/restbase/base.py mailmanclient-3.2.0/src/mailmanclient/restbase/base.py --- mailmanclient-3.1.1/src/mailmanclient/restbase/base.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restbase/base.py 2018-07-11 03:11:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # diff -Nru mailmanclient-3.1.1/src/mailmanclient/restbase/connection.py mailmanclient-3.2.0/src/mailmanclient/restbase/connection.py --- mailmanclient-3.1.1/src/mailmanclient/restbase/connection.py 2017-09-28 20:19:48.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restbase/connection.py 2018-07-11 03:11:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -15,10 +15,9 @@ # along with mailmanclient. If not, see . import json from base64 import b64encode -from six.moves.urllib_error import HTTPError -from six.moves.urllib_parse import urljoin, urlencode +from urllib.error import HTTPError +from urllib.parse import urljoin, urlencode -import six from httplib2 import Http from mailmanclient.constants import __version__ @@ -81,10 +80,7 @@ } data_str = None if data is not None: - for k, v in data.items(): - if not isinstance(v, bytes): - data[k] = six.text_type(v).encode('utf-8') - data_str = urlencode(data, doseq=True) + data_str = urlencode(data, doseq=True, encoding='utf-8') headers['Content-Type'] = 'application/x-www-form-urlencoded' if method is None: if data_str is None: @@ -104,7 +100,7 @@ if len(content) == 0: return response, None # XXX Work around for http://bugs.python.org/issue10038 - if isinstance(content, six.binary_type): + if isinstance(content, bytes): content = content.decode('utf-8') return response, json.loads(content) except HTTPError: diff -Nru mailmanclient-3.1.1/src/mailmanclient/restbase/page.py mailmanclient-3.2.0/src/mailmanclient/restbase/page.py --- mailmanclient-3.1.1/src/mailmanclient/restbase/page.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restbase/page.py 2018-07-11 03:11:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/address.py mailmanclient-3.2.0/src/mailmanclient/restobjects/address.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/address.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/address.py 2018-07-11 03:11:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -51,6 +51,9 @@ 'self_link', 'verified_on') def __repr__(self): + return '
'.format(self.email) + + def __str__(self): return self.email @property diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/archivers.py mailmanclient-3.2.0/src/mailmanclient/restobjects/archivers.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/archivers.py 2017-08-16 10:11:25.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/archivers.py 2018-04-22 00:08:49.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -42,4 +42,7 @@ self._mlist = mlist def __repr__(self): - return ''.format(self._mlist.list_id) + return ''.format(self._mlist.list_id) + + def __str__(self): + return 'Archivers on {}'.format(self._mlist.list_id) diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/ban.py mailmanclient-3.2.0/src/mailmanclient/restobjects/ban.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/ban.py 2017-08-16 10:04:07.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/ban.py 2018-04-22 00:09:56.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -45,7 +45,7 @@ if self._mlist is None: return '' else: - return ''.format(self._mlist.list_id) + return ''.format(self._mlist.list_id) def __contains__(self, item): # Accept email addresses and BannedAddress restobjects @@ -92,6 +92,9 @@ _writable_properties = [] def __repr__(self): + return ''.format(self.email) + + def __str__(self): return self.email @property diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/configuration.py mailmanclient-3.2.0/src/mailmanclient/restobjects/configuration.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/configuration.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/configuration.py 2018-04-22 00:08:49.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -31,4 +31,4 @@ self.name = name def __repr__(self): - return ''.format(self.name) + return ''.format(self.name) diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/domain.py mailmanclient-3.2.0/src/mailmanclient/restobjects/domain.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/domain.py 2017-08-16 10:03:56.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/domain.py 2018-05-06 02:04:37.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -16,6 +16,8 @@ import warnings from mailmanclient.restobjects.mailinglist import MailingList +from mailmanclient.restobjects.templates import TemplateList +from mailmanclient.restobjects.user import User from mailmanclient.restbase.base import RESTObject from mailmanclient.restbase.page import Page @@ -27,10 +29,13 @@ class Domain(RESTObject): - _properties = ('description', 'mail_host', 'self_link') + _properties = ('alias_domain', 'description', 'mail_host', 'self_link') def __repr__(self): - return ''.format(self.mail_host) + return ''.format(self.mail_host) + + def __str__(self): + return self.mail_host @property def web_host(self): @@ -55,7 +60,8 @@ if 'entries' not in content: return [] else: - return [item for item in content['entries']] + return [User(self._connection, entry['self_link'], entry) + for entry in content['entries']] @property def lists(self): @@ -77,15 +83,20 @@ url += '?advertised=true' return Page(self._connection, url, MailingList, count, page) - def create_list(self, list_name): + def create_list(self, list_name, style_name=None): fqdn_listname = '{0}@{1}'.format(list_name, self.mail_host) - response, content = self._connection.call( - 'lists', dict(fqdn_listname=fqdn_listname)) + data = dict(fqdn_listname=fqdn_listname) + if style_name is not None: + data['style_name'] = style_name + response, content = self._connection.call('lists', data) return MailingList(self._connection, response['location']) + # TODO: Add this when the API supports removing a single owner. # def remove_owner(self, owner): - # TODO: add this when API supports it. - # pass + # url = self._url + '/owners/{}'.format(owner) + # response, content = self._connection.call( + # url, method='DELETE') + # return response def remove_all_owners(self): url = self._url + '/owners' @@ -97,3 +108,16 @@ url = self._url + '/owners' response, content = self._connection.call( url, {'owner': owner}) + + @property + def templates(self): + url = self._url + '/uris' + return TemplateList(self._connection, url) + + def set_template(self, template_name, uri, username=None, password=None): + url = self._url + '/uris' + data = {template_name: uri} + if username is not None and password is not None: + data['username'] = username + data['password'] = password + return self._connection.call(url, data, 'PATCH')[1] diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/header_match.py mailmanclient-3.2.0/src/mailmanclient/restobjects/header_match.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/header_match.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/header_match.py 2018-04-22 00:08:49.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -38,7 +38,10 @@ self._connection, data['self_link'], data) def __repr__(self): - return ''.format(self._mlist.list_id) + return ''.format(self._mlist.list_id) + + def __str__(self): + return 'Header matches for "{}"'.format(self._mlist.list_id) def add(self, header, pattern, action=None): """ @@ -64,4 +67,7 @@ _writable_properties = ('header', 'pattern', 'position', 'action') def __repr__(self): - return ''.format(self.header) + return ''.format(self.header) + + def __str__(self): + return 'Header match on "{}"'.format(self.header) diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/held_message.py mailmanclient-3.2.0/src/mailmanclient/restobjects/held_message.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/held_message.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/held_message.py 2018-06-15 01:43:33.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -29,7 +29,7 @@ 'self_link', 'sender', 'subject', 'type') def __repr__(self): - return ''.format( + return ''.format( self.request_id, self.sender) def __unicode__(self): diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/mailinglist.py mailmanclient-3.2.0/src/mailmanclient/restobjects/mailinglist.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/mailinglist.py 2017-08-16 10:18:55.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/mailinglist.py 2018-07-11 03:12:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -23,6 +23,7 @@ from mailmanclient.restobjects.member import Member from mailmanclient.restobjects.settings import Settings from mailmanclient.restobjects.held_message import HeldMessage +from mailmanclient.restobjects.templates import TemplateList from mailmanclient.restbase.base import RESTObject from mailmanclient.restbase.page import Page @@ -42,7 +43,7 @@ self._settings = None def __repr__(self): - return ''.format(self.fqdn_listname) + return ''.format(self.fqdn_listname) @property def owners(self): @@ -51,7 +52,9 @@ if 'entries' not in content: return [] else: - return [item['email'] for item in content['entries']] + return [Member(self._connection, entry['self_link'], entry) + for entry in sorted(content['entries'], + key=itemgetter('address'))] @property def moderators(self): @@ -60,7 +63,9 @@ if 'entries' not in content: return [] else: - return [item['email'] for item in content['entries']] + return [Member(self._connection, entry['self_link'], entry) + for entry in sorted(content['entries'], + key=itemgetter('address'))] @property def members(self): @@ -160,15 +165,16 @@ archivers.update(new_value) archivers.save() - def add_owner(self, address): - self.add_role('owner', address) + def add_owner(self, address, display_name=None): + self.add_role('owner', address, display_name) - def add_moderator(self, address): - self.add_role('moderator', address) + def add_moderator(self, address, display_name=None): + self.add_role('moderator', address, display_name) - def add_role(self, role, address): + def add_role(self, role, address, display_name=None): data = dict(list_id=self.list_id, subscriber=address, + display_name=display_name, role=role) self._connection.call('members', data) @@ -331,3 +337,67 @@ def header_matches(self): url = 'lists/{0}/header-matches'.format(self.list_id) return HeaderMatches(self._connection, url, self) + + @property + def templates(self): + url = self._url + '/uris' + return TemplateList(self._connection, url) + + def set_template(self, template_name, uri, username=None, password=None): + url = self._url + '/uris' + data = {template_name: uri} + if username is not None and password is not None: + data['username'] = username + data['password'] = password + return self._connection.call(url, data, 'PATCH')[1] + + def _check_membership(self, address, allowed_roles): + """ + Given an address and role, check if there is a membership record that + matches the given address with a given role for this Mailing List. + """ + url = 'members/find' + data = {'subscriber': address, + 'list_id': self.list_id} + response, content = self._connection.call(url, data=data) + if 'entries' not in content: + return False + for membership in content['entries']: + # We check for all the returned roles for this User and MailingList + if membership['role'] in allowed_roles: + return True + return False + + def is_owner(self, address): + """ + Given an address, checks if the given address is an owner of this + mailing list. + """ + return self._check_membership(address=address, + allowed_roles=('owner',)) + + def is_moderator(self, address): + """ + Given an address, checks if the given address is a moderator of this + mailing list. + """ + return self._check_membership(address=address, + allowed_roles=('moderator',)) + + def is_member(self, address): + """ + Given an address, checks if the given address is subscribed to this + mailing list. + """ + return self._check_membership(address=address, + allowed_roles=('member',)) + + def is_owner_or_mod(self, address): + """ + Given an address, checks if the given address is either a owner or + a moderator of this list. + + It is possible for them to be both owner and moderator. + """ + return self._check_membership(address=address, + allowed_roles=('owner', 'moderator')) diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/member.py mailmanclient-3.2.0/src/mailmanclient/restobjects/member.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/member.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/member.py 2018-07-11 03:11:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -25,14 +25,17 @@ class Member(RESTObject, PreferencesMixin): _properties = ('delivery_mode', 'email', 'list_id', 'moderation_action', - 'role', 'self_link') + 'display_name', 'role', 'self_link') _writable_properties = ('address', 'delivery_mode', 'moderation_action') def __repr__(self): - return ''.format(self.email, self.list_id) + return ''.format(self.email, self.list_id) + + def __str__(self): + return 'Member "{0}" on "{1}"'.format(self.email, self.list_id) def __unicode__(self): - return ''.format(self.email, self.list_id) + return u'Member "{0}" on "{1}"'.format(self.email, self.list_id) @property def address(self): diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/preferences.py mailmanclient-3.2.0/src/mailmanclient/restobjects/preferences.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/preferences.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/preferences.py 2018-07-11 03:11:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/queue.py mailmanclient-3.2.0/src/mailmanclient/restobjects/queue.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/queue.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/queue.py 2018-07-11 03:11:53.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # @@ -26,7 +26,7 @@ _properties = ('name', 'directory', 'files') def __repr__(self): - return ''.format(self.name) + return ''.format(self.name) def inject(self, list_id, text): self._connection.call(self._url, dict(list_id=list_id, text=text)) diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/settings.py mailmanclient-3.2.0/src/mailmanclient/restobjects/settings.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/settings.py 2017-08-16 10:00:57.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/settings.py 2018-02-25 20:14:59.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2017 The Free Software Foundation, Inc. +# Copyright (C) 2010-2018 by the Free Software Foundation, Inc. # # This file is part of mailmanclient. # diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/styles.py mailmanclient-3.2.0/src/mailmanclient/restobjects/styles.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/styles.py 1970-01-01 00:00:00.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/styles.py 2018-05-03 03:42:03.000000000 +0000 @@ -0,0 +1,24 @@ +# Copyright (C) 2018 The Free Software Foundation, Inc. +# +# This file is part of mailmanclient. +# +# mailmanclient is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, version 3 of the License. +# +# mailmanclient is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mailmanclient. If not, see . +from mailmanclient.restbase.base import RESTDict + + +class Styles(RESTDict): + _read_only_properties = ( + 'style_names', + 'styles', + 'default' + ) diff -Nru mailmanclient-3.1.1/src/mailmanclient/restobjects/templates.py mailmanclient-3.2.0/src/mailmanclient/restobjects/templates.py --- mailmanclient-3.1.1/src/mailmanclient/restobjects/templates.py 1970-01-01 00:00:00.000000000 +0000 +++ mailmanclient-3.2.0/src/mailmanclient/restobjects/templates.py 2018-07-11 03:11:53.000000000 +0000 @@ -0,0 +1,43 @@ +# Copyright (C) 2017-2018 by the Free Software Foundation, Inc. +# +# This file is part of mailman.client. +# +# mailman.client is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, version 3 of the License. +# +# mailman.client is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mailman.client. If not, see . + +"""Template objects.""" + +from __future__ import absolute_import, print_function, unicode_literals + +from mailmanclient.restbase.base import RESTList, RESTObject + + +__all__ = [ + 'Template', + 'TemplateList' +] + + +class TemplateList(RESTList): + + def __init__(self, connection, url, data=None, context=None): + super(RESTList, self).__init__(connection, url, data) + self._factory = lambda data: Template( + self._connection, data['self_link'], data) + + +class Template(RESTObject): + _properties = ('self_link', 'name', 'uri', 'username', 'password') + _writable_properties = ['uri', 'username', 'password'] + + def __repr__(self): + return '