diff options
Diffstat (limited to 'src')
38 files changed, 121 insertions, 343 deletions
diff --git a/src/mailman/rest/addresses.py b/src/mailman/rest/addresses.py index 421e17bf1..1cf2d2f41 100644 --- a/src/mailman/rest/addresses.py +++ b/src/mailman/rest/addresses.py @@ -17,13 +17,7 @@ """REST for addresses.""" -__all__ = [ - 'AllAddresses', - 'AnAddress', - 'UserAddresses', - ] - - +from mailman import public from mailman.interfaces.address import ( ExistingAddressError, InvalidEmailAddressError) from mailman.interfaces.usermanager import IUserManager @@ -38,7 +32,6 @@ from operator import attrgetter from zope.component import getUtility - class _AddressBase(CollectionMixin): """Shared base class for address representations.""" @@ -68,7 +61,7 @@ class _AddressBase(CollectionMixin): return list(getUtility(IUserManager).addresses) - +@public class AllAddresses(_AddressBase): """The addresses.""" @@ -78,7 +71,6 @@ class AllAddresses(_AddressBase): okay(response, etag(resource)) - class _VerifyResource: """A helper resource for verify/unverify POSTS.""" @@ -96,6 +88,7 @@ class _VerifyResource: no_content(response) +@public class AnAddress(_AddressBase): """An address.""" @@ -172,7 +165,7 @@ class AnAddress(_AddressBase): return AddressUser(self._address) - +@public class UserAddresses(_AddressBase): """The addresses of a user.""" @@ -225,12 +218,12 @@ class UserAddresses(_AddressBase): created(response, location) - def membership_key(member): # Sort first by mailing list, then by address, then by role. return member.list_id, member.address.email, member.role.value +@public class AddressMemberships(MemberCollection): """All the memberships of a particular email address.""" diff --git a/src/mailman/rest/bans.py b/src/mailman/rest/bans.py index ac0a1ca7f..70a61cc73 100644 --- a/src/mailman/rest/bans.py +++ b/src/mailman/rest/bans.py @@ -17,12 +17,7 @@ """REST for banned emails.""" -__all__ = [ - 'BannedEmail', - 'BannedEmails', - ] - - +from mailman import public from mailman.interfaces.bans import IBanManager from mailman.rest.helpers import ( CollectionMixin, bad_request, child, created, etag, no_content, not_found, @@ -45,6 +40,7 @@ class _BannedBase: return self.api.path_to('{}bans/{}'.format(base_location, email)) +@public class BannedEmail(_BannedBase): """A banned email.""" @@ -74,6 +70,7 @@ class BannedEmail(_BannedBase): not_found(response, 'Email is not banned: {}'.format(self._email)) +@public class BannedEmails(_BannedBase, CollectionMixin): """The list of all banned emails.""" diff --git a/src/mailman/rest/docs/__init__.py b/src/mailman/rest/docs/__init__.py index cb8282e33..4b83845fc 100644 --- a/src/mailman/rest/docs/__init__.py +++ b/src/mailman/rest/docs/__init__.py @@ -17,11 +17,7 @@ """Doctest layer setup.""" -__all__ = [ - 'layer', - ] - - - from mailman.testing.layers import RESTLayer + layer = RESTLayer +__all__ = ['layer'] diff --git a/src/mailman/rest/domains.py b/src/mailman/rest/domains.py index 0128c9da0..dbf9f94c1 100644 --- a/src/mailman/rest/domains.py +++ b/src/mailman/rest/domains.py @@ -17,12 +17,7 @@ """REST for domains.""" -__all__ = [ - 'ADomain', - 'AllDomains', - ] - - +from mailman import public from mailman.interfaces.domain import ( BadDomainSpecificationError, IDomainManager) from mailman.rest.helpers import ( @@ -34,7 +29,6 @@ from mailman.rest.validator import Validator, list_of_strings_validator from zope.component import getUtility - class _DomainBase(CollectionMixin): """Shared base class for domain representations.""" @@ -53,6 +47,7 @@ class _DomainBase(CollectionMixin): return list(getUtility(IDomainManager)) +@public class ADomain(_DomainBase): """A domain.""" @@ -100,6 +95,7 @@ class ADomain(_DomainBase): return NotFound(), [] +@public class AllDomains(_DomainBase): """The domains.""" diff --git a/src/mailman/rest/header_matches.py b/src/mailman/rest/header_matches.py index f54c181ef..07a1f2c1c 100644 --- a/src/mailman/rest/header_matches.py +++ b/src/mailman/rest/header_matches.py @@ -17,12 +17,7 @@ """REST API for a mailing list's header matches.""" -__all__ = [ - 'HeaderMatch', - 'HeaderMatches', - ] - - +from mailman import public from mailman.interfaces.action import Action from mailman.interfaces.mailinglist import IHeaderMatchList from mailman.rest.helpers import ( @@ -59,6 +54,7 @@ class _HeaderMatchBase: return resource +@public class HeaderMatch(_HeaderMatchBase): """A header match.""" @@ -129,6 +125,7 @@ class HeaderMatch(_HeaderMatchBase): self.patch_put(request, response, is_optional=True) +@public class HeaderMatches(_HeaderMatchBase, CollectionMixin): """The list of all header matches.""" diff --git a/src/mailman/rest/helpers.py b/src/mailman/rest/helpers.py index a29c0599b..34fff3ba3 100644 --- a/src/mailman/rest/helpers.py +++ b/src/mailman/rest/helpers.py @@ -17,24 +17,6 @@ """Web service helpers.""" -__all__ = [ - 'BadRequest', - 'ChildError', - 'CollectionMixin', - 'GetterSetter', - 'NotFound', - 'bad_request', - 'child', - 'conflict', - 'created', - 'etag', - 'forbidden', - 'no_content', - 'not_found', - 'okay', - ] - - import json import falcon import hashlib @@ -42,11 +24,11 @@ import hashlib from datetime import datetime, timedelta from enum import Enum from lazr.config import as_boolean +from mailman import public from mailman.config import config from pprint import pformat - class ExtendedEncoder(json.JSONEncoder): """An extended JSON encoder which knows about other data types.""" @@ -58,8 +40,8 @@ class ExtendedEncoder(json.JSONEncoder): # to floating seconds, but only if there are any seconds. if obj.seconds > 0 or obj.microseconds > 0: seconds = obj.seconds + obj.microseconds / 1000000.0 - return '{0}d{1}s'.format(obj.days, seconds) - return '{0}d'.format(obj.days) + return '{}d{}s'.format(obj.days, seconds) + return '{}d'.format(obj.days) elif isinstance(obj, Enum): # It's up to the decoding validator to associate this name with # the right Enum class. @@ -67,6 +49,7 @@ class ExtendedEncoder(json.JSONEncoder): return super().default(obj) +@public def etag(resource): """Calculate the etag and return a JSON representation. @@ -94,7 +77,7 @@ def etag(resource): sort_keys=as_boolean(config.devmode.enabled)) - +@public class CollectionMixin: """Mixin class for common collection-ish things.""" @@ -164,7 +147,7 @@ class CollectionMixin: return result - +@public class GetterSetter: """Get and set attributes on an object. @@ -225,9 +208,9 @@ class GetterSetter: return self.decoder(value) - # Falcon REST framework add-ons. +@public def child(matcher=None): def decorator(func): if matcher is None: @@ -238,6 +221,7 @@ def child(matcher=None): return decorator +@public class ChildError: def __init__(self, status): self._status = status @@ -252,55 +236,65 @@ class ChildError: on_delete = _oops +@public class BadRequest(ChildError): def __init__(self): super().__init__(falcon.HTTP_400) +@public class NotFound(ChildError): def __init__(self): super().__init__(falcon.HTTP_404) +@public def okay(response, body=None): response.status = falcon.HTTP_200 if body is not None: response.body = body +@public def no_content(response): response.status = falcon.HTTP_204 +@public def not_found(response, body=b'404 Not Found'): response.status = falcon.HTTP_404 if body is not None: response.body = body +@public def accepted(response, body=None): response.status = falcon.HTTP_202 if body is not None: response.body = body +@public def bad_request(response, body='400 Bad Request'): response.status = falcon.HTTP_400 if body is not None: response.body = body +@public def created(response, location): response.status = falcon.HTTP_201 response.location = location +@public def conflict(response, body=b'409 Conflict'): response.status = falcon.HTTP_409 if body is not None: response.body = body +@public def forbidden(response, body=b'403 Forbidden'): response.status = falcon.HTTP_403 if body is not None: diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py index a6c15d428..65ce8c5f7 100644 --- a/src/mailman/rest/listconf.py +++ b/src/mailman/rest/listconf.py @@ -17,12 +17,8 @@ """Mailing list configuration via REST API.""" -__all__ = [ - 'ListConfiguration', - ] - - from lazr.config import as_boolean, as_timedelta +from mailman import public from mailman.config import config from mailman.interfaces.action import Action from mailman.interfaces.archiver import ArchivePolicy @@ -37,7 +33,6 @@ from mailman.rest.validator import ( Validator, enum_validator, list_of_strings_validator) - class AcceptableAliases(GetterSetter): """Resource for the acceptable aliases of a mailing list.""" @@ -63,7 +58,6 @@ class AcceptableAliases(GetterSetter): alias_set.add(alias) - # Additional validators for converting from web request strings to internal # data types. See below for details. @@ -74,7 +68,6 @@ def pipeline_validator(pipeline_name): raise ValueError('Unknown pipeline: {}'.format(pipeline_name)) - # This is the list of IMailingList attributes that are exposed through the # REST API. The values of the keys are the GetterSetter instance holding the # decoder used to convert the web request string to an internally valid value. @@ -153,7 +146,7 @@ for attribute, gettersetter in list(VALIDATORS.items()): del VALIDATORS[attribute] - +@public class ListConfiguration: """A mailing list configuration resource.""" diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py index 0273c5d3a..2d669a267 100644 --- a/src/mailman/rest/lists.py +++ b/src/mailman/rest/lists.py @@ -17,17 +17,8 @@ """REST for mailing lists.""" -__all__ = [ - 'AList', - 'AllLists', - 'ListArchivers', - 'ListConfiguration', - 'ListsForDomain', - 'Styles', - ] - - from lazr.config import as_boolean +from mailman import public from mailman.app.digests import ( bump_digest_number_and_volume, maybe_send_digest_now) from mailman.app.lifecycle import create_list, remove_list @@ -118,6 +109,7 @@ class _ListBase(CollectionMixin): return list(getUtility(IListManager)) +@public class AList(_ListBase): """A mailing list.""" @@ -213,6 +205,7 @@ class AList(_ListBase): return HeaderMatches(self._mlist) +@public class AllLists(_ListBase): """The mailing lists.""" @@ -238,6 +231,7 @@ class AllLists(_ListBase): okay(response, etag(resource)) +@public class MembersOfList(MemberCollection): """The members of a mailing list.""" @@ -255,6 +249,7 @@ class MembersOfList(MemberCollection): role=self._role) +@public class ListsForDomain(_ListBase): """The mailing lists for a particular domain.""" @@ -271,6 +266,7 @@ class ListsForDomain(_ListBase): return list(self._domain.mailing_lists) +@public class ArchiverGetterSetter(GetterSetter): """Resource for updating archiver statuses.""" @@ -287,6 +283,7 @@ class ArchiverGetterSetter(GetterSetter): archiver.is_enabled = as_boolean(value) +@public class ListArchivers: """The archivers for a list, with their enabled flags.""" @@ -323,6 +320,7 @@ class ListArchivers: self.patch_put(request, response, is_optional=True) +@public class ListDigest: """Simple resource representing actions on a list's digest.""" @@ -357,6 +355,7 @@ class ListDigest: accepted(response) +@public class Styles: """Simple resource representing all list styles.""" diff --git a/src/mailman/rest/members.py b/src/mailman/rest/members.py index 22db939b3..b56fa3c8d 100644 --- a/src/mailman/rest/members.py +++ b/src/mailman/rest/members.py @@ -17,14 +17,7 @@ """REST for members.""" -__all__ = [ - 'AMember', - 'AllMembers', - 'FindMembers', - 'MemberCollection', - ] - - +from mailman import public from mailman.app.membership import add_member, delete_member from mailman.interfaces.action import Action from mailman.interfaces.address import IAddress @@ -47,7 +40,6 @@ from uuid import UUID from zope.component import getUtility - class _MemberBase(CollectionMixin): """Shared base class for member representations.""" @@ -85,7 +77,7 @@ class _MemberBase(CollectionMixin): return list(getUtility(ISubscriptionService)) - +@public class MemberCollection(_MemberBase): """Abstract class for supporting submemberships. @@ -103,7 +95,7 @@ class MemberCollection(_MemberBase): okay(response, etag(resource)) - +@public class AMember(_MemberBase): """A member.""" @@ -201,7 +193,7 @@ class AMember(_MemberBase): no_content(response) - +@public class AllMembers(_MemberBase): """The members.""" @@ -350,7 +342,6 @@ class AllMembers(_MemberBase): okay(response, etag(resource)) - class _FoundMembers(MemberCollection): """The found members collection.""" @@ -364,6 +355,7 @@ class _FoundMembers(MemberCollection): return self._members +@public class FindMembers(_MemberBase): """/members/find""" diff --git a/src/mailman/rest/post_moderation.py b/src/mailman/rest/post_moderation.py index 6283add75..a020b3350 100644 --- a/src/mailman/rest/post_moderation.py +++ b/src/mailman/rest/post_moderation.py @@ -17,12 +17,7 @@ """REST API for held message moderation.""" -__all__ = [ - 'HeldMessage', - 'HeldMessages', - ] - - +from mailman import public from mailman.app.moderator import handle_message from mailman.interfaces.action import Action from mailman.interfaces.messages import IMessageStore @@ -33,7 +28,6 @@ from mailman.rest.validator import Validator, enum_validator from zope.component import getUtility - class _ModerationBase: """Common base class.""" @@ -66,7 +60,6 @@ class _ModerationBase: return resource - class _HeldMessageBase(_ModerationBase): """Held messages are a little different.""" @@ -95,6 +88,7 @@ class _HeldMessageBase(_ModerationBase): return resource +@public class HeldMessage(_HeldMessageBase): """Resource for moderating a held message.""" @@ -135,7 +129,7 @@ class HeldMessage(_HeldMessageBase): no_content(response) - +@public class HeldMessages(_HeldMessageBase, CollectionMixin): """Resource for messages held for moderation.""" diff --git a/src/mailman/rest/preferences.py b/src/mailman/rest/preferences.py index 694aa47e9..cf26380fe 100644 --- a/src/mailman/rest/preferences.py +++ b/src/mailman/rest/preferences.py @@ -17,13 +17,8 @@ """Preferences.""" -__all__ = [ - 'ReadOnlyPreferences', - 'Preferences', - ] - - from lazr.config import as_boolean +from mailman import public from mailman.interfaces.member import DeliveryMode, DeliveryStatus from mailman.rest.helpers import ( GetterSetter, bad_request, etag, no_content, not_found, okay) @@ -42,7 +37,7 @@ PREFERENCES = ( ) - +@public class ReadOnlyPreferences: """.../<object>/preferences""" @@ -69,7 +64,7 @@ class ReadOnlyPreferences: okay(response, etag(resource)) - +@public class Preferences(ReadOnlyPreferences): """Preferences which can be changed.""" @@ -79,7 +74,7 @@ class Preferences(ReadOnlyPreferences): return kws = dict( acknowledge_posts=GetterSetter(as_boolean), - hide_address = GetterSetter(as_boolean), + hide_address=GetterSetter(as_boolean), delivery_mode=GetterSetter(enum_validator(DeliveryMode)), delivery_status=GetterSetter(enum_validator(DeliveryStatus)), preferred_language=GetterSetter(language_validator), diff --git a/src/mailman/rest/queues.py b/src/mailman/rest/queues.py index 4d3c9f58b..69f6df973 100644 --- a/src/mailman/rest/queues.py +++ b/src/mailman/rest/queues.py @@ -17,13 +17,7 @@ """<api>/queues.""" -__all__ = [ - 'AQueue', - 'AQueueFile', - 'AllQueues', - ] - - +from mailman import public from mailman.config import config from mailman.app.inject import inject_text from mailman.interfaces.listmanager import IListManager @@ -33,7 +27,6 @@ from mailman.rest.validator import Validator from zope.component import getUtility - class _QueuesBase(CollectionMixin): """Shared base class for queues.""" @@ -54,7 +47,7 @@ class _QueuesBase(CollectionMixin): return sorted(config.switchboards) - +@public class AQueue(_QueuesBase): """A single queue.""" @@ -94,7 +87,7 @@ class AQueue(_QueuesBase): created(response, location) - +@public class AQueueFile: def __init__(self, name, filebase): self._name = name @@ -115,7 +108,7 @@ class AQueueFile: no_content(response) - +@public class AllQueues(_QueuesBase): """All queues.""" diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py index 027ed3777..988c228e7 100644 --- a/src/mailman/rest/root.py +++ b/src/mailman/rest/root.py @@ -17,14 +17,10 @@ """The root of the REST API.""" -__all__ = [ - 'Root', - ] - - import falcon from base64 import b64decode +from mailman import public from mailman.config import config from mailman.core.api import API30, API31 from mailman.core.constants import system_preferences @@ -48,7 +44,7 @@ from zope.component import getUtility SLASH = '/' - +@public class Root: """The RESTful root resource. @@ -90,7 +86,7 @@ class Root: credentials = b64decode(request.auth[6:]).decode('utf-8') username, password = credentials.split(':', 1) if (username != config.webservice.admin_user or - password != config.webservice.admin_pass): + password != config.webservice.admin_pass): # Not authorized. raise falcon.HTTPUnauthorized( '401 Unauthorized', @@ -98,6 +94,7 @@ class Root: return TopLevel() +@public class Versions: def on_get(self, request, response): """/<api>/system/versions""" @@ -110,6 +107,7 @@ class Versions: okay(response, etag(resource)) +@public class SystemConfiguration: def __init__(self, section=None): self._section = section @@ -131,6 +129,7 @@ class SystemConfiguration: okay(response, etag(resource)) +@public class Reserved: """Top level API for reserved operations. @@ -150,6 +149,7 @@ class Reserved: no_content(response) +@public class TopLevel: """Top level collections and entries.""" diff --git a/src/mailman/rest/sub_moderation.py b/src/mailman/rest/sub_moderation.py index e019b7bf4..033f95317 100644 --- a/src/mailman/rest/sub_moderation.py +++ b/src/mailman/rest/sub_moderation.py @@ -17,11 +17,7 @@ """REST API for held subscription requests.""" -__all__ = [ - 'SubscriptionRequests', - ] - - +from mailman import public from mailman.app.moderator import send_rejection from mailman.interfaces.action import Action from mailman.interfaces.member import AlreadySubscribedError @@ -35,7 +31,6 @@ from mailman.utilities.i18n import _ from zope.component import getUtility - class _ModerationBase: """Common base class.""" @@ -52,7 +47,7 @@ class _ModerationBase: return resource - +@public class IndividualRequest(_ModerationBase): """Resource for moderating a membership change.""" @@ -118,7 +113,7 @@ class IndividualRequest(_ModerationBase): _('[No reason given]')) - +@public class SubscriptionRequests(_ModerationBase, CollectionMixin): """Resource for membership change requests.""" diff --git a/src/mailman/rest/templates.py b/src/mailman/rest/templates.py index a21334e93..fe5e34a8e 100644 --- a/src/mailman/rest/templates.py +++ b/src/mailman/rest/templates.py @@ -17,11 +17,7 @@ """Template finder.""" -__all__ = [ - 'TemplateFinder', - ] - - +from mailman import public from mailman.rest.helpers import not_found from mailman.utilities.i18n import TemplateNotFoundError, find @@ -33,7 +29,7 @@ EXTENSIONS = { } - +@public class TemplateFinder: """Template finder resource.""" diff --git a/src/mailman/rest/tests/test_addresses.py b/src/mailman/rest/tests/test_addresses.py index 0af52f607..edcfdfa86 100644 --- a/src/mailman/rest/tests/test_addresses.py +++ b/src/mailman/rest/tests/test_addresses.py @@ -17,12 +17,6 @@ """REST address tests.""" -__all__ = [ - 'TestAPI31Addresses', - 'TestAddresses', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -35,7 +29,6 @@ from urllib.error import HTTPError from zope.component import getUtility - class TestAddresses(unittest.TestCase): layer = RESTLayer @@ -488,7 +481,6 @@ class TestAddresses(unittest.TestCase): self.assertEqual(cm.exception.code, 404) - class TestAPI31Addresses(unittest.TestCase): """UUIDs are represented as hex instead of int in API 3.1 diff --git a/src/mailman/rest/tests/test_api.py b/src/mailman/rest/tests/test_api.py index 1d050ed42..4bca75849 100644 --- a/src/mailman/rest/tests/test_api.py +++ b/src/mailman/rest/tests/test_api.py @@ -17,11 +17,6 @@ """API version tests.""" -__all__ = [ - 'TestAPIVersion', - ] - - import unittest from mailman.core.system import system @@ -30,7 +25,6 @@ from mailman.testing.layers import RESTLayer from urllib.error import HTTPError - class TestAPIVersion(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/tests/test_bans.py b/src/mailman/rest/tests/test_bans.py index ce6d8c843..e6159d9a4 100644 --- a/src/mailman/rest/tests/test_bans.py +++ b/src/mailman/rest/tests/test_bans.py @@ -17,11 +17,6 @@ """Test address bans.""" -__all__ = [ - 'TestBans', - ] - - import unittest from mailman.app.lifecycle import create_list diff --git a/src/mailman/rest/tests/test_basic.py b/src/mailman/rest/tests/test_basic.py index c79a3a340..64c60842c 100644 --- a/src/mailman/rest/tests/test_basic.py +++ b/src/mailman/rest/tests/test_basic.py @@ -20,11 +20,6 @@ For example, test the integration between Mailman and Falcon. """ -__all__ = [ - 'TestBasicREST', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -33,7 +28,6 @@ from mailman.testing.helpers import call_api from mailman.testing.layers import RESTLayer - class TestBasicREST(unittest.TestCase): """Test basic REST integration and functionality.""" diff --git a/src/mailman/rest/tests/test_domains.py b/src/mailman/rest/tests/test_domains.py index cab0def27..e98f75b53 100644 --- a/src/mailman/rest/tests/test_domains.py +++ b/src/mailman/rest/tests/test_domains.py @@ -17,12 +17,6 @@ """REST domain tests.""" -__all__ = [ - 'TestDomainOwners', - 'TestDomains', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -35,7 +29,6 @@ from urllib.error import HTTPError from zope.component import getUtility - class TestDomains(unittest.TestCase): layer = RESTLayer @@ -125,7 +118,6 @@ class TestDomains(unittest.TestCase): self.assertEqual(cm.exception.code, 404) - class TestDomainOwners(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/tests/test_header_matches.py b/src/mailman/rest/tests/test_header_matches.py index 26baddd34..c8f6615af 100644 --- a/src/mailman/rest/tests/test_header_matches.py +++ b/src/mailman/rest/tests/test_header_matches.py @@ -17,11 +17,6 @@ """Test REST header matches.""" -__all__ = [ - 'TestHeaderMatches', - ] - - import unittest from mailman.app.lifecycle import create_list diff --git a/src/mailman/rest/tests/test_helpers.py b/src/mailman/rest/tests/test_helpers.py index cab1f06fa..982b97ef3 100644 --- a/src/mailman/rest/tests/test_helpers.py +++ b/src/mailman/rest/tests/test_helpers.py @@ -17,11 +17,6 @@ """Additional tests for helpers.""" -__all__ = [ - 'TestHelpers', - ] - - import unittest from datetime import timedelta @@ -38,7 +33,6 @@ class Unserializable: pass - class TestHelpers(unittest.TestCase): layer = ConfigLayer diff --git a/src/mailman/rest/tests/test_listconf.py b/src/mailman/rest/tests/test_listconf.py index 2132a5387..35bd516e0 100644 --- a/src/mailman/rest/tests/test_listconf.py +++ b/src/mailman/rest/tests/test_listconf.py @@ -17,11 +17,6 @@ """Test list configuration via the REST API.""" -__all__ = [ - 'TestConfiguration', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -80,7 +75,6 @@ RESOURCE = dict( ) - class TestConfiguration(unittest.TestCase): """Test list configuration via the REST API.""" @@ -168,7 +162,7 @@ class TestConfiguration(unittest.TestCase): call_api('http://localhost:9001/3.0/lists/ant.example.com' '/config/mail_host', dict(mail_host='foo.example.com'), - 'PUT') + 'PUT') self.assertEqual(cm.exception.code, 400) self.assertEqual(cm.exception.reason, b'Read-only attribute: mail_host') @@ -213,8 +207,8 @@ class TestConfiguration(unittest.TestCase): def test_unknown_patch_attribute(self): with self.assertRaises(HTTPError) as cm: call_api('http://localhost:9001/3.0/lists/ant.example.com/config', - dict(bogus=1), - 'PATCH') + dict(bogus=1), + 'PATCH') self.assertEqual(cm.exception.code, 400) self.assertEqual(cm.exception.reason, b'Unknown attribute: bogus') @@ -223,7 +217,7 @@ class TestConfiguration(unittest.TestCase): call_api('http://localhost:9001/3.0/lists/ant.example.com' '/config/mail_host', dict(mail_host='foo.example.com'), - 'PATCH') + 'PATCH') self.assertEqual(cm.exception.code, 400) self.assertEqual(cm.exception.reason, b'Read-only attribute: mail_host') diff --git a/src/mailman/rest/tests/test_lists.py b/src/mailman/rest/tests/test_lists.py index 202725a99..08c29151f 100644 --- a/src/mailman/rest/tests/test_lists.py +++ b/src/mailman/rest/tests/test_lists.py @@ -17,15 +17,6 @@ """REST list tests.""" -__all__ = [ - 'TestListArchivers', - 'TestListDigests', - 'TestListPagination', - 'TestLists', - 'TestListsMissing', - ] - - import unittest from datetime import timedelta diff --git a/src/mailman/rest/tests/test_membership.py b/src/mailman/rest/tests/test_membership.py index 57f641007..3d47b13a7 100644 --- a/src/mailman/rest/tests/test_membership.py +++ b/src/mailman/rest/tests/test_membership.py @@ -17,13 +17,6 @@ """REST membership tests.""" -__all__ = [ - 'TestAPI31Members', - 'TestMembership', - 'TestNonmembership', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -45,7 +38,6 @@ from urllib.error import HTTPError from zope.component import getUtility - class TestMembership(unittest.TestCase): layer = RESTLayer @@ -428,7 +420,6 @@ class TestMembership(unittest.TestCase): self.assertEqual(cm.exception.reason, b'Membership is banned') - class CustomLayer(ConfigLayer): """Custom layer which starts both the REST and LMTP servers.""" @@ -487,7 +478,7 @@ Some text. # Now use the REST API to try to find the nonmember. response, content = call_api( 'http://localhost:9001/3.0/members/find', { - #'list_id': 'test.example.com', + # 'list_id': 'test.example.com', 'role': 'nonmember', }) self.assertEqual(response['total_size'], 1) @@ -518,7 +509,7 @@ Some text. # Now use the REST API to try to find the nonmember. response, content = call_api( 'http://localhost:9001/3.0/members/find', { - #'list_id': 'test.example.com', + # 'list_id': 'test.example.com', 'role': 'nonmember', }) self.assertEqual(response['total_size'], 1) @@ -534,7 +525,6 @@ Some text. 'http://localhost:9001/3.0/users/1') - class TestAPI31Members(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/tests/test_moderation.py b/src/mailman/rest/tests/test_moderation.py index 9d4bee92a..9f9da6b18 100644 --- a/src/mailman/rest/tests/test_moderation.py +++ b/src/mailman/rest/tests/test_moderation.py @@ -17,12 +17,6 @@ """REST moderation tests.""" -__all__ = [ - 'TestPostModeration', - 'TestSubscriptionModeration', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -41,7 +35,6 @@ from urllib.error import HTTPError from zope.component import getUtility - class TestPostModeration(unittest.TestCase): layer = RESTLayer @@ -150,7 +143,6 @@ Something else. self.assertEqual(cm.exception.code, 404) - class TestSubscriptionModeration(unittest.TestCase): layer = RESTLayer maxDiff = None diff --git a/src/mailman/rest/tests/test_owners.py b/src/mailman/rest/tests/test_owners.py index 8bc512632..d511a8cae 100644 --- a/src/mailman/rest/tests/test_owners.py +++ b/src/mailman/rest/tests/test_owners.py @@ -17,11 +17,6 @@ """Additional tests for the top-level owners resource.""" -__all__ = [ - 'TestOwners', - ] - - import unittest from mailman.testing.helpers import call_api @@ -29,7 +24,6 @@ from mailman.testing.layers import RESTLayer from urllib.error import HTTPError - class TestOwners(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/tests/test_paginate.py b/src/mailman/rest/tests/test_paginate.py index a01efc2a2..ad9fb0d2a 100644 --- a/src/mailman/rest/tests/test_paginate.py +++ b/src/mailman/rest/tests/test_paginate.py @@ -17,11 +17,6 @@ """paginate helper tests.""" -__all__ = [ - 'TestPaginateHelper', - ] - - import unittest from falcon import HTTPInvalidParam, Request @@ -31,7 +26,6 @@ from mailman.rest.helpers import CollectionMixin from mailman.testing.layers import RESTLayer - class _FakeRequest(Request): def __init__(self, count=None, page=None): self._params = {} @@ -41,7 +35,6 @@ class _FakeRequest(Request): self._params['page'] = page - class TestPaginateHelper(unittest.TestCase): """Test the @paginate decorator.""" @@ -55,7 +48,7 @@ class TestPaginateHelper(unittest.TestCase): class Resource(CollectionMixin): def _get_collection(self, request): return ['one', 'two', 'three', 'four', 'five'] - def _resource_as_dict(self, res): + def _resource_as_dict(self, res): # flake8: noqa return {'value': res} return Resource() diff --git a/src/mailman/rest/tests/test_preferences.py b/src/mailman/rest/tests/test_preferences.py index cd743d960..424ca4f36 100644 --- a/src/mailman/rest/tests/test_preferences.py +++ b/src/mailman/rest/tests/test_preferences.py @@ -17,11 +17,6 @@ """Test various preference functionality.""" -__all__ = [ - 'TestPreferences', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -33,7 +28,6 @@ from urllib.error import HTTPError from zope.component import getUtility - class TestPreferences(unittest.TestCase): """Test various preference functionality.""" diff --git a/src/mailman/rest/tests/test_queues.py b/src/mailman/rest/tests/test_queues.py index 812156e64..d4fd2e7d7 100644 --- a/src/mailman/rest/tests/test_queues.py +++ b/src/mailman/rest/tests/test_queues.py @@ -17,11 +17,6 @@ """Test the `queues` resource.""" -__all__ = [ - 'TestQueues', - ] - - import unittest from mailman.app.lifecycle import create_list @@ -41,7 +36,6 @@ Message-ID: <ant> """ - class TestQueues(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/tests/test_root.py b/src/mailman/rest/tests/test_root.py index 2f4c9d3b8..ea2f2da5e 100644 --- a/src/mailman/rest/tests/test_root.py +++ b/src/mailman/rest/tests/test_root.py @@ -17,11 +17,6 @@ """REST root object tests.""" -__all__ = [ - 'TestRoot', - ] - - import os import json import unittest @@ -35,7 +30,6 @@ from mailman.testing.layers import RESTLayer from urllib.error import HTTPError - class TestRoot(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/tests/test_systemconf.py b/src/mailman/rest/tests/test_systemconf.py index 8555eb7fa..9335480bf 100644 --- a/src/mailman/rest/tests/test_systemconf.py +++ b/src/mailman/rest/tests/test_systemconf.py @@ -17,11 +17,6 @@ """Test system configuration read-only access.""" -__all__ = [ - 'TestSystemConfiguration', - ] - - import unittest from mailman.testing.helpers import call_api @@ -29,7 +24,6 @@ from mailman.testing.layers import RESTLayer from urllib.error import HTTPError - class TestSystemConfiguration(unittest.TestCase): layer = RESTLayer maxDiff = None @@ -88,7 +82,6 @@ class TestSystemConfiguration(unittest.TestCase): 'xref', ]) - def test_all_sections(self): # Getting the top level configuration object returns a list of all # existing sections. diff --git a/src/mailman/rest/tests/test_uids.py b/src/mailman/rest/tests/test_uids.py index cbfad8d22..96792f8fa 100644 --- a/src/mailman/rest/tests/test_uids.py +++ b/src/mailman/rest/tests/test_uids.py @@ -21,11 +21,6 @@ There is no doctest for this functionality, since it's only useful for testing of external clients of the REST API. """ -__all__ = [ - 'TestUIDs', - ] - - import unittest from mailman.config import config @@ -37,7 +32,6 @@ from mailman.testing.layers import RESTLayer from zope.component import getUtility - class TestUIDs(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/tests/test_users.py b/src/mailman/rest/tests/test_users.py index 3a6508771..e42bdf623 100644 --- a/src/mailman/rest/tests/test_users.py +++ b/src/mailman/rest/tests/test_users.py @@ -17,15 +17,6 @@ """REST user tests.""" -__all__ = [ - 'TestAPI31Users', - 'TestLP1074374', - 'TestLP1419519', - 'TestLogin', - 'TestUsers', - ] - - import os import unittest @@ -41,7 +32,6 @@ from zope.component import getUtility from mailman.model.preferences import Preferences - class TestUsers(unittest.TestCase): layer = RESTLayer @@ -305,7 +295,6 @@ class TestUsers(unittest.TestCase): 'http://localhost:9001/3.0/users/1/preferences') - class TestLogin(unittest.TestCase): """Test user 'login' (really just password verification).""" @@ -384,7 +373,6 @@ schemes = hex_md5 self.assertEqual(self.anne.password, '{plaintext}abc123') - class TestLP1074374(unittest.TestCase): """LP: #1074374 - deleting a user left their address records active.""" @@ -472,7 +460,6 @@ class TestLP1074374(unittest.TestCase): self.assertEqual(member['role'], 'member') - class TestLP1419519(unittest.TestCase): # LP: #1419519 - deleting a user with many linked addresses does not delete # all address records. @@ -516,7 +503,6 @@ class TestLP1419519(unittest.TestCase): self.assertEqual(len(emails), 0) - class TestAPI31Users(unittest.TestCase): """UUIDs are represented as hex in API 3.1.""" diff --git a/src/mailman/rest/tests/test_validator.py b/src/mailman/rest/tests/test_validator.py index 287928d3c..de2207108 100644 --- a/src/mailman/rest/tests/test_validator.py +++ b/src/mailman/rest/tests/test_validator.py @@ -17,11 +17,6 @@ """Test REST validators.""" -__all__ = [ - 'TestValidators', - ] - - import unittest from mailman.interfaces.usermanager import IUserManager @@ -32,7 +27,6 @@ from mailman.testing.layers import RESTLayer from zope.component import getUtility - class TestValidators(unittest.TestCase): layer = RESTLayer diff --git a/src/mailman/rest/users.py b/src/mailman/rest/users.py index 21ed26745..6ac4462c8 100644 --- a/src/mailman/rest/users.py +++ b/src/mailman/rest/users.py @@ -17,16 +17,8 @@ """REST for users.""" -__all__ = [ - 'AUser', - 'AddressUser', - 'AllUsers', - 'Login', - 'OwnersForDomain', - ] - - from lazr.config import as_boolean +from mailman import public from mailman.config import config from mailman.interfaces.address import ExistingAddressError from mailman.interfaces.usermanager import IUserManager @@ -42,24 +34,27 @@ from passlib.utils import generate_password as generate from zope.component import getUtility - # Attributes of a user which can be changed via the REST API. +@public class PasswordEncrypterGetterSetter(GetterSetter): def __init__(self): super().__init__(config.password_context.encrypt) + def get(self, obj, attribute): assert attribute == 'cleartext_password' super().get(obj, 'password') + def put(self, obj, attribute, value): assert attribute == 'cleartext_password' super().put(obj, 'password', value) +@public class ListOfDomainOwners(GetterSetter): def get(self, domain, attribute): assert attribute == 'owner', ( 'Unexpected attribute: {}'.format(attribute)) - def sort_key(owner): + def sort_key(owner): # flake8: noqa return owner.addresses[0].email return sorted(domain.owners, key=sort_key) @@ -85,7 +80,6 @@ CREATION_FIELDS = dict( ) - def create_user(arguments, request, response): """Create a new user.""" # We can't pass the 'password' argument to the user creation method, so @@ -122,7 +116,6 @@ def create_user(arguments, request, response): return user - class _UserBase(CollectionMixin): """Shared base class for user representations.""" @@ -152,7 +145,7 @@ class _UserBase(CollectionMixin): return list(getUtility(IUserManager).users) - +@public class AllUsers(_UserBase): """The users.""" @@ -172,7 +165,7 @@ class AllUsers(_UserBase): create_user(arguments, request, response) - +@public class AUser(_UserBase): """A user.""" @@ -284,7 +277,7 @@ class AUser(_UserBase): return Login(self._user) - +@public class AddressUser(_UserBase): """The user linked to an address.""" @@ -381,7 +374,7 @@ class AddressUser(_UserBase): user.link(self._address) - +@public class Login: """<api>/users/<uid>/login""" @@ -409,7 +402,7 @@ class Login: forbidden(response) - +@public class OwnersForDomain(_UserBase): """Owners for a particular domain.""" @@ -461,7 +454,7 @@ class OwnersForDomain(_UserBase): return list(self._domain.owners) - +@public class ServerOwners(_UserBase): """All server owners.""" diff --git a/src/mailman/rest/validator.py b/src/mailman/rest/validator.py index 748a63d5c..861b869de 100644 --- a/src/mailman/rest/validator.py +++ b/src/mailman/rest/validator.py @@ -17,16 +17,7 @@ """REST web form validation.""" -__all__ = [ - 'PatchValidator', - 'Validator', - 'enum_validator', - 'language_validator', - 'list_of_strings_validator', - 'subscriber_validator', - ] - - +from mailman import public from mailman.interfaces.address import IEmailValidator from mailman.interfaces.errors import MailmanError from mailman.interfaces.languages import ILanguageManager @@ -36,10 +27,12 @@ from zope.component import getUtility COMMASPACE = ', ' +@public class RESTError(MailmanError): """Base class for REST API errors.""" +@public class UnknownPATCHRequestError(RESTError): """A PATCH request contained an unknown attribute.""" @@ -47,6 +40,7 @@ class UnknownPATCHRequestError(RESTError): self.attribute = attribute +@public class ReadOnlyPATCHRequestError(RESTError): """A PATCH request contained a read-only attribute.""" @@ -54,7 +48,7 @@ class ReadOnlyPATCHRequestError(RESTError): self.attribute = attribute - +@public class enum_validator: """Convert an enum value name into an enum value.""" @@ -71,6 +65,7 @@ class enum_validator: raise ValueError(exception.args[0]) +@public def subscriber_validator(api): """Convert an email-or-(int|hex) to an email-or-UUID.""" def _inner(subscriber): @@ -84,11 +79,13 @@ def subscriber_validator(api): return _inner +@public def language_validator(code): """Convert a language code to a Language object.""" return getUtility(ILanguageManager)[code] +@public def list_of_strings_validator(values): """Turn a list of things, or a single thing, into a list of unicodes.""" if not isinstance(values, (list, tuple)): @@ -99,7 +96,7 @@ def list_of_strings_validator(values): return values - +@public class Validator: """A validator of parameter input.""" @@ -139,17 +136,17 @@ class Validator: # Make sure there are no unexpected values. if len(extras) != 0: extras = COMMASPACE.join(sorted(extras)) - raise ValueError('Unexpected parameters: {0}'.format(extras)) + raise ValueError('Unexpected parameters: {}'.format(extras)) # Make sure everything could be converted. if len(cannot_convert) != 0: bad = COMMASPACE.join(sorted(cannot_convert)) - raise ValueError('Cannot convert parameters: {0}'.format(bad)) + raise ValueError('Cannot convert parameters: {}'.format(bad)) # Make sure nothing's missing. value_keys = set(values) required_keys = set(self._converters) - self._optional if value_keys & required_keys != required_keys: missing = COMMASPACE.join(sorted(required_keys - value_keys)) - raise ValueError('Missing parameters: {0}'.format(missing)) + raise ValueError('Missing parameters: {}'.format(missing)) return values def update(self, obj, request): @@ -167,7 +164,7 @@ class Validator: self._converters[key].put(obj, key, value) - +@public class PatchValidator(Validator): """Create a special validator for PATCH requests. diff --git a/src/mailman/rest/wsgiapp.py b/src/mailman/rest/wsgiapp.py index 3155ee510..6184da4ee 100644 --- a/src/mailman/rest/wsgiapp.py +++ b/src/mailman/rest/wsgiapp.py @@ -17,18 +17,13 @@ """Basic WSGI Application object for REST server.""" -__all__ = [ - 'make_application', - 'make_server', - ] - - import re import logging from falcon import API from falcon.responders import path_not_found from falcon.routing import create_http_method_map +from mailman import public from mailman.config import config from mailman.database.transaction import transactional from mailman.rest.root import Root @@ -42,7 +37,6 @@ SLASH = '/' EMPTYSTRING = '' - class AdminWSGIServer(WSGIServer): """Server class that integrates error handling with our log files.""" @@ -53,6 +47,19 @@ class AdminWSGIServer(WSGIServer): client_address) +class StderrLogger: + def __init__(self): + self._buffer = [] + + def write(self, message): + self._buffer.append(message) + + def flush(self): + self._buffer.insert(0, 'REST request handler error:\n') + log.error(EMPTYSTRING.join(self._buffer)) + self._buffer = [] + + class AdminWebServiceWSGIRequestHandler(WSGIRequestHandler): """Handler class which just logs output to the right place.""" @@ -63,15 +70,6 @@ class AdminWebServiceWSGIRequestHandler(WSGIRequestHandler): def get_stderr(self): # Return a fake stderr object that will actually write its output to # the log file. - class StderrLogger: - def __init__(self): - self._buffer = [] - def write(self, message): - self._buffer.append(message) - def flush(self): - self._buffer.insert(0, 'REST request handler error:\n') - log.error(EMPTYSTRING.join(self._buffer)) - self._buffer = [] return StderrLogger() @@ -182,7 +180,7 @@ class RootedAPI(API): return path_not_found, {}, None - +@public def make_application(): """Create the WSGI application. @@ -192,6 +190,7 @@ def make_application(): return RootedAPI(Root()) +@public def make_server(): """Create the Mailman REST server. |
