diff --git a/README.md b/README.md index 00ec911..a690058 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **What does this mean?** I do not have time to fix issues myself. The only way fixes or new features will be added is by people submitting PRs. -**Current status.** Voluptuous is largely feature stable. There hasn't been a need to add new features in a while, but there are some bugs that should be fixed. +**Current status:** Voluptuous is largely feature stable. There hasn't been a need to add new features in a while, but there are some bugs that should be fixed. **Why?** I no longer use Voluptuous personally (in fact I no longer regularly write Python code). Rather than leave the project in a limbo of people filing issues and wondering why they're not being worked on, I believe this notice will more clearly set expectations. @@ -44,6 +44,28 @@ The documentation is provided [here](http://alecthomas.github.io/voluptuous/). See [CHANGELOG.md](https://github.com/alecthomas/voluptuous/blob/master/CHANGELOG.md). +## Why use Voluptuous over another validation library? + +**Validators are simple callables:** +No need to subclass anything, just use a function. + +**Errors are simple exceptions:** +A validator can just `raise Invalid(msg)` and expect the user to get +useful messages. + +**Schemas are basic Python data structures:** +Should your data be a dictionary of integer keys to strings? +`{int: str}` does what you expect. List of integers, floats or +strings? `[int, float, str]`. + +**Designed from the ground up for validating more than just forms:** +Nested data structures are treated in the same way as any other +type. Need a list of dictionaries? `[{}]` + +**Consistency:** +Types in the schema are checked as types. Values are compared as +values. Callables are called to validate. Simple. + ## Show me an example Twitter's [user search API](https://dev.twitter.com/rest/reference/get/users/search) accepts @@ -691,35 +713,13 @@ cross-field validator will not run: s({'password':'123', 'password_again': 1337}) ``` -## Running tests. +## Running tests Voluptuous is using nosetests: $ nosetests -## Why use Voluptuous over another validation library? - -**Validators are simple callables** -: No need to subclass anything, just use a function. - -**Errors are simple exceptions.** -: A validator can just `raise Invalid(msg)` and expect the user to get -useful messages. - -**Schemas are basic Python data structures.** -: Should your data be a dictionary of integer keys to strings? -`{int: str}` does what you expect. List of integers, floats or -strings? `[int, float, str]`. - -**Designed from the ground up for validating more than just forms.** -: Nested data structures are treated in the same way as any other -type. Need a list of dictionaries? `[{}]` - -**Consistency.** -: Types in the schema are checked as types. Values are compared as -values. Callables are called to validate. Simple. - ## Other libraries and inspirations Voluptuous is heavily inspired by diff --git a/voluptuous/error.py b/voluptuous/error.py index 86c4e0a..97f37d2 100644 --- a/voluptuous/error.py +++ b/voluptuous/error.py @@ -142,11 +142,11 @@ class BooleanInvalid(Invalid): class UrlInvalid(Invalid): - """The value is not a url.""" + """The value is not a URL.""" class EmailInvalid(Invalid): - """The value is not a email.""" + """The value is not an email address.""" class FileInvalid(Invalid): diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py index 6172935..01ed91b 100644 --- a/voluptuous/tests/tests.py +++ b/voluptuous/tests/tests.py @@ -197,7 +197,7 @@ class C2: def test_email_validation(): - """ test with valid email """ + """ test with valid email address """ schema = Schema({"email": Email()}) out_ = schema({"email": "example@example.com"}) @@ -205,43 +205,43 @@ def test_email_validation(): def test_email_validation_with_none(): - """ test with invalid None Email""" + """ test with invalid None email address """ schema = Schema({"email": Email()}) try: schema({"email": None}) except MultipleInvalid as e: assert_equal(str(e), - "expected an Email for dictionary value @ data['email']") + "expected an email address for dictionary value @ data['email']") else: - assert False, "Did not raise Invalid for None url" + assert False, "Did not raise Invalid for None URL" def test_email_validation_with_empty_string(): - """ test with empty string Email""" + """ test with empty string email address""" schema = Schema({"email": Email()}) try: schema({"email": ''}) except MultipleInvalid as e: assert_equal(str(e), - "expected an Email for dictionary value @ data['email']") + "expected an email address for dictionary value @ data['email']") else: - assert False, "Did not raise Invalid for empty string url" + assert False, "Did not raise Invalid for empty string URL" def test_email_validation_without_host(): - """ test with empty host name in email """ + """ test with empty host name in email address """ schema = Schema({"email": Email()}) try: schema({"email": 'a@.com'}) except MultipleInvalid as e: assert_equal(str(e), - "expected an Email for dictionary value @ data['email']") + "expected an email address for dictionary value @ data['email']") else: - assert False, "Did not raise Invalid for empty string url" + assert False, "Did not raise Invalid for empty string URL" def test_fqdn_url_validation(): - """ test with valid fully qualified domain name url """ + """ test with valid fully qualified domain name URL """ schema = Schema({"url": FqdnUrl()}) out_ = schema({"url": "http://example.com/"}) @@ -249,27 +249,27 @@ def test_fqdn_url_validation(): def test_fqdn_url_without_domain_name(): - """ test with invalid fully qualified domain name url """ + """ test with invalid fully qualified domain name URL """ schema = Schema({"url": FqdnUrl()}) try: schema({"url": "http://localhost/"}) except MultipleInvalid as e: assert_equal(str(e), - "expected a Fully qualified domain name URL for dictionary value @ data['url']") + "expected a fully qualified domain name URL for dictionary value @ data['url']") else: - assert False, "Did not raise Invalid for None url" + assert False, "Did not raise Invalid for None URL" def test_fqdnurl_validation_with_none(): - """ test with invalid None FQDN url""" + """ test with invalid None FQDN URL """ schema = Schema({"url": FqdnUrl()}) try: schema({"url": None}) except MultipleInvalid as e: assert_equal(str(e), - "expected a Fully qualified domain name URL for dictionary value @ data['url']") + "expected a fully qualified domain name URL for dictionary value @ data['url']") else: - assert False, "Did not raise Invalid for None url" + assert False, "Did not raise Invalid for None URL" def test_fqdnurl_validation_with_empty_string(): @@ -279,9 +279,9 @@ def test_fqdnurl_validation_with_empty_string(): schema({"url": ''}) except MultipleInvalid as e: assert_equal(str(e), - "expected a Fully qualified domain name URL for dictionary value @ data['url']") + "expected a fully qualified domain name URL for dictionary value @ data['url']") else: - assert False, "Did not raise Invalid for empty string url" + assert False, "Did not raise Invalid for empty string URL" def test_fqdnurl_validation_without_host(): @@ -291,9 +291,9 @@ def test_fqdnurl_validation_without_host(): schema({"url": 'http://'}) except MultipleInvalid as e: assert_equal(str(e), - "expected a Fully qualified domain name URL for dictionary value @ data['url']") + "expected a fully qualified domain name URL for dictionary value @ data['url']") else: - assert False, "Did not raise Invalid for empty string url" + assert False, "Did not raise Invalid for empty string URL" def test_url_validation(): @@ -305,7 +305,7 @@ def test_url_validation(): def test_url_validation_with_none(): - """ test with invalid None url""" + """ test with invalid None URL""" schema = Schema({"url": Url()}) try: schema({"url": None}) @@ -313,7 +313,7 @@ def test_url_validation_with_none(): assert_equal(str(e), "expected a URL for dictionary value @ data['url']") else: - assert False, "Did not raise Invalid for None url" + assert False, "Did not raise Invalid for None URL" def test_url_validation_with_empty_string(): @@ -325,7 +325,7 @@ def test_url_validation_with_empty_string(): assert_equal(str(e), "expected a URL for dictionary value @ data['url']") else: - assert False, "Did not raise Invalid for empty string url" + assert False, "Did not raise Invalid for empty string URL" def test_url_validation_without_host(): @@ -337,7 +337,7 @@ def test_url_validation_without_host(): assert_equal(str(e), "expected a URL for dictionary value @ data['url']") else: - assert False, "Did not raise Invalid for empty string url" + assert False, "Did not raise Invalid for empty string URL" def test_copy_dict_undefined(): diff --git a/voluptuous/validators.py b/voluptuous/validators.py index 21a3a65..2add5d1 100644 --- a/voluptuous/validators.py +++ b/voluptuous/validators.py @@ -229,7 +229,7 @@ class Any(_WithSubValidators): """Use the first validated value. :param msg: Message to deliver to user if validation fails. - :param kwargs: All other keyword arguments are passed to the sub-Schema constructors. + :param kwargs: All other keyword arguments are passed to the sub-schema constructors. :returns: Return value of the first validator that passes. >>> validate = Schema(Any('true', 'false', @@ -274,11 +274,11 @@ def _exec(self, funcs, v, path=None): class Union(_WithSubValidators): - """Use the first validated value among those selected by discrminant. + """Use the first validated value among those selected by discriminant. :param msg: Message to deliver to user if validation fails. - :param discriminant(value, validators): Returns the filtered list of validators based on the value - :param kwargs: All other keyword arguments are passed to the sub-Schema constructors. + :param discriminant(value, validators): Returns the filtered list of validators based on the value. + :param kwargs: All other keyword arguments are passed to the sub-schema constructors. :returns: Return value of the first validator that passes. >>> validate = Schema(Union({'type':'a', 'a_val':'1'},{'type':'b', 'b_val':'2'}, @@ -323,7 +323,7 @@ class All(_WithSubValidators): The output of each validator is passed as input to the next. :param msg: Message to deliver to user if validation fails. - :param kwargs: All other keyword arguments are passed to the sub-Schema constructors. + :param kwargs: All other keyword arguments are passed to the sub-schema constructors. >>> validate = Schema(All('10', Coerce(int))) >>> validate('10') @@ -358,7 +358,7 @@ class Match(object): >>> with raises(MultipleInvalid, 'expected string or buffer'): ... validate(123) - Pattern may also be a _compiled regular expression: + Pattern may also be a compiled regular expression: >>> validate = Schema(Match(re.compile(r'0x[A-F0-9]+', re.I))) >>> validate('0x123ef4') @@ -416,38 +416,38 @@ def _url_validation(v): return parsed -@message('expected an Email', cls=EmailInvalid) +@message('expected an email address', cls=EmailInvalid) def Email(v): - """Verify that the value is an Email or not. + """Verify that the value is an email address or not. >>> s = Schema(Email()) - >>> with raises(MultipleInvalid, 'expected an Email'): + >>> with raises(MultipleInvalid, 'expected an email address'): ... s("a.com") - >>> with raises(MultipleInvalid, 'expected an Email'): + >>> with raises(MultipleInvalid, 'expected an email address'): ... s("a@.com") - >>> with raises(MultipleInvalid, 'expected an Email'): + >>> with raises(MultipleInvalid, 'expected an email address'): ... s("a@.com") >>> s('t@x.com') 't@x.com' """ try: if not v or "@" not in v: - raise EmailInvalid("Invalid Email") + raise EmailInvalid("Invalid email address") user_part, domain_part = v.rsplit('@', 1) if not (USER_REGEX.match(user_part) and DOMAIN_REGEX.match(domain_part)): - raise EmailInvalid("Invalid Email") + raise EmailInvalid("Invalid email address") return v except: raise ValueError -@message('expected a Fully qualified domain name URL', cls=UrlInvalid) +@message('expected a fully qualified domain name URL', cls=UrlInvalid) def FqdnUrl(v): - """Verify that the value is a Fully qualified domain name URL. + """Verify that the value is a fully qualified domain name URL. >>> s = Schema(FqdnUrl()) - >>> with raises(MultipleInvalid, 'expected a Fully qualified domain name URL'): + >>> with raises(MultipleInvalid, 'expected a fully qualified domain name URL'): ... s("http://localhost/") >>> s('http://w3.org') 'http://w3.org' @@ -478,14 +478,14 @@ def Url(v): raise ValueError -@message('not a file', cls=FileInvalid) +@message('Not a file', cls=FileInvalid) @truth def IsFile(v): """Verify the file exists. >>> os.path.basename(IsFile()(__file__)).startswith('validators.py') True - >>> with raises(FileInvalid, 'not a file'): + >>> with raises(FileInvalid, 'Not a file'): ... IsFile()("random_filename_goes_here.py") >>> with raises(FileInvalid, 'Not a file'): ... IsFile()(None) @@ -500,7 +500,7 @@ def IsFile(v): raise FileInvalid('Not a file') -@message('not a directory', cls=DirInvalid) +@message('Not a directory', cls=DirInvalid) @truth def IsDir(v): """Verify the directory exists. @@ -545,8 +545,8 @@ def PathExists(v): def Maybe(validator, msg=None): """Validate that the object matches given validator or is None. - :raises Invalid: if the value does not match the given validator and is not - None + :raises Invalid: If the value does not match the given validator and is not + None. >>> s = Schema(Maybe(int)) >>> s(10) @@ -796,7 +796,7 @@ class ExactSequence(object): the validators. :param msg: Message to deliver to user if validation fails. - :param kwargs: All other keyword arguments are passed to the sub-Schema + :param kwargs: All other keyword arguments are passed to the sub-schema constructors. >>> from voluptuous import Schema, ExactSequence @@ -961,7 +961,7 @@ def __repr__(self): class Number(object): """ Verify the number of digits that are present in the number(Precision), - and the decimal places(Scale) + and the decimal places(Scale). :raises Invalid: If the value does not match the provided Precision and Scale. @@ -1025,13 +1025,13 @@ class SomeOf(_WithSubValidators): The output of each validator is passed as input to the next. :param min_valid: Minimum number of valid schemas. - :param validators: a list of schemas or validators to match input against + :param validators: List of schemas or validators to match input against. :param max_valid: Maximum number of valid schemas. :param msg: Message to deliver to user if validation fails. - :param kwargs: All other keyword arguments are passed to the sub-Schema constructors. + :param kwargs: All other keyword arguments are passed to the sub-schema constructors. - :raises NotEnoughValid: if the minimum number of validations isn't met - :raises TooManyValid: if the more validations than the given amount is met + :raises NotEnoughValid: If the minimum number of validations isn't met. + :raises TooManyValid: If the more validations than the given amount is met. >>> validate = Schema(SomeOf(min_valid=2, validators=[Range(1, 5), Any(float, int), 6.6])) >>> validate(6.6)