Skip to content

voluptuous compatibility

The drop-in promise is the point of Probatio. This page makes it checkable. Below is voluptuous’s meaningful public surface, name by name, with its status in Probatio and a short note. Every name in voluptuous 0.16.0’s public surface was checked against Probatio with hasattr(probatio, name), not from memory.

The status column uses three values:

  • Supported: same name, same behavior.
  • Supported, with deviation: present and compatible, but Probatio behaves differently in one documented way. Each one is a deliberate improvement.
  • Not mirrored: Probatio does not expose this name. For everything on this page that is “not mirrored”, the reason is that the name is not part of voluptuous’s intended public API. See What Probatio does not mirror.

For the narrative version, read Migrating from voluptuous and Compatibility.

Name Status Notes
Schema Supported Same calling convention, including positional required/extra.
Schemable Supported The type alias for anything Schema accepts.
ALLOW_EXTRA Supported Extra-key policy constant.
PREVENT_EXTRA Supported The default extra-key policy.
REMOVE_EXTRA Supported Extra-key policy constant.
UNDEFINED Supported The “no value” sentinel for marker defaults.
Undefined Supported The type of UNDEFINED.
default_factory Supported Wraps a callable as a marker default factory.
Name Status Notes
Marker Supported Base class for all markers.
Required Supported The key must be present.
Optional Supported The key may be present; default fills it in.
Remove Supported Drop matching keys from the output.
Extra Supported The catch-all key.
Inclusive Supported, with deviation Keys in a group appear together or not at all. See Intentional deviations.
Exclusive Supported, with deviation At most one key from a group. See Intentional deviations.
Self Supported, with deviation Recursive schema reference. See Intentional deviations.
Name Status Notes
All Supported Every validator must pass, output feeding the next.
Any Supported First accepting validator wins.
And Supported Alias of All.
Or Supported Alias of Any.
Union Supported Like Any, with an optional discriminant.
Switch Supported Alias of Union.
SomeOf Supported Pass between min_valid and max_valid of the validators.
Name Status Notes
Coerce Supported Convert with type(value).
Boolean Supported Factory (Boolean()), as in voluptuous. Reads truthy/falsy strings as bool.
Literal Supported Require equality with a literal.
Equal Supported Require equality with a target.
In Supported Membership in a container.
NotIn Supported Non-membership in a container.
Contains Supported A collection must contain an item.
Match Supported Match a regular expression.
Replace Supported Replace regex matches in a string.
Range Supported Numeric bounds, inclusive by default.
Clamp Supported Pin a value into a range.
Number Supported Validate a numeric string, optionally precision and scale.
Length Supported Bound the length of a sized value.
Unique Supported Require distinct items.
Set Supported Convert an iterable into a set.
ExactSequence Supported Validate a fixed-length sequence by position.
Unordered Supported Validate a sequence in any order.
Object Supported Validate an object’s attributes like a mapping.
Maybe Supported Allow None, otherwise validate.
Lower Supported Lowercase transform.
Upper Supported Uppercase transform.
Capitalize Supported Capitalize transform.
Title Supported Title-case transform.
Strip Supported Whitespace-strip transform.
Email Supported Backtracking-safe email format check.
Url Supported Backtracking-safe URL format check.
FqdnUrl Supported URL with a fully qualified domain.
Datetime Supported Validate a datetime string (default %Y-%m-%dT%H:%M:%S.%fZ).
Date Supported Validate a date string (default %Y-%m-%d).
IsDir Supported An existing directory path.
IsFile Supported An existing file path.
PathExists Supported A path that exists.
IsTrue Supported The value must be truthy.
IsFalse Supported The value must be falsy.
DefaultTo Supported Replace None with a default.
SetTo Supported Always produce a fixed value.
Msg Supported Replace a validator’s failure message.
Name Status Notes
Error Supported Base for everything Probatio raises.
SchemaError Supported The schema definition itself is invalid.
Invalid Supported A single validation failure, with a path.
MultipleInvalid Supported, with deviation Collects several errors and proxies the first. See note below.
RequiredFieldInvalid Supported Semantic subclass of Invalid.
ObjectInvalid Supported Semantic subclass of Invalid.
DictInvalid Supported Semantic subclass of Invalid.
SequenceTypeInvalid Supported Semantic subclass of Invalid.
TypeInvalid Supported Semantic subclass of Invalid.
ValueInvalid Supported Semantic subclass of Invalid.
ScalarInvalid Supported Semantic subclass of Invalid.
LiteralInvalid Supported Semantic subclass of Invalid.
CoerceInvalid Supported Semantic subclass of Invalid.
AnyInvalid Supported Semantic subclass of Invalid.
AllInvalid Supported Semantic subclass of Invalid.
MatchInvalid Supported Semantic subclass of Invalid.
RangeInvalid Supported Semantic subclass of Invalid.
LengthInvalid Supported Semantic subclass of Invalid.
InInvalid Supported Semantic subclass of Invalid.
NotInInvalid Supported Semantic subclass of Invalid.
ContainsInvalid Supported Semantic subclass of Invalid.
ExactSequenceInvalid Supported Semantic subclass of Invalid.
ExclusiveInvalid Supported Semantic subclass of Invalid.
InclusiveInvalid Supported Semantic subclass of Invalid.
TrueInvalid Supported Semantic subclass of Invalid.
FalseInvalid Supported Semantic subclass of Invalid.
BooleanInvalid Supported Semantic subclass of Invalid.
UrlInvalid Supported Semantic subclass of Invalid.
EmailInvalid Supported Semantic subclass of Invalid.
DirInvalid Supported Semantic subclass of Invalid.
FileInvalid Supported Semantic subclass of Invalid.
PathInvalid Supported Semantic subclass of Invalid.
DatetimeInvalid Supported Semantic subclass of Invalid.
DateInvalid Supported Semantic subclass of Invalid.
NotEnoughValid Supported Raised by SomeOf.
TooManyValid Supported Raised by SomeOf.
Name Status Notes
validate Supported Decorator that validates a function’s arguments and __return__.
message Supported Decorator turning a ValueError-raising function into a configurable validator factory.
truth Supported Decorator turning a predicate into a validator that returns the value when truthy.
raises Supported Context manager asserting a block raises a given exception.
humanize_error Supported In probatio.humanize. Render an error against the data.
validate_with_humanized_errors Supported In probatio.humanize. Validate, raising a humanized message.

These names and options are not in voluptuous; they are Probatio additions, so they do not affect the drop-in promise (existing code keeps working). Each carries forward an upstream request.

  • Forbidden: a marker requiring a key to be absent ({Forbidden("password"): object}). Carries forward issue #193.
  • TaggedUnion: a discriminated union that routes on one key’s value to the matching schema (TaggedUnion("type", {"grid": ..., "solar": ...})), reporting the chosen branch’s own errors and naming the valid tags on a miss. Cases are a {tag: schema} mapping, or a list of branches that each pin the tag as a literal (read once from the branch); the routing table is built once either way. The readable form of a Union with a hand-written discriminant, and the direct equivalent of Home Assistant’s cv.key_value_schemas.
  • Alias: a key marker accepting a value under one or more alias names and emitting it under a canonical name ({Alias("user_name", "user-name"): str}). Multiple aliases, first-present-wins by declaration order, optional strict rename. voluptuous has no equivalent.
  • DataclassSchema, create_dataclass_schema, is_dataclass: build a schema from a dataclass, validate a mapping, and construct an instance. Carries forward draft PR #533.
  • TypedDictSchema, create_typeddict_schema: build a schema from a TypedDict and return the validated mapping typed as that TypedDict (nothing constructed, since a TypedDict is a plain dict at runtime). voluptuous has no equivalent.
  • Exclusive(..., required=True): an exclusive group must hold exactly one key (an empty group is an error). Carries forward issue #115.
  • Exclusive(..., default=...): fill a group member when the exclusive group is empty. Carries forward issue #245.
  • IPv4Address, IPv6Address, IPAddress, IPNetwork, MacAddress, UUID, Hostname, Fqdn: network and identifier validators; they validate and return the value unchanged (wrap with Coerce for the parsed object). NormalizeMacAddress returns a MAC in canonical form, and Port returns an int. Common across ecosystems, reinvented across Home Assistant and ESPHome.
  • Time, Duration, TimeZoneInfo, TimeZone: time-of-day (the sibling of Date/Datetime), duration validation, IANA zone validation, and UTC offset validation, each returning the value unchanged. Time is voluptuous issue #335; the others recur across ecosystems.
  • AsDatetime, AsDate, AsTime, AsTimedelta, AsTimezone: the object-returning siblings of Datetime/Date/Time/Duration/TimeZone, parsing to a datetime/date/time/timedelta/datetime.timezone instead of validating and passing the value through. The date and time ones parse ISO 8601 by default (standard library only, for deterministic results), or a strptime format; AsDatetime can require a timezone-aware result. (TimeZoneInfo has no As* sibling: Coerce(zoneinfo.ZoneInfo) gives the object, since the constructor takes the name.)
  • FromEpoch: parse a Unix timestamp (seconds or milliseconds) into a timezone-aware UTC datetime. Common in APIs and device payloads.
  • EnsureList, Slug, Positive, Negative, NonNegative, MultipleOf, Percentage, FromPercentage: list-wrapping, slug format, sign conveniences, integer-multiple, and a 0 to 100 percentage (Percentage validates and returns the value; FromPercentage parses it to a float). Common config helpers.
  • Secret: a key marker that redacts the key’s value from validation error output (<redacted> instead of the value), so a rejected credential does not leak into rendered validation error output. Composes with the presence markers by nesting (Optional(Secret("password"))). Voluptuous has no equivalent.
  • NonEmpty, Byte, SmallFloat, IsRegex: a non-empty check, 0 to 255 and 0 to 1 bounded numbers, and a “value is a compilable regex” check.
  • Multiply, Divide, Offset, Round, RoundUp, RoundDown, Snap, Abs, Modulo, Remap, Scale: arithmetic mutators for the small calculations common on device and sensor fields (a gain, a unit divide, a shift, rounding up/down or to a nearest step, a magnitude, a modulo wrap-around, a range remap like the Arduino map(), or the whole affine transform in one Scale). Readable, composable stand-ins for Coerce(lambda ...).
  • CollapseWhitespace, RemovePrefix, RemoveSuffix, Truncate: string normalizers beyond the plain transforms (squeeze internal whitespace, strip a known prefix or suffix, cap the length). Unlike Lower/Strip, they reject a non-string rather than coercing it.
  • Map, EmptyToNone: Map translates a value through a table you supply (a device status code to a name), the bring-your-own-mapping answer to a domain conversion so probatio never picks a disputed table; EmptyToNone turns an empty string or container into None (the “empty means unset” shape), the near-reverse of DefaultTo.
  • Split, Join, Sort, Dedupe, First, Last, Without: collection shapers. Split/Join convert between a delimited string and a list; Sort and Dedupe are the doing siblings of the Sorted and Unique validators; First/Last pick an element; Without prunes listed values (so Without(None) clears the holes).
  • JSONString, FromJSONString: validate a JSON string (and optionally the decoded value against an inner schema); JSONString returns the string unchanged, while FromJSONString returns the decoded value.
  • IsSymlink, IsSocket, IsFifo, IsBlockDevice: filesystem predicates for the special file types, alongside IsDir/IsFile.
  • Alpha, Alphanumeric, ASCII, PrintableASCII, NoWhitespace, StartsWith, EndsWith, ByteLength, HexColor: character-class and affix string checks, UTF-8 byte length, and a hex color.
  • Base64, Hex, Sorted, ULID, Latitude, Longitude: base64/hex validation, an ascending-order check, a ULID, and geographic coordinate bounds.
  • CreditCard, IBAN, DataURI, E164: format and checksum validators (Luhn, ISO 13616 mod-97, RFC 2397 data URIs, international phone format). Pure checks, no network or extra dependency. voluptuous has none of these.
  • RequiredWith, RequiredWithout, RequiredIf, Check: cross-field rules over a whole mapping (used after a dict schema with All), for conditional requirements (on a key’s presence, absence, or value, combined with an any/all mode) and an arbitrary predicate. voluptuous has no equivalent.
  • AtLeastOne, AtMostOne, ExactlyOne, AllOrNone: key-group presence rules over a mapping, for how many of a set of keys may or must appear. The dict-level form of Inclusive/Exclusive. They reject a non-mapping by default (require_mapping=False to opt out). Home Assistant and ESPHome roll their own has_at_least_one_key/has_none_or_all_keys; voluptuous has no equivalent.
  • Immutable, WriteOnce: transition rules that compare new data against its previous value (passed as the call’s context), rejecting a change to an immutable field or a second write to a write-once one. voluptuous has no equivalent.
  • ExtraKeysInvalid with .candidates: an unknown key under PREVENT_EXTRA reports the closest known keys (“did you mean …?”) and carries them on the error, instead of a bare “extra keys not allowed”. ESPHome forks voluptuous internals for this; first-class here, so any consumer gets the suggestion.
  • __probatio_validate__: a protocol a type defines (a classmethod) to validate a raw value of itself, used in place of the bare isinstance check when that type is a schema. EnumInvalid is the error its built-in consumer (an enum class) raises. voluptuous has no such hook.
  • current_context with schema(data, context=...): an optional call argument that hands per-call state to validators that read current_context(), so one compiled schema validates against state known only at call time. Additive (a plain schema(data) is unchanged). voluptuous has no equivalent.

Compatibility is the target, so this list is short. Each entry is a deliberate improvement over a sharp edge, not a regression.

Each entry reads the same way: the behavior, then what voluptuous does, then what Probatio does.

  • An int in a float schema (Schema(float)(5), ADR-017). voluptuous validates float by isinstance, so an int is rejected. Probatio honors the PEP 484 numeric tower: an int is a valid float value, so it is accepted and normalized to float (5 becomes 5.0), while bool, a string, and None are still rejected. This aligns the validator with the {"type": "number"} that to_json_schema already emits. A strict widening: only a value voluptuous would have rejected is affected, and a float matcher now matches an int too (so Remove(float) in a list drops ints, and a {float: ...} type key matches an int key).
  • A validator as the key of an Inclusive/Exclusive group. A group member is usually a literal key (Inclusive("host", "server")), but the marker accepts any key schema, including an Any of names (Inclusive(Any("hours", "minutes"), "duration")). voluptuous never counts such a member as present, which leaves Inclusive unsatisfiable (no input containing another member of the group is ever accepted, whatever the Any names hold) and Exclusive a silent no-op (the member never triggers the exclusion). Probatio treats the key as one member satisfied by any of the names it lists, so Inclusive(Any("hours", "minutes"), "d") with Inclusive("name", "d") accepts {"hours": 1, "name": "tea"} and rejects either one alone. A strict improvement: the voluptuous readings make the group either impossible or meaningless.
  • The rendered error string, str(error) (ADR-015). voluptuous renders expected int for dictionary value @ data['server']['port']. Probatio renders the same error as expected int at 'server.port': the path is a dotted trail (sequence indices as [n], so at 'hosts[2].name'), and the error-type clause is not rendered. A non-mapping is rejected as “expected a mapping” instead of “expected a dictionary” (the engine accepts any Mapping, and the message now says so). The attributes are unchanged: path, msg, error_message, and error_type keep their voluptuous semantics, so code that reads them (rather than parsing the rendered string) is unaffected. Code that string-matches str(error) should switch to path and error_message, or to as_dict().
  • Reworded default messages (ADR-015). A handful of voluptuous default messages were broken or developer-speak; Probatio words them for the person reading them. Literal reads expected {lit} instead of {value} not match for {lit}. Unordered reads “expected a sequence”, “expected a sequence of N items”, and “item N (…) does not match any validator” instead of “Value … is not sequence!”, “List lengths differ, value:N != target:M”, and “Element #N (…) is not valid against any validator”. The filesystem predicates lose their stray capitals (“not a directory”, not “Not a directory”). Boolean reads “expected a boolean”. Classes, paths, and translation keys are unaffected; only the sentence changed.
  • Recursive Self on cyclic or very deep data. voluptuous crashes with RecursionError. Probatio raises a clean Invalid with a path, caught with the rest of your errors. The depth limit tracks a fraction of the interpreter’s recursion limit, so raising sys.setrecursionlimit allows deeper data, up to the point where the operating system’s own stack is the real ceiling; past that a very high limit can still overflow, so keep the limit within reason.
  • from_json_schema on untrusted input. voluptuous can hang or overflow the stack. Probatio refuses a catastrophically backtracking pattern and a pathologically deep document with SchemaError.
  • MultipleInvalid.error_type. voluptuous has no such attribute. Probatio proxies the first error’s type. Additive, so existing code is unaffected.
  • Comparing a Remove marker (Remove("b") == "b"). voluptuous compares a Remove to the key underneath it, so that is True, while hashing it by identity, so equal markers hash differently and the hash/equality contract is broken. Probatio both compares and hashes Remove by identity, so it is False. This covers every direct == against a Remove, whatever is on the other side: a bare key, or another marker for the same key (Remove("b") == Required("b")). Schema behavior is unaffected: a Remove still replaces the key it shadows when merged with extend, and several Remove markers still coexist in one schema. Every other marker compares to its underlying key the way voluptuous does.
  • Missing complex required key (Required(Any("a", "b"))). voluptuous reports it twice (the “at least one of […]” error plus a redundant “required key not provided”). Probatio reports the single meaningful error; the first error matches voluptuous.
  • Non-dict Mapping input (a MappingProxyType, a multidict, a custom mapping). voluptuous rejects it with “expected a dictionary”. Probatio validates any object implementing the Mapping protocol and returns a plain dict. A genuine dict subclass, on the other hand, is preserved as its own class, the same as voluptuous, so a NodeDictClass and similar metadata-carrying subclasses survive validation. A strict superset, so existing dict code is unaffected.
  • A callable validator raising ValueError("reason"). voluptuous reports a bare “not a valid value”, dropping the reason. Probatio appends it: “not a valid value: reason”. A ValueError with no message stays bare.
  • An enum member as a schema or mapping key ({Color.RED: str}). voluptuous rejects it with SchemaError (added upstream in PR #537, not yet released). Probatio accepts it: a member is matched by equality as a scalar, and works as a literal key, with Required/Optional, and as a value.
  • An enum class as a schema (Schema(Color)). voluptuous treats it as a type and validates by isinstance, so it accepts only an already-built member and rejects the member’s value. Probatio accepts a member unchanged or any value that maps to one, returning the member, so the string a loader hands you ("red") validates and you get Color.RED back. A strict widening: input that was valid before still is, and the rejection now carries the list of valid values. Acceptance is whatever the enum’s own constructor allows, so an IntEnum takes a numeric equivalent (1.0, True) and an IntFlag builds a combined value from any integer (Schema(Perm)(3) is Perm.R | Perm.W), the same as Coerce of that enum. Use an explicit In([...]) when you need to accept only the exact listed members.
  • Coerce(T) on a value already of type T whose constructor rejects it (Coerce(uuid.UUID)(a_uuid)). uuid.UUID(a_uuid) raises, so voluptuous reports a CoerceInvalid. Probatio returns the value unchanged, making Coerce idempotent, so re-validating an already-coerced value does not fail. This only affects a value that voluptuous would have rejected (a value that converts, or is not already a T, is unchanged), so it is a strict widening.
  • A built-in validator on a wrong-typed value (Replace("a", "b")(42), Number()(None)). voluptuous leaks a raw TypeError/ValueError from the underlying call (fixed upstream in PR #540 and PR #539, not yet released). Probatio raises a clean Invalid (a MatchInvalid for Replace, an Invalid for Number).
  • A signaling Decimal (Decimal("sNaN")) compared by a numeric validator (Range, In, Equal, MultipleOf). A signaling NaN raises decimal.InvalidOperation on any comparison, which voluptuous leaks. Probatio catches the ArithmeticError and reports a clean Invalid (the value fails the check), so a crafted value cannot leak a raw exception.
  • A quoted email local part ("a..b"@example.com). voluptuous’s email regex accepts the RFC quoted-string form of the local part; Probatio’s Email does not (it validates the common unquoted form with plain string checks, no backtracking regex). The quoted form is effectively never used in configuration.
  • extend with another Schema (base.extend(other_schema)). voluptuous raises AssertionError (added upstream in PR #538, not yet released). Probatio merges the extension’s keys with its required intent preserved (recursively); its extra must match the result’s, so pass its .schema dict for a raw merge.
  • A list with several failing items ([{"name": str}] against two bad items). voluptuous stops at the first failing nested item, reporting one error (open request, issue #171). Probatio reports every failing item, each with its index in the path. Existing error-iterating code is unaffected.
  • A set schema with a transforming element ({Coerce(int)}). voluptuous returns the set with its elements untransformed (bug, issue #400). Probatio applies the element schema to set items like it does to a list, so a Coerce actually coerces.
  • A failing Any whose branches are all concrete (Any(int, str, None)). voluptuous reports only the first branch (“expected int”) or “not a valid value” (open request, issue #412). Probatio lists every expected branch (“expected int or str or None”) as AnyInvalid. A branch that is an arbitrary validator has no label, so Any still surfaces that branch’s error.
  • An unknown key under PREVENT_EXTRA ({"name": str} given {"nmae": ...}). voluptuous raises a bare Invalid("extra keys not allowed"). Probatio raises ExtraKeysInvalid("not a valid option, did you mean 'name'?"), carrying the close matches on .candidates. The stable code stays extra_keys_not_allowed, so code matching on it is unaffected; only the rendered string changed.
  • Keyword parameter names. Probatio renames a few of voluptuous’s parameters for clarity: Lower, Upper, Capitalize, Title, Strip take value (voluptuous v); Marker/Remove take schema (voluptuous schema_); Msg takes validator (voluptuous schema); DefaultTo takes default (voluptuous default_value). Positional calls are unaffected; only keyword callers using the old names need to update.
  • Inclusive positional argument order. Probatio is Inclusive(key, group, msg=None, default=UNDEFINED, description=None), matching the other markers; voluptuous puts description before default. Pass default and description by keyword to be safe across both.
  • discriminant on All/Any. A tagged-union discriminant is honored on Union (alias Switch), not on All/Any; passing it to All/Any has no effect. Use Union for a discriminated union.
  • An ExactSequence element error carries the element’s index in the path (at '[1]'), where voluptuous reports the bare parent path. Probatio’s path is more precise; existing path-walking code sees a deeper path.
  • The order of multiple missing-required-key errors is the schema’s definition order in Probatio, and hash (set) order in voluptuous. The first error still matches whenever any non-missing error is present; only the ordering among several all-missing keys differs.

These match what the Compatibility and Migrating from voluptuous pages describe. If you hit a difference that is not here, treat it as a bug.

Voluptuous has no __all__, so dir(voluptuous) returns everything the module happens to import: standard-library modules, regex constants, and internal helpers, none of which are part of its intended public API. Probatio defines an explicit __all__ and does not re-export these. Reaching for one of them was already reaching into voluptuous internals.

The notable names, so you are not surprised:

  • Enum: the standard-library enum.Enum, leaked into the namespace. Reach for import enum.
  • Decimal, InvalidOperation: the standard-library decimal types. Reach for from decimal import Decimal.
  • Generator: collections.abc.Generator. Reach for from collections.abc import Generator.
  • collections, datetime, inspect, itertools, os, re, sys, typing, urlparse: imported standard-library modules. Import them yourself.
  • DOMAIN_REGEX, USER_REGEX, primitive_types: internal regex and type constants. Not public; do not depend on them.
  • DefaultFactory, VirtualPathComponent: internal type aliases and helpers. Not public.
  • extra, cache, wraps, contextmanager, basestring: internal helpers and imported builtins. Not public.
  • er: an alias of voluptuous.error. Use the error submodule directly.

None of these are validators or markers. If your code uses one, it is coupled to a voluptuous implementation detail, and you want to import the real thing directly.

The submodules resolve too, so code that imports voluptuous internals has a Probatio counterpart.

voluptuous module Probatio module
voluptuous probatio
voluptuous.error probatio.error
voluptuous.validators probatio.validators
voluptuous.humanize probatio.humanize
voluptuous.schema_builder probatio.schema

For dependencies that import voluptuous internals under the voluptuous name (not Probatio), probatio.compat.install_as_voluptuous() aliases Probatio into sys.modules so every later import voluptuous resolves to probatio. The Compatibility page covers what it registers and how and when to call it.