Package: valideer
Lightweight data validation and adaptation library for Python.At a Glance:Supports both validation (check if a value is valid) and adaptation (convert
a valid input to an appropriate output).Succinct: validation schemas can be specified in a declarative and extensible
mini “language”; no need to define verbose schema classes upfront. A regular
Python API is also available if the compact syntax is not your cup of tea.Batteries included: validators for most common types are included out of the box.Extensible: New custom validators and adaptors can be easily defined and
registered.Informative, customizable error messages: Validation errors include the reason
and location of the error.Agnostic: not tied to any particular framework or application domain (e.g.
Web form validation).Well tested: Extensive test suite with 100% coverage.Production ready: Used for validating every access to thePodio API.Licence: MIT.InstallationTo install run:pip install valideerOr for the latest version:git clone git@github.com:podio/valideer.git
cd valideer
python setup.py installYou may run the unit tests with:$ python setup.py test --quiet
running test
running egg_info
writing dependency_links to valideer.egg-info/dependency_links.txt
writing requirements to valideer.egg-info/requires.txt
writing valideer.egg-info/PKG-INFO
writing top-level names to valideer.egg-info/top_level.txt
reading manifest file 'valideer.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'valideer.egg-info/SOURCES.txt'
running build_ext
...........................................................................................................................................................................
----------------------------------------------------------------------
Ran 171 tests in 0.106s

OKBasic UsageWe’ll demonstratevalideerusing the followingJSON schema example:{
    "name": "Product",
    "properties": {
        "id": {
            "type": "number",
            "description": "Product identifier",
            "required": true
        },
        "name": {
            "type": "string",
            "description": "Name of the product",
            "required": true
        },
        "price": {
            "type": "number",
            "minimum": 0,
            "required": true
        },
        "tags": {
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "stock": {
            "type": "object",
            "properties": {
                "warehouse": {
                    "type": "number"
                },
                "retail": {
                    "type": "number"
                }
            }
        }
    }
}This can be specified by passing a similar but less verbose structure to thevalideer.parsefunction:>>> import valideer as V
>>> product_schema = {
>>>     "+id": "number",
>>>     "+name": "string",
>>>     "+price": V.Range("number", min_value=0),
>>>     "tags": ["string"],
>>>     "stock": {
>>>         "warehouse": "number",
>>>         "retail": "number",
>>>     }
>>> }
>>> validator = V.parse(product_schema)parsereturns aValidatorinstance, which can be then used to validate
or adapt values.ValidationTo check if an input is valid call theis_validmethod:>>> product1 = {
>>>     "id": 1,
>>>     "name": "Foo",
>>>     "price": 123,
>>>     "tags": ["Bar", "Eek"],
>>>     "stock": {
>>>         "warehouse": 300,
>>>         "retail": 20
>>>     }
>>> }
>>> validator.is_valid(product1)
True
>>> product2 = {
>>>     "id": 1,
>>>     "price": 123,
>>> }
>>> validator.is_valid(product2)
FalseAnother option is thevalidatemethod. If the input is invalid, it raisesValidationError:>>> validator.validate(product2)
ValidationError: Invalid value {'price': 123, 'id': 1} (dict): missing required properties: ['name']For the common use case of validating inputs when entering a function, the@acceptsdecorator provides some nice syntax sugar (shamelessly stolen fromtypecheck):>>> from valideer import accepts
>>> @accepts(product=product_schema, quantity="integer")
>>> def get_total_price(product, quantity=1):
>>>     return product["price"] * quantity
>>>
>>> get_total_price(product1, 2)
246
>>> get_total_price(product1, 0.5)
ValidationError: Invalid value 0.5 (float): must be integer (at quantity)
>>> get_total_price(product2)
ValidationError: Invalid value {'price': 123, 'id': 1} (dict): missing required properties: ['name'] (at product)AdaptationOften input data have to be converted from their original form before they are
ready to use; for example a number that may arrive as integer or string and
needs to be adapted to a float. Since validation and adaptation usually happen
simultaneously,validatereturns the adapted version of the (valid) input
by default.An existing class can be easily used as an adaptor by being wrapped inAdaptTo:>>> import valideer as V
>>> adapt_prices = V.parse({"prices": [V.AdaptTo(float)]}).validate
>>> adapt_prices({"prices": ["2", "3.1", 1]})
{'prices': [2.0, 3.1, 1.0]}
>>> adapt_prices({"prices": ["2", "3f"]})
ValidationError: Invalid value '3f' (str): invalid literal for float(): 3f (at prices[1])
>>> adapt_prices({"prices": ["2", 1, None]})
ValidationError: Invalid value None (NoneType): float() argument must be a string or a number (at prices[2])Similar to@accepts, the@adaptsdecorator provides a convenient syntax
for adapting function inputs:>>> from valideer import adapts
>>> @adapts(json={"prices": [AdaptTo(float)]})
>>> def get_sum_price(json):
>>>     return sum(json["prices"])
>>> get_sum_price({"prices": ["2", "3.1", 1]})
6.1
>>> get_sum_price({"prices": ["2", "3f"]})
ValidationError: Invalid value '3f' (str): invalid literal for float(): 3f (at json['prices'][1])
>>> get_sum_price({"prices": ["2", 1, None]})
ValidationError: Invalid value None (NoneType): float() argument must be a string or a number (at json['prices'][2])Required and optional object propertiesBy default object properties are considered optional unless they start with “+”.
This default can be inverted by using theparsingcontext manager withrequired_properties=True. In this case object properties are considered
required by default unless they start with “?”. For example:validator = V.parse({
    "+name": "string",
    "duration": {
        "+hours": "integer",
        "+minutes": "integer",
        "seconds": "integer"
    }
})is equivalent to:with V.parsing(required_properties=True):
    validator = V.parse({
        "name": "string",
        "?duration": {
            "hours": "integer",
            "minutes": "integer",
            "?seconds": "integer"
        }
    })Ignoring optional object property errorsBy default an invalid object property value raisesValidationError,
regardless of whether it’s required or optional. It is possible to ignore invalid
values for optional properties by using theparsingcontext manager withignore_optional_property_errors=True:>>> schema = {
...     "+name": "string",
...     "price": "number",
... }
>>> data = {"name": "wine", "price": "12.50"}
>>> V.parse(schema).validate(data)
valideer.base.ValidationError: Invalid value '12.50' (str): must be number (at price)
>>> with V.parsing(ignore_optional_property_errors=True):
...     print V.parse(schema).validate(data)
{'name': 'wine'}Additional object propertiesAny properties that are not specified as either required or optional are allowed
by default. This default can be overriden by callingparsingwithadditional_properties=Falseto disallow all additional propertiesObject.REMOVEto remove all additional properties from the adapted valueany validator or parseable schema to validate all additional property
values using this schema:>>> schema = {
>>>     "name": "string",
>>>     "duration": {
>>>         "hours": "integer",
>>>         "minutes": "integer",
>>>     }
>>> }
>>> data = {"name": "lap", "duration": {"hours":3, "minutes":33, "seconds": 12}}
>>> V.parse(schema).validate(data)
{'duration': {'hours': 3, 'minutes': 33, 'seconds': 12}, 'name': 'lap'}
>>> with V.parsing(additional_properties=False):
...    V.parse(schema).validate(data)
ValidationError: Invalid value {'hours': 3, 'seconds': 12, 'minutes': 33} (dict): additional properties: ['seconds'] (at duration)
>>> with V.parsing(additional_properties=V.Object.REMOVE):
...    print V.parse(schema).validate(data)
{'duration': {'hours': 3, 'minutes': 33}, 'name': 'lap'}
>>> with V.parsing(additional_properties="string"):
...    V.parse(schema).validate(data)
ValidationError: Invalid value 12 (int): must be string (at duration['seconds'])Explicit InstantiationThe usual way to create a validator is by passing an appropriate nested structure
toparse, as outlined above.  This enables concise schema definitions with
minimal boilerplate. In case this seems too cryptic or “unpythonic” for your
taste, a validator can be also created explicitly from regular Python classes:>>> from valideer import Object, HomogeneousSequence, Number, String, Range
>>> validator = Object(
>>>     required={
>>>         "id": Number(),
>>>         "name": String(),
>>>         "price": Range(Number(), min_value=0),
>>>     },
>>>     optional={
>>>         "tags": HomogeneousSequence(String()),
>>>         "stock": Object(
>>>             optional={
>>>                 "warehouse": Number(),
>>>                 "retail": Number(),
>>>             }
>>>         )
>>>     }
>>> )Built-in Validatorsvalideercomes with several predefined validators, each implemented as aValidatorsubclass. As shown above, some validator classes also support a
shortcut form that can be used to specify implicitly a validator instance.Basicvalideer.Boolean(): Acceptsboolinstances.Shortcut:"boolean"valideer.Integer(): Accepts integers (numbers.Integralinstances),
excludingbool.Shortcut:"integer"valideer.Number(): Accepts numbers (numbers.Numberinstances),
excludingbool.Shortcut:"number"valideer.Date(): Acceptsdatetime.dateinstances.Shortcut:"date"valideer.Time(): Acceptsdatetime.timeinstances.Shortcut:"time"valideer.Datetime(): Acceptsdatetime.datetimeinstances.Shortcut:"datetime"valideer.String(min_length=None, max_length=None): Accepts strings
(basestringinstances).Shortcut:"string"valideer.Pattern(regexp): Accepts strings that match the given regular
expression.Shortcut:Compiled regular expressionvalideer.Condition(predicate, traps=Exception): Accepts values for whichpredicate(value)is true. Any raised exception that is instance oftrapsis re-raised as aValidationError.Shortcut:Python function or method.valideer.Type(accept_types=None, reject_types=None): Accepts instances of
the givenaccept_typesbut excluding instances ofreject_types.Shortcut:Python type. For exampleintis equivalent tovalideer.Type(int).valideer.Enum(values): Accepts a fixed set of values.Shortcut:N/AContainersvalideer.HomogeneousSequence(item_schema=None, min_length=None, max_length=None):
Accepts sequences (collections.Sequenceinstances excluding strings) with
elements that are valid foritem_schema(if specified) and length betweenmin_lengthandmax_length(if specified).Shortcut:[item_schema]valideer.HeterogeneousSequence(*item_schemas): Accepts fixed length
sequences (collections.Sequenceinstances excluding strings) where thei-th element is valid for thei-thitem_schema.Shortcut:(item_schema,item_schema, …,item_schema)valideer.Mapping(key_schema=None, value_schema=None): Accepts mappings
(collections.Mappinginstances) with keys that are valid forkey_schema(if specified) and values that are valid forvalue_schema(if specified).Shortcut:N/Avalideer.Object(optional={},required={},additional=True): Accepts JSON-like
objects (collections.Mappinginstances with string keys). Properties that
are specified asoptionalorrequiredare validated against the respective
value schema. Any additional properties are either allowed (ifadditionalis True), disallowed (ifadditionalis False) or validated against theadditionalschema.Shortcut:{”property”:value_schema, “property”:value_schema, …,
“property”:value_schema}. Properties that start with'+'are required, the rest are optional and additional properties are
allowed.Adaptorsvalideer.AdaptBy(adaptor, traps=Exception): Adapts a value by callingadaptor(value). Any raised exception that is instance oftrapsis
wrapped into aValidationError.Shortcut:N/Avalideer.AdaptTo(adaptor, traps=Exception, exact=False): Similar toAdaptBybut for types. Any value that is already instance ofadaptoris returned as is, otherwise it is adapted by callingadaptor(value). IfexactisTrue, instances ofadaptorsubclasses are also adapted.Shortcut:N/ACompositevalideer.Nullable(schema, default=None): Accepts values that are valid forschemaorNone.defaultis returned as the adapted value ofNone.defaultcan also be a zero-argument callable, in which case the adapted
value ofNoneisdefault().Shortcut:“?{validator_name}”. For example"?integer"accepts any integer
orNonevalue.valideer.NonNullable(schema=None): Accepts values that are valid forschema(if specified) except forNone.Shortcut:“+{validator_name}”valideer.Range(schema, min_value=None, max_value=None): Accepts values that
are valid forschemaand within the given[min_value, max_value]range.Shortcut:N/Avalideer.AnyOf(*schemas): Accepts values that are valid for at least one
of the givenschemas.Shortcut:N/Avalideer.AllOf(*schemas): Accepts values that are valid for all the givenschemas.Shortcut:N/Avalideer.ChainOf(*schemas): Passes values through a chain of validator and
adaptorschemas.Shortcut:N/AUser Defined ValidatorsThe set of predefined validators listed above can be easily extended with user
defined validators. All you need to do is extendValidator(or a more
convenient subclass) and implement thevalidatemethod. Here is an example
of a custom validator that could be used to enforce minimal password strength:from valideer import String, ValidationError

class Password(String):

    name = "password"

    def __init__(self, min_length=6, min_lower=1, min_upper=1, min_digits=0):
        super(Password, self).__init__(min_length=min_length)
        self.min_lower = min_lower
        self.min_upper = min_upper
        self.min_digits = min_digits

    def validate(self, value, adapt=True):
        super(Password, self).validate(value)

        if len(filter(str.islower, value)) < self.min_lower:
            raise ValidationError("At least %d lowercase characters required" % self.min_lower)

        if len(filter(str.isupper, value)) < self.min_upper:
            raise ValidationError("At least %d uppercase characters required" % self.min_upper)

        if len(filter(str.isdigit, value)) < self.min_digits:
            raise ValidationError("At least %d digits required" % self.min_digits)

        return valueA few notes:The optionalnameclass attribute creates a shortcut for referring to a
default instance of the validator. In this example the string"password"becomes an alias to aPassword()instance.validatetakes an optional booleanadaptparameter that defaults toTrue. If it isFalse, the validator is allowed to skip adaptation and
perform validation only. This is basically an optimization hint that can be
useful if adaptation happens to be significantly more expensive than validation.
This isn’t common though and soadaptis usually ignored.Shortcut RegistrationSetting anameclass attribute is the simplest way to create a validator
shortcut. A shortcut can also be created explicitly with thevalideer.registerfunction:>>> import valideer as V
>>> V.register("strong_password", Password(min_length=8, min_digits=1))
>>> is_fair_password = V.parse("password").is_valid
>>> is_strong_password = V.parse("strong_password").is_valid
>>> for pwd in "passwd", "Passwd", "PASSWd", "Pas5word":
>>>     print (pwd, is_fair_password(pwd), is_strong_password(pwd))
('passwd', False, False)
('Passwd', True, False)
('PASSWd', True, False)
('Pas5word', True, True)Finally it is possible to parse arbitrary Python objects as validator shortcuts.
For example let’s define aNotcomposite validator, a validator that accepts
a value if and only if it is rejected by another validator:class Not(Validator):

    def __init__(self, schema):
        self._validator = Validator.parse(schema)

    def validate(self, value, adapt=True):
        if self._validator.is_valid(value):
            raise ValidationError("Should not be a %s" % self._validator.__class__.__name__, value)
        return valueIf we’d like to parse'!foo'strings as a shortcut forNot('foo'), we
can do so with thevalideer.register_factorydecorator:>>> @V.register_factory
>>> def NotFactory(obj):
>>>     if isinstance(obj, basestring) and obj.startswith("!"):
>>>         return Not(obj[1:])
>>>
>>> validate = V.parse({"i": "integer", "s": "!number"}).validate
>>> validate({"i": 4, "s": ""})
{'i': 4, 's': ''}
>>> validate({"i": 4, "s": 1.2})
ValidationError: Invalid value 1.2 (float): Should not be a Number (at s)