voluptuous

repository·master·Indexed 23 days ago

https://github.com/alecthomas/voluptuous

A Python data validation library designed for validating incoming data from formats like JSON and YAML. It uses standard Python data structures to define schemas, supporting complex nested data, custom callables as validators, and automatic schema generation from Python dataclasses via DataclassSchema.

Tokens
3.2K
Snippets
11
Records
16
Agent score
34%

What's inside voluptuous

  1. What is Voluptuous?

    master

    Voluptuous is a Python data validation library designed for validating data coming into Python from formats like JSON or YAML. It focuses on simplicity, support for complex nested data structures, and providing useful error messages.

    Key characteristics include:

    • Validators as callables: You can use any function as a validator without subclassing.
    • Simple error handling: Validators raise Invalid exceptions to provide clear error messages.
    • Schemas as Python data structures: Schemas are defined using standard Python types like dictionaries and lists (e.g., {int: str} or [int, float, str]).
    • Nested data support: It treats nested structures (like a list of dictionaries [{}]) as first-class citizens.
  2. Handle validation errors with Invalid and MultipleInvalid

    master

    When validation fails, Voluptuous raises exceptions that provide details about where and why the failure occurred.

    • Custom Validators: Must raise Invalid to signal a validation failure. Other exceptions are treated as unexpected errors and are not caught by Voluptuous.
    • Invalid Exception: Contains path (the location in the data structure), msg (the error message), and error_message (the original exception message).
    • MultipleInvalid Exception: Raised when a Schema validation fails. It can be caught to inspect the specific errors.

    Matching Behavior: Matching is depth-first and fail-fast. If a value matches a part of the schema but fails deeper within that branch, the error is reported immediately without backtracking to try other schema elements.

    >>> def validate_email(email):
    ...     if not "@" in email:
    ...         raise Invalid("This email is invalid.")
    ...     return email
    >>> schema = Schema({"email": validate_email})
    >>> try:
    ...     schema({"email": "whatever"})
    ... except MultipleInvalid as e:
    ...     print(e.path)  # ['email']
    ...     print(e.msg)   # 'This email is invalid.'
  3. Handle validation errors with MultipleInvalid

    master
    When validation fails, Voluptuous raises exceptions. MultipleInvalid is used to catch validation errors. The error message typically includes the reason for failure and the path to the invalid data (e.g., required key not provided @ data['q']).
  4. Validate Lists and Sets

    master

    In Voluptuous, lists and sets are treated as a set of valid values. Each element in the schema is compared against every value in the input data.

    • Lists: A schema like [1, 'a'] requires every element in the input list to be either 1 or 'a'. To allow a list to contain any value, use the list type instead of an empty list [].
    • Sets/Frozensets: Similar to lists, elements in the schema set are compared to input values. To allow a set to contain anything, use the set type.

    Note: An empty list [] or empty set set() is treated as a literal empty collection and will not match non-empty input.

  5. Define schemas using Literals, Types, and URLs

    master

    Voluptuous schemas are nested data structures (dictionaries, lists, scalars, and validators) that pattern match against input data.

    • Literals: Use standard values (strings, integers, etc.) for exact equality checks.
    • Types: Use Python types (e.g., int, str) to validate that a value is an instance of that type.
    • URLs: Use the Url() validator to validate strings using urlparse logic.
  6. Use Validation Functions and Coercion

    master

    Validators are callables that raise an Invalid exception when data is invalid. They can also mutate (coerce) data into a valid form.

    • Simple Validators: Any function that raises ValueError can act as a validator.
    • Coercion: You can create validators that transform input (e.g., converting a string to an integer) using a pattern similar to Coerce(type).
    from datetime import datetime
    from voluptuous import Schema, Invalid
    
    # Custom validator factory
    def Date(fmt='%Y-%m-%d'):
        return lambda v: datetime.strptime(v, fmt)
    
    schema = Schema(Date())
    schema('2013-03-03')  # Returns datetime object
    
    # Coercion pattern
    def Coerce(type, msg=None):
        def f(v):
            try:
                return type(v)
            except ValueError:
                raise Invalid(msg or ('expected %s' % type.__name__))
        return f
    
    schema_coerce = Schema(Coerce(int))
    schema_coerce('123')  # Returns 123 (int)
  7. Implement multi-field validation using All()

    master

    To validate rules that depend on multiple fields (cross-field validation), use All() to create a two-pass validation process:

    1. First Pass: Use a dictionary schema to validate the basic structure and types of individual fields.
    2. Second Pass: Pass a custom function to All() that performs the cross-field logic.

    This ensures your custom validator receives pre-validated data, so it doesn't need to perform its own type checking. If the first pass fails, the second pass (the cross-field validator) will not execute.

    def passwords_must_match(passwords):
        if passwords['password'] != passwords['password_again']:
            raise Invalid('passwords must match')
        return passwords
    
    schema = Schema(All(
        # First pass: field types
        {'password': str, 'password_again': str},
        # Second pass: multi-field rules
        passwords_must_match
    ))
  8. Configure Dictionary Key requirements and extra keys

    master

    Voluptuous provides fine-grained control over dictionary validation via schema-level settings or marker tokens.

    Extra Keys

    By default, extra keys in the data trigger exceptions. Use these to change behavior:

    • extra=ALLOW_EXTRA: Allows additional keys.
    • extra=REMOVE_EXTRA: Removes additional keys from the validated output.
    • Extra: Use the Extra marker as a key within a dictionary schema to allow arbitrary extra keys for that specific dictionary.

    Required and Optional Keys

    By default, keys in a schema are optional.

    • Required: Use required=True in the Schema constructor to make all keys required, or use the Required(key) marker for specific keys.
    • Optional: If required=True is set on the schema, use the Optional(key) marker to mark specific keys as not required.
  9. Define advanced Schemas with constraints and defaults

    master

    For more precise validation, use specialized tools to enforce constraints and provide default values:

    • Required(key): Ensures a key must be present in the data.
    • Required(key, default=value): Ensures a key is present, or inserts the default value if it is missing.
    • All(validator, ...): Chains multiple validators together.
    • Length(min=n): Validates the length of a value.
    • Range(min=n, max=m): Validates that a numeric value falls within a range.

    Example of a robust schema for a Twitter-like search API:

    from voluptuous import Required, All, Length, Range, Schema
    
    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)),
    })
  10. Define a basic Schema

    master

    You can define a schema by mapping keys to types using a standard Python dictionary. When you call the Schema object with data, it validates that the data matches the specified types.

    Example of a simple schema for an API query:

    from voluptuous import Schema
    
    schema = Schema({
      'q': str,
      'per_page': int,
      'page': int,
    })
  11. Extend an existing Schema

    master

    Use Schema.extend() to create a new schema based on an existing one. This is useful for adding requirements to a base schema without modifying the original.

    • Schema.extend(dict): Adds new key-value pairs to the schema.
    • Schema.extend(Schema): Merges another schema. The extension schema's required settings will be respected.
    from voluptuous import Schema
    
    person = Schema({'name': str})
    # Extend with a new field
    person_with_age = person.extend({'age': int})
    
    # Extending with another Schema object
    contact = Schema({'email': str})
    person_with_contact = person.extend(contact)
  12. Define Recursive Schemas with Self

    master

    Use voluptuous.Self to define schemas that can contain nested versions of themselves, useful for tree-like structures.

    from voluptuous import Schema, Self
    
    recursive = Schema({"more": Self, "value": int})
    recursive({"more": {"value": 42}, "value": 41})