Migrating from voluptuous
Probatio is a clean-room reimplementation of voluptuous with the same public API. The goal is simple: you should be able to switch with a one-line import change and keep your existing schemas.
Behavioral compatibility with voluptuous is the correctness target, checked against voluptuous itself. If you hit a difference that is not listed below as intentional, treat it as a bug and open an issue.
The short version
Section titled “The short version”Replace the import:
# Beforefrom voluptuous import Schema, Required, Optional, All, Any, Coerce, Invalid
# Afterfrom probatio import Schema, Required, Optional, All, Any, Coerce, InvalidYour schemas stay the same:
from probatio import Schema, Required, Optional, All, Range
schema = Schema( { Required("name"): str, Optional("port"): All(int, Range(min=1, max=65535)), })What carries over
Section titled “What carries over”The names you reach for in voluptuous are present in Probatio with the same
behavior and the same signatures (including positional required/extra on
Schema):
Schema, its calling convention, andextend.- Markers:
Required,Optional,Remove,Extra,Inclusive,Exclusive. - Combinators:
All,Any,Union,Switch,SomeOf(andAnd/Or). - Validators:
Coerce,Range,Clamp,Length,In,NotIn,Match,Contains,Equal,Literal,Set,Unordered,Object,Maybe,ExactSequence,Unique, the string transforms,Email/Url/FqdnUrl,Datetime/Date,IsDir/IsFile/PathExists, and the rest. - Helpers: the
validatedecorator and theraisesguard. - Errors:
Invalid,MultipleInvalid, and the semantic subclasses, including the error path, andhumanize_error/validate_with_humanized_errorsinprobatio.humanize.
Imports that reach into voluptuous submodules keep working too: voluptuous,
voluptuous.error, voluptuous.validators, voluptuous.humanize,
voluptuous.util, and voluptuous.schema_builder all have Probatio equivalents.
Known differences
Section titled “Known differences”Probatio targets behavioral compatibility, so the list is short and every entry is a deliberate improvement, not a regression:
- Recursive
Selfschemas fail cleanly. Cyclic or pathologically deep data raises a normalInvalid(caught with the rest of your validation errors) instead of crashing withRecursionError, as voluptuous does. The depth limit scales with the interpreter’s recursion limit, so legitimately deep data is unaffected. from_json_schematreats its input as untrusted. A catastrophically backtrackingpatternor a pathologically deep document is refused withSchemaError, rather than hanging or overflowing the stack. This only matters when the schema document itself comes from an untrusted source.- A missing complex required key reports once. For
Required(Any("a", "b"))(“at least one of these keys”), voluptuous 0.16.0 emits both the “at least one of […] is required” error and a redundant “required key not provided” for the same key. Probatio reports the single, meaningful error; the first error matches voluptuous exactly. - Any
Mappingis accepted, not onlydict. AMappingProxyType, a multidict, or any custom type implementing theMappingprotocol validates and returns a plaindict, where voluptuous rejects it with “expected a dictionary”. This is a strict superset, so dict code is unaffected. - A callable’s
ValueErrormessage is kept. When a plain callable validator raisesValueError("reason"), the reason is carried into the error (“not a valid value: reason”) instead of being dropped. AValueErrorwith no message still reads “not a valid value”. - Enum members work as schemas and keys. An
enummember (aStrEnumorIntEnumvalue) is matched by equality and can be used as a mapping key, withRequired/Optional, or as a value. voluptuous 0.16.0 rejects this withSchemaError; it is being added upstream in PR #537. - Built-in validators never leak raw exceptions. A wrong-typed value raises
a clean
Invalidrather than a bareTypeError/ValueErrorfrom the underlying call:Replace("a", "b")(42)raisesMatchInvalid, andNumber()onNone, adict, aset,bytes, or an empty sequence raisesInvalid. voluptuous fixes the same edges upstream in PR #540 and PR #539. extendaccepts anotherSchema.base.extend(other_schema)merges the extension’s keys and preserves itsrequiredintent across the merge (recursively into nested mappings); itsextramust match the resulting schema’s. voluptuous 0.16.0 raises anAssertionError; it is added upstream in PR #538. Pass the extension’s.schemadict for the old raw-merge behavior.- Every failing list item is reported. Validating a list collects an error
for each failing item, not just the first. voluptuous 0.16.0 stops at the first
failing nested item (open request, issue #171). You get more complete
diagnostics; code that iterates
MultipleInvalid.errorsis unaffected. - Set schemas transform their elements. A set schema like
{Coerce(int)}runs the element schema on each item, so aCoerceactually coerces. voluptuous returns the set untransformed (bug, issue #400). Anynames its alternatives. When every branch is concrete (a type,None, or a literal), a failingAnyreports “expected int or str or None” instead of voluptuous’s first-branch-only “expected int” or “not a valid value” (issue #412). A branch that is an arbitrary validator keeps surfacing its own error. The error class for the combined message isAnyInvalid.
If you depend on any of the old behaviors (you almost certainly do not), that is the place to know about it.