diff options
| author | J08nY | 2017-08-07 19:00:49 +0200 |
|---|---|---|
| committer | J08nY | 2017-08-07 19:00:49 +0200 |
| commit | ee9da27283ffb7adc836f764f1442cd06e3fb2a5 (patch) | |
| tree | 2b687f39714580b1de70baf9e3dd9957326c4989 /src/mailman/rest | |
| parent | d107fd41f03b57f7731b60bb7ba921febc3ce3b9 (diff) | |
| parent | b902d7858d8302d248add89a5983c521c3581c4c (diff) | |
| download | mailman-plugin.tar.gz mailman-plugin.tar.zst mailman-plugin.zip | |
Diffstat (limited to 'src/mailman/rest')
| -rw-r--r-- | src/mailman/rest/docs/membership.rst | 2 | ||||
| -rw-r--r-- | src/mailman/rest/docs/sub-moderation.rst | 9 | ||||
| -rw-r--r-- | src/mailman/rest/listconf.py | 26 | ||||
| -rw-r--r-- | src/mailman/rest/members.py | 61 | ||||
| -rw-r--r-- | src/mailman/rest/sub_moderation.py | 14 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_listconf.py | 6 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_membership.py | 19 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_moderation.py | 7 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_users.py | 2 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_validator.py | 44 | ||||
| -rw-r--r-- | src/mailman/rest/validator.py | 49 |
11 files changed, 178 insertions, 61 deletions
diff --git a/src/mailman/rest/docs/membership.rst b/src/mailman/rest/docs/membership.rst index b0b5e1254..6e24d14f9 100644 --- a/src/mailman/rest/docs/membership.rst +++ b/src/mailman/rest/docs/membership.rst @@ -642,7 +642,6 @@ won't have to approve her subscription request. ... 'display_name': 'Elly Person', ... 'pre_verified': True, ... 'pre_confirmed': True, - ... 'pre_approved': True, ... }) content-length: 0 content-type: application/json; charset=UTF-8 @@ -699,7 +698,6 @@ list with her preferred address. ... 'subscriber': user_id, ... 'pre_verified': True, ... 'pre_confirmed': True, - ... 'pre_approved': True, ... }) content-length: 0 content-type: application/json; charset=UTF-8 diff --git a/src/mailman/rest/docs/sub-moderation.rst b/src/mailman/rest/docs/sub-moderation.rst index 92c0c8849..8bf95fcd2 100644 --- a/src/mailman/rest/docs/sub-moderation.rst +++ b/src/mailman/rest/docs/sub-moderation.rst @@ -13,8 +13,8 @@ A mailing list starts with no pending subscription or unsubscription requests. >>> ant = create_list('ant@example.com') >>> ant.admin_immed_notify = False - >>> from mailman.interfaces.mailinglist import SubscriptionPolicy - >>> ant.subscription_policy = SubscriptionPolicy.moderate + >>> from mailman.workflows.subscription import ModerationSubscriptionPolicy + >>> ant.subscription_policy = ModerationSubscriptionPolicy >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/requests') http_etag: "..." @@ -31,7 +31,6 @@ is returned to track her subscription request. ... 'subscriber': 'anne@example.com', ... 'display_name': 'Anne Person', ... 'pre_verified': True, - ... 'pre_confirmed': True, ... }) http_etag: ... token: 0000000000000000000000000000000000000001 @@ -48,7 +47,7 @@ The subscription request can be viewed in the REST API. list_id: ant.example.com token: 0000000000000000000000000000000000000001 token_owner: moderator - type: subscription + type: sub-policy-moderate when: 2005-08-01T07:49:23 http_etag: "..." start: 0 @@ -69,7 +68,7 @@ You can view an individual membership change request by providing the token list_id: ant.example.com token: 0000000000000000000000000000000000000001 token_owner: moderator - type: subscription + type: sub-policy-moderate when: 2005-08-01T07:49:23 diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py index b62d34529..70f04999c 100644 --- a/src/mailman/rest/listconf.py +++ b/src/mailman/rest/listconf.py @@ -24,14 +24,17 @@ from mailman.interfaces.archiver import ArchivePolicy from mailman.interfaces.autorespond import ResponseAction from mailman.interfaces.digests import DigestFrequency from mailman.interfaces.mailinglist import ( - DMARCMitigateAction, IAcceptableAliasSet, IMailingList, ReplyToMunging, - SubscriptionPolicy) + DMARCMitigateAction, IAcceptableAliasSet, IMailingList, ReplyToMunging) from mailman.interfaces.template import ITemplateManager +from mailman.interfaces.workflows import ISubscriptionWorkflow from mailman.rest.helpers import ( GetterSetter, bad_request, etag, no_content, not_found, okay) from mailman.rest.validator import ( PatchValidator, ReadOnlyPATCHRequestError, UnknownPATCHRequestError, - Validator, enum_validator, list_of_strings_validator) + Validator, enum_validator, list_of_strings_validator, policy_validator) +from mailman.workflows.subscription import ( + ConfirmModerationSubscriptionPolicy, ConfirmSubscriptionPolicy, + ModerationSubscriptionPolicy, OpenSubscriptionPolicy) from public import public from zope.component import getUtility @@ -86,6 +89,20 @@ class URIAttributeMapper(GetterSetter): getUtility(ITemplateManager).set(template_name, obj.list_id, value) +class SubscriptionPolicyMapper(GetterSetter): + + def get(self, obj, attribute): + assert IMailingList.providedBy(obj), obj + old_sub_map = { + OpenSubscriptionPolicy: 'open', + ConfirmSubscriptionPolicy: 'confirm', + ModerationSubscriptionPolicy: 'moderate', + ConfirmModerationSubscriptionPolicy: 'confirm_then_moderate' + } + cls = getattr(obj, attribute) + return old_sub_map.get(cls, cls.name) + + # Additional validators for converting from web request strings to internal # data types. See below for details. @@ -179,7 +196,8 @@ ATTRIBUTES = dict( request_address=GetterSetter(None), send_welcome_message=GetterSetter(as_boolean), subject_prefix=GetterSetter(str), - subscription_policy=GetterSetter(enum_validator(SubscriptionPolicy)), + subscription_policy=SubscriptionPolicyMapper( + policy_validator(ISubscriptionWorkflow)), volume=GetterSetter(None), ) diff --git a/src/mailman/rest/members.py b/src/mailman/rest/members.py index 3ff712259..2d5b95115 100644 --- a/src/mailman/rest/members.py +++ b/src/mailman/rest/members.py @@ -17,7 +17,6 @@ """REST for members.""" -from lazr.config import as_boolean from mailman.app.membership import add_member, delete_member from mailman.interfaces.action import Action from mailman.interfaces.address import IAddress @@ -198,22 +197,43 @@ class AllMembers(_MemberBase): def on_post(self, request, response): """Create a new member.""" - try: - validator = Validator( - list_id=str, - subscriber=subscriber_validator(self.api), - display_name=str, - delivery_mode=enum_validator(DeliveryMode), - role=enum_validator(MemberRole), - pre_verified=as_boolean, - pre_confirmed=as_boolean, - pre_approved=as_boolean, - _optional=('delivery_mode', 'display_name', 'role', - 'pre_verified', 'pre_confirmed', 'pre_approved')) - arguments = validator(request) - except ValueError as error: - bad_request(response, str(error)) + # Validate the params manually as the subscription_policy workflow + # attributes are dynamic, so parse all known and optional attirbutes + # and leave the rest to policy_kwargs. + required_arguments = ['list_id', 'subscriber'] + known_arguments = dict(list_id=str, + subscriber=subscriber_validator(self.api), + display_name=str, + delivery_mode=enum_validator(DeliveryMode), + role=enum_validator(MemberRole)) + + arguments = {} + policy_kwargs = {} + + wrong = [] + for key, value in request.params.items(): + try: + if key in known_arguments: + converter = known_arguments[key] + arguments[key] = converter(value) + else: + policy_kwargs[key] = value + except ValueError: + wrong.append(key) + + if len(wrong) > 0: + params = ', '.join(wrong) + bad_request(response, + 'Cannot convert parameters: {}'.format(params)) return + + missing = [key for key in required_arguments if key not in arguments] + if len(missing) > 0: + bad_request(response, 'Missing parameters: {}'.format(missing)) + return + # XXX policy_kwargs are string, while subscription workflows expects + # whatever. + # Dig the mailing list out of the arguments. list_id = arguments.pop('list_id') mlist = getUtility(IListManager).get_by_list_id(list_id) @@ -247,20 +267,13 @@ class AllMembers(_MemberBase): # nonmembers go through the legacy API for now. role = arguments.pop('role', MemberRole.member) if role is MemberRole.member: - # Get the pre_ flags for the subscription workflow. - pre_verified = arguments.pop('pre_verified', False) - pre_confirmed = arguments.pop('pre_confirmed', False) - pre_approved = arguments.pop('pre_approved', False) # Now we can run the registration process until either the # subscriber is subscribed, or the workflow is paused for # verification, confirmation, or approval. registrar = ISubscriptionManager(mlist) try: token, token_owner, member = registrar.register( - subscriber, - pre_verified=pre_verified, - pre_confirmed=pre_confirmed, - pre_approved=pre_approved) + subscriber, **policy_kwargs) except AlreadySubscribedError: conflict(response, b'Member already subscribed') return diff --git a/src/mailman/rest/sub_moderation.py b/src/mailman/rest/sub_moderation.py index 47745b729..ad1c5751b 100644 --- a/src/mailman/rest/sub_moderation.py +++ b/src/mailman/rest/sub_moderation.py @@ -16,13 +16,16 @@ # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. """REST API for held subscription requests.""" +from itertools import chain from mailman.app.moderator import send_rejection +from mailman.config import config from mailman.core.i18n import _ from mailman.interfaces.action import Action from mailman.interfaces.member import AlreadySubscribedError from mailman.interfaces.pending import IPendings from mailman.interfaces.subscriptions import ISubscriptionManager +from mailman.interfaces.workflows import ISubscriptionWorkflow from mailman.rest.helpers import ( CollectionMixin, bad_request, child, conflict, etag, no_content, not_found, okay) @@ -122,8 +125,15 @@ class SubscriptionRequests(_ModerationBase, CollectionMixin): self._mlist = mlist def _get_collection(self, request): - pendings = getUtility(IPendings).find( - mlist=self._mlist, pend_type='subscription') + sub_workflows = [workflow_class + for workflow_class in config.workflows.values() + if ISubscriptionWorkflow.implementedBy(workflow_class) + ] + generators = [getUtility(IPendings).find(mlist=self._mlist, + pend_type=sub_workflow.name) + for + sub_workflow in sub_workflows] + pendings = chain.from_iterable(generators) return [token for token, pendable in pendings] def on_get(self, request, response): diff --git a/src/mailman/rest/tests/test_listconf.py b/src/mailman/rest/tests/test_listconf.py index 782effd29..bb88feb61 100644 --- a/src/mailman/rest/tests/test_listconf.py +++ b/src/mailman/rest/tests/test_listconf.py @@ -22,11 +22,11 @@ import unittest from mailman.app.lifecycle import create_list from mailman.database.transaction import transaction from mailman.interfaces.digests import DigestFrequency -from mailman.interfaces.mailinglist import ( - IAcceptableAliasSet, SubscriptionPolicy) +from mailman.interfaces.mailinglist import IAcceptableAliasSet from mailman.interfaces.template import ITemplateManager from mailman.testing.helpers import call_api from mailman.testing.layers import RESTLayer +from mailman.workflows.subscription import ConfirmModerationSubscriptionPolicy from urllib.error import HTTPError from zope.component import getUtility @@ -197,7 +197,7 @@ class TestConfiguration(unittest.TestCase): self.assertEqual(response.status_code, 204) # And now we verify that it has the requested setting. self.assertEqual(self._mlist.subscription_policy, - SubscriptionPolicy.confirm_then_moderate) + ConfirmModerationSubscriptionPolicy) def test_patch_attribute_double(self): with self.assertRaises(HTTPError) as cm: diff --git a/src/mailman/rest/tests/test_membership.py b/src/mailman/rest/tests/test_membership.py index e5a2ce283..bb0134ac5 100644 --- a/src/mailman/rest/tests/test_membership.py +++ b/src/mailman/rest/tests/test_membership.py @@ -23,7 +23,6 @@ from mailman.app.lifecycle import create_list from mailman.config import config from mailman.database.transaction import transaction from mailman.interfaces.bans import IBanManager -from mailman.interfaces.mailinglist import SubscriptionPolicy from mailman.interfaces.member import DeliveryMode, MemberRole from mailman.interfaces.subscriptions import ISubscriptionManager, TokenOwner from mailman.interfaces.usermanager import IUserManager @@ -33,6 +32,8 @@ from mailman.testing.helpers import ( set_preferred, subscribe, wait_for_webservice) from mailman.testing.layers import ConfigLayer, RESTLayer from mailman.utilities.datetime import now +from mailman.workflows.subscription import ( + ConfirmModerationSubscriptionPolicy, ModerationSubscriptionPolicy) from urllib.error import HTTPError from zope.component import getUtility @@ -93,7 +94,6 @@ class TestMembership(unittest.TestCase): 'subscriber': 'anne@example.com', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) self.assertEqual(cm.exception.code, 409) self.assertEqual(cm.exception.reason, 'Member already subscribed') @@ -108,7 +108,6 @@ class TestMembership(unittest.TestCase): 'subscriber': 'anne@example.com', 'pre_verified': False, 'pre_confirmed': False, - 'pre_approved': False, }) self.assertEqual(cm.exception.code, 409) self.assertEqual(cm.exception.reason, 'Member already subscribed') @@ -123,7 +122,6 @@ class TestMembership(unittest.TestCase): 'subscriber': '00000000000000000000000000000001', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) self.assertEqual(cm.exception.code, 400) self.assertEqual(cm.exception.reason, 'User has no preferred address') @@ -135,7 +133,6 @@ class TestMembership(unittest.TestCase): 'subscriber': '00000000000000000000000000000801', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) self.assertEqual(cm.exception.code, 400) self.assertEqual(cm.exception.reason, 'No such user') @@ -153,7 +150,6 @@ class TestMembership(unittest.TestCase): 'subscriber': 'ANNE@example.com', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) self.assertEqual(cm.exception.code, 409) self.assertEqual(cm.exception.reason, 'Member already subscribed') @@ -171,7 +167,6 @@ class TestMembership(unittest.TestCase): 'subscriber': 'anne@example.com', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) self.assertEqual(cm.exception.code, 409) self.assertEqual(cm.exception.reason, 'Member already subscribed') @@ -195,7 +190,6 @@ class TestMembership(unittest.TestCase): 'display_name': 'Hugh Person', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) self.assertEqual(json, None) self.assertEqual(response.status_code, 201) @@ -231,10 +225,10 @@ class TestMembership(unittest.TestCase): # to subscribe again. registrar = ISubscriptionManager(self._mlist) with transaction(): - self._mlist.subscription_policy = SubscriptionPolicy.moderate + self._mlist.subscription_policy = ModerationSubscriptionPolicy anne = self._usermanager.create_address('anne@example.com') token, token_owner, member = registrar.register( - anne, pre_verified=True, pre_confirmed=True) + anne, pre_verified=True) self.assertEqual(token_owner, TokenOwner.moderator) self.assertIsNone(member) with self.assertRaises(HTTPError) as cm: @@ -242,7 +236,6 @@ class TestMembership(unittest.TestCase): 'list_id': 'test.example.com', 'subscriber': 'anne@example.com', 'pre_verified': True, - 'pre_confirmed': True, }) self.assertEqual(cm.exception.code, 409) self.assertEqual(cm.exception.reason, @@ -255,7 +248,7 @@ class TestMembership(unittest.TestCase): registrar = ISubscriptionManager(self._mlist) with transaction(): self._mlist.subscription_policy = ( - SubscriptionPolicy.confirm_then_moderate) + ConfirmModerationSubscriptionPolicy) anne = self._usermanager.create_address('anne@example.com') token, token_owner, member = registrar.register( anne, pre_verified=True) @@ -645,7 +638,6 @@ class TestAPI31Members(unittest.TestCase): 'subscriber': '00000000000000000000000000000001', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) self.assertEqual(response.status_code, 201) self.assertEqual( @@ -681,7 +673,6 @@ class TestAPI31Members(unittest.TestCase): 'subscriber': '1', 'pre_verified': True, 'pre_confirmed': True, - 'pre_approved': True, }) # This is a bad request because the `subscriber` value isn't something # that's known to the system, in API 3.1. It's not technically a 404 diff --git a/src/mailman/rest/tests/test_moderation.py b/src/mailman/rest/tests/test_moderation.py index 0a9bb2608..5b55eb575 100644 --- a/src/mailman/rest/tests/test_moderation.py +++ b/src/mailman/rest/tests/test_moderation.py @@ -24,7 +24,6 @@ from mailman.app.lifecycle import create_list from mailman.app.moderator import hold_message from mailman.database.transaction import transaction from mailman.interfaces.bans import IBanManager -from mailman.interfaces.mailinglist import SubscriptionPolicy from mailman.interfaces.requests import IListRequests, RequestType from mailman.interfaces.subscriptions import ISubscriptionManager from mailman.interfaces.usermanager import IUserManager @@ -32,6 +31,7 @@ from mailman.testing.helpers import ( call_api, get_queue_messages, set_preferred, specialized_message_from_string as mfs) from mailman.testing.layers import RESTLayer +from mailman.workflows.subscription import ModerationSubscriptionPolicy from pkg_resources import resource_filename from urllib.error import HTTPError from zope.component import getUtility @@ -296,10 +296,9 @@ class TestSubscriptionModeration(unittest.TestCase): # Anne tries to subscribe to a list that only requests moderator # approval. with transaction(): - self._mlist.subscription_policy = SubscriptionPolicy.moderate + self._mlist.subscription_policy = ModerationSubscriptionPolicy token, token_owner, member = self._registrar.register( - self._anne, - pre_verified=True, pre_confirmed=True) + self._anne, pre_verified=True) # There's now one request in the queue, and it's waiting on moderator # approval. json, response = call_api( diff --git a/src/mailman/rest/tests/test_users.py b/src/mailman/rest/tests/test_users.py index 97f4b76d2..4c97e5c1d 100644 --- a/src/mailman/rest/tests/test_users.py +++ b/src/mailman/rest/tests/test_users.py @@ -437,7 +437,7 @@ class TestLP1074374(unittest.TestCase): list_id='test.example.com', subscriber='anne@example.com', role='member', - pre_verified=True, pre_confirmed=True, pre_approved=True)) + pre_verified=True, pre_confirmed=True)) # This is not the Anne you're looking for. (IOW, the new Anne is a # different user). json, response = call_api( diff --git a/src/mailman/rest/tests/test_validator.py b/src/mailman/rest/tests/test_validator.py index 0d197032e..95855d170 100644 --- a/src/mailman/rest/tests/test_validator.py +++ b/src/mailman/rest/tests/test_validator.py @@ -22,9 +22,14 @@ import unittest from mailman.core.api import API30, API31 from mailman.interfaces.action import Action from mailman.interfaces.usermanager import IUserManager +from mailman.interfaces.workflows import (ISubscriptionWorkflow, + IUnsubscriptionWorkflow) from mailman.rest.validator import ( - enum_validator, list_of_strings_validator, subscriber_validator) + enum_validator, list_of_strings_validator, policy_validator, + subscriber_validator) from mailman.testing.layers import RESTLayer +from mailman.workflows.subscription import ConfirmSubscriptionPolicy +from mailman.workflows.unsubscription import ConfirmUnsubscriptionPolicy from zope.component import getUtility @@ -91,3 +96,40 @@ class TestValidators(unittest.TestCase): def test_enum_validator_blank(self): self.assertEqual(enum_validator(Action, allow_blank=True)(''), None) + + def test_policy_validator_wrong_policy_class(self): + self.assertRaises(ValueError, policy_validator, None) + + def test_policy_validator_sub_name(self): + self.assertEqual(policy_validator(ISubscriptionWorkflow)( + ConfirmSubscriptionPolicy.name), ConfirmSubscriptionPolicy) + + def test_policy_validator_sub_class(self): + self.assertEqual(policy_validator(ISubscriptionWorkflow)( + ConfirmSubscriptionPolicy), ConfirmSubscriptionPolicy) + + def test_policy_validator_sub_backward_compat(self): + self.assertEqual(policy_validator(ISubscriptionWorkflow)('confirm'), + ConfirmSubscriptionPolicy) + + def test_policy_validator_sub_wrong_policy(self): + validator = policy_validator(ISubscriptionWorkflow) + with self.assertRaises(ValueError): + validator('not a subscription policy') + + def test_policy_validator_unsub_name(self): + self.assertEqual(policy_validator(IUnsubscriptionWorkflow)( + ConfirmUnsubscriptionPolicy.name), ConfirmUnsubscriptionPolicy) + + def test_policy_validator_unsub_class(self): + self.assertEqual(policy_validator(IUnsubscriptionWorkflow)( + ConfirmUnsubscriptionPolicy), ConfirmUnsubscriptionPolicy) + + def test_policy_validator_unsub_backward_compat(self): + self.assertEqual(policy_validator(IUnsubscriptionWorkflow)('confirm'), + ConfirmUnsubscriptionPolicy) + + def test_policy_validator_unsub_wrong_policy(self): + validator = policy_validator(IUnsubscriptionWorkflow) + with self.assertRaises(ValueError): + validator('not an unsubscription policy') diff --git a/src/mailman/rest/validator.py b/src/mailman/rest/validator.py index b325fdc84..d5f684df9 100644 --- a/src/mailman/rest/validator.py +++ b/src/mailman/rest/validator.py @@ -16,15 +16,35 @@ # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. """REST web form validation.""" - +from mailman.config import config from mailman.interfaces.address import IEmailValidator from mailman.interfaces.errors import MailmanError from mailman.interfaces.languages import ILanguageManager +from mailman.interfaces.workflows import (ISubscriptionWorkflow, + IUnsubscriptionWorkflow) +from mailman.workflows.subscription import ( + ConfirmModerationSubscriptionPolicy, ConfirmSubscriptionPolicy, + ModerationSubscriptionPolicy, OpenSubscriptionPolicy) +from mailman.workflows.unsubscription import ( + ConfirmModerationUnsubscriptionPolicy, ConfirmUnsubscriptionPolicy, + ModerationUnsubscriptionPolicy, OpenUnsubscriptionPolicy) from public import public from zope.component import getUtility COMMASPACE = ', ' +OLD_SUB_MAP = { + 'open': OpenSubscriptionPolicy, + 'confirm': ConfirmSubscriptionPolicy, + 'moderate': ModerationSubscriptionPolicy, + 'confirm_then_moderate': ConfirmModerationSubscriptionPolicy + } +OLD_UNSUB_MAP = { + 'open': OpenUnsubscriptionPolicy, + 'confirm': ConfirmUnsubscriptionPolicy, + 'moderate': ModerationUnsubscriptionPolicy, + 'confirm_then_moderate': ConfirmModerationUnsubscriptionPolicy + } @public @@ -100,6 +120,33 @@ def list_of_strings_validator(values): @public +class policy_validator: + """""" + + def __init__(self, policy_interface): + self._policy_interface = policy_interface + if policy_interface is ISubscriptionWorkflow: + self._old_map = OLD_SUB_MAP + elif policy_interface is IUnsubscriptionWorkflow: + self._old_map = OLD_UNSUB_MAP + else: + raise ValueError('Expected a workflow interface.') + + def __call__(self, policy): + if self._policy_interface.implementedBy(policy): + return policy + if (policy in config.workflows and + self._policy_interface.implementedBy( + config.workflows[policy])): + return config.workflows[policy] + # For backwards compatibility. + policy = self._old_map.get(policy, None) + if policy is not None: + return policy + raise ValueError('Unknown policy: {}'.format(policy)) + + +@public class Validator: """A validator of parameter input.""" |
