deleted file mode 100644
--- a/third_party/python/voluptuous/COPYING
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright (c) 2010, Alec Thomas
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
- - Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
- - Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- - Neither the name of SwapOff.org nor the names of its contributors may
- be used to endorse or promote products derived from this software without
- specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
deleted file mode 100644
--- a/third_party/python/voluptuous/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include *.md
-include COPYING
deleted file mode 100644
--- a/third_party/python/voluptuous/PKG-INFO
+++ /dev/null
@@ -1,611 +0,0 @@
-Metadata-Version: 1.1
-Name: voluptuous
-Version: 0.8.11
-Summary: Voluptuous is a Python data validation library
-Home-page: https://github.com/alecthomas/voluptuous
-Author: Alec Thomas
-Author-email: alec@swapoff.org
-License: BSD
-Download-URL: https://pypi.python.org/pypi/voluptuous
-Description: Voluptuous is a Python data validation library
- ==============================================
-
- |Build Status| |Stories in Ready|
-
- Voluptuous, *despite* the name, is a Python data validation library. It
- is primarily intended for validating data coming into Python as JSON,
- YAML, etc.
-
- It has three goals:
-
- 1. Simplicity.
- 2. Support for complex data structures.
- 3. Provide useful error messages.
-
- Contact
- -------
-
- Voluptuous now has a mailing list! Send a mail to
- ` <mailto:voluptuous@librelist.com>`__ to subscribe. Instructions will
- follow.
-
- You can also contact me directly via `email <mailto:alec@swapoff.org>`__
- or `Twitter <https://twitter.com/alecthomas>`__.
-
- To file a bug, create a `new
- issue <https://github.com/alecthomas/voluptuous/issues/new>`__ on GitHub
- with a short example of how to replicate the issue.
-
- Show me an example
- ------------------
-
- Twitter's `user search
- API <https://dev.twitter.com/docs/api/1/get/users/search>`__ accepts
- query URLs like:
-
- ::
-
- $ curl 'http://api.twitter.com/1/users/search.json?q=python&per_page=20&page=1
-
- To validate this we might use a schema like:
-
- .. code:: pycon
-
- >>> from voluptuous import Schema
- >>> schema = Schema({
- ... 'q': str,
- ... 'per_page': int,
- ... 'page': int,
- ... })
-
- This schema very succinctly and roughly describes the data required by
- the API, and will work fine. But it has a few problems. Firstly, it
- doesn't fully express the constraints of the API. According to the API,
- ``per_page`` should be restricted to at most 20, defaulting to 5, for
- example. To describe the semantics of the API more accurately, our
- schema will need to be more thoroughly defined:
-
- .. code:: pycon
-
- >>> from voluptuous import Required, All, Length, Range
- >>> schema = Schema({
- ... Required('q'): All(str, Length(min=1)),
- ... Required('per_page', default=5): All(int, Range(min=1, max=20)),
- ... 'page': All(int, Range(min=0)),
- ... })
-
- This schema fully enforces the interface defined in Twitter's
- documentation, and goes a little further for completeness.
-
- "q" is required:
-
- .. code:: pycon
-
- >>> from voluptuous import MultipleInvalid, Invalid
- >>> try:
- ... schema({})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data['q']"
- True
-
- ...must be a string:
-
- .. code:: pycon
-
- >>> try:
- ... schema({'q': 123})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "expected str for dictionary value @ data['q']"
- True
-
- ...and must be at least one character in length:
-
- .. code:: pycon
-
- >>> try:
- ... schema({'q': ''})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
- True
- >>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
- True
-
- "per\_page" is a positive integer no greater than 20:
-
- .. code:: pycon
-
- >>> try:
- ... schema({'q': '#topic', 'per_page': 900})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
- True
- >>> try:
- ... schema({'q': '#topic', 'per_page': -10})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
- True
-
- "page" is an integer >= 0:
-
- .. code:: pycon
-
- >>> try:
- ... schema({'q': '#topic', 'per_page': 'one'})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "expected int for dictionary value @ data['per_page']"
- >>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
- True
-
- Defining schemas
- ----------------
-
- Schemas are nested data structures consisting of dictionaries, lists,
- scalars and *validators*. Each node in the input schema is pattern
- matched against corresponding nodes in the input data.
-
- Literals
- ~~~~~~~~
-
- Literals in the schema are matched using normal equality checks:
-
- .. code:: pycon
-
- >>> schema = Schema(1)
- >>> schema(1)
- 1
- >>> schema = Schema('a string')
- >>> schema('a string')
- 'a string'
-
- Types
- ~~~~~
-
- Types in the schema are matched by checking if the corresponding value
- is an instance of the type:
-
- .. code:: pycon
-
- >>> schema = Schema(int)
- >>> schema(1)
- 1
- >>> try:
- ... schema('one')
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "expected int"
- True
-
- URL's
- ~~~~~
-
- URL's in the schema are matched by using ``urlparse`` library.
-
- .. code:: pycon
-
- >>> from voluptuous import Url
- >>> schema = Schema(Url())
- >>> schema('http://w3.org')
- 'http://w3.org'
- >>> try:
- ... schema('one')
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "expected a URL"
- True
-
- Lists
- ~~~~~
-
- Lists in the schema are treated as a set of valid values. Each element
- in the schema list is compared to each value in the input data:
-
- .. code:: pycon
-
- >>> schema = Schema([1, 'a', 'string'])
- >>> schema([1])
- [1]
- >>> schema([1, 1, 1])
- [1, 1, 1]
- >>> schema(['a', 1, 'string', 1, 'string'])
- ['a', 1, 'string', 1, 'string']
-
- Validation functions
- ~~~~~~~~~~~~~~~~~~~~
-
- Validators are simple callables that raise an ``Invalid`` exception when
- they encounter invalid data. The criteria for determining validity is
- entirely up to the implementation; it may check that a value is a valid
- username with ``pwd.getpwnam()``, it may check that a value is of a
- specific type, and so on.
-
- The simplest kind of validator is a Python function that raises
- ValueError when its argument is invalid. Conveniently, many builtin
- Python functions have this property. Here's an example of a date
- validator:
-
- .. code:: pycon
-
- >>> from datetime import datetime
- >>> def Date(fmt='%Y-%m-%d'):
- ... return lambda v: datetime.strptime(v, fmt)
-
- .. code:: pycon
-
- >>> schema = Schema(Date())
- >>> schema('2013-03-03')
- datetime.datetime(2013, 3, 3, 0, 0)
- >>> try:
- ... schema('2013-03')
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "not a valid value"
- True
-
- In addition to simply determining if a value is valid, validators may
- mutate the value into a valid form. An example of this is the
- ``Coerce(type)`` function, which returns a function that coerces its
- argument to the given type:
-
- .. code:: python
-
- def Coerce(type, msg=None):
- """Coerce a value to a type.
-
- If the type constructor throws a ValueError, the value will be marked as
- Invalid.
- """
- def f(v):
- try:
- return type(v)
- except ValueError:
- raise Invalid(msg or ('expected %s' % type.__name__))
- return f
-
- This example also shows a common idiom where an optional human-readable
- message can be provided. This can vastly improve the usefulness of the
- resulting error messages.
-
- Dictionaries
- ~~~~~~~~~~~~
-
- Each key-value pair in a schema dictionary is validated against each
- key-value pair in the corresponding data dictionary:
-
- .. code:: pycon
-
- >>> schema = Schema({1: 'one', 2: 'two'})
- >>> schema({1: 'one'})
- {1: 'one'}
-
- Extra dictionary keys
- ^^^^^^^^^^^^^^^^^^^^^
-
- By default any additional keys in the data, not in the schema will
- trigger exceptions:
-
- .. code:: pycon
-
- >>> schema = Schema({2: 3})
- >>> try:
- ... schema({1: 2, 2: 3})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "extra keys not allowed @ data[1]"
- True
-
- This behaviour can be altered on a per-schema basis. To allow additional
- keys use ``Schema(..., extra=ALLOW_EXTRA)``:
-
- .. code:: pycon
-
- >>> from voluptuous import ALLOW_EXTRA
- >>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
- >>> schema({1: 2, 2: 3})
- {1: 2, 2: 3}
-
- To remove additional keys use ``Schema(..., extra=REMOVE_EXTRA)``:
-
- .. code:: pycon
-
- >>> from voluptuous import REMOVE_EXTRA
- >>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
- >>> schema({1: 2, 2: 3})
- {2: 3}
-
- It can also be overridden per-dictionary by using the catch-all marker
- token ``extra`` as a key:
-
- .. code:: pycon
-
- >>> from voluptuous import Extra
- >>> schema = Schema({1: {Extra: object}})
- >>> schema({1: {'foo': 'bar'}})
- {1: {'foo': 'bar'}}
-
- Required dictionary keys
- ^^^^^^^^^^^^^^^^^^^^^^^^
-
- By default, keys in the schema are not required to be in the data:
-
- .. code:: pycon
-
- >>> schema = Schema({1: 2, 3: 4})
- >>> schema({3: 4})
- {3: 4}
-
- Similarly to how extra\_ keys work, this behaviour can be overridden
- per-schema:
-
- .. code:: pycon
-
- >>> schema = Schema({1: 2, 3: 4}, required=True)
- >>> try:
- ... schema({3: 4})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data[1]"
- True
-
- And per-key, with the marker token ``Required(key)``:
-
- .. code:: pycon
-
- >>> schema = Schema({Required(1): 2, 3: 4})
- >>> try:
- ... schema({3: 4})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data[1]"
- True
- >>> schema({1: 2})
- {1: 2}
-
- Optional dictionary keys
- ^^^^^^^^^^^^^^^^^^^^^^^^
-
- If a schema has ``required=True``, keys may be individually marked as
- optional using the marker token ``Optional(key)``:
-
- .. code:: pycon
-
- >>> from voluptuous import Optional
- >>> schema = Schema({1: 2, Optional(3): 4}, required=True)
- >>> try:
- ... schema({})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data[1]"
- True
- >>> schema({1: 2})
- {1: 2}
- >>> try:
- ... schema({1: 2, 4: 5})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "extra keys not allowed @ data[4]"
- True
-
- .. code:: pycon
-
- >>> schema({1: 2, 3: 4})
- {1: 2, 3: 4}
-
- Recursive schema
- ~~~~~~~~~~~~~~~~
-
- There is no syntax to have a recursive schema. The best way to do it is
- to have a wrapper like this:
-
- .. code:: pycon
-
- >>> from voluptuous import Schema, Any
- >>> def s2(v):
- ... return s1(v)
- ...
- >>> s1 = Schema({"key": Any(s2, "value")})
- >>> s1({"key": {"key": "value"}})
- {'key': {'key': 'value'}}
-
- Extending an existing Schema
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Often it comes handy to have a base ``Schema`` that is extended with
- more requirements. In that case you can use ``Schema.extend`` to create
- a new ``Schema``:
-
- .. code:: pycon
-
- >>> from voluptuous import Schema
- >>> person = Schema({'name': str})
- >>> person_with_age = person.extend({'age': int})
- >>> sorted(list(person_with_age.schema.keys()))
- ['age', 'name']
-
- The original ``Schema`` remains unchanged.
-
- Objects
- ~~~~~~~
-
- Each key-value pair in a schema dictionary is validated against each
- attribute-value pair in the corresponding object:
-
- .. code:: pycon
-
- >>> from voluptuous import Object
- >>> class Structure(object):
- ... def __init__(self, q=None):
- ... self.q = q
- ... def __repr__(self):
- ... return '<Structure(q={0.q!r})>'.format(self)
- ...
- >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
- >>> schema(Structure(q='one'))
- <Structure(q='one')>
-
- Allow None values
- ~~~~~~~~~~~~~~~~~
-
- To allow value to be None as well, use Any:
-
- .. code:: pycon
-
- >>> from voluptuous import Any
-
- >>> schema = Schema(Any(None, int))
- >>> schema(None)
- >>> schema(5)
- 5
-
- Error reporting
- ---------------
-
- Validators must throw an ``Invalid`` exception if invalid data is passed
- to them. All other exceptions are treated as errors in the validator and
- will not be caught.
-
- Each ``Invalid`` exception has an associated ``path`` attribute
- representing the path in the data structure to our currently validating
- value, as well as an ``error_message`` attribute that contains the
- message of the original exception. This is especially useful when you
- want to catch ``Invalid`` exceptions and give some feedback to the user,
- for instance in the context of an HTTP API.
-
- .. code:: pycon
-
- >>> def validate_email(email):
- ... """Validate email."""
- ... if not "@" in email:
- ... raise Invalid("This email is invalid.")
- ... return email
- >>> schema = Schema({"email": validate_email})
- >>> exc = None
- >>> try:
- ... schema({"email": "whatever"})
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "This email is invalid. for dictionary value @ data['email']"
- >>> exc.path
- ['email']
- >>> exc.msg
- 'This email is invalid.'
- >>> exc.error_message
- 'This email is invalid.'
-
- The ``path`` attribute is used during error reporting, but also during
- matching to determine whether an error should be reported to the user or
- if the next match should be attempted. This is determined by comparing
- the depth of the path where the check is, to the depth of the path where
- the error occurred. If the error is more than one level deeper, it is
- reported.
-
- The upshot of this is that *matching is depth-first and fail-fast*.
-
- To illustrate this, here is an example schema:
-
- .. code:: pycon
-
- >>> schema = Schema([[2, 3], 6])
-
- Each value in the top-level list is matched depth-first in-order. Given
- input data of ``[[6]]``, the inner list will match the first element of
- the schema, but the literal ``6`` will not match any of the elements of
- that list. This error will be reported back to the user immediately. No
- backtracking is attempted:
-
- .. code:: pycon
-
- >>> try:
- ... schema([[6]])
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "not a valid value @ data[0][0]"
- True
-
- If we pass the data ``[6]``, the ``6`` is not a list type and so will
- not recurse into the first element of the schema. Matching will continue
- on to the second element in the schema, and succeed:
-
- .. code:: pycon
-
- >>> schema([6])
- [6]
-
- 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
- `Validino <http://code.google.com/p/validino/>`__, and to a lesser
- extent, `jsonvalidator <http://code.google.com/p/jsonvalidator/>`__ and
- `json\_schema <http://blog.sendapatch.se/category/json_schema.html>`__.
-
- I greatly prefer the light-weight style promoted by these libraries to
- the complexity of libraries like FormEncode.
-
- .. |Build Status| image:: https://travis-ci.org/alecthomas/voluptuous.png
- :target: https://travis-ci.org/alecthomas/voluptuous
- .. |Stories in Ready| image:: https://badge.waffle.io/alecthomas/voluptuous.png?label=ready&title=Ready
- :target: https://waffle.io/alecthomas/voluptuous
-
-Platform: any
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.1
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
deleted file mode 100644
--- a/third_party/python/voluptuous/README.md
+++ /dev/null
@@ -1,596 +0,0 @@
-# Voluptuous is a Python data validation library
-
-[![Build Status](https://travis-ci.org/alecthomas/voluptuous.png)](https://travis-ci.org/alecthomas/voluptuous) [![Stories in Ready](https://badge.waffle.io/alecthomas/voluptuous.png?label=ready&title=Ready)](https://waffle.io/alecthomas/voluptuous)
-
-Voluptuous, *despite* the name, is a Python data validation library. It
-is primarily intended for validating data coming into Python as JSON,
-YAML, etc.
-
-It has three goals:
-
-1. Simplicity.
-2. Support for complex data structures.
-3. Provide useful error messages.
-
-## Contact
-
-Voluptuous now has a mailing list! Send a mail to
-[<voluptuous@librelist.com>](mailto:voluptuous@librelist.com) to subscribe. Instructions
-will follow.
-
-You can also contact me directly via [email](mailto:alec@swapoff.org) or
-[Twitter](https://twitter.com/alecthomas).
-
-To file a bug, create a [new issue](https://github.com/alecthomas/voluptuous/issues/new) on GitHub with a short example of how to replicate the issue.
-
-## Show me an example
-
-Twitter's [user search API](https://dev.twitter.com/docs/api/1/get/users/search) accepts
-query URLs like:
-
-```
-$ curl 'http://api.twitter.com/1/users/search.json?q=python&per_page=20&page=1
-```
-
-To validate this we might use a schema like:
-
-```pycon
->>> from voluptuous import Schema
->>> schema = Schema({
-... 'q': str,
-... 'per_page': int,
-... 'page': int,
-... })
-
-```
-
-This schema very succinctly and roughly describes the data required by
-the API, and will work fine. But it has a few problems. Firstly, it
-doesn't fully express the constraints of the API. According to the API,
-`per_page` should be restricted to at most 20, defaulting to 5, for
-example. To describe the semantics of the API more accurately, our
-schema will need to be more thoroughly defined:
-
-```pycon
->>> from voluptuous import Required, All, Length, Range
->>> schema = Schema({
-... Required('q'): All(str, Length(min=1)),
-... Required('per_page', default=5): All(int, Range(min=1, max=20)),
-... 'page': All(int, Range(min=0)),
-... })
-
-```
-
-This schema fully enforces the interface defined in Twitter's
-documentation, and goes a little further for completeness.
-
-"q" is required:
-
-```pycon
->>> from voluptuous import MultipleInvalid, Invalid
->>> try:
-... schema({})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "required key not provided @ data['q']"
-True
-
-```
-
-...must be a string:
-
-```pycon
->>> try:
-... schema({'q': 123})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "expected str for dictionary value @ data['q']"
-True
-
-```
-
-...and must be at least one character in length:
-
-```pycon
->>> try:
-... schema({'q': ''})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
-True
->>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
-True
-
-```
-
-"per\_page" is a positive integer no greater than 20:
-
-```pycon
->>> try:
-... schema({'q': '#topic', 'per_page': 900})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
-True
->>> try:
-... schema({'q': '#topic', 'per_page': -10})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
-True
-
-```
-
-"page" is an integer \>= 0:
-
-```pycon
->>> try:
-... schema({'q': '#topic', 'per_page': 'one'})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc)
-"expected int for dictionary value @ data['per_page']"
->>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
-True
-
-```
-
-## Defining schemas
-
-Schemas are nested data structures consisting of dictionaries, lists,
-scalars and *validators*. Each node in the input schema is pattern
-matched against corresponding nodes in the input data.
-
-### Literals
-
-Literals in the schema are matched using normal equality checks:
-
-```pycon
->>> schema = Schema(1)
->>> schema(1)
-1
->>> schema = Schema('a string')
->>> schema('a string')
-'a string'
-
-```
-
-### Types
-
-Types in the schema are matched by checking if the corresponding value
-is an instance of the type:
-
-```pycon
->>> schema = Schema(int)
->>> schema(1)
-1
->>> try:
-... schema('one')
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "expected int"
-True
-
-```
-
-### URL's
-
-URL's in the schema are matched by using `urlparse` library.
-
-```pycon
->>> from voluptuous import Url
->>> schema = Schema(Url())
->>> schema('http://w3.org')
-'http://w3.org'
->>> try:
-... schema('one')
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "expected a URL"
-True
-
-```
-
-### Lists
-
-Lists in the schema are treated as a set of valid values. Each element
-in the schema list is compared to each value in the input data:
-
-```pycon
->>> schema = Schema([1, 'a', 'string'])
->>> schema([1])
-[1]
->>> schema([1, 1, 1])
-[1, 1, 1]
->>> schema(['a', 1, 'string', 1, 'string'])
-['a', 1, 'string', 1, 'string']
-
-```
-
-### Validation functions
-
-Validators are simple callables that raise an `Invalid` exception when
-they encounter invalid data. The criteria for determining validity is
-entirely up to the implementation; it may check that a value is a valid
-username with `pwd.getpwnam()`, it may check that a value is of a
-specific type, and so on.
-
-The simplest kind of validator is a Python function that raises
-ValueError when its argument is invalid. Conveniently, many builtin
-Python functions have this property. Here's an example of a date
-validator:
-
-```pycon
->>> from datetime import datetime
->>> def Date(fmt='%Y-%m-%d'):
-... return lambda v: datetime.strptime(v, fmt)
-
-```
-
-```pycon
->>> schema = Schema(Date())
->>> schema('2013-03-03')
-datetime.datetime(2013, 3, 3, 0, 0)
->>> try:
-... schema('2013-03')
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "not a valid value"
-True
-
-```
-
-In addition to simply determining if a value is valid, validators may
-mutate the value into a valid form. An example of this is the
-`Coerce(type)` function, which returns a function that coerces its
-argument to the given type:
-
-```python
-def Coerce(type, msg=None):
- """Coerce a value to a type.
-
- If the type constructor throws a ValueError, the value will be marked as
- Invalid.
- """
- def f(v):
- try:
- return type(v)
- except ValueError:
- raise Invalid(msg or ('expected %s' % type.__name__))
- return f
-
-```
-
-This example also shows a common idiom where an optional human-readable
-message can be provided. This can vastly improve the usefulness of the
-resulting error messages.
-
-### Dictionaries
-
-Each key-value pair in a schema dictionary is validated against each
-key-value pair in the corresponding data dictionary:
-
-```pycon
->>> schema = Schema({1: 'one', 2: 'two'})
->>> schema({1: 'one'})
-{1: 'one'}
-
-```
-
-#### Extra dictionary keys
-
-By default any additional keys in the data, not in the schema will
-trigger exceptions:
-
-```pycon
->>> schema = Schema({2: 3})
->>> try:
-... schema({1: 2, 2: 3})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "extra keys not allowed @ data[1]"
-True
-
-```
-
-This behaviour can be altered on a per-schema basis. To allow
-additional keys use
-`Schema(..., extra=ALLOW_EXTRA)`:
-
-```pycon
->>> from voluptuous import ALLOW_EXTRA
->>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
->>> schema({1: 2, 2: 3})
-{1: 2, 2: 3}
-
-```
-
-To remove additional keys use
-`Schema(..., extra=REMOVE_EXTRA)`:
-
-```pycon
->>> from voluptuous import REMOVE_EXTRA
->>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
->>> schema({1: 2, 2: 3})
-{2: 3}
-
-```
-
-It can also be overridden per-dictionary by using the catch-all marker
-token `extra` as a key:
-
-```pycon
->>> from voluptuous import Extra
->>> schema = Schema({1: {Extra: object}})
->>> schema({1: {'foo': 'bar'}})
-{1: {'foo': 'bar'}}
-
-```
-
-#### Required dictionary keys
-
-By default, keys in the schema are not required to be in the data:
-
-```pycon
->>> schema = Schema({1: 2, 3: 4})
->>> schema({3: 4})
-{3: 4}
-
-```
-
-Similarly to how extra\_ keys work, this behaviour can be overridden
-per-schema:
-
-```pycon
->>> schema = Schema({1: 2, 3: 4}, required=True)
->>> try:
-... schema({3: 4})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "required key not provided @ data[1]"
-True
-
-```
-
-And per-key, with the marker token `Required(key)`:
-
-```pycon
->>> schema = Schema({Required(1): 2, 3: 4})
->>> try:
-... schema({3: 4})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "required key not provided @ data[1]"
-True
->>> schema({1: 2})
-{1: 2}
-
-```
-
-#### Optional dictionary keys
-
-If a schema has `required=True`, keys may be individually marked as
-optional using the marker token `Optional(key)`:
-
-```pycon
->>> from voluptuous import Optional
->>> schema = Schema({1: 2, Optional(3): 4}, required=True)
->>> try:
-... schema({})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "required key not provided @ data[1]"
-True
->>> schema({1: 2})
-{1: 2}
->>> try:
-... schema({1: 2, 4: 5})
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "extra keys not allowed @ data[4]"
-True
-
-```
-
-```pycon
->>> schema({1: 2, 3: 4})
-{1: 2, 3: 4}
-
-```
-
-### Recursive schema
-
-There is no syntax to have a recursive schema. The best way to do it is to have a wrapper like this:
-
-```pycon
->>> from voluptuous import Schema, Any
->>> def s2(v):
-... return s1(v)
-...
->>> s1 = Schema({"key": Any(s2, "value")})
->>> s1({"key": {"key": "value"}})
-{'key': {'key': 'value'}}
-
-```
-
-### Extending an existing Schema
-
-Often it comes handy to have a base `Schema` that is extended with more
-requirements. In that case you can use `Schema.extend` to create a new
-`Schema`:
-
-```pycon
->>> from voluptuous import Schema
->>> person = Schema({'name': str})
->>> person_with_age = person.extend({'age': int})
->>> sorted(list(person_with_age.schema.keys()))
-['age', 'name']
-
-```
-
-The original `Schema` remains unchanged.
-
-### Objects
-
-Each key-value pair in a schema dictionary is validated against each
-attribute-value pair in the corresponding object:
-
-```pycon
->>> from voluptuous import Object
->>> class Structure(object):
-... def __init__(self, q=None):
-... self.q = q
-... def __repr__(self):
-... return '<Structure(q={0.q!r})>'.format(self)
-...
->>> schema = Schema(Object({'q': 'one'}, cls=Structure))
->>> schema(Structure(q='one'))
-<Structure(q='one')>
-
-```
-
-### Allow None values
-
-To allow value to be None as well, use Any:
-
-```pycon
->>> from voluptuous import Any
-
->>> schema = Schema(Any(None, int))
->>> schema(None)
->>> schema(5)
-5
-
-```
-
-## Error reporting
-
-Validators must throw an `Invalid` exception if invalid data is passed
-to them. All other exceptions are treated as errors in the validator and
-will not be caught.
-
-Each `Invalid` exception has an associated `path` attribute representing
-the path in the data structure to our currently validating value, as well
-as an `error_message` attribute that contains the message of the original
-exception. This is especially useful when you want to catch `Invalid`
-exceptions and give some feedback to the user, for instance in the context of
-an HTTP API.
-
-
-```pycon
->>> def validate_email(email):
-... """Validate email."""
-... if not "@" in email:
-... raise Invalid("This email is invalid.")
-... return email
->>> schema = Schema({"email": validate_email})
->>> exc = None
->>> try:
-... schema({"email": "whatever"})
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc)
-"This email is invalid. for dictionary value @ data['email']"
->>> exc.path
-['email']
->>> exc.msg
-'This email is invalid.'
->>> exc.error_message
-'This email is invalid.'
-
-```
-
-The `path` attribute is used during error reporting, but also during matching
-to determine whether an error should be reported to the user or if the next
-match should be attempted. This is determined by comparing the depth of the
-path where the check is, to the depth of the path where the error occurred. If
-the error is more than one level deeper, it is reported.
-
-The upshot of this is that *matching is depth-first and fail-fast*.
-
-To illustrate this, here is an example schema:
-
-```pycon
->>> schema = Schema([[2, 3], 6])
-
-```
-
-Each value in the top-level list is matched depth-first in-order. Given
-input data of `[[6]]`, the inner list will match the first element of
-the schema, but the literal `6` will not match any of the elements of
-that list. This error will be reported back to the user immediately. No
-backtracking is attempted:
-
-```pycon
->>> try:
-... schema([[6]])
-... raise AssertionError('MultipleInvalid not raised')
-... except MultipleInvalid as e:
-... exc = e
->>> str(exc) == "not a valid value @ data[0][0]"
-True
-
-```
-
-If we pass the data `[6]`, the `6` is not a list type and so will not
-recurse into the first element of the schema. Matching will continue on
-to the second element in the schema, and succeed:
-
-```pycon
->>> schema([6])
-[6]
-
-```
-
-## 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
-[Validino](http://code.google.com/p/validino/), and to a lesser extent,
-[jsonvalidator](http://code.google.com/p/jsonvalidator/) and
-[json\_schema](http://blog.sendapatch.se/category/json_schema.html).
-
-I greatly prefer the light-weight style promoted by these libraries to
-the complexity of libraries like FormEncode.
deleted file mode 100644
--- a/third_party/python/voluptuous/README.rst
+++ /dev/null
@@ -1,589 +0,0 @@
-Voluptuous is a Python data validation library
-==============================================
-
-|Build Status| |Stories in Ready|
-
-Voluptuous, *despite* the name, is a Python data validation library. It
-is primarily intended for validating data coming into Python as JSON,
-YAML, etc.
-
-It has three goals:
-
-1. Simplicity.
-2. Support for complex data structures.
-3. Provide useful error messages.
-
-Contact
--------
-
-Voluptuous now has a mailing list! Send a mail to
-` <mailto:voluptuous@librelist.com>`__ to subscribe. Instructions will
-follow.
-
-You can also contact me directly via `email <mailto:alec@swapoff.org>`__
-or `Twitter <https://twitter.com/alecthomas>`__.
-
-To file a bug, create a `new
-issue <https://github.com/alecthomas/voluptuous/issues/new>`__ on GitHub
-with a short example of how to replicate the issue.
-
-Show me an example
-------------------
-
-Twitter's `user search
-API <https://dev.twitter.com/docs/api/1/get/users/search>`__ accepts
-query URLs like:
-
-::
-
- $ curl 'http://api.twitter.com/1/users/search.json?q=python&per_page=20&page=1
-
-To validate this we might use a schema like:
-
-.. code:: pycon
-
- >>> from voluptuous import Schema
- >>> schema = Schema({
- ... 'q': str,
- ... 'per_page': int,
- ... 'page': int,
- ... })
-
-This schema very succinctly and roughly describes the data required by
-the API, and will work fine. But it has a few problems. Firstly, it
-doesn't fully express the constraints of the API. According to the API,
-``per_page`` should be restricted to at most 20, defaulting to 5, for
-example. To describe the semantics of the API more accurately, our
-schema will need to be more thoroughly defined:
-
-.. code:: pycon
-
- >>> from voluptuous import Required, All, Length, Range
- >>> schema = Schema({
- ... Required('q'): All(str, Length(min=1)),
- ... Required('per_page', default=5): All(int, Range(min=1, max=20)),
- ... 'page': All(int, Range(min=0)),
- ... })
-
-This schema fully enforces the interface defined in Twitter's
-documentation, and goes a little further for completeness.
-
-"q" is required:
-
-.. code:: pycon
-
- >>> from voluptuous import MultipleInvalid, Invalid
- >>> try:
- ... schema({})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data['q']"
- True
-
-...must be a string:
-
-.. code:: pycon
-
- >>> try:
- ... schema({'q': 123})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "expected str for dictionary value @ data['q']"
- True
-
-...and must be at least one character in length:
-
-.. code:: pycon
-
- >>> try:
- ... schema({'q': ''})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
- True
- >>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
- True
-
-"per\_page" is a positive integer no greater than 20:
-
-.. code:: pycon
-
- >>> try:
- ... schema({'q': '#topic', 'per_page': 900})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
- True
- >>> try:
- ... schema({'q': '#topic', 'per_page': -10})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
- True
-
-"page" is an integer >= 0:
-
-.. code:: pycon
-
- >>> try:
- ... schema({'q': '#topic', 'per_page': 'one'})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "expected int for dictionary value @ data['per_page']"
- >>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
- True
-
-Defining schemas
-----------------
-
-Schemas are nested data structures consisting of dictionaries, lists,
-scalars and *validators*. Each node in the input schema is pattern
-matched against corresponding nodes in the input data.
-
-Literals
-~~~~~~~~
-
-Literals in the schema are matched using normal equality checks:
-
-.. code:: pycon
-
- >>> schema = Schema(1)
- >>> schema(1)
- 1
- >>> schema = Schema('a string')
- >>> schema('a string')
- 'a string'
-
-Types
-~~~~~
-
-Types in the schema are matched by checking if the corresponding value
-is an instance of the type:
-
-.. code:: pycon
-
- >>> schema = Schema(int)
- >>> schema(1)
- 1
- >>> try:
- ... schema('one')
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "expected int"
- True
-
-URL's
-~~~~~
-
-URL's in the schema are matched by using ``urlparse`` library.
-
-.. code:: pycon
-
- >>> from voluptuous import Url
- >>> schema = Schema(Url())
- >>> schema('http://w3.org')
- 'http://w3.org'
- >>> try:
- ... schema('one')
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "expected a URL"
- True
-
-Lists
-~~~~~
-
-Lists in the schema are treated as a set of valid values. Each element
-in the schema list is compared to each value in the input data:
-
-.. code:: pycon
-
- >>> schema = Schema([1, 'a', 'string'])
- >>> schema([1])
- [1]
- >>> schema([1, 1, 1])
- [1, 1, 1]
- >>> schema(['a', 1, 'string', 1, 'string'])
- ['a', 1, 'string', 1, 'string']
-
-Validation functions
-~~~~~~~~~~~~~~~~~~~~
-
-Validators are simple callables that raise an ``Invalid`` exception when
-they encounter invalid data. The criteria for determining validity is
-entirely up to the implementation; it may check that a value is a valid
-username with ``pwd.getpwnam()``, it may check that a value is of a
-specific type, and so on.
-
-The simplest kind of validator is a Python function that raises
-ValueError when its argument is invalid. Conveniently, many builtin
-Python functions have this property. Here's an example of a date
-validator:
-
-.. code:: pycon
-
- >>> from datetime import datetime
- >>> def Date(fmt='%Y-%m-%d'):
- ... return lambda v: datetime.strptime(v, fmt)
-
-.. code:: pycon
-
- >>> schema = Schema(Date())
- >>> schema('2013-03-03')
- datetime.datetime(2013, 3, 3, 0, 0)
- >>> try:
- ... schema('2013-03')
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "not a valid value"
- True
-
-In addition to simply determining if a value is valid, validators may
-mutate the value into a valid form. An example of this is the
-``Coerce(type)`` function, which returns a function that coerces its
-argument to the given type:
-
-.. code:: python
-
- def Coerce(type, msg=None):
- """Coerce a value to a type.
-
- If the type constructor throws a ValueError, the value will be marked as
- Invalid.
- """
- def f(v):
- try:
- return type(v)
- except ValueError:
- raise Invalid(msg or ('expected %s' % type.__name__))
- return f
-
-This example also shows a common idiom where an optional human-readable
-message can be provided. This can vastly improve the usefulness of the
-resulting error messages.
-
-Dictionaries
-~~~~~~~~~~~~
-
-Each key-value pair in a schema dictionary is validated against each
-key-value pair in the corresponding data dictionary:
-
-.. code:: pycon
-
- >>> schema = Schema({1: 'one', 2: 'two'})
- >>> schema({1: 'one'})
- {1: 'one'}
-
-Extra dictionary keys
-^^^^^^^^^^^^^^^^^^^^^
-
-By default any additional keys in the data, not in the schema will
-trigger exceptions:
-
-.. code:: pycon
-
- >>> schema = Schema({2: 3})
- >>> try:
- ... schema({1: 2, 2: 3})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "extra keys not allowed @ data[1]"
- True
-
-This behaviour can be altered on a per-schema basis. To allow additional
-keys use ``Schema(..., extra=ALLOW_EXTRA)``:
-
-.. code:: pycon
-
- >>> from voluptuous import ALLOW_EXTRA
- >>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
- >>> schema({1: 2, 2: 3})
- {1: 2, 2: 3}
-
-To remove additional keys use ``Schema(..., extra=REMOVE_EXTRA)``:
-
-.. code:: pycon
-
- >>> from voluptuous import REMOVE_EXTRA
- >>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
- >>> schema({1: 2, 2: 3})
- {2: 3}
-
-It can also be overridden per-dictionary by using the catch-all marker
-token ``extra`` as a key:
-
-.. code:: pycon
-
- >>> from voluptuous import Extra
- >>> schema = Schema({1: {Extra: object}})
- >>> schema({1: {'foo': 'bar'}})
- {1: {'foo': 'bar'}}
-
-Required dictionary keys
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-By default, keys in the schema are not required to be in the data:
-
-.. code:: pycon
-
- >>> schema = Schema({1: 2, 3: 4})
- >>> schema({3: 4})
- {3: 4}
-
-Similarly to how extra\_ keys work, this behaviour can be overridden
-per-schema:
-
-.. code:: pycon
-
- >>> schema = Schema({1: 2, 3: 4}, required=True)
- >>> try:
- ... schema({3: 4})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data[1]"
- True
-
-And per-key, with the marker token ``Required(key)``:
-
-.. code:: pycon
-
- >>> schema = Schema({Required(1): 2, 3: 4})
- >>> try:
- ... schema({3: 4})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data[1]"
- True
- >>> schema({1: 2})
- {1: 2}
-
-Optional dictionary keys
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-If a schema has ``required=True``, keys may be individually marked as
-optional using the marker token ``Optional(key)``:
-
-.. code:: pycon
-
- >>> from voluptuous import Optional
- >>> schema = Schema({1: 2, Optional(3): 4}, required=True)
- >>> try:
- ... schema({})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "required key not provided @ data[1]"
- True
- >>> schema({1: 2})
- {1: 2}
- >>> try:
- ... schema({1: 2, 4: 5})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "extra keys not allowed @ data[4]"
- True
-
-.. code:: pycon
-
- >>> schema({1: 2, 3: 4})
- {1: 2, 3: 4}
-
-Recursive schema
-~~~~~~~~~~~~~~~~
-
-There is no syntax to have a recursive schema. The best way to do it is
-to have a wrapper like this:
-
-.. code:: pycon
-
- >>> from voluptuous import Schema, Any
- >>> def s2(v):
- ... return s1(v)
- ...
- >>> s1 = Schema({"key": Any(s2, "value")})
- >>> s1({"key": {"key": "value"}})
- {'key': {'key': 'value'}}
-
-Extending an existing Schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Often it comes handy to have a base ``Schema`` that is extended with
-more requirements. In that case you can use ``Schema.extend`` to create
-a new ``Schema``:
-
-.. code:: pycon
-
- >>> from voluptuous import Schema
- >>> person = Schema({'name': str})
- >>> person_with_age = person.extend({'age': int})
- >>> sorted(list(person_with_age.schema.keys()))
- ['age', 'name']
-
-The original ``Schema`` remains unchanged.
-
-Objects
-~~~~~~~
-
-Each key-value pair in a schema dictionary is validated against each
-attribute-value pair in the corresponding object:
-
-.. code:: pycon
-
- >>> from voluptuous import Object
- >>> class Structure(object):
- ... def __init__(self, q=None):
- ... self.q = q
- ... def __repr__(self):
- ... return '<Structure(q={0.q!r})>'.format(self)
- ...
- >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
- >>> schema(Structure(q='one'))
- <Structure(q='one')>
-
-Allow None values
-~~~~~~~~~~~~~~~~~
-
-To allow value to be None as well, use Any:
-
-.. code:: pycon
-
- >>> from voluptuous import Any
-
- >>> schema = Schema(Any(None, int))
- >>> schema(None)
- >>> schema(5)
- 5
-
-Error reporting
----------------
-
-Validators must throw an ``Invalid`` exception if invalid data is passed
-to them. All other exceptions are treated as errors in the validator and
-will not be caught.
-
-Each ``Invalid`` exception has an associated ``path`` attribute
-representing the path in the data structure to our currently validating
-value, as well as an ``error_message`` attribute that contains the
-message of the original exception. This is especially useful when you
-want to catch ``Invalid`` exceptions and give some feedback to the user,
-for instance in the context of an HTTP API.
-
-.. code:: pycon
-
- >>> def validate_email(email):
- ... """Validate email."""
- ... if not "@" in email:
- ... raise Invalid("This email is invalid.")
- ... return email
- >>> schema = Schema({"email": validate_email})
- >>> exc = None
- >>> try:
- ... schema({"email": "whatever"})
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "This email is invalid. for dictionary value @ data['email']"
- >>> exc.path
- ['email']
- >>> exc.msg
- 'This email is invalid.'
- >>> exc.error_message
- 'This email is invalid.'
-
-The ``path`` attribute is used during error reporting, but also during
-matching to determine whether an error should be reported to the user or
-if the next match should be attempted. This is determined by comparing
-the depth of the path where the check is, to the depth of the path where
-the error occurred. If the error is more than one level deeper, it is
-reported.
-
-The upshot of this is that *matching is depth-first and fail-fast*.
-
-To illustrate this, here is an example schema:
-
-.. code:: pycon
-
- >>> schema = Schema([[2, 3], 6])
-
-Each value in the top-level list is matched depth-first in-order. Given
-input data of ``[[6]]``, the inner list will match the first element of
-the schema, but the literal ``6`` will not match any of the elements of
-that list. This error will be reported back to the user immediately. No
-backtracking is attempted:
-
-.. code:: pycon
-
- >>> try:
- ... schema([[6]])
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == "not a valid value @ data[0][0]"
- True
-
-If we pass the data ``[6]``, the ``6`` is not a list type and so will
-not recurse into the first element of the schema. Matching will continue
-on to the second element in the schema, and succeed:
-
-.. code:: pycon
-
- >>> schema([6])
- [6]
-
-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
-`Validino <http://code.google.com/p/validino/>`__, and to a lesser
-extent, `jsonvalidator <http://code.google.com/p/jsonvalidator/>`__ and
-`json\_schema <http://blog.sendapatch.se/category/json_schema.html>`__.
-
-I greatly prefer the light-weight style promoted by these libraries to
-the complexity of libraries like FormEncode.
-
-.. |Build Status| image:: https://travis-ci.org/alecthomas/voluptuous.png
- :target: https://travis-ci.org/alecthomas/voluptuous
-.. |Stories in Ready| image:: https://badge.waffle.io/alecthomas/voluptuous.png?label=ready&title=Ready
- :target: https://waffle.io/alecthomas/voluptuous
deleted file mode 100644
--- a/third_party/python/voluptuous/setup.cfg
+++ /dev/null
@@ -1,10 +0,0 @@
-[nosetests]
-doctest-extension = md
-with-doctest = 1
-where = .
-
-[egg_info]
-tag_build =
-tag_date = 0
-tag_svn_revision = 0
-
deleted file mode 100644
--- a/third_party/python/voluptuous/setup.py
+++ /dev/null
@@ -1,54 +0,0 @@
-try:
- from setuptools import setup
-except ImportError:
- from distutils.core import setup
-
-import sys
-import os
-import atexit
-sys.path.insert(0, '.')
-version = __import__('voluptuous').__version__
-
-try:
- import pypandoc
- long_description = pypandoc.convert('README.md', 'rst')
- with open('README.rst', 'w') as f:
- f.write(long_description)
- atexit.register(lambda: os.unlink('README.rst'))
-except (ImportError, OSError):
- print('WARNING: Could not locate pandoc, using Markdown long_description.')
- with open('README.md') as f:
- long_description = f.read()
-
-description = long_description.splitlines()[0].strip()
-
-
-setup(
- name='voluptuous',
- url='https://github.com/alecthomas/voluptuous',
- download_url='https://pypi.python.org/pypi/voluptuous',
- version=version,
- description=description,
- long_description=long_description,
- license='BSD',
- platforms=['any'],
- py_modules=['voluptuous'],
- author='Alec Thomas',
- author_email='alec@swapoff.org',
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.1',
- 'Programming Language :: Python :: 3.2',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- ],
- install_requires=[
- 'setuptools >= 0.6b1',
- ],
-)
deleted file mode 100644
--- a/third_party/python/voluptuous/tests.md
+++ /dev/null
@@ -1,268 +0,0 @@
-Error reporting should be accurate:
-
- >>> from voluptuous import *
- >>> schema = Schema(['one', {'two': 'three', 'four': ['five'],
- ... 'six': {'seven': 'eight'}}])
- >>> schema(['one'])
- ['one']
- >>> schema([{'two': 'three'}])
- [{'two': 'three'}]
-
-It should show the exact index and container type, in this case a list
-value:
-
- >>> try:
- ... schema(['one', 'two'])
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc) == 'expected a dictionary @ data[1]'
- True
-
-It should also be accurate for nested values:
-
- >>> try:
- ... schema([{'two': 'nine'}])
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "not a valid value for dictionary value @ data[0]['two']"
-
- >>> try:
- ... schema([{'four': ['nine']}])
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "not a valid value @ data[0]['four'][0]"
-
- >>> try:
- ... schema([{'six': {'seven': 'nine'}}])
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "not a valid value for dictionary value @ data[0]['six']['seven']"
-
-Errors should be reported depth-first:
-
- >>> validate = Schema({'one': {'two': 'three', 'four': 'five'}})
- >>> try:
- ... validate({'one': {'four': 'six'}})
- ... except Invalid as e:
- ... print(e)
- ... print(e.path)
- not a valid value for dictionary value @ data['one']['four']
- ['one', 'four']
-
-Voluptuous supports validation when extra fields are present in the
-data:
-
- >>> schema = Schema({'one': 1, Extra: object})
- >>> schema({'two': 'two', 'one': 1}) == {'two': 'two', 'one': 1}
- True
- >>> schema = Schema({'one': 1})
- >>> try:
- ... schema({'two': 2})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "extra keys not allowed @ data['two']"
-
-dict, list, and tuple should be available as type validators:
-
- >>> Schema(dict)({'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
- True
- >>> Schema(list)([1,2,3])
- [1, 2, 3]
- >>> Schema(tuple)((1,2,3))
- (1, 2, 3)
-
-Validation should return instances of the right types when the types are
-subclasses of dict or list:
-
- >>> class Dict(dict):
- ... pass
- >>>
- >>> d = Schema(dict)(Dict(a=1, b=2))
- >>> d == {'a': 1, 'b': 2}
- True
- >>> type(d) is Dict
- True
- >>> class List(list):
- ... pass
- >>>
- >>> l = Schema(list)(List([1,2,3]))
- >>> l
- [1, 2, 3]
- >>> type(l) is List
- True
-
-Multiple errors are reported:
-
- >>> schema = Schema({'one': 1, 'two': 2})
- >>> try:
- ... schema({'one': 2, 'two': 3, 'three': 4})
- ... except MultipleInvalid as e:
- ... errors = sorted(e.errors, key=lambda k: str(k))
- ... print([str(i) for i in errors]) # doctest: +NORMALIZE_WHITESPACE
- ["extra keys not allowed @ data['three']",
- "not a valid value for dictionary value @ data['one']",
- "not a valid value for dictionary value @ data['two']"]
- >>> schema = Schema([[1], [2], [3]])
- >>> try:
- ... schema([1, 2, 3])
- ... except MultipleInvalid as e:
- ... print([str(i) for i in e.errors]) # doctest: +NORMALIZE_WHITESPACE
- ['expected a list @ data[0]',
- 'expected a list @ data[1]',
- 'expected a list @ data[2]']
-
-Required fields in dictionary which are invalid should not have required :
-
- >>> from voluptuous import *
- >>> schema = Schema({'one': {'two': 3}}, required=True)
- >>> try:
- ... schema({'one': {'two': 2}})
- ... except MultipleInvalid as e:
- ... errors = e.errors
- >>> 'required' in ' '.join([x.msg for x in errors])
- False
-
-Multiple errors for nested fields in dicts and objects:
-
-> \>\>\> from collections import namedtuple \>\>\> validate = Schema({
-> ... 'anobject': Object({ ... 'strfield': str, ... 'intfield': int ...
-> }) ... }) \>\>\> try: ... SomeObj = namedtuple('SomeObj', ('strfield',
-> 'intfield')) ... validate({'anobject': SomeObj(strfield=123,
-> intfield='one')}) ... except MultipleInvalid as e: ...
-> print(sorted(str(i) for i in e.errors)) \# doctest:
-> +NORMALIZE\_WHITESPACE ["expected int for object value @
-> data['anobject']['intfield']", "expected str for object value @
-> data['anobject']['strfield']"]
-
-Custom classes validate as schemas:
-
- >>> class Thing(object):
- ... pass
- >>> schema = Schema(Thing)
- >>> t = schema(Thing())
- >>> type(t) is Thing
- True
-
-Classes with custom metaclasses should validate as schemas:
-
- >>> class MyMeta(type):
- ... pass
- >>> class Thing(object):
- ... __metaclass__ = MyMeta
- >>> schema = Schema(Thing)
- >>> t = schema(Thing())
- >>> type(t) is Thing
- True
-
-Schemas built with All() should give the same error as the original
-validator (Issue \#26):
-
- >>> schema = Schema({
- ... Required('items'): All([{
- ... Required('foo'): str
- ... }])
- ... })
-
- >>> try:
- ... schema({'items': [{}]})
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "required key not provided @ data['items'][0]['foo']"
-
-Validator should return same instance of the same type for object:
-
- >>> class Structure(object):
- ... def __init__(self, q=None):
- ... self.q = q
- ... def __repr__(self):
- ... return '{0.__name__}(q={1.q!r})'.format(type(self), self)
- ...
- >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
- >>> type(schema(Structure(q='one'))) is Structure
- True
-
-Object validator should treat cls argument as optional. In this case it
-shouldn't check object type:
-
- >>> from collections import namedtuple
- >>> NamedTuple = namedtuple('NamedTuple', ('q',))
- >>> schema = Schema(Object({'q': 'one'}))
- >>> named = NamedTuple(q='one')
- >>> schema(named) == named
- True
- >>> schema(named)
- NamedTuple(q='one')
-
-If cls argument passed to object validator we should check object type:
-
- >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
- >>> schema(NamedTuple(q='one')) # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- MultipleInvalid: expected a <class 'Structure'>
- >>> schema = Schema(Object({'q': 'one'}, cls=NamedTuple))
- >>> schema(NamedTuple(q='one'))
- NamedTuple(q='one')
-
-Ensure that objects with \_\_slots\_\_ supported properly:
-
- >>> class SlotsStructure(Structure):
- ... __slots__ = ['q']
- ...
- >>> schema = Schema(Object({'q': 'one'}))
- >>> schema(SlotsStructure(q='one'))
- SlotsStructure(q='one')
- >>> class DictStructure(object):
- ... __slots__ = ['q', '__dict__']
- ... def __init__(self, q=None, page=None):
- ... self.q = q
- ... self.page = page
- ... def __repr__(self):
- ... return '{0.__name__}(q={1.q!r}, page={1.page!r})'.format(type(self), self)
- ...
- >>> structure = DictStructure(q='one')
- >>> structure.page = 1
- >>> try:
- ... schema(structure)
- ... raise AssertionError('MultipleInvalid not raised')
- ... except MultipleInvalid as e:
- ... exc = e
- >>> str(exc)
- "extra keys not allowed @ data['page']"
-
- >>> schema = Schema(Object({'q': 'one', Extra: object}))
- >>> schema(structure)
- DictStructure(q='one', page=1)
-
-Ensure that objects can be used with other validators:
-
- >>> schema = Schema({'meta': Object({'q': 'one'})})
- >>> schema({'meta': Structure(q='one')})
- {'meta': Structure(q='one')}
-
-Ensure that subclasses of Invalid of are raised as is.
-
- >>> class SpecialInvalid(Invalid):
- ... pass
- ...
- >>> def custom_validator(value):
- ... raise SpecialInvalid('boom')
- ...
- >>> schema = Schema({'thing': custom_validator})
- >>> try:
- ... schema({'thing': 'not an int'})
- ... except MultipleInvalid as e:
- ... exc = e
- >>> exc.errors[0].__class__.__name__
- 'SpecialInvalid'
deleted file mode 100644
--- a/third_party/python/voluptuous/voluptuous.py
+++ /dev/null
@@ -1,1954 +0,0 @@
-# encoding: utf-8
-#
-# Copyright (C) 2010-2013 Alec Thomas <alec@swapoff.org>
-# All rights reserved.
-#
-# This software is licensed as described in the file COPYING, which
-# you should have received as part of this distribution.
-#
-# Author: Alec Thomas <alec@swapoff.org>
-
-"""Schema validation for Python data structures.
-
-Given eg. a nested data structure like this:
-
- {
- 'exclude': ['Users', 'Uptime'],
- 'include': [],
- 'set': {
- 'snmp_community': 'public',
- 'snmp_timeout': 15,
- 'snmp_version': '2c',
- },
- 'targets': {
- 'localhost': {
- 'exclude': ['Uptime'],
- 'features': {
- 'Uptime': {
- 'retries': 3,
- },
- 'Users': {
- 'snmp_community': 'monkey',
- 'snmp_port': 15,
- },
- },
- 'include': ['Users'],
- 'set': {
- 'snmp_community': 'monkeys',
- },
- },
- },
- }
-
-A schema like this:
-
- >>> settings = {
- ... 'snmp_community': str,
- ... 'retries': int,
- ... 'snmp_version': All(Coerce(str), Any('3', '2c', '1')),
- ... }
- >>> features = ['Ping', 'Uptime', 'Http']
- >>> schema = Schema({
- ... 'exclude': features,
- ... 'include': features,
- ... 'set': settings,
- ... 'targets': {
- ... 'exclude': features,
- ... 'include': features,
- ... 'features': {
- ... str: settings,
- ... },
- ... },
- ... })
-
-Validate like so:
-
- >>> schema({
- ... 'set': {
- ... 'snmp_community': 'public',
- ... 'snmp_version': '2c',
- ... },
- ... 'targets': {
- ... 'exclude': ['Ping'],
- ... 'features': {
- ... 'Uptime': {'retries': 3},
- ... 'Users': {'snmp_community': 'monkey'},
- ... },
- ... },
- ... }) == {
- ... 'set': {'snmp_version': '2c', 'snmp_community': 'public'},
- ... 'targets': {
- ... 'exclude': ['Ping'],
- ... 'features': {'Uptime': {'retries': 3},
- ... 'Users': {'snmp_community': 'monkey'}}}}
- True
-"""
-import collections
-import datetime
-import inspect
-import os
-import re
-import sys
-from contextlib import contextmanager
-from functools import wraps
-
-
-if sys.version_info >= (3,):
- import urllib.parse as urlparse
- long = int
- unicode = str
- basestring = str
- ifilter = filter
- iteritems = lambda d: d.items()
-else:
- from itertools import ifilter
- import urlparse
- iteritems = lambda d: d.iteritems()
-
-
-__author__ = 'Alec Thomas <alec@swapoff.org>'
-__version__ = '0.8.11'
-
-
-@contextmanager
-def raises(exc, msg=None, regex=None):
- try:
- yield
- except exc as e:
- if msg is not None:
- assert str(e) == msg, '%r != %r' % (str(e), msg)
- if regex is not None:
- assert re.search(regex, str(e)), '%r does not match %r' % (str(e), regex)
-
-
-class Undefined(object):
- def __nonzero__(self):
- return False
-
- def __repr__(self):
- return '...'
-
-
-UNDEFINED = Undefined()
-
-
-def default_factory(value):
- if value is UNDEFINED or callable(value):
- return value
- return lambda: value
-
-
-# options for extra keys
-PREVENT_EXTRA = 0 # any extra key not in schema will raise an error
-ALLOW_EXTRA = 1 # extra keys not in schema will be included in output
-REMOVE_EXTRA = 2 # extra keys not in schema will be excluded from output
-
-
-class Error(Exception):
- """Base validation exception."""
-
-
-class SchemaError(Error):
- """An error was encountered in the schema."""
-
-
-class Invalid(Error):
- """The data was invalid.
-
- :attr msg: The error message.
- :attr path: The path to the error, as a list of keys in the source data.
- :attr error_message: The actual error message that was raised, as a
- string.
-
- """
-
- def __init__(self, message, path=None, error_message=None, error_type=None):
- Error.__init__(self, message)
- self.path = path or []
- self.error_message = error_message or message
- self.error_type = error_type
-
- @property
- def msg(self):
- return self.args[0]
-
- def __str__(self):
- path = ' @ data[%s]' % ']['.join(map(repr, self.path)) \
- if self.path else ''
- output = Exception.__str__(self)
- if self.error_type:
- output += ' for ' + self.error_type
- return output + path
-
- def prepend(self, path):
- self.path = path + self.path
-
-
-class MultipleInvalid(Invalid):
- def __init__(self, errors=None):
- self.errors = errors[:] if errors else []
-
- def __repr__(self):
- return 'MultipleInvalid(%r)' % self.errors
-
- @property
- def msg(self):
- return self.errors[0].msg
-
- @property
- def path(self):
- return self.errors[0].path
-
- @property
- def error_message(self):
- return self.errors[0].error_message
-
- def add(self, error):
- self.errors.append(error)
-
- def __str__(self):
- return str(self.errors[0])
-
- def prepend(self, path):
- for error in self.errors:
- error.prepend(path)
-
-
-class RequiredFieldInvalid(Invalid):
- """Required field was missing."""
-
-
-class ObjectInvalid(Invalid):
- """The value we found was not an object."""
-
-
-class DictInvalid(Invalid):
- """The value found was not a dict."""
-
-
-class ExclusiveInvalid(Invalid):
- """More than one value found in exclusion group."""
-
-
-class InclusiveInvalid(Invalid):
- """Not all values found in inclusion group."""
-
-
-class SequenceTypeInvalid(Invalid):
- """The type found is not a sequence type."""
-
-
-class TypeInvalid(Invalid):
- """The value was not of required type."""
-
-
-class ValueInvalid(Invalid):
- """The value was found invalid by evaluation function."""
-
-
-class ScalarInvalid(Invalid):
- """Scalars did not match."""
-
-
-class CoerceInvalid(Invalid):
- """Impossible to coerce value to type."""
-
-
-class AnyInvalid(Invalid):
- """The value did not pass any validator."""
-
-
-class AllInvalid(Invalid):
- """The value did not pass all validators."""
-
-
-class MatchInvalid(Invalid):
- """The value does not match the given regular expression."""
-
-
-class RangeInvalid(Invalid):
- """The value is not in given range."""
-
-
-class TrueInvalid(Invalid):
- """The value is not True."""
-
-
-class FalseInvalid(Invalid):
- """The value is not False."""
-
-
-class BooleanInvalid(Invalid):
- """The value is not a boolean."""
-
-
-class UrlInvalid(Invalid):
- """The value is not a url."""
-
-
-class FileInvalid(Invalid):
- """The value is not a file."""
-
-
-class DirInvalid(Invalid):
- """The value is not a directory."""
-
-
-class PathInvalid(Invalid):
- """The value is not a path."""
-
-
-class LiteralInvalid(Invalid):
- """The literal values do not match."""
-
-
-class VirtualPathComponent(str):
- def __str__(self):
- return '<' + self + '>'
-
- def __repr__(self):
- return self.__str__()
-
-
-class Schema(object):
- """A validation schema.
-
- The schema is a Python tree-like structure where nodes are pattern
- matched against corresponding trees of values.
-
- Nodes can be values, in which case a direct comparison is used, types,
- in which case an isinstance() check is performed, or callables, which will
- validate and optionally convert the value.
- """
-
- _extra_to_name = {
- REMOVE_EXTRA: 'REMOVE_EXTRA',
- ALLOW_EXTRA: 'ALLOW_EXTRA',
- PREVENT_EXTRA: 'PREVENT_EXTRA',
- }
-
- def __init__(self, schema, required=False, extra=PREVENT_EXTRA):
- """Create a new Schema.
-
- :param schema: Validation schema. See :module:`voluptuous` for details.
- :param required: Keys defined in the schema must be in the data.
- :param extra: Specify how extra keys in the data are treated:
- - :const:`~voluptuous.PREVENT_EXTRA`: to disallow any undefined
- extra keys (raise ``Invalid``).
- - :const:`~voluptuous.ALLOW_EXTRA`: to include undefined extra
- keys in the output.
- - :const:`~voluptuous.REMOVE_EXTRA`: to exclude undefined extra keys
- from the output.
- - Any value other than the above defaults to
- :const:`~voluptuous.PREVENT_EXTRA`
- """
- self.schema = schema
- self.required = required
- self.extra = int(extra) # ensure the value is an integer
- self._compiled = self._compile(schema)
-
- def __repr__(self):
- return "<Schema(%s, extra=%s, required=%s) object at 0x%x>" % (
- self.schema, self._extra_to_name.get(self.extra, '??'),
- self.required, id(self))
-
- def __call__(self, data):
- """Validate data against this schema."""
- try:
- return self._compiled([], data)
- except MultipleInvalid:
- raise
- except Invalid as e:
- raise MultipleInvalid([e])
- # return self.validate([], self.schema, data)
-
- def _compile(self, schema):
- if schema is Extra:
- return lambda _, v: v
- if isinstance(schema, Object):
- return self._compile_object(schema)
- if isinstance(schema, collections.Mapping):
- return self._compile_dict(schema)
- elif isinstance(schema, list):
- return self._compile_list(schema)
- elif isinstance(schema, tuple):
- return self._compile_tuple(schema)
- type_ = type(schema)
- if type_ is type:
- type_ = schema
- if type_ in (bool, int, long, str, unicode, float, complex, object,
- list, dict, type(None)) or callable(schema):
- return _compile_scalar(schema)
- raise SchemaError('unsupported schema data type %r' %
- type(schema).__name__)
-
- def _compile_mapping(self, schema, invalid_msg=None):
- """Create validator for given mapping."""
- invalid_msg = invalid_msg or 'mapping value'
-
- # Keys that may be required
- all_required_keys = set(key for key in schema
- if key is not Extra
- and ((self.required and not isinstance(key, (Optional, Remove)))
- or isinstance(key, Required)))
-
- # Keys that may have defaults
- all_default_keys = set(key for key in schema
- if isinstance(key, Required)
- or isinstance(key, Optional))
-
- _compiled_schema = {}
- for skey, svalue in iteritems(schema):
- new_key = self._compile(skey)
- new_value = self._compile(svalue)
- _compiled_schema[skey] = (new_key, new_value)
-
- candidates = list(_iterate_mapping_candidates(_compiled_schema))
-
- def validate_mapping(path, iterable, out):
- required_keys = all_required_keys.copy()
- # keeps track of all default keys that haven't been filled
- default_keys = all_default_keys.copy()
- error = None
- errors = []
- for key, value in iterable:
- key_path = path + [key]
- remove_key = False
-
- # compare each given key/value against all compiled key/values
- # schema key, (compiled key, compiled value)
- for skey, (ckey, cvalue) in candidates:
- try:
- new_key = ckey(key_path, key)
- except Invalid as e:
- if len(e.path) > len(key_path):
- raise
- if not error or len(e.path) > len(error.path):
- error = e
- continue
- # Backtracking is not performed once a key is selected, so if
- # the value is invalid we immediately throw an exception.
- exception_errors = []
- # check if the key is marked for removal
- is_remove = new_key is Remove
- try:
- cval = cvalue(key_path, value)
- # include if it's not marked for removal
- if not is_remove:
- out[new_key] = cval
- else:
- remove_key = True
- continue
- except MultipleInvalid as e:
- exception_errors.extend(e.errors)
- except Invalid as e:
- exception_errors.append(e)
-
- if exception_errors:
- if is_remove or remove_key:
- continue
- for err in exception_errors:
- if len(err.path) <= len(key_path):
- err.error_type = invalid_msg
- errors.append(err)
- # If there is a validation error for a required
- # key, this means that the key was provided.
- # Discard the required key so it does not
- # create an additional, noisy exception.
- required_keys.discard(skey)
- break
-
- # Key and value okay, mark any Required() fields as found.
- required_keys.discard(skey)
-
- # No need for a default if it was filled
- default_keys.discard(skey)
-
- break
- else:
- if remove_key:
- # remove key
- continue
- elif self.extra == ALLOW_EXTRA:
- out[key] = value
- elif self.extra != REMOVE_EXTRA:
- errors.append(Invalid('extra keys not allowed', key_path))
- # else REMOVE_EXTRA: ignore the key so it's removed from output
-
- # set defaults for any that can have defaults
- for key in default_keys:
- if not isinstance(key.default, Undefined): # if the user provides a default with the node
- out[key.schema] = key.default()
- if key in required_keys:
- required_keys.discard(key)
-
- # for any required keys left that weren't found and don't have defaults:
- for key in required_keys:
- msg = key.msg if hasattr(key, 'msg') and key.msg else 'required key not provided'
- errors.append(RequiredFieldInvalid(msg, path + [key]))
- if errors:
- raise MultipleInvalid(errors)
-
- return out
-
- return validate_mapping
-
- def _compile_object(self, schema):
- """Validate an object.
-
- Has the same behavior as dictionary validator but work with object
- attributes.
-
- For example:
-
- >>> class Structure(object):
- ... def __init__(self, one=None, three=None):
- ... self.one = one
- ... self.three = three
- ...
- >>> validate = Schema(Object({'one': 'two', 'three': 'four'}, cls=Structure))
- >>> with raises(MultipleInvalid, "not a valid value for object value @ data['one']"):
- ... validate(Structure(one='three'))
-
- """
- base_validate = self._compile_mapping(
- schema, invalid_msg='object value')
-
- def validate_object(path, data):
- if (schema.cls is not UNDEFINED
- and not isinstance(data, schema.cls)):
- raise ObjectInvalid('expected a {0!r}'.format(schema.cls), path)
- iterable = _iterate_object(data)
- iterable = ifilter(lambda item: item[1] is not None, iterable)
- out = base_validate(path, iterable, {})
- return type(data)(**out)
-
- return validate_object
-
- def _compile_dict(self, schema):
- """Validate a dictionary.
-
- A dictionary schema can contain a set of values, or at most one
- validator function/type.
-
- A dictionary schema will only validate a dictionary:
-
- >>> validate = Schema({})
- >>> with raises(MultipleInvalid, 'expected a dictionary'):
- ... validate([])
-
- An invalid dictionary value:
-
- >>> validate = Schema({'one': 'two', 'three': 'four'})
- >>> with raises(MultipleInvalid, "not a valid value for dictionary value @ data['one']"):
- ... validate({'one': 'three'})
-
- An invalid key:
-
- >>> with raises(MultipleInvalid, "extra keys not allowed @ data['two']"):
- ... validate({'two': 'three'})
-
-
- Validation function, in this case the "int" type:
-
- >>> validate = Schema({'one': 'two', 'three': 'four', int: str})
-
- Valid integer input:
-
- >>> validate({10: 'twenty'})
- {10: 'twenty'}
-
- By default, a "type" in the schema (in this case "int") will be used
- purely to validate that the corresponding value is of that type. It
- will not Coerce the value:
-
- >>> with raises(MultipleInvalid, "extra keys not allowed @ data['10']"):
- ... validate({'10': 'twenty'})
-
- Wrap them in the Coerce() function to achieve this:
-
- >>> validate = Schema({'one': 'two', 'three': 'four',
- ... Coerce(int): str})
- >>> validate({'10': 'twenty'})
- {10: 'twenty'}
-
- Custom message for required key
-
- >>> validate = Schema({Required('one', 'required'): 'two'})
- >>> with raises(MultipleInvalid, "required @ data['one']"):
- ... validate({})
-
- (This is to avoid unexpected surprises.)
-
- Multiple errors for nested field in a dict:
-
- >>> validate = Schema({
- ... 'adict': {
- ... 'strfield': str,
- ... 'intfield': int
- ... }
- ... })
- >>> try:
- ... validate({
- ... 'adict': {
- ... 'strfield': 123,
- ... 'intfield': 'one'
- ... }
- ... })
- ... except MultipleInvalid as e:
- ... print(sorted(str(i) for i in e.errors)) # doctest: +NORMALIZE_WHITESPACE
- ["expected int for dictionary value @ data['adict']['intfield']",
- "expected str for dictionary value @ data['adict']['strfield']"]
-
- """
- base_validate = self._compile_mapping(
- schema, invalid_msg='dictionary value')
-
- groups_of_exclusion = {}
- groups_of_inclusion = {}
- for node in schema:
- if isinstance(node, Exclusive):
- g = groups_of_exclusion.setdefault(node.group_of_exclusion, [])
- g.append(node)
- elif isinstance(node, Inclusive):
- g = groups_of_inclusion.setdefault(node.group_of_inclusion, [])
- g.append(node)
-
- def validate_dict(path, data):
- if not isinstance(data, dict):
- raise DictInvalid('expected a dictionary', path)
-
- errors = []
- for label, group in groups_of_exclusion.items():
- exists = False
- for exclusive in group:
- if exclusive.schema in data:
- if exists:
- msg = exclusive.msg if hasattr(exclusive, 'msg') and exclusive.msg else \
- "two or more values in the same group of exclusion '%s'" % label
- next_path = path + [VirtualPathComponent(label)]
- errors.append(ExclusiveInvalid(msg, next_path))
- break
- exists = True
-
- if errors:
- raise MultipleInvalid(errors)
-
- for label, group in groups_of_inclusion.items():
- included = [node.schema in data for node in group]
- if any(included) and not all(included):
- msg = "some but not all values in the same group of inclusion '%s'" % label
- for g in group:
- if hasattr(g, 'msg') and g.msg:
- msg = g.msg
- break
- next_path = path + [VirtualPathComponent(label)]
- errors.append(InclusiveInvalid(msg, next_path))
- break
-
- if errors:
- raise MultipleInvalid(errors)
-
- out = {}
- return base_validate(path, iteritems(data), out)
-
- return validate_dict
-
- def _compile_sequence(self, schema, seq_type):
- """Validate a sequence type.
-
- This is a sequence of valid values or validators tried in order.
-
- >>> validator = Schema(['one', 'two', int])
- >>> validator(['one'])
- ['one']
- >>> with raises(MultipleInvalid, 'expected int @ data[0]'):
- ... validator([3.5])
- >>> validator([1])
- [1]
- """
- _compiled = [self._compile(s) for s in schema]
- seq_type_name = seq_type.__name__
-
- def validate_sequence(path, data):
- if not isinstance(data, seq_type):
- raise SequenceTypeInvalid('expected a %s' % seq_type_name, path)
-
- # Empty seq schema, allow any data.
- if not schema:
- return data
-
- out = []
- invalid = None
- errors = []
- index_path = UNDEFINED
- for i, value in enumerate(data):
- index_path = path + [i]
- invalid = None
- for validate in _compiled:
- try:
- cval = validate(index_path, value)
- if cval is not Remove: # do not include Remove values
- out.append(cval)
- break
- except Invalid as e:
- if len(e.path) > len(index_path):
- raise
- invalid = e
- else:
- errors.append(invalid)
- if errors:
- raise MultipleInvalid(errors)
- return type(data)(out)
- return validate_sequence
-
- def _compile_tuple(self, schema):
- """Validate a tuple.
-
- A tuple is a sequence of valid values or validators tried in order.
-
- >>> validator = Schema(('one', 'two', int))
- >>> validator(('one',))
- ('one',)
- >>> with raises(MultipleInvalid, 'expected int @ data[0]'):
- ... validator((3.5,))
- >>> validator((1,))
- (1,)
- """
- return self._compile_sequence(schema, tuple)
-
- def _compile_list(self, schema):
- """Validate a list.
-
- A list is a sequence of valid values or validators tried in order.
-
- >>> validator = Schema(['one', 'two', int])
- >>> validator(['one'])
- ['one']
- >>> with raises(MultipleInvalid, 'expected int @ data[0]'):
- ... validator([3.5])
- >>> validator([1])
- [1]
- """
- return self._compile_sequence(schema, list)
-
- def extend(self, schema, required=None, extra=None):
- """Create a new `Schema` by merging this and the provided `schema`.
-
- Neither this `Schema` nor the provided `schema` are modified. The
- resulting `Schema` inherits the `required` and `extra` parameters of
- this, unless overridden.
-
- Both schemas must be dictionary-based.
-
- :param schema: dictionary to extend this `Schema` with
- :param required: if set, overrides `required` of this `Schema`
- :param extra: if set, overrides `extra` of this `Schema`
- """
-
- assert type(self.schema) == dict and type(schema) == dict, 'Both schemas must be dictionary-based'
-
- result = self.schema.copy()
- result.update(schema)
-
- result_required = (required if required is not None else self.required)
- result_extra = (extra if extra is not None else self.extra)
- return Schema(result, required=result_required, extra=result_extra)
-
-
-def _compile_scalar(schema):
- """A scalar value.
-
- The schema can either be a value or a type.
-
- >>> _compile_scalar(int)([], 1)
- 1
- >>> with raises(Invalid, 'expected float'):
- ... _compile_scalar(float)([], '1')
-
- Callables have
- >>> _compile_scalar(lambda v: float(v))([], '1')
- 1.0
-
- As a convenience, ValueError's are trapped:
-
- >>> with raises(Invalid, 'not a valid value'):
- ... _compile_scalar(lambda v: float(v))([], 'a')
- """
- if isinstance(schema, type):
- def validate_instance(path, data):
- if isinstance(data, schema):
- return data
- else:
- msg = 'expected %s' % schema.__name__
- raise TypeInvalid(msg, path)
- return validate_instance
-
- if callable(schema):
- def validate_callable(path, data):
- try:
- return schema(data)
- except ValueError as e:
- raise ValueInvalid('not a valid value', path)
- except Invalid as e:
- e.prepend(path)
- raise
- return validate_callable
-
- def validate_value(path, data):
- if data != schema:
- raise ScalarInvalid('not a valid value', path)
- return data
-
- return validate_value
-
-
-def _compile_itemsort():
- '''return sort function of mappings'''
- def is_extra(key_):
- return key_ is Extra
-
- def is_remove(key_):
- return isinstance(key_, Remove)
-
- def is_marker(key_):
- return isinstance(key_, Marker)
-
- def is_type(key_):
- return inspect.isclass(key_)
-
- def is_callable(key_):
- return callable(key_)
-
- # priority list for map sorting (in order of checking)
- # We want Extra to match last, because it's a catch-all. On the other hand,
- # Remove markers should match first (since invalid values will not
- # raise an Error, instead the validator will check if other schemas match
- # the same value).
- priority = [(1, is_remove), # Remove highest priority after values
- (2, is_marker), # then other Markers
- (4, is_type), # types/classes lowest before Extra
- (3, is_callable), # callables after markers
- (5, is_extra)] # Extra lowest priority
-
- def item_priority(item_):
- key_ = item_[0]
- for i, check_ in priority:
- if check_(key_):
- return i
- # values have hightest priorities
- return 0
-
- return item_priority
-
-_sort_item = _compile_itemsort()
-
-
-def _iterate_mapping_candidates(schema):
- """Iterate over schema in a meaningful order."""
- # Without this, Extra might appear first in the iterator, and fail to
- # validate a key even though it's a Required that has its own validation,
- # generating a false positive.
- return sorted(iteritems(schema), key=_sort_item)
-
-
-def _iterate_object(obj):
- """Return iterator over object attributes. Respect objects with
- defined __slots__.
-
- """
- d = {}
- try:
- d = vars(obj)
- except TypeError:
- # maybe we have named tuple here?
- if hasattr(obj, '_asdict'):
- d = obj._asdict()
- for item in iteritems(d):
- yield item
- try:
- slots = obj.__slots__
- except AttributeError:
- pass
- else:
- for key in slots:
- if key != '__dict__':
- yield (key, getattr(obj, key))
- raise StopIteration()
-
-
-class Object(dict):
- """Indicate that we should work with attributes, not keys."""
-
- def __init__(self, schema, cls=UNDEFINED):
- self.cls = cls
- super(Object, self).__init__(schema)
-
-
-class Marker(object):
- """Mark nodes for special treatment."""
-
- def __init__(self, schema, msg=None):
- self.schema = schema
- self._schema = Schema(schema)
- self.msg = msg
-
- def __call__(self, v):
- try:
- return self._schema(v)
- except Invalid as e:
- if not self.msg or len(e.path) > 1:
- raise
- raise Invalid(self.msg)
-
- def __str__(self):
- return str(self.schema)
-
- def __repr__(self):
- return repr(self.schema)
-
- def __lt__(self, other):
- return self.schema < other.schema
-
-
-class Optional(Marker):
- """Mark a node in the schema as optional, and optionally provide a default
-
- >>> schema = Schema({Optional('key'): str})
- >>> schema({})
- {}
- >>> schema = Schema({Optional('key', default='value'): str})
- >>> schema({})
- {'key': 'value'}
- >>> schema = Schema({Optional('key', default=list): list})
- >>> schema({})
- {'key': []}
-
- If 'required' flag is set for an entire schema, optional keys aren't required
-
- >>> schema = Schema({
- ... Optional('key'): str,
- ... 'key2': str
- ... }, required=True)
- >>> schema({'key2':'value'})
- {'key2': 'value'}
- """
- def __init__(self, schema, msg=None, default=UNDEFINED):
- super(Optional, self).__init__(schema, msg=msg)
- self.default = default_factory(default)
-
-
-class Exclusive(Optional):
- """Mark a node in the schema as exclusive.
-
- Exclusive keys inherited from Optional:
-
- >>> schema = Schema({Exclusive('alpha', 'angles'): int, Exclusive('beta', 'angles'): int})
- >>> schema({'alpha': 30})
- {'alpha': 30}
-
- Keys inside a same group of exclusion cannot be together, it only makes sense for dictionaries:
-
- >>> with raises(MultipleInvalid, "two or more values in the same group of exclusion 'angles' @ data[<angles>]"):
- ... schema({'alpha': 30, 'beta': 45})
-
- For example, API can provides multiple types of authentication, but only one works in the same time:
-
- >>> msg = 'Please, use only one type of authentication at the same time.'
- >>> schema = Schema({
- ... Exclusive('classic', 'auth', msg=msg):{
- ... Required('email'): basestring,
- ... Required('password'): basestring
- ... },
- ... Exclusive('internal', 'auth', msg=msg):{
- ... Required('secret_key'): basestring
- ... },
- ... Exclusive('social', 'auth', msg=msg):{
- ... Required('social_network'): basestring,
- ... Required('token'): basestring
- ... }
- ... })
-
- >>> with raises(MultipleInvalid, "Please, use only one type of authentication at the same time. @ data[<auth>]"):
- ... schema({'classic': {'email': 'foo@example.com', 'password': 'bar'},
- ... 'social': {'social_network': 'barfoo', 'token': 'tEMp'}})
- """
- def __init__(self, schema, group_of_exclusion, msg=None):
- super(Exclusive, self).__init__(schema, msg=msg)
- self.group_of_exclusion = group_of_exclusion
-
-
-class Inclusive(Optional):
- """ Mark a node in the schema as inclusive.
-
- Exclusive keys inherited from Optional:
-
- >>> schema = Schema({
- ... Inclusive('filename', 'file'): str,
- ... Inclusive('mimetype', 'file'): str
- ... })
- >>> data = {'filename': 'dog.jpg', 'mimetype': 'image/jpeg'}
- >>> data == schema(data)
- True
-
- Keys inside a same group of inclusive must exist together, it only makes sense for dictionaries:
-
- >>> with raises(MultipleInvalid, "some but not all values in the same group of inclusion 'file' @ data[<file>]"):
- ... schema({'filename': 'dog.jpg'})
-
- If none of the keys in the group are present, it is accepted:
-
- >>> schema({})
- {}
-
- For example, API can return 'height' and 'width' together, but not separately.
-
- >>> msg = "Height and width must exist together"
- >>> schema = Schema({
- ... Inclusive('height', 'size', msg=msg): int,
- ... Inclusive('width', 'size', msg=msg): int
- ... })
-
- >>> with raises(MultipleInvalid, msg + " @ data[<size>]"):
- ... schema({'height': 100})
-
- >>> with raises(MultipleInvalid, msg + " @ data[<size>]"):
- ... schema({'width': 100})
-
- >>> data = {'height': 100, 'width': 100}
- >>> data == schema(data)
- True
- """
-
- def __init__(self, schema, group_of_inclusion, msg=None):
- super(Inclusive, self).__init__(schema, msg=msg)
- self.group_of_inclusion = group_of_inclusion
-
-
-class Required(Marker):
- """Mark a node in the schema as being required, and optionally provide a default value.
-
- >>> schema = Schema({Required('key'): str})
- >>> with raises(MultipleInvalid, "required key not provided @ data['key']"):
- ... schema({})
-
- >>> schema = Schema({Required('key', default='value'): str})
- >>> schema({})
- {'key': 'value'}
- >>> schema = Schema({Required('key', default=list): list})
- >>> schema({})
- {'key': []}
- """
- def __init__(self, schema, msg=None, default=UNDEFINED):
- super(Required, self).__init__(schema, msg=msg)
- self.default = default_factory(default)
-
-
-class Remove(Marker):
- """Mark a node in the schema to be removed and excluded from the validated
- output. Keys that fail validation will not raise ``Invalid``. Instead, these
- keys will be treated as extras.
-
- >>> schema = Schema({str: int, Remove(int): str})
- >>> with raises(MultipleInvalid, "extra keys not allowed @ data[1]"):
- ... schema({'keep': 1, 1: 1.0})
- >>> schema({1: 'red', 'red': 1, 2: 'green'})
- {'red': 1}
- >>> schema = Schema([int, Remove(float), Extra])
- >>> schema([1, 2, 3, 4.0, 5, 6.0, '7'])
- [1, 2, 3, 5, '7']
- """
- def __call__(self, v):
- super(Remove, self).__call__(v)
- return self.__class__
-
- def __repr__(self):
- return "Remove(%r)" % (self.schema,)
-
-
-def Extra(_):
- """Allow keys in the data that are not present in the schema."""
- raise SchemaError('"Extra" should never be called')
-
-
-# As extra() is never called there's no way to catch references to the
-# deprecated object, so we just leave an alias here instead.
-extra = Extra
-
-class Msg(object):
- """Report a user-friendly message if a schema fails to validate.
-
- >>> validate = Schema(
- ... Msg(['one', 'two', int],
- ... 'should be one of "one", "two" or an integer'))
- >>> with raises(MultipleInvalid, 'should be one of "one", "two" or an integer'):
- ... validate(['three'])
-
- Messages are only applied to invalid direct descendants of the schema:
-
- >>> validate = Schema(Msg([['one', 'two', int]], 'not okay!'))
- >>> with raises(MultipleInvalid, 'expected int @ data[0][0]'):
- ... validate([['three']])
-
- The type which is thrown can be overridden but needs to be a subclass of Invalid
-
- >>> with raises(SchemaError, 'Msg can only use subclases of Invalid as custom class'):
- ... validate = Schema(Msg([int], 'should be int', cls=KeyError))
-
- If you do use a subclass of Invalid, that error will be thrown (wrapped in a MultipleInvalid)
-
- >>> validate = Schema(Msg([['one', 'two', int]], 'not okay!', cls=RangeInvalid))
- >>> try:
- ... validate(['three'])
- ... except MultipleInvalid as e:
- ... assert isinstance(e.errors[0], RangeInvalid)
- """
-
- def __init__(self, schema, msg, cls=None):
- if cls and not issubclass(cls, Invalid):
- raise SchemaError("Msg can only use subclases of"
- " Invalid as custom class")
- self._schema = schema
- self.schema = Schema(schema)
- self.msg = msg
- self.cls = cls
-
- def __call__(self, v):
- try:
- return self.schema(v)
- except Invalid as e:
- if len(e.path) > 1:
- raise e
- else:
- raise (self.cls or Invalid)(self.msg)
-
- def __repr__(self):
- return 'Msg(%s, %s, cls=%s)' % (self._schema, self.msg, self.cls)
-
-
-def message(default=None, cls=None):
- """Convenience decorator to allow functions to provide a message.
-
- Set a default message:
-
- >>> @message('not an integer')
- ... def isint(v):
- ... return int(v)
-
- >>> validate = Schema(isint())
- >>> with raises(MultipleInvalid, 'not an integer'):
- ... validate('a')
-
- The message can be overridden on a per validator basis:
-
- >>> validate = Schema(isint('bad'))
- >>> with raises(MultipleInvalid, 'bad'):
- ... validate('a')
-
- The class thrown too:
-
- >>> class IntegerInvalid(Invalid): pass
- >>> validate = Schema(isint('bad', clsoverride=IntegerInvalid))
- >>> try:
- ... validate('a')
- ... except MultipleInvalid as e:
- ... assert isinstance(e.errors[0], IntegerInvalid)
- """
- if cls and not issubclass(cls, Invalid):
- raise SchemaError("message can only use subclases of Invalid as custom class")
-
- def decorator(f):
- @wraps(f)
- def check(msg=None, clsoverride=None):
- @wraps(f)
- def wrapper(*args, **kwargs):
- try:
- return f(*args, **kwargs)
- except ValueError:
- raise (clsoverride or cls or ValueInvalid)(msg or default or 'invalid value')
- return wrapper
- return check
- return decorator
-
-
-def truth(f):
- """Convenience decorator to convert truth functions into validators.
-
- >>> @truth
- ... def isdir(v):
- ... return os.path.isdir(v)
- >>> validate = Schema(isdir)
- >>> validate('/')
- '/'
- >>> with raises(MultipleInvalid, 'not a valid value'):
- ... validate('/notavaliddir')
- """
- @wraps(f)
- def check(v):
- t = f(v)
- if not t:
- raise ValueError
- return v
- return check
-
-
-class Coerce(object):
- """Coerce a value to a type.
-
- If the type constructor throws a ValueError or TypeError, the value
- will be marked as Invalid.
-
- Default behavior:
-
- >>> validate = Schema(Coerce(int))
- >>> with raises(MultipleInvalid, 'expected int'):
- ... validate(None)
- >>> with raises(MultipleInvalid, 'expected int'):
- ... validate('foo')
-
- With custom message:
-
- >>> validate = Schema(Coerce(int, "moo"))
- >>> with raises(MultipleInvalid, 'moo'):
- ... validate('foo')
- """
-
- def __init__(self, type, msg=None):
- self.type = type
- self.msg = msg
- self.type_name = type.__name__
-
- def __call__(self, v):
- try:
- return self.type(v)
- except (ValueError, TypeError):
- msg = self.msg or ('expected %s' % self.type_name)
- raise CoerceInvalid(msg)
-
- def __repr__(self):
- return 'Coerce(%s, msg=%r)' % (self.type_name, self.msg)
-
-
-@message('value was not true', cls=TrueInvalid)
-@truth
-def IsTrue(v):
- """Assert that a value is true, in the Python sense.
-
- >>> validate = Schema(IsTrue())
-
- "In the Python sense" means that implicitly false values, such as empty
- lists, dictionaries, etc. are treated as "false":
-
- >>> with raises(MultipleInvalid, "value was not true"):
- ... validate([])
- >>> validate([1])
- [1]
- >>> with raises(MultipleInvalid, "value was not true"):
- ... validate(False)
-
- ...and so on.
-
- >>> try:
- ... validate([])
- ... except MultipleInvalid as e:
- ... assert isinstance(e.errors[0], TrueInvalid)
- """
- return v
-
-
-@message('value was not false', cls=FalseInvalid)
-def IsFalse(v):
- """Assert that a value is false, in the Python sense.
-
- (see :func:`IsTrue` for more detail)
-
- >>> validate = Schema(IsFalse())
- >>> validate([])
- []
- >>> with raises(MultipleInvalid, "value was not false"):
- ... validate(True)
-
- >>> try:
- ... validate(True)
- ... except MultipleInvalid as e:
- ... assert isinstance(e.errors[0], FalseInvalid)
- """
- if v:
- raise ValueError
- return v
-
-
-@message('expected boolean', cls=BooleanInvalid)
-def Boolean(v):
- """Convert human-readable boolean values to a bool.
-
- Accepted values are 1, true, yes, on, enable, and their negatives.
- Non-string values are cast to bool.
-
- >>> validate = Schema(Boolean())
- >>> validate(True)
- True
- >>> validate("1")
- True
- >>> validate("0")
- False
- >>> with raises(MultipleInvalid, "expected boolean"):
- ... validate('moo')
- >>> try:
- ... validate('moo')
- ... except MultipleInvalid as e:
- ... assert isinstance(e.errors[0], BooleanInvalid)
- """
- if isinstance(v, basestring):
- v = v.lower()
- if v in ('1', 'true', 'yes', 'on', 'enable'):
- return True
- if v in ('0', 'false', 'no', 'off', 'disable'):
- return False
- raise ValueError
- return bool(v)
-
-
-class Any(object):
- """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.
- :returns: Return value of the first validator that passes.
-
- >>> validate = Schema(Any('true', 'false',
- ... All(Any(int, bool), Coerce(bool))))
- >>> validate('true')
- 'true'
- >>> validate(1)
- True
- >>> with raises(MultipleInvalid, "not a valid value"):
- ... validate('moo')
-
- msg argument is used
-
- >>> validate = Schema(Any(1, 2, 3, msg="Expected 1 2 or 3"))
- >>> validate(1)
- 1
- >>> with raises(MultipleInvalid, "Expected 1 2 or 3"):
- ... validate(4)
- """
-
- def __init__(self, *validators, **kwargs):
- self.validators = validators
- self.msg = kwargs.pop('msg', None)
- self._schemas = [Schema(val, **kwargs) for val in validators]
-
- def __call__(self, v):
- error = None
- for schema in self._schemas:
- try:
- return schema(v)
- except Invalid as e:
- if error is None or len(e.path) > len(error.path):
- error = e
- else:
- if error:
- raise error if self.msg is None else AnyInvalid(self.msg)
- raise AnyInvalid(self.msg or 'no valid value found')
-
- def __repr__(self):
- return 'Any([%s])' % (", ".join(repr(v) for v in self.validators))
-
-
-# Convenience alias
-Or = Any
-
-
-class All(object):
- """Value must pass all validators.
-
- 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.
-
- >>> validate = Schema(All('10', Coerce(int)))
- >>> validate('10')
- 10
- """
-
- def __init__(self, *validators, **kwargs):
- self.validators = validators
- self.msg = kwargs.pop('msg', None)
- self._schemas = [Schema(val, **kwargs) for val in validators]
-
- def __call__(self, v):
- try:
- for schema in self._schemas:
- v = schema(v)
- except Invalid as e:
- raise e if self.msg is None else AllInvalid(self.msg)
- return v
-
- def __repr__(self):
- return 'All(%s, msg=%r)' % (
- ", ".join(repr(v) for v in self.validators),
- self.msg
- )
-
-
-# Convenience alias
-And = All
-
-
-class Match(object):
- """Value must be a string that matches the regular expression.
-
- >>> validate = Schema(Match(r'^0x[A-F0-9]+$'))
- >>> validate('0x123EF4')
- '0x123EF4'
- >>> with raises(MultipleInvalid, "does not match regular expression"):
- ... validate('123EF4')
-
- >>> with raises(MultipleInvalid, 'expected string or buffer'):
- ... validate(123)
-
- Pattern may also be a _compiled regular expression:
-
- >>> validate = Schema(Match(re.compile(r'0x[A-F0-9]+', re.I)))
- >>> validate('0x123ef4')
- '0x123ef4'
- """
-
- def __init__(self, pattern, msg=None):
- if isinstance(pattern, basestring):
- pattern = re.compile(pattern)
- self.pattern = pattern
- self.msg = msg
-
- def __call__(self, v):
- try:
- match = self.pattern.match(v)
- except TypeError:
- raise MatchInvalid("expected string or buffer")
- if not match:
- raise MatchInvalid(self.msg or 'does not match regular expression')
- return v
-
- def __repr__(self):
- return 'Match(%r, msg=%r)' % (self.pattern.pattern, self.msg)
-
-
-class Replace(object):
- """Regex substitution.
-
- >>> validate = Schema(All(Replace('you', 'I'),
- ... Replace('hello', 'goodbye')))
- >>> validate('you say hello')
- 'I say goodbye'
- """
-
- def __init__(self, pattern, substitution, msg=None):
- if isinstance(pattern, basestring):
- pattern = re.compile(pattern)
- self.pattern = pattern
- self.substitution = substitution
- self.msg = msg
-
- def __call__(self, v):
- return self.pattern.sub(self.substitution, v)
-
- def __repr__(self):
- return 'Replace(%r, %r, msg=%r)' % (self.pattern.pattern,
- self.substitution,
- self.msg)
-
-
-def _url_validation(v):
- parsed = urlparse.urlparse(v)
- if not parsed.scheme or not parsed.netloc:
- raise UrlInvalid("must have a URL scheme and host")
- return parsed
-
-
-@message('expected a Fully qualified domain name URL', cls=UrlInvalid)
-def FqdnUrl(v):
- """Verify that the value is a Fully qualified domain name URL.
-
- >>> s = Schema(FqdnUrl())
- >>> with raises(MultipleInvalid, 'expected a Fully qualified domain name URL'):
- ... s("http://localhost/")
- >>> s('http://w3.org')
- 'http://w3.org'
- """
- try:
- parsed_url = _url_validation(v)
- if "." not in parsed_url.netloc:
- raise UrlInvalid("must have a domain name in URL")
- return v
- except:
- raise ValueError
-
-
-@message('expected a URL', cls=UrlInvalid)
-def Url(v):
- """Verify that the value is a URL.
-
- >>> s = Schema(Url())
- >>> with raises(MultipleInvalid, 'expected a URL'):
- ... s(1)
- >>> s('http://w3.org')
- 'http://w3.org'
- """
- try:
- _url_validation(v)
- return v
- except:
- raise ValueError
-
-
-@message('not a file', cls=FileInvalid)
-@truth
-def IsFile(v):
- """Verify the file exists.
-
- >>> os.path.basename(IsFile()(__file__)).startswith('voluptuous.py')
- True
- >>> with raises(FileInvalid, 'not a file'):
- ... IsFile()("random_filename_goes_here.py")
- """
- return os.path.isfile(v)
-
-
-@message('not a directory', cls=DirInvalid)
-@truth
-def IsDir(v):
- """Verify the directory exists.
-
- >>> IsDir()('/')
- '/'
- """
- return os.path.isdir(v)
-
-
-@message('path does not exist', cls=PathInvalid)
-@truth
-def PathExists(v):
- """Verify the path exists, regardless of its type.
-
- >>> os.path.basename(PathExists()(__file__)).startswith('voluptuous.py')
- True
- >>> with raises(Invalid, 'path does not exist'):
- ... PathExists()("random_filename_goes_here.py")
- """
- return os.path.exists(v)
-
-
-class Range(object):
- """Limit a value to a range.
-
- Either min or max may be omitted.
- Either min or max can be excluded from the range of accepted values.
-
- :raises Invalid: If the value is outside the range.
-
- >>> s = Schema(Range(min=1, max=10, min_included=False))
- >>> s(5)
- 5
- >>> s(10)
- 10
- >>> with raises(MultipleInvalid, 'value must be at most 10'):
- ... s(20)
- >>> with raises(MultipleInvalid, 'value must be higher than 1'):
- ... s(1)
- >>> with raises(MultipleInvalid, 'value must be lower than 10'):
- ... Schema(Range(max=10, max_included=False))(20)
- """
-
- def __init__(self, min=None, max=None, min_included=True,
- max_included=True, msg=None):
- self.min = min
- self.max = max
- self.min_included = min_included
- self.max_included = max_included
- self.msg = msg
-
- def __call__(self, v):
- if self.min_included:
- if self.min is not None and v < self.min:
- raise RangeInvalid(
- self.msg or 'value must be at least %s' % self.min)
- else:
- if self.min is not None and v <= self.min:
- raise RangeInvalid(
- self.msg or 'value must be higher than %s' % self.min)
- if self.max_included:
- if self.max is not None and v > self.max:
- raise RangeInvalid(
- self.msg or 'value must be at most %s' % self.max)
- else:
- if self.max is not None and v >= self.max:
- raise RangeInvalid(
- self.msg or 'value must be lower than %s' % self.max)
- return v
-
- def __repr__(self):
- return ('Range(min=%r, max=%r, min_included=%r,'
- ' max_included=%r, msg=%r)' % (self.min, self.max,
- self.min_included,
- self.max_included,
- self.msg))
-
-
-class Clamp(object):
- """Clamp a value to a range.
-
- Either min or max may be omitted.
- >>> s = Schema(Clamp(min=0, max=1))
- >>> s(0.5)
- 0.5
- >>> s(5)
- 1
- >>> s(-1)
- 0
- """
-
- def __init__(self, min=None, max=None, msg=None):
- self.min = min
- self.max = max
- self.msg = msg
-
- def __call__(self, v):
- if self.min is not None and v < self.min:
- v = self.min
- if self.max is not None and v > self.max:
- v = self.max
- return v
-
- def __repr__(self):
- return 'Clamp(min=%s, max=%s)' % (self.min, self.max)
-
-
-class LengthInvalid(Invalid):
- pass
-
-
-class Length(object):
- """The length of a value must be in a certain range."""
-
- def __init__(self, min=None, max=None, msg=None):
- self.min = min
- self.max = max
- self.msg = msg
-
- def __call__(self, v):
- if self.min is not None and len(v) < self.min:
- raise LengthInvalid(
- self.msg or 'length of value must be at least %s' % self.min)
- if self.max is not None and len(v) > self.max:
- raise LengthInvalid(
- self.msg or 'length of value must be at most %s' % self.max)
- return v
-
- def __repr__(self):
- return 'Length(min=%s, max=%s)' % (self.min, self.max)
-
-
-class DatetimeInvalid(Invalid):
- """The value is not a formatted datetime string."""
-
-
-class Datetime(object):
- """Validate that the value matches the datetime format."""
-
- DEFAULT_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
-
- def __init__(self, format=None, msg=None):
- self.format = format or self.DEFAULT_FORMAT
- self.msg = msg
-
- def __call__(self, v):
- try:
- datetime.datetime.strptime(v, self.format)
- except (TypeError, ValueError):
- raise DatetimeInvalid(
- self.msg or 'value does not match'
- ' expected format %s' % self.format)
- return v
-
- def __repr__(self):
- return 'Datetime(format=%s)' % self.format
-
-
-class InInvalid(Invalid):
- pass
-
-
-class In(object):
- """Validate that a value is in a collection."""
-
- def __init__(self, container, msg=None):
- self.container = container
- self.msg = msg
-
- def __call__(self, v):
- try:
- check = v not in self.container
- except TypeError:
- check = True
- if check:
- raise InInvalid(self.msg or 'value is not allowed')
- return v
-
- def __repr__(self):
- return 'In(%s)' % (self.container,)
-
-
-class NotInInvalid(Invalid):
- pass
-
-
-class NotIn(object):
- """Validate that a value is not in a collection."""
-
- def __init__(self, container, msg=None):
- self.container = container
- self.msg = msg
-
- def __call__(self, v):
- try:
- check = v in self.container
- except TypeError:
- check = True
- if check:
- raise NotInInvalid(self.msg or 'value is not allowed')
- return v
-
- def __repr__(self):
- return 'NotIn(%s)' % (self.container,)
-
-
-def Lower(v):
- """Transform a string to lower case.
-
- >>> s = Schema(Lower)
- >>> s('HI')
- 'hi'
- """
- return str(v).lower()
-
-
-def Upper(v):
- """Transform a string to upper case.
-
- >>> s = Schema(Upper)
- >>> s('hi')
- 'HI'
- """
- return str(v).upper()
-
-
-def Capitalize(v):
- """Capitalise a string.
-
- >>> s = Schema(Capitalize)
- >>> s('hello world')
- 'Hello world'
- """
- return str(v).capitalize()
-
-
-def Title(v):
- """Title case a string.
-
- >>> s = Schema(Title)
- >>> s('hello world')
- 'Hello World'
- """
- return str(v).title()
-
-
-def Strip(v):
- """Strip whitespace from a string.
-
- >>> s = Schema(Strip)
- >>> s(' hello world ')
- 'hello world'
- """
- return str(v).strip()
-
-
-class DefaultTo(object):
- """Sets a value to default_value if none provided.
-
- >>> s = Schema(DefaultTo(42))
- >>> s(None)
- 42
- >>> s = Schema(DefaultTo(list))
- >>> s(None)
- []
- """
-
- def __init__(self, default_value, msg=None):
- self.default_value = default_factory(default_value)
- self.msg = msg
-
- def __call__(self, v):
- if v is None:
- v = self.default_value()
- return v
-
- def __repr__(self):
- return 'DefaultTo(%s)' % (self.default_value(),)
-
-
-class SetTo(object):
- """Set a value, ignoring any previous value.
-
- >>> s = Schema(Any(int, SetTo(42)))
- >>> s(2)
- 2
- >>> s("foo")
- 42
- """
-
- def __init__(self, value):
- self.value = default_factory(value)
-
- def __call__(self, v):
- return self.value()
-
- def __repr__(self):
- return 'SetTo(%s)' % (self.value(),)
-
-
-class ExactSequenceInvalid(Invalid):
- pass
-
-
-class ExactSequence(object):
- """Matches each element in a sequence against the corresponding element in
- the validators.
-
- :param msg: Message to deliver to user if validation fails.
- :param kwargs: All other keyword arguments are passed to the sub-Schema
- constructors.
-
- >>> from voluptuous import *
- >>> validate = Schema(ExactSequence([str, int, list, list]))
- >>> validate(['hourly_report', 10, [], []])
- ['hourly_report', 10, [], []]
- >>> validate(('hourly_report', 10, [], []))
- ('hourly_report', 10, [], [])
- """
-
- def __init__(self, validators, **kwargs):
- self.validators = validators
- self.msg = kwargs.pop('msg', None)
- self._schemas = [Schema(val, **kwargs) for val in validators]
-
- def __call__(self, v):
- if not isinstance(v, (list, tuple)):
- raise ExactSequenceInvalid(self.msg)
- try:
- v = type(v)(schema(x) for x, schema in zip(v, self._schemas))
- except Invalid as e:
- raise e if self.msg is None else ExactSequenceInvalid(self.msg)
- return v
-
- def __repr__(self):
- return 'ExactSequence([%s])' % (", ".join(repr(v)
- for v in self.validators))
-
-
-class Literal(object):
- def __init__(self, lit):
- self.lit = lit
-
- def __call__(self, value, msg=None):
- if self.lit != value:
- raise LiteralInvalid(
- msg or '%s not match for %s' % (value, self.lit)
- )
- else:
- return self.lit
-
- def __str__(self):
- return str(self.lit)
-
- def __repr__(self):
- return repr(self.lit)
-
-
-class Unique(object):
- """Ensure an iterable does not contain duplicate items.
-
- Only iterables convertable to a set are supported (native types and
- objects with correct __eq__).
-
- JSON does not support set, so they need to be presented as arrays.
- Unique allows ensuring that such array does not contain dupes.
-
- >>> s = Schema(Unique())
- >>> s([])
- []
- >>> s([1, 2])
- [1, 2]
- >>> with raises(Invalid, 'contains duplicate items: [1]'):
- ... s([1, 1, 2])
- >>> with raises(Invalid, "contains duplicate items: ['one']"):
- ... s(['one', 'two', 'one'])
- >>> with raises(Invalid, regex="^contains unhashable elements: "):
- ... s([set([1, 2]), set([3, 4])])
- >>> s('abc')
- 'abc'
- >>> with raises(Invalid, regex="^contains duplicate items: "):
- ... s('aabbc')
- """
-
- def __init__(self, msg=None):
- self.msg = msg
-
- def __call__(self, v):
- try:
- set_v = set(v)
- except TypeError as e:
- raise TypeInvalid(
- self.msg or 'contains unhashable elements: {0}'.format(e))
- if len(set_v) != len(v):
- seen = set()
- dupes = list(set(x for x in v if x in seen or seen.add(x)))
- raise Invalid(
- self.msg or 'contains duplicate items: {0}'.format(dupes))
- return v
-
- def __repr__(self):
- return 'Unique()'
-
-
-class Set(object):
- """Convert a list into a set.
-
- >>> s = Schema(Set())
- >>> s([]) == set([])
- True
- >>> s([1, 2]) == set([1, 2])
- True
- >>> with raises(Invalid, regex="^cannot be presented as set: "):
- ... s([set([1, 2]), set([3, 4])])
- """
-
- def __init__(self, msg=None):
- self.msg = msg
-
- def __call__(self, v):
- try:
- set_v = set(v)
- except Exception as e:
- raise TypeInvalid(
- self.msg or 'cannot be presented as set: {0}'.format(e))
- return set_v
-
- def __repr__(self):
- return 'Set()'
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
new file mode 100644
--- /dev/null
+++ b/third_party/python/voluptuous/voluptuous/__init__.py
@@ -0,0 +1,15 @@
+# flake8: noqa
+
+try:
+ from schema_builder import *
+ from validators import *
+ from util import *
+ from error import *
+except ImportError:
+ from .schema_builder import *
+ from .validators import *
+ from .util import *
+ from .error import *
+
+__version__ = '0.10.5'
+__author__ = 'tusharmakkar08'
new file mode 100644
--- /dev/null
+++ b/third_party/python/voluptuous/voluptuous/error.py
@@ -0,0 +1,189 @@
+
+class Error(Exception):
+ """Base validation exception."""
+
+
+class SchemaError(Error):
+ """An error was encountered in the schema."""
+
+
+class Invalid(Error):
+ """The data was invalid.
+
+ :attr msg: The error message.
+ :attr path: The path to the error, as a list of keys in the source data.
+ :attr error_message: The actual error message that was raised, as a
+ string.
+
+ """
+
+ def __init__(self, message, path=None, error_message=None, error_type=None):
+ Error.__init__(self, message)
+ self.path = path or []
+ self.error_message = error_message or message
+ self.error_type = error_type
+
+ @property
+ def msg(self):
+ return self.args[0]
+
+ def __str__(self):
+ path = ' @ data[%s]' % ']['.join(map(repr, self.path)) \
+ if self.path else ''
+ output = Exception.__str__(self)
+ if self.error_type:
+ output += ' for ' + self.error_type
+ return output + path
+
+ def prepend(self, path):
+ self.path = path + self.path
+
+
+class MultipleInvalid(Invalid):
+ def __init__(self, errors=None):
+ self.errors = errors[:] if errors else []
+
+ def __repr__(self):
+ return 'MultipleInvalid(%r)' % self.errors
+
+ @property
+ def msg(self):
+ return self.errors[0].msg
+
+ @property
+ def path(self):
+ return self.errors[0].path
+
+ @property
+ def error_message(self):
+ return self.errors[0].error_message
+
+ def add(self, error):
+ self.errors.append(error)
+
+ def __str__(self):
+ return str(self.errors[0])
+
+ def prepend(self, path):
+ for error in self.errors:
+ error.prepend(path)
+
+
+class RequiredFieldInvalid(Invalid):
+ """Required field was missing."""
+
+
+class ObjectInvalid(Invalid):
+ """The value we found was not an object."""
+
+
+class DictInvalid(Invalid):
+ """The value found was not a dict."""
+
+
+class ExclusiveInvalid(Invalid):
+ """More than one value found in exclusion group."""
+
+
+class InclusiveInvalid(Invalid):
+ """Not all values found in inclusion group."""
+
+
+class SequenceTypeInvalid(Invalid):
+ """The type found is not a sequence type."""
+
+
+class TypeInvalid(Invalid):
+ """The value was not of required type."""
+
+
+class ValueInvalid(Invalid):
+ """The value was found invalid by evaluation function."""
+
+
+class ContainsInvalid(Invalid):
+ """List does not contain item"""
+
+
+class ScalarInvalid(Invalid):
+ """Scalars did not match."""
+
+
+class CoerceInvalid(Invalid):
+ """Impossible to coerce value to type."""
+
+
+class AnyInvalid(Invalid):
+ """The value did not pass any validator."""
+
+
+class AllInvalid(Invalid):
+ """The value did not pass all validators."""
+
+
+class MatchInvalid(Invalid):
+ """The value does not match the given regular expression."""
+
+
+class RangeInvalid(Invalid):
+ """The value is not in given range."""
+
+
+class TrueInvalid(Invalid):
+ """The value is not True."""
+
+
+class FalseInvalid(Invalid):
+ """The value is not False."""
+
+
+class BooleanInvalid(Invalid):
+ """The value is not a boolean."""
+
+
+class UrlInvalid(Invalid):
+ """The value is not a url."""
+
+
+class EmailInvalid(Invalid):
+ """The value is not a email."""
+
+
+class FileInvalid(Invalid):
+ """The value is not a file."""
+
+
+class DirInvalid(Invalid):
+ """The value is not a directory."""
+
+
+class PathInvalid(Invalid):
+ """The value is not a path."""
+
+
+class LiteralInvalid(Invalid):
+ """The literal values do not match."""
+
+
+class LengthInvalid(Invalid):
+ pass
+
+
+class DatetimeInvalid(Invalid):
+ """The value is not a formatted datetime string."""
+
+
+class DateInvalid(Invalid):
+ """The value is not a formatted date string."""
+
+
+class InInvalid(Invalid):
+ pass
+
+
+class NotInInvalid(Invalid):
+ pass
+
+
+class ExactSequenceInvalid(Invalid):
+ pass
new file mode 100644
--- /dev/null
+++ b/third_party/python/voluptuous/voluptuous/humanize.py
@@ -0,0 +1,40 @@
+from voluptuous import Invalid, MultipleInvalid
+from voluptuous.error import Error
+
+
+MAX_VALIDATION_ERROR_ITEM_LENGTH = 500
+
+
+def _nested_getitem(data, path):
+ for item_index in path:
+ try:
+ data = data[item_index]
+ except (KeyError, IndexError, TypeError):
+ # The index is not present in the dictionary, list or other
+ # indexable or data is not subscriptable
+ return None
+ return data
+
+
+def humanize_error(data, validation_error, max_sub_error_length=MAX_VALIDATION_ERROR_ITEM_LENGTH):
+ """ Provide a more helpful + complete validation error message than that provided automatically
+ Invalid and MultipleInvalid do not include the offending value in error messages,
+ and MultipleInvalid.__str__ only provides the first error.
+ """
+ if isinstance(validation_error, MultipleInvalid):
+ return '\n'.join(sorted(
+ humanize_error(data, sub_error, max_sub_error_length)
+ for sub_error in validation_error.errors
+ ))
+ else:
+ offending_item_summary = repr(_nested_getitem(data, validation_error.path))
+ if len(offending_item_summary) > max_sub_error_length:
+ offending_item_summary = offending_item_summary[:max_sub_error_length - 3] + '...'
+ return '%s. Got %s' % (validation_error, offending_item_summary)
+
+
+def validate_with_humanized_errors(data, schema, max_sub_error_length=MAX_VALIDATION_ERROR_ITEM_LENGTH):
+ try:
+ return schema(data)
+ except (Invalid, MultipleInvalid) as e:
+ raise Error(humanize_error(data, e, max_sub_error_length))
new file mode 100644
--- /dev/null
+++ b/third_party/python/voluptuous/voluptuous/schema_builder.py
@@ -0,0 +1,1190 @@
+import collections
+import inspect
+import re
+from functools import wraps
+import sys
+from contextlib import contextmanager
+
+import itertools
+
+try:
+ import error as er
+except ImportError:
+ from . import error as er
+
+if sys.version_info >= (3,):
+ long = int
+ unicode = str
+ basestring = str
+ ifilter = filter
+
+ def iteritems(d):
+ return d.items()
+else:
+ from itertools import ifilter
+
+ def iteritems(d):
+ return d.iteritems()
+
+"""Schema validation for Python data structures.
+
+Given eg. a nested data structure like this:
+
+ {
+ 'exclude': ['Users', 'Uptime'],
+ 'include': [],
+ 'set': {
+ 'snmp_community': 'public',
+ 'snmp_timeout': 15,
+ 'snmp_version': '2c',
+ },
+ 'targets': {
+ 'localhost': {
+ 'exclude': ['Uptime'],
+ 'features': {
+ 'Uptime': {
+ 'retries': 3,
+ },
+ 'Users': {
+ 'snmp_community': 'monkey',
+ 'snmp_port': 15,
+ },
+ },
+ 'include': ['Users'],
+ 'set': {
+ 'snmp_community': 'monkeys',
+ },
+ },
+ },
+ }
+
+A schema like this:
+
+ >>> settings = {
+ ... 'snmp_community': str,
+ ... 'retries': int,
+ ... 'snmp_version': All(Coerce(str), Any('3', '2c', '1')),
+ ... }
+ >>> features = ['Ping', 'Uptime', 'Http']
+ >>> schema = Schema({
+ ... 'exclude': features,
+ ... 'include': features,
+ ... 'set': settings,
+ ... 'targets': {
+ ... 'exclude': features,
+ ... 'include': features,
+ ... 'features': {
+ ... str: settings,
+ ... },
+ ... },
+ ... })
+
+Validate like so:
+
+ >>> schema({
+ ... 'set': {
+ ... 'snmp_community': 'public',
+ ... 'snmp_version': '2c',
+ ... },
+ ... 'targets': {
+ ... 'exclude': ['Ping'],
+ ... 'features': {
+ ... 'Uptime': {'retries': 3},
+ ... 'Users': {'snmp_community': 'monkey'},
+ ... },
+ ... },
+ ... }) == {
+ ... 'set': {'snmp_version': '2c', 'snmp_community': 'public'},
+ ... 'targets': {
+ ... 'exclude': ['Ping'],
+ ... 'features': {'Uptime': {'retries': 3},
+ ... 'Users': {'snmp_community': 'monkey'}}}}
+ True
+"""
+
+# options for extra keys
+PREVENT_EXTRA = 0 # any extra key not in schema will raise an error
+ALLOW_EXTRA = 1 # extra keys not in schema will be included in output
+REMOVE_EXTRA = 2 # extra keys not in schema will be excluded from output
+
+
+def _isnamedtuple(obj):
+ return isinstance(obj, tuple) and hasattr(obj, '_fields')
+
+
+primitive_types = (str, unicode, bool, int, float)
+
+
+class Undefined(object):
+ def __nonzero__(self):
+ return False
+
+ def __repr__(self):
+ return '...'
+
+
+UNDEFINED = Undefined()
+
+
+def default_factory(value):
+ if value is UNDEFINED or callable(value):
+ return value
+ return lambda: value
+
+
+@contextmanager
+def raises(exc, msg=None, regex=None):
+ try:
+ yield
+ except exc as e:
+ if msg is not None:
+ assert str(e) == msg, '%r != %r' % (str(e), msg)
+ if regex is not None:
+ assert re.search(regex, str(e)), '%r does not match %r' % (str(e), regex)
+
+
+def Extra(_):
+ """Allow keys in the data that are not present in the schema."""
+ raise er.SchemaError('"Extra" should never be called')
+
+
+# As extra() is never called there's no way to catch references to the
+# deprecated object, so we just leave an alias here instead.
+extra = Extra
+
+
+class Schema(object):
+ """A validation schema.
+
+ The schema is a Python tree-like structure where nodes are pattern
+ matched against corresponding trees of values.
+
+ Nodes can be values, in which case a direct comparison is used, types,
+ in which case an isinstance() check is performed, or callables, which will
+ validate and optionally convert the value.
+
+ We can equate schemas also.
+
+ For Example:
+
+ >>> v = Schema({Required('a'): unicode})
+ >>> v1 = Schema({Required('a'): unicode})
+ >>> v2 = Schema({Required('b'): unicode})
+ >>> assert v == v1
+ >>> assert v != v2
+
+ """
+
+ _extra_to_name = {
+ REMOVE_EXTRA: 'REMOVE_EXTRA',
+ ALLOW_EXTRA: 'ALLOW_EXTRA',
+ PREVENT_EXTRA: 'PREVENT_EXTRA',
+ }
+
+ def __init__(self, schema, required=False, extra=PREVENT_EXTRA):
+ """Create a new Schema.
+
+ :param schema: Validation schema. See :module:`voluptuous` for details.
+ :param required: Keys defined in the schema must be in the data.
+ :param extra: Specify how extra keys in the data are treated:
+ - :const:`~voluptuous.PREVENT_EXTRA`: to disallow any undefined
+ extra keys (raise ``Invalid``).
+ - :const:`~voluptuous.ALLOW_EXTRA`: to include undefined extra
+ keys in the output.
+ - :const:`~voluptuous.REMOVE_EXTRA`: to exclude undefined extra keys
+ from the output.
+ - Any value other than the above defaults to
+ :const:`~voluptuous.PREVENT_EXTRA`
+ """
+ self.schema = schema
+ self.required = required
+ self.extra = int(extra) # ensure the value is an integer
+ self._compiled = self._compile(schema)
+
+ def __eq__(self, other):
+ if str(other) == str(self.schema):
+ # Because repr is combination mixture of object and schema
+ return True
+ return False
+
+ def __str__(self):
+ return str(self.schema)
+
+ def __repr__(self):
+ return "<Schema(%s, extra=%s, required=%s) object at 0x%x>" % (
+ self.schema, self._extra_to_name.get(self.extra, '??'),
+ self.required, id(self))
+
+ def __call__(self, data):
+ """Validate data against this schema."""
+ try:
+ return self._compiled([], data)
+ except er.MultipleInvalid:
+ raise
+ except er.Invalid as e:
+ raise er.MultipleInvalid([e])
+ # return self.validate([], self.schema, data)
+
+ def _compile(self, schema):
+ if schema is Extra:
+ return lambda _, v: v
+ if isinstance(schema, Object):
+ return self._compile_object(schema)
+ if isinstance(schema, collections.Mapping) and len(schema):
+ return self._compile_dict(schema)
+ elif isinstance(schema, list) and len(schema):
+ return self._compile_list(schema)
+ elif isinstance(schema, tuple):
+ return self._compile_tuple(schema)
+ type_ = type(schema)
+ if type_ is type:
+ type_ = schema
+ if type_ in (bool, bytes, int, long, str, unicode, float, complex, object,
+ list, dict, type(None)) or callable(schema):
+ return _compile_scalar(schema)
+ raise er.SchemaError('unsupported schema data type %r' %
+ type(schema).__name__)
+
+ def _compile_mapping(self, schema, invalid_msg=None):
+ """Create validator for given mapping."""
+ invalid_msg = invalid_msg or 'mapping value'
+
+ # Keys that may be required
+ all_required_keys = set(key for key in schema
+ if key is not Extra and
+ ((self.required and not isinstance(key, (Optional, Remove))) or
+ isinstance(key, Required)))
+
+ # Keys that may have defaults
+ all_default_keys = set(key for key in schema
+ if isinstance(key, Required) or
+ isinstance(key, Optional))
+
+ _compiled_schema = {}
+ for skey, svalue in iteritems(schema):
+ new_key = self._compile(skey)
+ new_value = self._compile(svalue)
+ _compiled_schema[skey] = (new_key, new_value)
+
+ candidates = list(_iterate_mapping_candidates(_compiled_schema))
+
+ # After we have the list of candidates in the correct order, we want to apply some optimization so that each
+ # key in the data being validated will be matched against the relevant schema keys only.
+ # No point in matching against different keys
+ additional_candidates = []
+ candidates_by_key = {}
+ for skey, (ckey, cvalue) in candidates:
+ if type(skey) in primitive_types:
+ candidates_by_key.setdefault(skey, []).append((skey, (ckey, cvalue)))
+ elif isinstance(skey, Marker) and type(skey.schema) in primitive_types:
+ candidates_by_key.setdefault(skey.schema, []).append((skey, (ckey, cvalue)))
+ else:
+ # These are wildcards such as 'int', 'str', 'Remove' and others which should be applied to all keys
+ additional_candidates.append((skey, (ckey, cvalue)))
+
+ def validate_mapping(path, iterable, out):
+ required_keys = all_required_keys.copy()
+ # keeps track of all default keys that haven't been filled
+ default_keys = all_default_keys.copy()
+ error = None
+ errors = []
+ for key, value in iterable:
+ key_path = path + [key]
+ remove_key = False
+
+ # Optimization. Validate against the matching key first, then fallback to the rest
+ relevant_candidates = itertools.chain(candidates_by_key.get(key, []), additional_candidates)
+
+ # compare each given key/value against all compiled key/values
+ # schema key, (compiled key, compiled value)
+ for skey, (ckey, cvalue) in relevant_candidates:
+ try:
+ new_key = ckey(key_path, key)
+ except er.Invalid as e:
+ if len(e.path) > len(key_path):
+ raise
+ if not error or len(e.path) > len(error.path):
+ error = e
+ continue
+ # Backtracking is not performed once a key is selected, so if
+ # the value is invalid we immediately throw an exception.
+ exception_errors = []
+ # check if the key is marked for removal
+ is_remove = new_key is Remove
+ try:
+ cval = cvalue(key_path, value)
+ # include if it's not marked for removal
+ if not is_remove:
+ out[new_key] = cval
+ else:
+ remove_key = True
+ continue
+ except er.MultipleInvalid as e:
+ exception_errors.extend(e.errors)
+ except er.Invalid as e:
+ exception_errors.append(e)
+
+ if exception_errors:
+ if is_remove or remove_key:
+ continue
+ for err in exception_errors:
+ if len(err.path) <= len(key_path):
+ err.error_type = invalid_msg
+ errors.append(err)
+ # If there is a validation error for a required
+ # key, this means that the key was provided.
+ # Discard the required key so it does not
+ # create an additional, noisy exception.
+ required_keys.discard(skey)
+ break
+
+ # Key and value okay, mark any Required() fields as found.
+ required_keys.discard(skey)
+
+ # No need for a default if it was filled
+ default_keys.discard(skey)
+
+ break
+ else:
+ if remove_key:
+ # remove key
+ continue
+ elif self.extra == ALLOW_EXTRA:
+ out[key] = value
+ elif self.extra != REMOVE_EXTRA:
+ errors.append(er.Invalid('extra keys not allowed', key_path))
+ # else REMOVE_EXTRA: ignore the key so it's removed from output
+
+ # set defaults for any that can have defaults
+ for key in default_keys:
+ if not isinstance(key.default, Undefined): # if the user provides a default with the node
+ out[key.schema] = key.default()
+ if key in required_keys:
+ required_keys.discard(key)
+
+ # for any required keys left that weren't found and don't have defaults:
+ for key in required_keys:
+ msg = key.msg if hasattr(key, 'msg') and key.msg else 'required key not provided'
+ errors.append(er.RequiredFieldInvalid(msg, path + [key]))
+ if errors:
+ raise er.MultipleInvalid(errors)
+
+ return out
+
+ return validate_mapping
+
+ def _compile_object(self, schema):
+ """Validate an object.
+
+ Has the same behavior as dictionary validator but work with object
+ attributes.
+
+ For example:
+
+ >>> class Structure(object):
+ ... def __init__(self, one=None, three=None):
+ ... self.one = one
+ ... self.three = three
+ ...
+ >>> validate = Schema(Object({'one': 'two', 'three': 'four'}, cls=Structure))
+ >>> with raises(er.MultipleInvalid, "not a valid value for object value @ data['one']"):
+ ... validate(Structure(one='three'))
+
+ """
+ base_validate = self._compile_mapping(
+ schema, invalid_msg='object value')
+
+ def validate_object(path, data):
+ if schema.cls is not UNDEFINED and not isinstance(data, schema.cls):
+ raise er.ObjectInvalid('expected a {0!r}'.format(schema.cls), path)
+ iterable = _iterate_object(data)
+ iterable = ifilter(lambda item: item[1] is not None, iterable)
+ out = base_validate(path, iterable, {})
+ return type(data)(**out)
+
+ return validate_object
+
+ def _compile_dict(self, schema):
+ """Validate a dictionary.
+
+ A dictionary schema can contain a set of values, or at most one
+ validator function/type.
+
+ A dictionary schema will only validate a dictionary:
+
+ >>> validate = Schema({'prop': str})
+ >>> with raises(er.MultipleInvalid, 'expected a dictionary'):
+ ... validate([])
+
+ An invalid dictionary value:
+
+ >>> validate = Schema({'one': 'two', 'three': 'four'})
+ >>> with raises(er.MultipleInvalid, "not a valid value for dictionary value @ data['one']"):
+ ... validate({'one': 'three'})
+
+ An invalid key:
+
+ >>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['two']"):
+ ... validate({'two': 'three'})
+
+ Validation function, in this case the "int" type:
+
+ >>> validate = Schema({'one': 'two', 'three': 'four', int: str})
+
+ Valid integer input:
+
+ >>> validate({10: 'twenty'})
+ {10: 'twenty'}
+
+ An empty dictionary is matched as value:
+
+ >>> validate = Schema({})
+ >>> with raises(er.MultipleInvalid, 'not a valid value'):
+ ... validate([])
+
+ By default, a "type" in the schema (in this case "int") will be used
+ purely to validate that the corresponding value is of that type. It
+ will not Coerce the value:
+
+ >>> validate = Schema({'one': 'two', 'three': 'four', int: str})
+ >>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['10']"):
+ ... validate({'10': 'twenty'})
+
+ Wrap them in the Coerce() function to achieve this:
+ >>> from voluptuous import Coerce
+ >>> validate = Schema({'one': 'two', 'three': 'four',
+ ... Coerce(int): str})
+ >>> validate({'10': 'twenty'})
+ {10: 'twenty'}
+
+ Custom message for required key
+
+ >>> validate = Schema({Required('one', 'required'): 'two'})
+ >>> with raises(er.MultipleInvalid, "required @ data['one']"):
+ ... validate({})
+
+ (This is to avoid unexpected surprises.)
+
+ Multiple errors for nested field in a dict:
+
+ >>> validate = Schema({
+ ... 'adict': {
+ ... 'strfield': str,
+ ... 'intfield': int
+ ... }
+ ... })
+ >>> try:
+ ... validate({
+ ... 'adict': {
+ ... 'strfield': 123,
+ ... 'intfield': 'one'
+ ... }
+ ... })
+ ... except er.MultipleInvalid as e:
+ ... print(sorted(str(i) for i in e.errors)) # doctest: +NORMALIZE_WHITESPACE
+ ["expected int for dictionary value @ data['adict']['intfield']",
+ "expected str for dictionary value @ data['adict']['strfield']"]
+
+ """
+ base_validate = self._compile_mapping(
+ schema, invalid_msg='dictionary value')
+
+ groups_of_exclusion = {}
+ groups_of_inclusion = {}
+ for node in schema:
+ if isinstance(node, Exclusive):
+ g = groups_of_exclusion.setdefault(node.group_of_exclusion, [])
+ g.append(node)
+ elif isinstance(node, Inclusive):
+ g = groups_of_inclusion.setdefault(node.group_of_inclusion, [])
+ g.append(node)
+
+ def validate_dict(path, data):
+ if not isinstance(data, dict):
+ raise er.DictInvalid('expected a dictionary', path)
+
+ errors = []
+ for label, group in groups_of_exclusion.items():
+ exists = False
+ for exclusive in group:
+ if exclusive.schema in data:
+ if exists:
+ msg = exclusive.msg if hasattr(exclusive, 'msg') and exclusive.msg else \
+ "two or more values in the same group of exclusion '%s'" % label
+ next_path = path + [VirtualPathComponent(label)]
+ errors.append(er.ExclusiveInvalid(msg, next_path))
+ break
+ exists = True
+
+ if errors:
+ raise er.MultipleInvalid(errors)
+
+ for label, group in groups_of_inclusion.items():
+ included = [node.schema in data for node in group]
+ if any(included) and not all(included):
+ msg = "some but not all values in the same group of inclusion '%s'" % label
+ for g in group:
+ if hasattr(g, 'msg') and g.msg:
+ msg = g.msg
+ break
+ next_path = path + [VirtualPathComponent(label)]
+ errors.append(er.InclusiveInvalid(msg, next_path))
+ break
+
+ if errors:
+ raise er.MultipleInvalid(errors)
+
+ out = data.__class__()
+ return base_validate(path, iteritems(data), out)
+
+ return validate_dict
+
+ def _compile_sequence(self, schema, seq_type):
+ """Validate a sequence type.
+
+ This is a sequence of valid values or validators tried in order.
+
+ >>> validator = Schema(['one', 'two', int])
+ >>> validator(['one'])
+ ['one']
+ >>> with raises(er.MultipleInvalid, 'expected int @ data[0]'):
+ ... validator([3.5])
+ >>> validator([1])
+ [1]
+ """
+ _compiled = [self._compile(s) for s in schema]
+ seq_type_name = seq_type.__name__
+
+ def validate_sequence(path, data):
+ if not isinstance(data, seq_type):
+ raise er.SequenceTypeInvalid('expected a %s' % seq_type_name, path)
+
+ # Empty seq schema, allow any data.
+ if not schema:
+ return data
+
+ out = []
+ invalid = None
+ errors = []
+ index_path = UNDEFINED
+ for i, value in enumerate(data):
+ index_path = path + [i]
+ invalid = None
+ for validate in _compiled:
+ try:
+ cval = validate(index_path, value)
+ if cval is not Remove: # do not include Remove values
+ out.append(cval)
+ break
+ except er.Invalid as e:
+ if len(e.path) > len(index_path):
+ raise
+ invalid = e
+ else:
+ errors.append(invalid)
+ if errors:
+ raise er.MultipleInvalid(errors)
+
+ if _isnamedtuple(data):
+ return type(data)(*out)
+ else:
+ return type(data)(out)
+
+ return validate_sequence
+
+ def _compile_tuple(self, schema):
+ """Validate a tuple.
+
+ A tuple is a sequence of valid values or validators tried in order.
+
+ >>> validator = Schema(('one', 'two', int))
+ >>> validator(('one',))
+ ('one',)
+ >>> with raises(er.MultipleInvalid, 'expected int @ data[0]'):
+ ... validator((3.5,))
+ >>> validator((1,))
+ (1,)
+ """
+ return self._compile_sequence(schema, tuple)
+
+ def _compile_list(self, schema):
+ """Validate a list.
+
+ A list is a sequence of valid values or validators tried in order.
+
+ >>> validator = Schema(['one', 'two', int])
+ >>> validator(['one'])
+ ['one']
+ >>> with raises(er.MultipleInvalid, 'expected int @ data[0]'):
+ ... validator([3.5])
+ >>> validator([1])
+ [1]
+ """
+ return self._compile_sequence(schema, list)
+
+ def extend(self, schema, required=None, extra=None):
+ """Create a new `Schema` by merging this and the provided `schema`.
+
+ Neither this `Schema` nor the provided `schema` are modified. The
+ resulting `Schema` inherits the `required` and `extra` parameters of
+ this, unless overridden.
+
+ Both schemas must be dictionary-based.
+
+ :param schema: dictionary to extend this `Schema` with
+ :param required: if set, overrides `required` of this `Schema`
+ :param extra: if set, overrides `extra` of this `Schema`
+ """
+
+ assert type(self.schema) == dict and type(schema) == dict, 'Both schemas must be dictionary-based'
+
+ result = self.schema.copy()
+
+ # returns the key that may have been passed as arugment to Marker constructor
+ def key_literal(key):
+ return (key.schema if isinstance(key, Marker) else key)
+
+ # build a map that takes the key literals to the needed objects
+ # literal -> Required|Optional|literal
+ result_key_map = dict((key_literal(key), key) for key in result)
+
+ # for each item in the extension schema, replace duplicates
+ # or add new keys
+ for key, value in iteritems(schema):
+
+ # if the key is already in the dictionary, we need to replace it
+ # transform key to literal before checking presence
+ if key_literal(key) in result_key_map:
+
+ result_key = result_key_map[key_literal(key)]
+ result_value = result[result_key]
+
+ # if both are dictionaries, we need to extend recursively
+ # create the new extended sub schema, then remove the old key and add the new one
+ if type(result_value) == dict and type(value) == dict:
+ new_value = Schema(result_value).extend(value).schema
+ del result[result_key]
+ result[key] = new_value
+ # one or the other or both are not sub-schemas, simple replacement is fine
+ # remove old key and add new one
+ else:
+ del result[result_key]
+ result[key] = value
+
+ # key is new and can simply be added
+ else:
+ result[key] = value
+
+ # recompile and send old object
+ result_required = (required if required is not None else self.required)
+ result_extra = (extra if extra is not None else self.extra)
+ return Schema(result, required=result_required, extra=result_extra)
+
+
+def _compile_scalar(schema):
+ """A scalar value.
+
+ The schema can either be a value or a type.
+
+ >>> _compile_scalar(int)([], 1)
+ 1
+ >>> with raises(er.Invalid, 'expected float'):
+ ... _compile_scalar(float)([], '1')
+
+ Callables have
+ >>> _compile_scalar(lambda v: float(v))([], '1')
+ 1.0
+
+ As a convenience, ValueError's are trapped:
+
+ >>> with raises(er.Invalid, 'not a valid value'):
+ ... _compile_scalar(lambda v: float(v))([], 'a')
+ """
+ if isinstance(schema, type):
+ def validate_instance(path, data):
+ if isinstance(data, schema):
+ return data
+ else:
+ msg = 'expected %s' % schema.__name__
+ raise er.TypeInvalid(msg, path)
+
+ return validate_instance
+
+ if callable(schema):
+ def validate_callable(path, data):
+ try:
+ return schema(data)
+ except ValueError as e:
+ raise er.ValueInvalid('not a valid value', path)
+ except er.Invalid as e:
+ e.prepend(path)
+ raise
+
+ return validate_callable
+
+ def validate_value(path, data):
+ if data != schema:
+ raise er.ScalarInvalid('not a valid value', path)
+ return data
+
+ return validate_value
+
+
+def _compile_itemsort():
+ '''return sort function of mappings'''
+
+ def is_extra(key_):
+ return key_ is Extra
+
+ def is_remove(key_):
+ return isinstance(key_, Remove)
+
+ def is_marker(key_):
+ return isinstance(key_, Marker)
+
+ def is_type(key_):
+ return inspect.isclass(key_)
+
+ def is_callable(key_):
+ return callable(key_)
+
+ # priority list for map sorting (in order of checking)
+ # We want Extra to match last, because it's a catch-all. On the other hand,
+ # Remove markers should match first (since invalid values will not
+ # raise an Error, instead the validator will check if other schemas match
+ # the same value).
+ priority = [(1, is_remove), # Remove highest priority after values
+ (2, is_marker), # then other Markers
+ (4, is_type), # types/classes lowest before Extra
+ (3, is_callable), # callables after markers
+ (5, is_extra)] # Extra lowest priority
+
+ def item_priority(item_):
+ key_ = item_[0]
+ for i, check_ in priority:
+ if check_(key_):
+ return i
+ # values have hightest priorities
+ return 0
+
+ return item_priority
+
+
+_sort_item = _compile_itemsort()
+
+
+def _iterate_mapping_candidates(schema):
+ """Iterate over schema in a meaningful order."""
+ # Without this, Extra might appear first in the iterator, and fail to
+ # validate a key even though it's a Required that has its own validation,
+ # generating a false positive.
+ return sorted(iteritems(schema), key=_sort_item)
+
+
+def _iterate_object(obj):
+ """Return iterator over object attributes. Respect objects with
+ defined __slots__.
+
+ """
+ d = {}
+ try:
+ d = vars(obj)
+ except TypeError:
+ # maybe we have named tuple here?
+ if hasattr(obj, '_asdict'):
+ d = obj._asdict()
+ for item in iteritems(d):
+ yield item
+ try:
+ slots = obj.__slots__
+ except AttributeError:
+ pass
+ else:
+ for key in slots:
+ if key != '__dict__':
+ yield (key, getattr(obj, key))
+ raise StopIteration()
+
+
+class Msg(object):
+ """Report a user-friendly message if a schema fails to validate.
+
+ >>> validate = Schema(
+ ... Msg(['one', 'two', int],
+ ... 'should be one of "one", "two" or an integer'))
+ >>> with raises(er.MultipleInvalid, 'should be one of "one", "two" or an integer'):
+ ... validate(['three'])
+
+ Messages are only applied to invalid direct descendants of the schema:
+
+ >>> validate = Schema(Msg([['one', 'two', int]], 'not okay!'))
+ >>> with raises(er.MultipleInvalid, 'expected int @ data[0][0]'):
+ ... validate([['three']])
+
+ The type which is thrown can be overridden but needs to be a subclass of Invalid
+
+ >>> with raises(er.SchemaError, 'Msg can only use subclases of Invalid as custom class'):
+ ... validate = Schema(Msg([int], 'should be int', cls=KeyError))
+
+ If you do use a subclass of Invalid, that error will be thrown (wrapped in a MultipleInvalid)
+
+ >>> validate = Schema(Msg([['one', 'two', int]], 'not okay!', cls=er.RangeInvalid))
+ >>> try:
+ ... validate(['three'])
+ ... except er.MultipleInvalid as e:
+ ... assert isinstance(e.errors[0], er.RangeInvalid)
+ """
+
+ def __init__(self, schema, msg, cls=None):
+ if cls and not issubclass(cls, er.Invalid):
+ raise er.SchemaError("Msg can only use subclases of"
+ " Invalid as custom class")
+ self._schema = schema
+ self.schema = Schema(schema)
+ self.msg = msg
+ self.cls = cls
+
+ def __call__(self, v):
+ try:
+ return self.schema(v)
+ except er.Invalid as e:
+ if len(e.path) > 1:
+ raise e
+ else:
+ raise (self.cls or er.Invalid)(self.msg)
+
+ def __repr__(self):
+ return 'Msg(%s, %s, cls=%s)' % (self._schema, self.msg, self.cls)
+
+
+class Object(dict):
+ """Indicate that we should work with attributes, not keys."""
+
+ def __init__(self, schema, cls=UNDEFINED):
+ self.cls = cls
+ super(Object, self).__init__(schema)
+
+
+class VirtualPathComponent(str):
+ def __str__(self):
+ return '<' + self + '>'
+
+ def __repr__(self):
+ return self.__str__()
+
+
+# Markers.py
+
+
+class Marker(object):
+ """Mark nodes for special treatment."""
+
+ def __init__(self, schema_, msg=None):
+ self.schema = schema_
+ self._schema = Schema(schema_)
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ return self._schema(v)
+ except er.Invalid as e:
+ if not self.msg or len(e.path) > 1:
+ raise
+ raise er.Invalid(self.msg)
+
+ def __str__(self):
+ return str(self.schema)
+
+ def __repr__(self):
+ return repr(self.schema)
+
+ def __lt__(self, other):
+ return self.schema < other.schema
+
+ def __hash__(self):
+ return hash(self.schema)
+
+ def __eq__(self, other):
+ return self.schema == other
+
+ def __ne__(self, other):
+ return not(self.schema == other)
+
+
+class Optional(Marker):
+ """Mark a node in the schema as optional, and optionally provide a default
+
+ >>> schema = Schema({Optional('key'): str})
+ >>> schema({})
+ {}
+ >>> schema = Schema({Optional('key', default='value'): str})
+ >>> schema({})
+ {'key': 'value'}
+ >>> schema = Schema({Optional('key', default=list): list})
+ >>> schema({})
+ {'key': []}
+
+ If 'required' flag is set for an entire schema, optional keys aren't required
+
+ >>> schema = Schema({
+ ... Optional('key'): str,
+ ... 'key2': str
+ ... }, required=True)
+ >>> schema({'key2':'value'})
+ {'key2': 'value'}
+ """
+
+ def __init__(self, schema, msg=None, default=UNDEFINED):
+ super(Optional, self).__init__(schema, msg=msg)
+ self.default = default_factory(default)
+
+
+class Exclusive(Optional):
+ """Mark a node in the schema as exclusive.
+
+ Exclusive keys inherited from Optional:
+
+ >>> schema = Schema({Exclusive('alpha', 'angles'): int, Exclusive('beta', 'angles'): int})
+ >>> schema({'alpha': 30})
+ {'alpha': 30}
+
+ Keys inside a same group of exclusion cannot be together, it only makes sense for dictionaries:
+
+ >>> with raises(er.MultipleInvalid, "two or more values in the same group of exclusion 'angles' @ data[<angles>]"):
+ ... schema({'alpha': 30, 'beta': 45})
+
+ For example, API can provides multiple types of authentication, but only one works in the same time:
+
+ >>> msg = 'Please, use only one type of authentication at the same time.'
+ >>> schema = Schema({
+ ... Exclusive('classic', 'auth', msg=msg):{
+ ... Required('email'): basestring,
+ ... Required('password'): basestring
+ ... },
+ ... Exclusive('internal', 'auth', msg=msg):{
+ ... Required('secret_key'): basestring
+ ... },
+ ... Exclusive('social', 'auth', msg=msg):{
+ ... Required('social_network'): basestring,
+ ... Required('token'): basestring
+ ... }
+ ... })
+
+ >>> with raises(er.MultipleInvalid, "Please, use only one type of authentication at the same time. @ data[<auth>]"):
+ ... schema({'classic': {'email': 'foo@example.com', 'password': 'bar'},
+ ... 'social': {'social_network': 'barfoo', 'token': 'tEMp'}})
+ """
+
+ def __init__(self, schema, group_of_exclusion, msg=None):
+ super(Exclusive, self).__init__(schema, msg=msg)
+ self.group_of_exclusion = group_of_exclusion
+
+
+class Inclusive(Optional):
+ """ Mark a node in the schema as inclusive.
+
+ Inclusive keys inherited from Optional:
+
+ >>> schema = Schema({
+ ... Inclusive('filename', 'file'): str,
+ ... Inclusive('mimetype', 'file'): str
+ ... })
+ >>> data = {'filename': 'dog.jpg', 'mimetype': 'image/jpeg'}
+ >>> data == schema(data)
+ True
+
+ Keys inside a same group of inclusive must exist together, it only makes sense for dictionaries:
+
+ >>> with raises(er.MultipleInvalid, "some but not all values in the same group of inclusion 'file' @ data[<file>]"):
+ ... schema({'filename': 'dog.jpg'})
+
+ If none of the keys in the group are present, it is accepted:
+
+ >>> schema({})
+ {}
+
+ For example, API can return 'height' and 'width' together, but not separately.
+
+ >>> msg = "Height and width must exist together"
+ >>> schema = Schema({
+ ... Inclusive('height', 'size', msg=msg): int,
+ ... Inclusive('width', 'size', msg=msg): int
+ ... })
+
+ >>> with raises(er.MultipleInvalid, msg + " @ data[<size>]"):
+ ... schema({'height': 100})
+
+ >>> with raises(er.MultipleInvalid, msg + " @ data[<size>]"):
+ ... schema({'width': 100})
+
+ >>> data = {'height': 100, 'width': 100}
+ >>> data == schema(data)
+ True
+ """
+
+ def __init__(self, schema, group_of_inclusion, msg=None):
+ super(Inclusive, self).__init__(schema, msg=msg)
+ self.group_of_inclusion = group_of_inclusion
+
+
+class Required(Marker):
+ """Mark a node in the schema as being required, and optionally provide a default value.
+
+ >>> schema = Schema({Required('key'): str})
+ >>> with raises(er.MultipleInvalid, "required key not provided @ data['key']"):
+ ... schema({})
+
+ >>> schema = Schema({Required('key', default='value'): str})
+ >>> schema({})
+ {'key': 'value'}
+ >>> schema = Schema({Required('key', default=list): list})
+ >>> schema({})
+ {'key': []}
+ """
+
+ def __init__(self, schema, msg=None, default=UNDEFINED):
+ super(Required, self).__init__(schema, msg=msg)
+ self.default = default_factory(default)
+
+
+class Remove(Marker):
+ """Mark a node in the schema to be removed and excluded from the validated
+ output. Keys that fail validation will not raise ``Invalid``. Instead, these
+ keys will be treated as extras.
+
+ >>> schema = Schema({str: int, Remove(int): str})
+ >>> with raises(er.MultipleInvalid, "extra keys not allowed @ data[1]"):
+ ... schema({'keep': 1, 1: 1.0})
+ >>> schema({1: 'red', 'red': 1, 2: 'green'})
+ {'red': 1}
+ >>> schema = Schema([int, Remove(float), Extra])
+ >>> schema([1, 2, 3, 4.0, 5, 6.0, '7'])
+ [1, 2, 3, 5, '7']
+ """
+
+ def __call__(self, v):
+ super(Remove, self).__call__(v)
+ return self.__class__
+
+ def __repr__(self):
+ return "Remove(%r)" % (self.schema,)
+
+ def __hash__(self):
+ return object.__hash__(self)
+
+def message(default=None, cls=None):
+ """Convenience decorator to allow functions to provide a message.
+
+ Set a default message:
+
+ >>> @message('not an integer')
+ ... def isint(v):
+ ... return int(v)
+
+ >>> validate = Schema(isint())
+ >>> with raises(er.MultipleInvalid, 'not an integer'):
+ ... validate('a')
+
+ The message can be overridden on a per validator basis:
+
+ >>> validate = Schema(isint('bad'))
+ >>> with raises(er.MultipleInvalid, 'bad'):
+ ... validate('a')
+
+ The class thrown too:
+
+ >>> class IntegerInvalid(er.Invalid): pass
+ >>> validate = Schema(isint('bad', clsoverride=IntegerInvalid))
+ >>> try:
+ ... validate('a')
+ ... except er.MultipleInvalid as e:
+ ... assert isinstance(e.errors[0], IntegerInvalid)
+ """
+ if cls and not issubclass(cls, er.Invalid):
+ raise er.SchemaError("message can only use subclases of Invalid as custom class")
+
+ def decorator(f):
+ @wraps(f)
+ def check(msg=None, clsoverride=None):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ try:
+ return f(*args, **kwargs)
+ except ValueError:
+ raise (clsoverride or cls or er.ValueInvalid)(msg or default or 'invalid value')
+
+ return wrapper
+
+ return check
+
+ return decorator
+
+
+def _args_to_dict(func, args):
+ """Returns argument names as values as key-value pairs."""
+ if sys.version_info >= (3, 0):
+ arg_count = func.__code__.co_argcount
+ arg_names = func.__code__.co_varnames[:arg_count]
+ else:
+ arg_count = func.func_code.co_argcount
+ arg_names = func.func_code.co_varnames[:arg_count]
+
+ arg_value_list = list(args)
+ arguments = dict((arg_name, arg_value_list[i])
+ for i, arg_name in enumerate(arg_names)
+ if i < len(arg_value_list))
+ return arguments
+
+
+def _merge_args_with_kwargs(args_dict, kwargs_dict):
+ """Merge args with kwargs."""
+ ret = args_dict.copy()
+ ret.update(kwargs_dict)
+ return ret
+
+
+def validate(*a, **kw):
+ """Decorator for validating arguments of a function against a given schema.
+
+ Set restrictions for arguments:
+
+ >>> @validate(arg1=int, arg2=int)
+ ... def foo(arg1, arg2):
+ ... return arg1 * arg2
+
+ Set restriction for returned value:
+
+ >>> @validate(arg=int, __return__=int)
+ ... def bar(arg1):
+ ... return arg1 * 2
+
+ """
+ RETURNS_KEY = '__return__'
+
+ def validate_schema_decorator(func):
+
+ returns_defined = False
+ returns = None
+
+ schema_args_dict = _args_to_dict(func, a)
+ schema_arguments = _merge_args_with_kwargs(schema_args_dict, kw)
+
+ if RETURNS_KEY in schema_arguments:
+ returns_defined = True
+ returns = schema_arguments[RETURNS_KEY]
+ del schema_arguments[RETURNS_KEY]
+
+ input_schema = Schema(schema_arguments) if len(schema_arguments) != 0 else lambda x: x
+ output_schema = Schema(returns) if returns_defined else lambda x: x
+
+ @wraps(func)
+ def func_wrapper(*args, **kwargs):
+ args_dict = _args_to_dict(func, args)
+ arguments = _merge_args_with_kwargs(args_dict, kwargs)
+ validated_arguments = input_schema(arguments)
+ output = func(**validated_arguments)
+ return output_schema(output)
+
+ return func_wrapper
+
+ return validate_schema_decorator
new file mode 100644
--- /dev/null
+++ b/third_party/python/voluptuous/voluptuous/util.py
@@ -0,0 +1,159 @@
+import sys
+
+try:
+ from error import LiteralInvalid, TypeInvalid, Invalid
+ from schema_builder import Schema, default_factory, raises
+ import validators
+except ImportError:
+ from .error import LiteralInvalid, TypeInvalid, Invalid
+ from .schema_builder import Schema, default_factory, raises
+ from . import validators
+
+__author__ = 'tusharmakkar08'
+
+
+def Lower(v):
+ """Transform a string to lower case.
+
+ >>> s = Schema(Lower)
+ >>> s('HI')
+ 'hi'
+ """
+ return str(v).lower()
+
+
+def Upper(v):
+ """Transform a string to upper case.
+
+ >>> s = Schema(Upper)
+ >>> s('hi')
+ 'HI'
+ """
+ return str(v).upper()
+
+
+def Capitalize(v):
+ """Capitalise a string.
+
+ >>> s = Schema(Capitalize)
+ >>> s('hello world')
+ 'Hello world'
+ """
+ return str(v).capitalize()
+
+
+def Title(v):
+ """Title case a string.
+
+ >>> s = Schema(Title)
+ >>> s('hello world')
+ 'Hello World'
+ """
+ return str(v).title()
+
+
+def Strip(v):
+ """Strip whitespace from a string.
+
+ >>> s = Schema(Strip)
+ >>> s(' hello world ')
+ 'hello world'
+ """
+ return str(v).strip()
+
+
+class DefaultTo(object):
+ """Sets a value to default_value if none provided.
+
+ >>> s = Schema(DefaultTo(42))
+ >>> s(None)
+ 42
+ >>> s = Schema(DefaultTo(list))
+ >>> s(None)
+ []
+ """
+
+ def __init__(self, default_value, msg=None):
+ self.default_value = default_factory(default_value)
+ self.msg = msg
+
+ def __call__(self, v):
+ if v is None:
+ v = self.default_value()
+ return v
+
+ def __repr__(self):
+ return 'DefaultTo(%s)' % (self.default_value(),)
+
+
+class SetTo(object):
+ """Set a value, ignoring any previous value.
+
+ >>> s = Schema(validators.Any(int, SetTo(42)))
+ >>> s(2)
+ 2
+ >>> s("foo")
+ 42
+ """
+
+ def __init__(self, value):
+ self.value = default_factory(value)
+
+ def __call__(self, v):
+ return self.value()
+
+ def __repr__(self):
+ return 'SetTo(%s)' % (self.value(),)
+
+
+class Set(object):
+ """Convert a list into a set.
+
+ >>> s = Schema(Set())
+ >>> s([]) == set([])
+ True
+ >>> s([1, 2]) == set([1, 2])
+ True
+ >>> with raises(Invalid, regex="^cannot be presented as set: "):
+ ... s([set([1, 2]), set([3, 4])])
+ """
+
+ def __init__(self, msg=None):
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ set_v = set(v)
+ except Exception as e:
+ raise TypeInvalid(
+ self.msg or 'cannot be presented as set: {0}'.format(e))
+ return set_v
+
+ def __repr__(self):
+ return 'Set()'
+
+
+class Literal(object):
+ def __init__(self, lit):
+ self.lit = lit
+
+ def __call__(self, value, msg=None):
+ if self.lit != value:
+ raise LiteralInvalid(
+ msg or '%s not match for %s' % (value, self.lit)
+ )
+ else:
+ return self.lit
+
+ def __str__(self):
+ return str(self.lit)
+
+ def __repr__(self):
+ return repr(self.lit)
+
+
+def u(x):
+ if sys.version_info < (3,):
+ return unicode(x)
+ else:
+ return x
new file mode 100644
--- /dev/null
+++ b/third_party/python/voluptuous/voluptuous/validators.py
@@ -0,0 +1,935 @@
+import os
+import re
+import datetime
+import sys
+from functools import wraps
+from decimal import Decimal, InvalidOperation
+
+try:
+ from schema_builder import Schema, raises, message
+ from error import (MultipleInvalid, CoerceInvalid, TrueInvalid, FalseInvalid, BooleanInvalid, Invalid, AnyInvalid,
+ AllInvalid, MatchInvalid, UrlInvalid, EmailInvalid, FileInvalid, DirInvalid, RangeInvalid,
+ PathInvalid, ExactSequenceInvalid, LengthInvalid, DatetimeInvalid, DateInvalid, InInvalid,
+ TypeInvalid, NotInInvalid, ContainsInvalid)
+except ImportError:
+ from .schema_builder import Schema, raises, message
+ from .error import (MultipleInvalid, CoerceInvalid, TrueInvalid, FalseInvalid, BooleanInvalid, Invalid, AnyInvalid,
+ AllInvalid, MatchInvalid, UrlInvalid, EmailInvalid, FileInvalid, DirInvalid, RangeInvalid,
+ PathInvalid, ExactSequenceInvalid, LengthInvalid, DatetimeInvalid, DateInvalid, InInvalid,
+ TypeInvalid, NotInInvalid, ContainsInvalid)
+
+
+if sys.version_info >= (3,):
+ import urllib.parse as urlparse
+ basestring = str
+else:
+ import urlparse
+
+# Taken from https://github.com/kvesteri/validators/blob/master/validators/email.py
+USER_REGEX = re.compile(
+ # dot-atom
+ r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+"
+ r"(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$"
+ # quoted-string
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|'
+ r"""\\[\001-\011\013\014\016-\177])*"$)""",
+ re.IGNORECASE
+)
+DOMAIN_REGEX = re.compile(
+ # domain
+ r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
+ r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?$)'
+ # literal form, ipv4 address (SMTP 4.1.3)
+ r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)'
+ r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$',
+ re.IGNORECASE)
+
+__author__ = 'tusharmakkar08'
+
+
+def truth(f):
+ """Convenience decorator to convert truth functions into validators.
+
+ >>> @truth
+ ... def isdir(v):
+ ... return os.path.isdir(v)
+ >>> validate = Schema(isdir)
+ >>> validate('/')
+ '/'
+ >>> with raises(MultipleInvalid, 'not a valid value'):
+ ... validate('/notavaliddir')
+ """
+
+ @wraps(f)
+ def check(v):
+ t = f(v)
+ if not t:
+ raise ValueError
+ return v
+
+ return check
+
+
+class Coerce(object):
+ """Coerce a value to a type.
+
+ If the type constructor throws a ValueError or TypeError, the value
+ will be marked as Invalid.
+
+ Default behavior:
+
+ >>> validate = Schema(Coerce(int))
+ >>> with raises(MultipleInvalid, 'expected int'):
+ ... validate(None)
+ >>> with raises(MultipleInvalid, 'expected int'):
+ ... validate('foo')
+
+ With custom message:
+
+ >>> validate = Schema(Coerce(int, "moo"))
+ >>> with raises(MultipleInvalid, 'moo'):
+ ... validate('foo')
+ """
+
+ def __init__(self, type, msg=None):
+ self.type = type
+ self.msg = msg
+ self.type_name = type.__name__
+
+ def __call__(self, v):
+ try:
+ return self.type(v)
+ except (ValueError, TypeError):
+ msg = self.msg or ('expected %s' % self.type_name)
+ raise CoerceInvalid(msg)
+
+ def __repr__(self):
+ return 'Coerce(%s, msg=%r)' % (self.type_name, self.msg)
+
+
+@message('value was not true', cls=TrueInvalid)
+@truth
+def IsTrue(v):
+ """Assert that a value is true, in the Python sense.
+
+ >>> validate = Schema(IsTrue())
+
+ "In the Python sense" means that implicitly false values, such as empty
+ lists, dictionaries, etc. are treated as "false":
+
+ >>> with raises(MultipleInvalid, "value was not true"):
+ ... validate([])
+ >>> validate([1])
+ [1]
+ >>> with raises(MultipleInvalid, "value was not true"):
+ ... validate(False)
+
+ ...and so on.
+
+ >>> try:
+ ... validate([])
+ ... except MultipleInvalid as e:
+ ... assert isinstance(e.errors[0], TrueInvalid)
+ """
+ return v
+
+
+@message('value was not false', cls=FalseInvalid)
+def IsFalse(v):
+ """Assert that a value is false, in the Python sense.
+
+ (see :func:`IsTrue` for more detail)
+
+ >>> validate = Schema(IsFalse())
+ >>> validate([])
+ []
+ >>> with raises(MultipleInvalid, "value was not false"):
+ ... validate(True)
+
+ >>> try:
+ ... validate(True)
+ ... except MultipleInvalid as e:
+ ... assert isinstance(e.errors[0], FalseInvalid)
+ """
+ if v:
+ raise ValueError
+ return v
+
+
+@message('expected boolean', cls=BooleanInvalid)
+def Boolean(v):
+ """Convert human-readable boolean values to a bool.
+
+ Accepted values are 1, true, yes, on, enable, and their negatives.
+ Non-string values are cast to bool.
+
+ >>> validate = Schema(Boolean())
+ >>> validate(True)
+ True
+ >>> validate("1")
+ True
+ >>> validate("0")
+ False
+ >>> with raises(MultipleInvalid, "expected boolean"):
+ ... validate('moo')
+ >>> try:
+ ... validate('moo')
+ ... except MultipleInvalid as e:
+ ... assert isinstance(e.errors[0], BooleanInvalid)
+ """
+ if isinstance(v, basestring):
+ v = v.lower()
+ if v in ('1', 'true', 'yes', 'on', 'enable'):
+ return True
+ if v in ('0', 'false', 'no', 'off', 'disable'):
+ return False
+ raise ValueError
+ return bool(v)
+
+
+class Any(object):
+ """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.
+ :returns: Return value of the first validator that passes.
+
+ >>> validate = Schema(Any('true', 'false',
+ ... All(Any(int, bool), Coerce(bool))))
+ >>> validate('true')
+ 'true'
+ >>> validate(1)
+ True
+ >>> with raises(MultipleInvalid, "not a valid value"):
+ ... validate('moo')
+
+ msg argument is used
+
+ >>> validate = Schema(Any(1, 2, 3, msg="Expected 1 2 or 3"))
+ >>> validate(1)
+ 1
+ >>> with raises(MultipleInvalid, "Expected 1 2 or 3"):
+ ... validate(4)
+ """
+
+ def __init__(self, *validators, **kwargs):
+ self.validators = validators
+ self.msg = kwargs.pop('msg', None)
+ self._schemas = [Schema(val, **kwargs) for val in validators]
+
+ def __call__(self, v):
+ error = None
+ for schema in self._schemas:
+ try:
+ return schema(v)
+ except Invalid as e:
+ if error is None or len(e.path) > len(error.path):
+ error = e
+ else:
+ if error:
+ raise error if self.msg is None else AnyInvalid(self.msg)
+ raise AnyInvalid(self.msg or 'no valid value found')
+
+ def __repr__(self):
+ return 'Any([%s])' % (", ".join(repr(v) for v in self.validators))
+
+
+# Convenience alias
+Or = Any
+
+
+class All(object):
+ """Value must pass all validators.
+
+ 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.
+
+ >>> validate = Schema(All('10', Coerce(int)))
+ >>> validate('10')
+ 10
+ """
+
+ def __init__(self, *validators, **kwargs):
+ self.validators = validators
+ self.msg = kwargs.pop('msg', None)
+ self._schemas = [Schema(val, **kwargs) for val in validators]
+
+ def __call__(self, v):
+ try:
+ for schema in self._schemas:
+ v = schema(v)
+ except Invalid as e:
+ raise e if self.msg is None else AllInvalid(self.msg)
+ return v
+
+ def __repr__(self):
+ return 'All(%s, msg=%r)' % (
+ ", ".join(repr(v) for v in self.validators),
+ self.msg
+ )
+
+
+# Convenience alias
+And = All
+
+
+class Match(object):
+ """Value must be a string that matches the regular expression.
+
+ >>> validate = Schema(Match(r'^0x[A-F0-9]+$'))
+ >>> validate('0x123EF4')
+ '0x123EF4'
+ >>> with raises(MultipleInvalid, "does not match regular expression"):
+ ... validate('123EF4')
+
+ >>> with raises(MultipleInvalid, 'expected string or buffer'):
+ ... validate(123)
+
+ Pattern may also be a _compiled regular expression:
+
+ >>> validate = Schema(Match(re.compile(r'0x[A-F0-9]+', re.I)))
+ >>> validate('0x123ef4')
+ '0x123ef4'
+ """
+
+ def __init__(self, pattern, msg=None):
+ if isinstance(pattern, basestring):
+ pattern = re.compile(pattern)
+ self.pattern = pattern
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ match = self.pattern.match(v)
+ except TypeError:
+ raise MatchInvalid("expected string or buffer")
+ if not match:
+ raise MatchInvalid(self.msg or 'does not match regular expression')
+ return v
+
+ def __repr__(self):
+ return 'Match(%r, msg=%r)' % (self.pattern.pattern, self.msg)
+
+
+class Replace(object):
+ """Regex substitution.
+
+ >>> validate = Schema(All(Replace('you', 'I'),
+ ... Replace('hello', 'goodbye')))
+ >>> validate('you say hello')
+ 'I say goodbye'
+ """
+
+ def __init__(self, pattern, substitution, msg=None):
+ if isinstance(pattern, basestring):
+ pattern = re.compile(pattern)
+ self.pattern = pattern
+ self.substitution = substitution
+ self.msg = msg
+
+ def __call__(self, v):
+ return self.pattern.sub(self.substitution, v)
+
+ def __repr__(self):
+ return 'Replace(%r, %r, msg=%r)' % (self.pattern.pattern,
+ self.substitution,
+ self.msg)
+
+
+def _url_validation(v):
+ parsed = urlparse.urlparse(v)
+ if not parsed.scheme or not parsed.netloc:
+ raise UrlInvalid("must have a URL scheme and host")
+ return parsed
+
+
+@message('expected an Email', cls=EmailInvalid)
+def Email(v):
+ """Verify that the value is an Email or not.
+
+ >>> s = Schema(Email())
+ >>> with raises(MultipleInvalid, 'expected an Email'):
+ ... s("a.com")
+ >>> with raises(MultipleInvalid, 'expected an Email'):
+ ... s("a@.com")
+ >>> with raises(MultipleInvalid, 'expected an Email'):
+ ... s("a@.com")
+ >>> s('t@x.com')
+ 't@x.com'
+ """
+ try:
+ if not v or "@" not in v:
+ raise EmailInvalid("Invalid Email")
+ user_part, domain_part = v.rsplit('@', 1)
+
+ if not (USER_REGEX.match(user_part) and DOMAIN_REGEX.match(domain_part)):
+ raise EmailInvalid("Invalid Email")
+ return v
+ except:
+ raise ValueError
+
+
+@message('expected a Fully qualified domain name URL', cls=UrlInvalid)
+def FqdnUrl(v):
+ """Verify that the value is a Fully qualified domain name URL.
+
+ >>> s = Schema(FqdnUrl())
+ >>> with raises(MultipleInvalid, 'expected a Fully qualified domain name URL'):
+ ... s("http://localhost/")
+ >>> s('http://w3.org')
+ 'http://w3.org'
+ """
+ try:
+ parsed_url = _url_validation(v)
+ if "." not in parsed_url.netloc:
+ raise UrlInvalid("must have a domain name in URL")
+ return v
+ except:
+ raise ValueError
+
+
+@message('expected a URL', cls=UrlInvalid)
+def Url(v):
+ """Verify that the value is a URL.
+
+ >>> s = Schema(Url())
+ >>> with raises(MultipleInvalid, 'expected a URL'):
+ ... s(1)
+ >>> s('http://w3.org')
+ 'http://w3.org'
+ """
+ try:
+ _url_validation(v)
+ return v
+ except:
+ raise ValueError
+
+
+@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'):
+ ... IsFile()("random_filename_goes_here.py")
+ >>> with raises(FileInvalid, 'Not a file'):
+ ... IsFile()(None)
+ """
+ if v:
+ return os.path.isfile(v)
+ else:
+ raise FileInvalid('Not a file')
+
+
+@message('not a directory', cls=DirInvalid)
+@truth
+def IsDir(v):
+ """Verify the directory exists.
+
+ >>> IsDir()('/')
+ '/'
+ >>> with raises(DirInvalid, 'Not a directory'):
+ ... IsDir()(None)
+ """
+ if v:
+ return os.path.isdir(v)
+ else:
+ raise DirInvalid("Not a directory")
+
+
+@message('path does not exist', cls=PathInvalid)
+@truth
+def PathExists(v):
+ """Verify the path exists, regardless of its type.
+
+ >>> os.path.basename(PathExists()(__file__)).startswith('validators.py')
+ True
+ >>> with raises(Invalid, 'path does not exist'):
+ ... PathExists()("random_filename_goes_here.py")
+ >>> with raises(PathInvalid, 'Not a Path'):
+ ... PathExists()(None)
+ """
+ if v:
+ return os.path.exists(v)
+ else:
+ 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 and is not 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.
+
+ Either min or max may be omitted.
+ Either min or max can be excluded from the range of accepted values.
+
+ :raises Invalid: If the value is outside the range.
+
+ >>> s = Schema(Range(min=1, max=10, min_included=False))
+ >>> s(5)
+ 5
+ >>> s(10)
+ 10
+ >>> with raises(MultipleInvalid, 'value must be at most 10'):
+ ... s(20)
+ >>> with raises(MultipleInvalid, 'value must be higher than 1'):
+ ... s(1)
+ >>> with raises(MultipleInvalid, 'value must be lower than 10'):
+ ... Schema(Range(max=10, max_included=False))(20)
+ """
+
+ def __init__(self, min=None, max=None, min_included=True,
+ max_included=True, msg=None):
+ self.min = min
+ self.max = max
+ self.min_included = min_included
+ self.max_included = max_included
+ self.msg = msg
+
+ def __call__(self, v):
+ if self.min_included:
+ if self.min is not None and not v >= self.min:
+ raise RangeInvalid(
+ self.msg or 'value must be at least %s' % self.min)
+ else:
+ if self.min is not None and not v > self.min:
+ raise RangeInvalid(
+ self.msg or 'value must be higher than %s' % self.min)
+ if self.max_included:
+ if self.max is not None and not v <= self.max:
+ raise RangeInvalid(
+ self.msg or 'value must be at most %s' % self.max)
+ else:
+ if self.max is not None and not v < self.max:
+ raise RangeInvalid(
+ self.msg or 'value must be lower than %s' % self.max)
+ return v
+
+ def __repr__(self):
+ return ('Range(min=%r, max=%r, min_included=%r,'
+ ' max_included=%r, msg=%r)' % (self.min, self.max,
+ self.min_included,
+ self.max_included,
+ self.msg))
+
+
+class Clamp(object):
+ """Clamp a value to a range.
+
+ Either min or max may be omitted.
+
+ >>> s = Schema(Clamp(min=0, max=1))
+ >>> s(0.5)
+ 0.5
+ >>> s(5)
+ 1
+ >>> s(-1)
+ 0
+ """
+
+ def __init__(self, min=None, max=None, msg=None):
+ self.min = min
+ self.max = max
+ self.msg = msg
+
+ def __call__(self, v):
+ if self.min is not None and v < self.min:
+ v = self.min
+ if self.max is not None and v > self.max:
+ v = self.max
+ return v
+
+ def __repr__(self):
+ return 'Clamp(min=%s, max=%s)' % (self.min, self.max)
+
+
+class Length(object):
+ """The length of a value must be in a certain range."""
+
+ def __init__(self, min=None, max=None, msg=None):
+ self.min = min
+ self.max = max
+ self.msg = msg
+
+ def __call__(self, v):
+ if self.min is not None and len(v) < self.min:
+ raise LengthInvalid(
+ self.msg or 'length of value must be at least %s' % self.min)
+ if self.max is not None and len(v) > self.max:
+ raise LengthInvalid(
+ self.msg or 'length of value must be at most %s' % self.max)
+ return v
+
+ def __repr__(self):
+ return 'Length(min=%s, max=%s)' % (self.min, self.max)
+
+
+class Datetime(object):
+ """Validate that the value matches the datetime format."""
+
+ DEFAULT_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
+
+ def __init__(self, format=None, msg=None):
+ self.format = format or self.DEFAULT_FORMAT
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ datetime.datetime.strptime(v, self.format)
+ except (TypeError, ValueError):
+ raise DatetimeInvalid(
+ self.msg or 'value does not match'
+ ' expected format %s' % self.format)
+ return v
+
+ def __repr__(self):
+ return 'Datetime(format=%s)' % self.format
+
+
+class Date(Datetime):
+ """Validate that the value matches the date format."""
+
+ DEFAULT_FORMAT = '%Y-%m-%d'
+ FORMAT_DESCRIPTION = 'yyyy-mm-dd'
+
+ def __call__(self, v):
+ try:
+ datetime.datetime.strptime(v, self.format)
+ if len(v) != len(self.FORMAT_DESCRIPTION):
+ raise DateInvalid(
+ self.msg or 'value has invalid length'
+ ' expected length %d (%s)' % (len(self.FORMAT_DESCRIPTION), self.FORMAT_DESCRIPTION))
+ except (TypeError, ValueError):
+ raise DateInvalid(
+ self.msg or 'value does not match'
+ ' expected format %s' % self.format)
+ return v
+
+ def __repr__(self):
+ return 'Date(format=%s)' % self.format
+
+
+class In(object):
+ """Validate that a value is in a collection."""
+
+ def __init__(self, container, msg=None):
+ self.container = container
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ check = v not in self.container
+ except TypeError:
+ check = True
+ if check:
+ raise InInvalid(self.msg or 'value is not allowed')
+ return v
+
+ def __repr__(self):
+ return 'In(%s)' % (self.container,)
+
+
+class NotIn(object):
+ """Validate that a value is not in a collection."""
+
+ def __init__(self, container, msg=None):
+ self.container = container
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ check = v in self.container
+ except TypeError:
+ check = True
+ if check:
+ raise NotInInvalid(self.msg or 'value is not allowed')
+ return v
+
+ def __repr__(self):
+ return 'NotIn(%s)' % (self.container,)
+
+
+class Contains(object):
+ """Validate that the given schema element is in the sequence being validated.
+
+ >>> s = Contains(1)
+ >>> s([3, 2, 1])
+ [3, 2, 1]
+ >>> with raises(ContainsInvalid, 'value is not allowed'):
+ ... s([3, 2])
+ """
+
+ def __init__(self, item, msg=None):
+ self.item = item
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ check = self.item not in v
+ except TypeError:
+ check = True
+ if check:
+ raise ContainsInvalid(self.msg or 'value is not allowed')
+ return v
+
+ def __repr__(self):
+ return 'Contains(%s)' % (self.item, )
+
+
+class ExactSequence(object):
+ """Matches each element in a sequence against the corresponding element in
+ the validators.
+
+ :param msg: Message to deliver to user if validation fails.
+ :param kwargs: All other keyword arguments are passed to the sub-Schema
+ constructors.
+
+ >>> from voluptuous import Schema, ExactSequence
+ >>> validate = Schema(ExactSequence([str, int, list, list]))
+ >>> validate(['hourly_report', 10, [], []])
+ ['hourly_report', 10, [], []]
+ >>> validate(('hourly_report', 10, [], []))
+ ('hourly_report', 10, [], [])
+ """
+
+ def __init__(self, validators, **kwargs):
+ self.validators = validators
+ self.msg = kwargs.pop('msg', None)
+ self._schemas = [Schema(val, **kwargs) for val in validators]
+
+ def __call__(self, v):
+ if not isinstance(v, (list, tuple)) or len(v) != len(self._schemas):
+ raise ExactSequenceInvalid(self.msg)
+ try:
+ v = type(v)(schema(x) for x, schema in zip(v, self._schemas))
+ except Invalid as e:
+ raise e if self.msg is None else ExactSequenceInvalid(self.msg)
+ return v
+
+ def __repr__(self):
+ return 'ExactSequence([%s])' % (", ".join(repr(v)
+ for v in self.validators))
+
+
+class Unique(object):
+ """Ensure an iterable does not contain duplicate items.
+
+ Only iterables convertable to a set are supported (native types and
+ objects with correct __eq__).
+
+ JSON does not support set, so they need to be presented as arrays.
+ Unique allows ensuring that such array does not contain dupes.
+
+ >>> s = Schema(Unique())
+ >>> s([])
+ []
+ >>> s([1, 2])
+ [1, 2]
+ >>> with raises(Invalid, 'contains duplicate items: [1]'):
+ ... s([1, 1, 2])
+ >>> with raises(Invalid, "contains duplicate items: ['one']"):
+ ... s(['one', 'two', 'one'])
+ >>> with raises(Invalid, regex="^contains unhashable elements: "):
+ ... s([set([1, 2]), set([3, 4])])
+ >>> s('abc')
+ 'abc'
+ >>> with raises(Invalid, regex="^contains duplicate items: "):
+ ... s('aabbc')
+ """
+
+ def __init__(self, msg=None):
+ self.msg = msg
+
+ def __call__(self, v):
+ try:
+ set_v = set(v)
+ except TypeError as e:
+ raise TypeInvalid(
+ self.msg or 'contains unhashable elements: {0}'.format(e))
+ if len(set_v) != len(v):
+ seen = set()
+ dupes = list(set(x for x in v if x in seen or seen.add(x)))
+ raise Invalid(
+ self.msg or 'contains duplicate items: {0}'.format(dupes))
+ return v
+
+ def __repr__(self):
+ return 'Unique()'
+
+
+class Equal(object):
+ """Ensure that value matches target.
+
+ >>> s = Schema(Equal(1))
+ >>> s(1)
+ 1
+ >>> with raises(Invalid):
+ ... s(2)
+
+ Validators are not supported, match must be exact:
+
+ >>> s = Schema(Equal(str))
+ >>> with raises(Invalid):
+ ... s('foo')
+ """
+
+ def __init__(self, target, msg=None):
+ self.target = target
+ self.msg = msg
+
+ def __call__(self, v):
+ if v != self.target:
+ raise Invalid(self.msg or 'Values are not equal: value:{} != target:{}'.format(v, self.target))
+ return v
+
+ def __repr__(self):
+ return 'Equal({})'.format(self.target)
+
+
+class Unordered(object):
+ """Ensures sequence contains values in unspecified order.
+
+ >>> s = Schema(Unordered([2, 1]))
+ >>> s([2, 1])
+ [2, 1]
+ >>> s([1, 2])
+ [1, 2]
+ >>> s = Schema(Unordered([str, int]))
+ >>> s(['foo', 1])
+ ['foo', 1]
+ >>> s([1, 'foo'])
+ [1, 'foo']
+ """
+
+ def __init__(self, validators, msg=None, **kwargs):
+ self.validators = validators
+ self.msg = msg
+ self._schemas = [Schema(val, **kwargs) for val in validators]
+
+ def __call__(self, v):
+ if not isinstance(v, (list, tuple)):
+ raise Invalid(self.msg or 'Value {} is not sequence!'.format(v))
+
+ if len(v) != len(self._schemas):
+ raise Invalid(self.msg or 'List lengths differ, value:{} != target:{}'.format(len(v), len(self._schemas)))
+
+ consumed = set()
+ missing = []
+ for index, value in enumerate(v):
+ found = False
+ for i, s in enumerate(self._schemas):
+ if i in consumed:
+ continue
+ try:
+ s(value)
+ except Invalid:
+ pass
+ else:
+ found = True
+ consumed.add(i)
+ break
+ if not found:
+ missing.append((index, value))
+
+ if len(missing) == 1:
+ el = missing[0]
+ raise Invalid(self.msg or 'Element #{} ({}) is not valid against any validator'.format(el[0], el[1]))
+ elif missing:
+ raise MultipleInvalid([
+ Invalid(self.msg or 'Element #{} ({}) is not valid against any validator'.format(el[0], el[1]))
+ for el in missing
+ ])
+ return v
+
+ def __repr__(self):
+ return 'Unordered([{}])'.format(", ".join(repr(v) for v in self.validators))
+
+
+class Number(object):
+ """
+ Verify the number of digits that are present in the number(Precision),
+ and the decimal places(Scale)
+
+ :raises Invalid: If the value does not match the provided Precision and Scale.
+
+ >>> schema = Schema(Number(precision=6, scale=2))
+ >>> schema('1234.01')
+ '1234.01'
+ >>> schema = Schema(Number(precision=6, scale=2, yield_decimal=True))
+ >>> schema('1234.01')
+ Decimal('1234.01')
+ """
+
+ def __init__(self, precision=None, scale=None, msg=None, yield_decimal=False):
+ self.precision = precision
+ self.scale = scale
+ self.msg = msg
+ self.yield_decimal = yield_decimal
+
+ def __call__(self, v):
+ """
+ :param v: is a number enclosed with string
+ :return: Decimal number
+ """
+ precision, scale, decimal_num = self._get_precision_scale(v)
+
+ if self.precision is not None and self.scale is not None and\
+ precision != self.precision and scale != self.scale:
+ raise Invalid(self.msg or "Precision must be equal to %s, and Scale must be equal to %s" %(self.precision, self.scale))
+ else:
+ if self.precision is not None and precision != self.precision:
+ raise Invalid(self.msg or "Precision must be equal to %s"%self.precision)
+
+ if self.scale is not None and scale != self.scale :
+ raise Invalid(self.msg or "Scale must be equal to %s"%self.scale)
+
+ if self.yield_decimal:
+ return decimal_num
+ else:
+ return v
+
+ def __repr__(self):
+ return ('Number(precision=%s, scale=%s, msg=%s)' % (self.precision, self.scale, self.msg))
+
+ def _get_precision_scale(self, number):
+ """
+ :param number:
+ :return: tuple(precision, scale, decimal_number)
+ """
+ try:
+ decimal_num = Decimal(number)
+ except InvalidOperation:
+ raise Invalid(self.msg or 'Value must be a number enclosed with string')
+
+ return (len(decimal_num.as_tuple().digits), -(decimal_num.as_tuple().exponent), decimal_num)