Compatibility
Probatio targets behavioral compatibility with voluptuous 0.16.0. The public surface mirrors it: the same names, the same signatures, the same semantics. See the API reference for the full surface. For most code, the only change is the import line.
The compatibility shim
Section titled “The compatibility shim”Most code uses voluptuous through its public API. There, swapping
import voluptuous for import probatio is enough, and your schemas keep
working.
Some dependencies are harder. They import voluptuous internals, not just the
public names. A real example is annotatedyaml, which imports
voluptuous.schema_builder._compile_scalar. You cannot edit that code, and a
plain import swap does not reach it.
For that case, Probatio ships install_as_voluptuous in probatio.compat. Call
it once, very early in process startup, before anything imports voluptuous. It
registers Probatio (and a small set of voluptuous-shaped submodules, including
schema_builder with the _compile_scalar internal) into sys.modules under
the voluptuous name. Every later import voluptuous, including the ones inside
your dependencies, then resolves to Probatio for the rest of the process.
from probatio.compat import install_as_voluptuous
install_as_voluptuous()The call is idempotent but has no teardown. It shadows a real voluptuous that was already imported, and it aliases process-wide, so it is meant for an application or a test harness that owns the process. Do not call it from library code that others import.
Intentional deviations
Section titled “Intentional deviations”Compatibility is the target. A few behaviors still differ on purpose, and each is a deliberate improvement over a sharp edge:
- Recursive
Selfon cyclic or very deep data raises a cleanInvalidinstead of letting Python crash with aRecursionError. You get a validation error with a path, not a stack overflow. from_json_schematreats its input as untrusted. It refuses a catastrophically backtrackingpatternand a pathologically deep document rather than hanging or overflowing the stack.- A missing complex required key (
Required(Any("a", "b")), “at least one of these keys”) raises one clear error. voluptuous 0.16.0 additionally emits a redundantrequired key not providedfor the same key; Probatio reports it once. The first, meaningful error is identical. - Any
Mappingis accepted, not onlydict. AMappingProxyType, a multidict, or any custom mapping validates and returns a plaindict, where voluptuous rejects it. A strict superset, so dict code is unaffected. - A callable validator that raises
ValueError("reason")keeps the reason in the error (“not a valid value: reason”), where voluptuous drops it. AValueErrorwith no message still reads “not a valid value”. - An
enummember works as a scalar schema and as a mapping key (matched by equality), where voluptuous 0.16.0 rejects it withSchemaError. Upstream adds this in PR #537; Probatio already has it, which Home Assistant relies on for itsStrEnum/IntEnumservice and attribute names. - Built-in validators never leak a raw exception on a wrong-typed value:
Replace("a", "b")(42)raisesMatchInvalidrather than theTypeErrorfromre.sub, andNumber()onNone/dict/set/bytes/an empty sequence raisesInvalid. voluptuous fixes the same edges upstream in PR #540 and #539. extendaccepts anotherSchema, merging its keys and preserving itsrequiredintent (itsextramust match the result’s). voluptuous 0.16.0 raises anAssertionError; it is added upstream in PR #538.- Validating a list reports every failing item, each with its index, where voluptuous 0.16.0 stops at the first failing nested item (open request, issue #171). The diagnostics are more complete; error-iterating code is unaffected.
- A set schema (
{Coerce(int)}) transforms its elements like a list schema does, so aCoercecoerces. voluptuous returns the set untransformed (bug, issue #400). - A failing
Anywith concrete branches lists them (“expected int or str or None”) asAnyInvalid, where voluptuous reports only the first branch or “not a valid value” (issue #412). A validator branch keeps surfacing its own error.
Where Probatio diverges, it tells you with a normal error, not a crash.
How compatibility is checked
Section titled “How compatibility is checked”Two things keep Probatio honest, and neither is a claim you have to take on faith.
First, differential tests. Probatio and voluptuous run the same schemas against the same inputs, and their results are compared. Voluptuous is the oracle: a divergence is treated as a Probatio bug unless it is one of the documented deviations above. The comparison is driven by generated schemas and data, so it covers more than a hand-written suite would.
Second, a real-world proof. Home Assistant’s own config_validation test suite
runs against Probatio through the compatibility shim, and it passes. That suite
exercises voluptuous hard across a large, real configuration surface, so it is a
strong signal that the drop-in promise holds in practice.
Where to next
Section titled “Where to next”- Migrating from voluptuous: the step-by-step swap.
- API reference: the full public surface.