From fea2d4db5fe4c110572dc5f2d2d65813e2e0b665 Mon Sep 17 00:00:00 2001 From: Andrea Crotti Date: Mon, 10 Oct 2016 10:21:44 +0100 Subject: [PATCH] Add a new validator Maybe(type) which can be used for values that are either of a certain type or None --- voluptuous/tests/tests.py | 13 ++++++++++++- voluptuous/validators.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py index 7ddd092..8907df1 100644 --- a/voluptuous/tests/tests.py +++ b/voluptuous/tests/tests.py @@ -6,7 +6,7 @@ Schema, Required, Optional, Extra, Invalid, In, Remove, Literal, Url, MultipleInvalid, LiteralInvalid, NotIn, Match, Email, Replace, Range, Coerce, All, Any, Length, FqdnUrl, ALLOW_EXTRA, PREVENT_EXTRA, - validate, ExactSequence, Equal, Unordered, Number + validate, ExactSequence, Equal, Unordered, Number, Maybe ) from voluptuous.humanize import humanize_error from voluptuous.util import to_utf8_py2, u @@ -371,6 +371,7 @@ def test_repr(): max_included=False, msg='number not in range') coerce_ = Coerce(int, msg="moo") all_ = All('10', Coerce(int), msg='all msg') + maybe_int = Maybe(int) assert_equal(repr(match), "Match('a pattern', msg='message')") assert_equal(repr(replace), "Replace('you', 'I', msg='you and I')") @@ -380,6 +381,7 @@ def test_repr(): ) assert_equal(repr(coerce_), "Coerce(int, msg='moo')") assert_equal(repr(all_), "All('10', Coerce(int, msg=None), msg='all msg')") + assert_equal(repr(maybe_int), "Maybe()") def test_list_validation_messages(): @@ -499,6 +501,15 @@ def test_unordered(): s([3, 2]) +def test_maybe(): + assert_raises(TypeError, Maybe, lambda x: x) + + s = Schema(Maybe(int)) + assert s(1) == 1 + + assert_raises(Invalid, s, 'foo') + + def test_empty_list_as_exact(): s = Schema([]) assert_raises(Invalid, s, [1]) diff --git a/voluptuous/validators.py b/voluptuous/validators.py index 51e247f..a05dd37 100644 --- a/voluptuous/validators.py +++ b/voluptuous/validators.py @@ -459,6 +459,35 @@ def PathExists(v): raise PathInvalid("Not a Path") +class Maybe(object): + """Validate that the object is of a given type or is None. + + :raises Invalid: if the value is not of the type declared or None + + >>> s = Schema(Maybe(int)) + >>> s(10) + 10 + >>> with raises(Invalid): + ... s("string") + + """ + def __init__(self, kind, msg=None): + if not isinstance(kind, type): + raise TypeError("kind has to be a type") + + self.kind = kind + self.msg = msg + + def __call__(self, v): + if v is not None and not isinstance(v, self.kind): + raise Invalid(self.msg or "%s must be None or of type %s" % (v, self.kind)) + + return v + + def __repr__(self): + return 'Maybe(%s)' % str(self.kind) + + class Range(object): """Limit a value to a range.