### Install Probatio with uv, pip, or Poetry Source: https://probatio.frenck.dev/getting-started/installation Install Probatio with your preferred package manager. The core has zero runtime dependencies and no build step. ```bash uv add probatio ``` ```bash pip install probatio ``` ```bash poetry add probatio ``` -------------------------------- ### Install pytest-probatio Source: https://probatio.frenck.dev/guides/testing-with-pytest Install the pytest-probatio package via pip. The plugin registers itself with pytest automatically; no configuration is needed. ```bash pip install pytest-probatio ``` -------------------------------- ### Schema.infer from example data Source: https://probatio.frenck.dev/reference Builds a schema from concrete example data using Schema.infer. Each value becomes its type, recursing into mappings and lists. Keyword arguments pass through to Schema. ```python from probatio import Schema, Required Schema.infer( {"name": "app", "port": 80} ) # == Schema({Required("name"): str, Required("port"): int}) ``` -------------------------------- ### Example schema using probatio imports Source: https://probatio.frenck.dev/getting-started/migrating-from-voluptuous Shows that existing schemas remain unchanged after switching the import. Uses Schema, Required, Optional, All, and Range. ```python from probatio import Schema, Required, Optional, All, Range schema = Schema( { Required("name"): str, Optional("port"): All(int, Range(min=1, max=65535)), } ) ``` -------------------------------- ### Replace the import from voluptuous to probatio Source: https://probatio.frenck.dev/getting-started/migrating-from-voluptuous Shows the one-line import change needed to migrate. The before and after blocks are displayed together as a single example. ```python # Before from voluptuous import Schema, Required, Optional, All, Any, Coerce, Invalid ``` ```python # After from probatio import Schema, Required, Optional, All, Any, Coerce, Invalid ``` -------------------------------- ### Check filesystem paths with IsDir, IsFile, and PathExists Source: https://probatio.frenck.dev/guides/validators Checks filesystem paths with IsDir, IsFile, and PathExists. The example creates a temporary directory and file first because these validators touch the real filesystem. ```python import os import tempfile from probatio import Schema, IsDir, IsFile, PathExists with tempfile.TemporaryDirectory() as path: file_path = os.path.join(path, "config.yaml") open(file_path, "w").close() Schema(IsDir())(path) # an existing directory Schema(IsFile())(file_path) # an existing file Schema(PathExists())(path) # any existing path ``` -------------------------------- ### Round trip: to_json_schema then from_json_schema Source: https://probatio.frenck.dev/guides/json-schema Shows a small round trip: render a schema to JSON Schema, build it back, and validate. The example demonstrates that the validatable shape survives the conversion. ```python from probatio import Schema, Required, to_json_schema, from_json_schema original = Schema({Required("name"): str, Required("age"): int}) rebuilt = from_json_schema(to_json_schema(original)) rebuilt({"name": "Ada", "age": 37}) # {'name': 'Ada', 'age': 37} ``` -------------------------------- ### Lazy schema validation Source: https://probatio.frenck.dev/guides/lazy-building Example of a schema that is registered at startup but only compiles upon its first validation. ```python from probatio import Schema, Required, BuildPolicy, set_build_policy set_build_policy(BuildPolicy.LAZY) # Registered at startup; the walk is deferred. schema = Schema({Required("resource"): str}) # ... much later, only if this command is ever invoked: schema({"resource": "sensor.sun"}) # compiles now, then validates ``` -------------------------------- ### Perform device and sensor arithmetic conversions Source: https://probatio.frenck.dev/recipes/cookbook Examples of using arithmetic mutators like Divide, Scale, Remap, and Map to process raw sensor readings. ```python from probatio import Schema, All, Divide, Scale, Remap, Clamp, Round, Map # Milliunits to units (millivolts to volts, milliseconds to seconds). Schema(Divide(1000))(21500) # 21.5 # A 0..255 byte to a 0..100 percentage. Schema(All(Remap(0, 255, 0, 100), Round(0)))(128) # 50.0 # Celsius to Fahrenheit, the affine transform in one call (value * 9 / 5 + 32). Schema(Scale(9, divisor=5, offset=32))(20) # 68.0 # A device status code to a name (you own the table). Schema(Map({0: "off", 1: "on", 2: "auto"}))(2) # 'auto' ``` -------------------------------- ### Install Probatio as voluptuous with install_as_voluptuous Source: https://probatio.frenck.dev/getting-started/compatibility Call this once, very early in process startup, before anything imports voluptuous. It registers Probatio and a small set of voluptuous-shaped submodules into sys.modules under the voluptuous name, so later imports resolve to Probatio. The call is idempotent but has no teardown, shadows a real voluptuous already imported, and aliases process-wide; it is meant for an application or test harness that owns the process, not for library code. ```python from probatio.compat import install_as_voluptuous install_as_voluptuous() ``` -------------------------------- ### Forbidden key failure example Source: https://probatio.frenck.dev/guides/dict-schemas-and-markers Shows the failure when a forbidden key is present. The validation error is "key not allowed at 'password'". ```python from probatio import Schema, Forbidden schema = Schema({Forbidden("password"): object}) schema({"password": "secret"}) # key not allowed at 'password' ``` -------------------------------- ### Example failure message for schema mismatch Source: https://probatio.frenck.dev/guides/testing-with-pytest Shows the output when a response does not match the schema. The failure names the exact field, even inside a composed list. ```text data does not match the probatio schema (==): users[0].email: expected an email address ``` -------------------------------- ### Enable return validation with returns=True Source: https://probatio.frenck.dev/guides/probatio-decorator Opt in to return validation with `returns=True` to check the result against the `-> R` annotation. The example validates that the return value satisfies `Range(min=0)` and prints 5. ```python from typing import Annotated from probatio import probatio, Range @probatio(returns=True) def clamp(value: int) -> Annotated[int, Range(min=0)]: return value clamp(5) # 5 ``` -------------------------------- ### DefaultTo, EmptyToNone, SetTo examples Source: https://probatio.frenck.dev/guides/validators Shows the behavior of `DefaultTo`, `EmptyToNone`, and `SetTo`. `DefaultTo` replaces `None` with a default; `EmptyToNone` converts empty strings/containers to `None` but leaves falsy scalars like `0` unchanged; `SetTo` always returns a fixed value. ```python from probatio import Schema, DefaultTo, EmptyToNone, SetTo Schema(DefaultTo("fallback"))(None) # 'fallback' Schema(DefaultTo("fallback"))("value") # 'value' Schema(EmptyToNone())("") # None Schema(EmptyToNone())(0) # 0 Schema(SetTo(42))("anything") # 42 ``` -------------------------------- ### Schema.infer Source: https://probatio.frenck.dev/reference The infer class method builds a schema from concrete example data, converting each value to its type and recursing into mappings and lists. ```APIDOC ## Schema.infer ### Description Builds a schema from concrete example data: each value becomes its type, recursing into mappings and lists, so an API response can seed a schema. Keyword arguments pass through to `Schema`. ### Method Class Method ### Endpoint `infer(cls, data, **kwargs) -> Schema` ### Parameters #### Method Parameters - **data** (any) - Required - The example data to infer the schema from. - **kwargs** - Optional - Additional keyword arguments passed to `Schema`. ### Request Example ```python from probatio import Schema, Required Schema.infer( {"name": "app", "port": 80} ) # == Schema({Required("name"): str, Required("port"): int}) ``` ### Response #### Success Response Returns a `Schema` instance inferred from the example data. ``` -------------------------------- ### Create a TypedDict schema with create_typeddict_schema Source: https://probatio.frenck.dev/guides/typeddict Builds a plain Schema from a TypedDict type without the class wrapper. The returned schema validates and returns the dict as-is, as shown by the example output. ```python from typing import TypedDict from probatio import create_typeddict_schema class Config(TypedDict): port: int host: str create_typeddict_schema(Config)({"port": 8080, "host": "nas"}) # {'port': 8080, 'host': 'nas'} ``` -------------------------------- ### from_json_schema: Build a Probatio schema from JSON Schema Source: https://probatio.frenck.dev/guides/json-schema Builds a Schema back from a JSON Schema dictionary. The example shows a JSON Schema with a required string and an optional integer with a minimum, and validates a document against the resulting schema. ```python from probatio import from_json_schema document = { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer", "minimum": 0}, }, "required": ["name"], } schema = from_json_schema(document) schema({"name": "Ada", "age": 37}) # {'name': 'Ada', 'age': 37} ``` -------------------------------- ### Implement custom-serializer hook Source: https://probatio.frenck.dev/guides/field-lists Example of a custom_serializer hook that overrides the default rendering for a Port type while deferring other nodes to the default handler. ```python from probatio import Schema, Required, Port, to_field_list, UNSUPPORTED def render_port(node): if isinstance(node, Port): return {"type": "port"} return UNSUPPORTED schema = Schema({Required("host"): str, Required("port"): Port()}) to_field_list(schema, custom_serializer=render_port) # [{'type': 'string', 'name': 'host', 'required': True}, {'type': 'port', 'name': 'port', 'required': True}] ``` -------------------------------- ### Enforce sequence bounds with All and Length Source: https://probatio.frenck.dev/guides/sequence-schemas Compose All with Length to validate each element and then check the sequence size. The example defines a tags schema that accepts a list of strings with a length between 1 and 3, and shows a successful validation. ```python from probatio import Schema, All, Length tags = Schema(All([str], Length(min=1, max=3))) tags(["a", "b"]) # ['a', 'b'] ``` -------------------------------- ### Access structured error layer with as_dict Source: https://probatio.frenck.dev/guides/error-handling Shows the machine-readable error layer: code, context, translation_key, placeholders, and as_dict(). The example prints the code 'type' and the path from as_dict(). ```python from probatio import Schema, Invalid schema = Schema({"port": int}) try: schema({"port": "nope"}) except Invalid as err: first = err.errors[0] print(first.code) # type print(first.as_dict()["path"]) # ['port'] ``` -------------------------------- ### Combine argument map and return schema Source: https://probatio.frenck.dev/guides/probatio-decorator Combine a map of argument schemas with a return schema in one decorator. The example validates `name` against `Length(min=2)` and the return value against `str`. ```python from probatio import probatio, Length @probatio({"name": Length(min=2)}, returns=str) def shout(name: str) -> str: return name.upper() shout("ada") # 'ADA' ``` -------------------------------- ### Mark all schema keys required with Schema(required=True) Source: https://probatio.frenck.dev/guides/dict-schemas-and-markers Use when you want every key in a schema to be required by default. Individual Optional keys still opt out of the requirement. The example creates a schema where 'a' is required and 'b' is optional, then validates a dictionary with only 'a'. ```python from probatio import Schema, Optional schema = Schema({"a": int, Optional("b"): int}, required=True) schema({"a": 1}) # {'a': 1} ``` -------------------------------- ### Parameterize a validator with a class (AtLeast) Source: https://probatio.frenck.dev/guides/custom-validators Shows an alternative way to parameterize a validator using a class with __call__. The AtLeast class stores the minimum and raises Invalid when the value is below it. The example validates 21 successfully, returning 21. ```python from probatio import Schema, Invalid class AtLeast: def __init__(self, minimum): self.minimum = minimum def __call__(self, value): if value < self.minimum: raise Invalid(f"must be at least {self.minimum}") return value Schema(AtLeast(18))(21) # 21 ``` -------------------------------- ### Catch MultipleInvalid and read first error Source: https://probatio.frenck.dev/reference/errors Shows how to catch a MultipleInvalid and read the first failure's path, code, and context. The schema always raises MultipleInvalid, so you must reach into err.errors[0] to get the individual Invalid. ```python from probatio import Schema, MultipleInvalid schema = Schema({"server": {"ports": [int]}}) try: schema({"server": {"ports": ["nope"]}}) except MultipleInvalid as err: first = err.errors[0] print(first.path) # ['server', 'ports', 1] print(first.code) # type print(first.as_dict()["context"]) # {'expected': 'int'} ``` -------------------------------- ### Fail empty list with All and NonEmpty Source: https://probatio.frenck.dev/guides/sequence-schemas Use All with NonEmpty to reject an empty sequence. The example shows that an empty list fails the bound check, producing the error 'value must not be empty'. ```python from probatio import Schema, All, NonEmpty Schema(All([str], NonEmpty()))([]) # value must not be empty ``` -------------------------------- ### Validate a dict with Schema, Required, and Optional Source: https://probatio.frenck.dev/getting-started/installation Define a minimal schema with required and optional fields, then validate a dict. The optional field gets its default when missing, so the result includes 'port': 8080. ```python from probatio import Schema, Required, Optional schema = Schema( { Required("name"): str, Optional("port", default=8080): int, } ) schema({"name": "app"}) # {'name': 'app', 'port': 8080} ``` -------------------------------- ### Build a schema with create_dataclass_schema and check with is_dataclass Source: https://probatio.frenck.dev/guides/dataclasses Use when you want the schema without a class wrapper. `create_dataclass_schema` builds the same schema as `DataclassSchema`, and `is_dataclass` is the re-exported standard-library check. The example shows both: `is_dataclass(User)` returns `True`, and calling the created schema with a dict returns a `User` instance. ```python from dataclasses import dataclass from probatio import create_dataclass_schema, is_dataclass @dataclass class User: name: str is_dataclass(User) # True create_dataclass_schema(User)({"name": "ada"}) # User(name='ada') ``` -------------------------------- ### Schema with Required and Optional Source: https://probatio.frenck.dev/reference Creates a schema from plain Python types, literals, callables, dicts, lists, tuples, sets, nested Schemas, or validators. The example shows a required string field and an optional integer with a default, and validates a dict that omits the optional key. ```python from probatio import Schema, Required, Optional schema = Schema({Required("name"): str, Optional("port", default=8080): int}) schema({"name": "app"}) # {'name': 'app', 'port': 8080} ``` -------------------------------- ### DataclassSchema with InitVar and init=False field Source: https://probatio.frenck.dev/guides/dataclasses Shows how `init=False` fields are excluded from the schema while `InitVar` fields become schema keys and are validated. The example defines a `Rect` dataclass with an `InitVar` scale and a computed `area` field, then validates a dictionary with `DataclassSchema(Rect)`, producing `Rect(width=2, height=3, area=60)`. ```python from dataclasses import InitVar, dataclass, field from probatio import DataclassSchema @dataclass class Rect: width: int height: int scale: InitVar[int] = 1 area: int = field(init=False, default=0) def __post_init__(self, scale: int) -> None: self.area = self.width * self.height * scale DataclassSchema(Rect)({"width": 2, "height": 3, "scale": 10}) # Rect(width=2, height=3, area=60) ``` -------------------------------- ### Assert exact or regex error message with probatio raises Source: https://probatio.frenck.dev/guides/testing-with-pytest Use the optional second argument to raises to assert the exact str() of the error, or pass regex= to match the message with re.search instead. Both examples expect a MultipleInvalid error for the same invalid input. ```python with raises(MultipleInvalid, "expected int at 'port'"): schema({"port": "nope"}) with raises(MultipleInvalid, regex=r"expected int"): schema({"port": "nope"}) ``` -------------------------------- ### Compose did-you-mean suggestions with context candidates Source: https://probatio.frenck.dev/guides/custom-error-messages Composes a 'did you mean' suggestion from context['candidates'] and appends it to the translated sentence. The example shows a Dutch renderer that joins candidates with ' of ' and falls back to error_message when the translation key is missing. ```python from probatio import Schema, MultipleInvalid DUTCH = { "not_a_valid_option": "geen geldige optie", "did_you_mean": ", bedoelde je {candidates}?", } def render_dutch(error): text = DUTCH.get(error.translation_key) if text is None: return error.error_message text = text.format(**error.placeholders) candidates = error.context.get("candidates") if candidates: joined = " of ".join(repr(name) for name in candidates) text += DUTCH["did_you_mean"].format(candidates=joined) return text schema = Schema({"name": str, "email": str}) try: schema({"nmae": "app"}) except MultipleInvalid as err: print(render_dutch(err.errors[0])) # geen geldige optie, bedoelde je 'name'? ``` -------------------------------- ### Set validator returns a real set with unstable ordering Source: https://probatio.frenck.dev/guides/validators Set returns a real set, so its repr ordering is not stable. The example converts a list with duplicates into a set and then sorts it to get a stable order. ```python from probatio import Schema, Set result = Schema(Set())([1, 2, 2, 3]) sorted(result) # [1, 2, 3] ``` -------------------------------- ### Write a JSON config file Source: https://probatio.frenck.dev/recipes/config-file Creates a sample JSON configuration file on disk, including a string-based port value to demonstrate later coercion. ```python import json from pathlib import Path config = { "name": "my-app", "server": {"host": "localhost", "port": "9000"}, } _ = Path("config.json").write_text(json.dumps(config, indent=2)) ``` -------------------------------- ### Validate extra keys with Extra catch-all Source: https://probatio.frenck.dev/guides/dict-schemas-and-markers Use `{Extra: validator}` to validate every otherwise-unmatched key against a validator. For example, `{Extra: int}` allows any extra key whose value is an integer. Use `{Extra: object}` to allow anything. ```python from probatio import Schema, Extra schema = Schema({"name": str, Extra: int}) schema({"name": "app", "a": 1, "b": 2}) # {'name': 'app', 'a': 1, 'b': 2} ``` -------------------------------- ### Assert schema rejection with probatio raises Source: https://probatio.frenck.dev/guides/testing-with-pytest Use probatio's raises context manager to assert that a schema rejects invalid data. The example imports Schema, MultipleInvalid, and raises from probatio, defines a schema requiring an integer for 'port', and expects a MultipleInvalid error when passing a string. This works with or without pytest because raises comes from the core library. ```python from probatio import Schema, MultipleInvalid, raises schema = Schema({"port": int}) with raises(MultipleInvalid): schema({"port": "nope"}) ``` -------------------------------- ### Schema Source: https://probatio.frenck.dev/reference The Schema class is the core validation entry point. It compiles a validation definition once and validates values when called, returning the normalized result or raising MultipleInvalid. It supports required and extra parameters, extend for merging schemas, and infer for building schemas from example data. ```APIDOC ## Schema ### Description Compiles a validation definition once, then validates values when called, returning the normalized result or raising `MultipleInvalid`. `required` and `extra` are positional, matching voluptuous. `extend` returns a new schema with another mapping's keys merged in (nested mappings merge recursively). Two schemas compare equal when their definitions match, regardless of dict key order. ### Method Constructor ### Endpoint `Schema(schema, required=False, extra=PREVENT_EXTRA)` ### Parameters #### Constructor Parameters - **schema** (any) - Required - The validation definition. - **required** (bool) - Optional - Default `False`. Whether keys are required by default. - **extra** (constant) - Optional - Default `PREVENT_EXTRA`. How to handle extra keys. ### Request Example ```python from probatio import Schema, Required, Optional schema = Schema({Required("name"): str, Optional("port", default=8080): int}) schema({"name": "app"}) # {'name': 'app', 'port': 8080} ``` ### Response #### Success Response Returns the normalized result of the validation. #### Error Response Raises `MultipleInvalid` if validation fails. ### Notes - `Schema.infer(data)` builds a schema from concrete example data: each value becomes its type, recursing into mappings and lists, so an API response can seed a schema. Keyword arguments pass through to `Schema`. - A schema is built from plain Python: a type (`int`), a literal (`"on"`), a callable, a `dict`, a `list`/`tuple`/`set`, a nested `Schema`, or any validator below. - Probatio validates parsed Python objects; parsing stays with the caller. Parse your JSON, YAML, or TOML with the library of your choice, then hand the result to the schema (`schema(json.loads(source))`). ``` -------------------------------- ### Validate all keys of a type with Schema({str: int}) Source: https://probatio.frenck.dev/guides/dict-schemas-and-markers Use a type key to validate every key of that type, describing an open mapping like "string keys, integer values". The example validates a dict with string keys and integer values. ```python from probatio import Schema schema = Schema({str: int}) schema({"a": 1, "b": 2}) # {'a': 1, 'b': 2} ``` -------------------------------- ### to_json_schema: Convert a Probatio schema to JSON Schema Source: https://probatio.frenck.dev/guides/json-schema Renders a Probatio schema as a JSON Schema dictionary. The example shows a schema with a required string field and an optional integer field with a default, producing a JSON Schema with additionalProperties set to false and a required list. ```python from probatio import Schema, Required, Optional, to_json_schema schema = Schema({Required("name"): str, Optional("port", default=8080): int}) to_json_schema(schema) # {'type': 'object', 'properties': {'name': {'type': 'string'}, 'port': {'type': 'integer', 'default': 8080}}, 'additionalProperties': False, 'required': ['name']} ``` -------------------------------- ### Using All, Any, and SomeOf combinators Source: https://probatio.frenck.dev/reference Shows how to use the All, Any, and SomeOf combinators with Schema. All applies validators in sequence, Any returns the first successful result, and SomeOf requires a minimum number of validators to pass. The output values are shown as comments. ```python from probatio import All, Any, SomeOf, Range, Schema Schema(All(str, str.strip))(" hi ") # 'hi' Schema(Any(int, str))("a") # 'a' Schema(SomeOf(min_valid=2, validators=[Range(1, 5), int, 3]))(3) # 3 ``` -------------------------------- ### Coerce failure example: Schema(Coerce(int))("not a number") Source: https://probatio.frenck.dev/guides/validators Coerce raises when the value cannot be converted. This example attempts to coerce the string "not a number" to int, which fails and raises CoerceInvalid. ```python from probatio import Schema, Coerce Schema(Coerce(int))("not a number") ``` -------------------------------- ### Home Assistant config schema with probatio as vol Source: https://probatio.frenck.dev/recipes/home-assistant A minimal Home Assistant configuration schema written against the probatio vol namespace. It runs on Probatio as-is and produces the expected default for the optional port. ```python import probatio as vol CONFIG_SCHEMA = vol.Schema( { vol.Required("name"): str, vol.Optional("port", default=8123): int, } ) CONFIG_SCHEMA({"name": "living-room"}) # {'name': 'living-room', 'port': 8123} ``` -------------------------------- ### Define schema with Required and Optional keys Source: https://probatio.frenck.dev/guides/dict-schemas-and-markers Shows how to create a dict schema where 'name' is required and 'nickname' is optional. Unknown keys are rejected by default. ```python from probatio import Schema, Required, Optional schema = Schema( { Required("name"): str, Optional("nickname"): str, } ) schema({"name": "Frenck"}) # {'name': 'Frenck'} ``` -------------------------------- ### Compile-on-AUTO: Schema with All, Coerce, Range Source: https://probatio.frenck.dev/guides/compiled-schemas Build a schema once at module scope and reuse it. Under the AUTO policy, after roughly 50 validations the schema compiles itself into a faster validator; calling it once or a few times keeps it interpreted. The example shows a port schema that coerces to int and enforces a range, then validates two values. ```python from probatio import Schema, All, Coerce, Range # Built once, reused for every value. After enough calls it compiles itself. PORT = Schema(All(Coerce(int), Range(min=1, max=65535))) PORT("443") # 443 PORT(8080) # 8080 ``` -------------------------------- ### Key-group presence rules: AtLeastOne, AtMostOne, ExactlyOne, AllOrNone Source: https://probatio.frenck.dev/guides/validators Key-group presence rules: AtLeastOne requires one or more, AtMostOne allows at most one, ExactlyOne requires exactly one, AllOrNone requires all or none. A non-mapping is rejected by default; pass require_mapping=False to leave it untouched. ```python from probatio import Schema, AtLeastOne, AtMostOne, ExactlyOne, AllOrNone Schema(AtLeastOne("host", "url"))({"host": "nas"}) # unchanged Schema(AtMostOne("include", "exclude"))({"include": 1}) # unchanged Schema(ExactlyOne("token", "password"))({"token": "t"}) # unchanged Schema(AllOrNone("lat", "lon"))({"lat": 1, "lon": 2}) # unchanged ``` -------------------------------- ### Sorted validator requires pre-ordered collection Source: https://probatio.frenck.dev/guides/validators Sorted requires a collection to already be in order; it does not reorder the input. The example validates a sorted list successfully. ```python from probatio import Schema, Sorted Schema(Sorted())([1, 2, 3]) # [1, 2, 3] ``` -------------------------------- ### Schema class definition Source: https://probatio.frenck.dev/reference Shows the public interface of the Schema class. Use it to compile a validation definition once, then call it to validate data. The constructor accepts required and extra as positional arguments, matching voluptuous. extend merges another mapping's keys recursively, and infer builds a schema from example data. ```python class Schema: def __init__(self, schema, required=False, extra=PREVENT_EXTRA): ... def __call__(self, data): ... def extend(self, schema, required=None, extra=None) -> Schema: ... @classmethod def infer(cls, data, **kwargs) -> Schema: ... ``` -------------------------------- ### Run rough benchmark against voluptuous Source: https://probatio.frenck.dev/reference/performance Runs the rough comparison against voluptuous. Prints a small table of per-validation times and ratios. Run it a few times because absolute numbers wander. ```bash just bench ``` -------------------------------- ### Load and validate YAML config Source: https://probatio.frenck.dev/recipes/config-file Demonstrates loading and validating a configuration file using the PyYAML library. ```python from pathlib import Path import yaml # PyYAML result = schema(yaml.safe_load(Path("config.yaml").read_text())) ``` -------------------------------- ### Setting the build policy Source: https://probatio.frenck.dev/guides/lazy-building Configuring the process-wide build policy using set_build_policy. ```python from probatio import BuildPolicy, set_build_policy set_build_policy(BuildPolicy.LAZY) # defer every eligible schema to first use set_build_policy(BuildPolicy.EAGER) # the default: compile at construction ``` -------------------------------- ### Humanize error message with humanize_error Source: https://probatio.frenck.dev/guides/error-handling Renders a human-readable error message that includes the failing value. The example prints 'expected int at 'port'. Got 'nope''. ```python from probatio import Schema, Invalid from probatio.humanize import humanize_error data = {"port": "nope"} schema = Schema({"port": int}) try: schema(data) except Invalid as err: print(humanize_error(data, err)) # expected int at 'port'. Got 'nope' ``` -------------------------------- ### Run cross-library benchmark Source: https://probatio.frenck.dev/reference/performance Runs the cross-library comparison, syncing the extra libraries on first run. Prints a small table of per-validation times and ratios. Run it a few times because absolute numbers wander. ```bash just bench-world ``` -------------------------------- ### Collect multiple errors with MultipleInvalid Source: https://probatio.frenck.dev/guides/error-handling A dict schema collects all failing keys into a MultipleInvalid. The example validates two failing keys and prints the count and each sub-error's path. ```python from probatio import Schema, MultipleInvalid schema = Schema({"a": int, "b": int}) try: schema({"a": "x", "b": "y"}) except MultipleInvalid as err: print(len(err.errors)) # 2 for sub in err.errors: print(sub.path) # ['a'] then ['b'] ``` -------------------------------- ### Catch Invalid base class Source: https://probatio.frenck.dev/guides/error-handling Catches any Probatio validation error by catching Invalid. The example prints the error message 'expected int' when validating 'nope' against an int schema. ```python from probatio import Schema, Invalid schema = Schema(int) try: schema("nope") except Invalid as err: print(err) # expected int ``` -------------------------------- ### Define a tool schema with Probatio and render it via to_json_schema Source: https://probatio.frenck.dev/recipes/llm-tools Define a Probatio schema with Required and Optional markers, then render it to a JSON Schema dict with to_json_schema. The description on a marker flows into the JSON Schema for the model, and the output includes additionalProperties: False, which strict tool use requires. ```python from probatio import Schema, Required, Optional, All, In, Range, to_json_schema get_weather = Schema( { Required("location", description="City name, like 'Amsterdam'"): str, Optional("unit", default="celsius"): In(["celsius", "fahrenheit"]), Optional("days", default=1): All(int, Range(min=1, max=14)), } ) to_json_schema(get_weather) # {'type': 'object', 'properties': {'location': {'type': 'string', 'description': "City name, like 'Amsterdam'"}, 'unit': {'enum': ['celsius', 'fahrenheit'], 'default': 'celsius'}, 'days': {'type': 'integer', 'minimum': 1, 'maximum': 14, 'default': 1}}, 'additionalProperties': False, 'required': ['location']} ``` -------------------------------- ### Coerce config and environment strings Source: https://probatio.frenck.dev/recipes/cookbook Demonstrates using Coerce, Boolean, and All to validate and transform input strings into typed values like integers or booleans. ```python from probatio import Schema, Coerce, Boolean, All, Range port = Schema(All(Coerce(int), Range(min=1, max=65535))) port("9000") # 9000 flag = Schema(Boolean()) flag("yes") # True flag("off") # False ``` -------------------------------- ### Compile once, validate many with Schema and Required Source: https://probatio.frenck.dev/guides/validation-model Builds a schema once with Required and reuses it in a loop. Compilation is the expensive step; repeated calls are cheap. ```python from probatio import Schema, Required schema = Schema({Required("name"): str}) # compiled for record in [{"name": "a"}, {"name": "b"}]: schema(record) # cheap, repeated calls ``` -------------------------------- ### Run dataclass benchmark against mashumaro Source: https://probatio.frenck.dev/reference/performance Runs the dataclass comparison against mashumaro. Prints a small table of per-validation times and ratios. Run it a few times because absolute numbers wander. ```bash just bench-dataclass ``` -------------------------------- ### Load and validate JSON config Source: https://probatio.frenck.dev/recipes/config-file Reads and parses the JSON file, then validates it against the schema, showing how values are coerced and defaults applied. ```python result = schema(json.loads(Path("config.json").read_text())) print(result) # {'name': 'my-app', 'server': {'host': 'localhost', 'port': 9000}, 'log_level': 'info'} ``` -------------------------------- ### Render path with render_path from probatio.error Source: https://probatio.frenck.dev/reference/errors Shows how to render a path the same way str(error) does. Use render_path from probatio.error to keep a consumer's own error output consistent with the library's rendering. ```python from probatio.error import render_path print(render_path(["server", "ports", 1])) # server.ports[1] ``` -------------------------------- ### Catch MultipleInvalid from ValueError Source: https://probatio.frenck.dev/guides/custom-validators Shows the failure path when a validator raises ValueError. The reason is preserved and appended after 'not a valid value: '. The example catches MultipleInvalid and prints the message for an out-of-range port. ```python from probatio import Schema, MultipleInvalid def port(value): number = int(value) if not 0 < number <= 65535: raise ValueError("out of range") return number schema = Schema(port) try: schema("99999") except MultipleInvalid as err: print(err) # not a valid value: out of range ``` -------------------------------- ### Change the import: import voluptuous as vol to import probatio as vol Source: https://probatio.frenck.dev/recipes/home-assistant Replace the voluptuous import with probatio in Home Assistant code. The rest of the file remains unchanged, and every vol. reference works as-is. ```python import voluptuous as vol import probatio as vol ``` -------------------------------- ### Nullable keyword: to_json_schema vs to_openapi Source: https://probatio.frenck.dev/guides/openapi Shows the difference between JSON Schema and OpenAPI 3.0 for a nullable value. `to_openapi` defaults to OpenAPI 3.0, producing `nullable: True`; `to_json_schema` uses an `anyOf` with a null branch. Pass `openapi_version="3.1.0"` to switch to 3.1, which drops `nullable` and uses the JSON Schema `anyOf` form. ```python from probatio import Schema, Maybe, to_json_schema, to_openapi schema = Schema({"nickname": Maybe(str)}) to_json_schema(schema)["properties"]["nickname"] # {'anyOf': [{'type': 'null'}, {'type': 'string'}]} to_openapi(schema)["properties"]["nickname"] # {'type': 'string', 'nullable': True} ``` -------------------------------- ### Object validator rebuilds same-type object Source: https://probatio.frenck.dev/guides/validators Object validates an object's attributes like a mapping and rebuilds an object of the same type after validation. The example defines a Point class and validates its x and y attributes. ```python from probatio import Schema, Object class Point: def __init__(self, x, y): self.x, self.y = x, y result = Schema(Object({"x": int, "y": int}))(Point(1, 2)) result.x # 1 result.y # 2 ``` -------------------------------- ### Opt in per schema with compile flag or Schema.compile() Source: https://probatio.frenck.dev/guides/compiled-schemas Opt a schema into compilation per schema. The compile flag generates on first validation, while Schema.compile() generates immediately and returns the same schema. Use when you know a schema is hot and do not want to wait for warmup. ```python from probatio import Schema, Required, Optional # Opt in at construction (generated on first validation). schema = Schema({Required("name"): str, Optional("age"): int}, compile=True) # Or opt in eagerly, generating now. Returns the same schema. schema = Schema({Required("name"): str, Optional("age"): int}).compile() ``` -------------------------------- ### Catch MultipleInvalid from a custom validator Source: https://probatio.frenck.dev/guides/custom-validators When a custom validator raises Invalid, the message appears inside the usual MultipleInvalid error, pointing at the key that broke. This example shows catching MultipleInvalid and printing the error message. ```python from probatio import Schema, Invalid, Required, MultipleInvalid def even(value): if value % 2 != 0: raise Invalid("must be even") return value schema = Schema({Required("count"): even}) try: schema({"count": 3}) except MultipleInvalid as err: print(err) # must be even at 'count' ``` -------------------------------- ### Compose custom validator with All, Strip, and Lower Source: https://probatio.frenck.dev/guides/custom-validators Shows how to compose a custom validator with built-in normalizers using All. The first block demonstrates a successful validation where Strip and Lower normalize the input before not_empty runs. The second block shows the failure case: a field of only spaces is stripped to an empty string and rejected with 'must not be empty at 'name''. ```python from probatio import Schema, All, Strip, Lower, Invalid, Required def not_empty(value): if not value: raise Invalid("must not be empty") return value schema = Schema({Required("name"): All(Strip, Lower, not_empty)}) schema({"name": " Frenck "}) # {'name': 'frenck'} ``` ```python from probatio import Schema, All, Strip, Lower, Invalid, Required, MultipleInvalid def not_empty(value): if not value: raise Invalid("must not be empty") return value schema = Schema({Required("name"): All(Strip, Lower, not_empty)}) try: schema({"name": " "}) except MultipleInvalid as err: print(err) # must not be empty at 'name' ``` -------------------------------- ### Create a DataclassSchema from a dataclass Source: https://probatio.frenck.dev/guides/dataclasses Builds a schema from a dataclass type. A field without a default is required; a field with a default (or default_factory) is optional and the default fills in when the key is absent. The result is a real User instance, not a dict. ```python from dataclasses import dataclass from probatio import DataclassSchema @dataclass class User: name: str age: int = 18 schema = DataclassSchema(User) schema({"name": "ada"}) # User(name='ada', age=18) ``` -------------------------------- ### Define signup schema and validate a valid body Source: https://probatio.frenck.dev/recipes/web-api Defines a signup schema with required and optional fields, defaults, and nested structures. The second block shows a valid body being normalized with defaults filled in. ```python from probatio import Schema, Required, Optional, All, Length, Range, Email signup = Schema( { Required("username"): All(str, Length(min=3, max=30)), Required("email"): Email(), Required("age"): All(int, Range(min=13)), Optional("newsletter", default=False): bool, Required("address"): { Required("city"): str, Required("postal_code"): str, }, Optional("tags", default=list): [str], } ) ``` ```python body = { "username": "ada", "email": "ada@example.com", "age": 37, "address": {"city": "Delft", "postal_code": "2611"}, } signup(body) # {'username': 'ada', 'email': 'ada@example.com', 'age': 37, 'address': {'city': 'Delft', 'postal_code': '2611'}, 'newsletter': False, 'tags': []} ``` -------------------------------- ### Wrap a predicate with truth Source: https://probatio.frenck.dev/guides/custom-validators Use truth to wrap a predicate (a function returning True/False) into a validator. The predicate passes the value through unchanged when truthy. This example wraps a positive check and shows a successful call returning 5. ```python from probatio import Schema, truth @truth def positive(value): return value > 0 schema = Schema(positive) schema(5) # 5 ``` -------------------------------- ### Loading JSON with orjson Source: https://probatio.frenck.dev/guides/loading-and-dumping Demonstrates swapping the standard library parser for the faster orjson library. ```python import orjson from probatio import Schema, Required schema = Schema({Required("port"): int}) schema(orjson.loads('{"port": 8080}')) # {'port': 8080} ``` -------------------------------- ### Reject a value with Invalid in a custom validator Source: https://probatio.frenck.dev/guides/custom-validators Reject a value by raising Invalid with a message. The message is what the caller sees, and Probatio attaches the path to the offending value. This example validates a dictionary key using a custom validator. ```python from probatio import Schema, Invalid, Required def even(value): if value % 2 != 0: raise Invalid("must be even") return value schema = Schema({Required("count"): even}) schema({"count": 4}) # {'count': 4} ``` -------------------------------- ### Validate async function with Coerce Source: https://probatio.frenck.dev/guides/probatio-decorator Async functions are validated the same way: arguments are checked before the call, and the result schema (if any) runs on the awaited value. The example coerces the string `"41"` to an int and returns 42. ```python import asyncio from typing import Annotated from probatio import probatio, Coerce @probatio async def fetch(user_id: Annotated[int, Coerce(int)]) -> int: return user_id + 1 asyncio.run(fetch("41")) # 42 ``` -------------------------------- ### Validate data with Probatio Schema Source: https://probatio.frenck.dev/project/comparison Demonstrates defining a schema with Required and Optional keys and validating a dictionary against it. ```python from probatio import Schema, Required, Optional schema = Schema({Required("name"): str, Optional("age"): int}) schema({"name": "Frenck", "age": 40}) # {'name': 'Frenck', 'age': 40} ``` -------------------------------- ### Defaults with Optional and callable default Source: https://probatio.frenck.dev/guides/dict-schemas-and-markers Shows how Optional accepts a default value or a callable default. A callable default is invoked each time to produce a fresh list or dict. ```python from probatio import Schema, Optional schema = Schema( { Optional("port", default=8080): int, Optional("tags", default=list): [str], } ) schema({}) # {'port': 8080, 'tags': []} ``` -------------------------------- ### Catch specific error subclass RangeInvalid Source: https://probatio.frenck.dev/guides/error-handling Branches on the specific error subclass. The example validates 99 against a Range(0,10) schema, catches MultipleInvalid, and checks the first error is a RangeInvalid with the message 'value must be at most 10'. ```python from probatio import Schema, Range, MultipleInvalid from probatio.error import RangeInvalid schema = Schema(Range(min=0, max=10)) try: schema(99) except MultipleInvalid as err: first = err.errors[0] print(isinstance(first, RangeInvalid)) # True print(first.error_message) # value must be at most 10 ``` -------------------------------- ### Raise custom error class with Msg cls parameter Source: https://probatio.frenck.dev/guides/custom-validators Pass cls to Msg to raise a custom error subclass on failure. Callers can catch that specific error type; the example prints the class name of the first error. ```python from probatio import Schema, Msg, Match, Invalid, MultipleInvalid class BadName(Invalid): """The name does not look right.""" schema = Schema(Msg(Match(r"^[a-z]+$"), "lowercase letters only", cls=BadName)) try: schema("ABC") except MultipleInvalid as err: print(type(err.errors[0]).__name__) # BadName ``` -------------------------------- ### Add constraints and return validation with @probatio Source: https://probatio.frenck.dev/guides/custom-validators Layer extra rules on top of type annotations with a `constraints` map, and validate the return value against the `-> R` annotation with `returns=True`. Here `Length(min=2)` constrains the `name` parameter, and the result is validated as `str`. The call `greet("ada")` succeeds and returns `'hi ada'`. ```python from probatio import probatio, Length @probatio({"name": Length(min=2)}, returns=True) def greet(name: str) -> str: return "hi " + name greet("ada") # 'hi ada' ``` -------------------------------- ### Report type key failure with Invalid Source: https://probatio.frenck.dev/guides/dict-schemas-and-markers Use when a key fails the type check; the error is reported against that key, the same way voluptuous does. The example shows a non-string key (1) being rejected with the message 'expected str at '[1]''. ```python from probatio import Schema, Invalid schema = Schema({str: int}) try: schema({1: 2}) except Invalid as err: print(err) # expected str at '[1]' ``` -------------------------------- ### Run CodSpeed benchmarks Source: https://probatio.frenck.dev/reference/performance Runs the tracked CodSpeed benchmarks (walltime locally, tracked in CI). Not part of the normal test run. Exercises hot paths interpreted and compiled: validating a config-style mapping, a dataclass, a nested mapping, a composed schema, and a combinator under the automatic compile policy; calling a @probatio-decorated function; compiling a schema from scratch; and the generation step itself. ```bash just codspeed ``` -------------------------------- ### Use Exact and Partial matchers with probatio schema Source: https://probatio.frenck.dev/guides/testing-with-pytest Import Exact and Partial from pytest_probatio and use them with a probatio schema. Exact requires no extra keys under ==, while Partial allows extra keys. The <= operator relaxes Exact to a partial match. ```python from pytest_probatio import Exact, Partial from probatio import Port def test_response(response): # Exact: an extra key makes it unequal. assert response == Exact({"name": str, "port": Port()}) # Partial: extra keys are allowed under ==. assert response == Partial({"name": str}) # The <= operator relaxes Exact to a partial match too. assert Exact({"name": str}) <= response ``` -------------------------------- ### Typed results with DataclassSchema Source: https://probatio.frenck.dev/reference/typing Use DataclassSchema to build a schema from a dataclass and get a typed result. The schema is generic in the dataclass type, so calling it returns a Config instance, not Any. Attribute access like config.port is statically known to be an int. ```python from dataclasses import dataclass from probatio import DataclassSchema @dataclass class Config: name: str port: int = 8080 schema = DataclassSchema(Config) config = schema({"name": "app"}) # config is typed as Config config.port # 8080, and the checker knows .port is an int ```