Skip to main content

⚖️ simplibs-validate

PyPI Python Licence

The validation layer on top of simplibs-rules — entry points, ready-made validators, and self-validating decorators.

simplibs-rules defines the atomic Rule objects and lets you compose them with |/&/~. simplibs-validate is what turns a rule (or a whole function signature) into an actual validation action: a single validate() call, a batteries-included validate_* wrapper for common types, or a decorator that validates a function or dataclass automatically from its own type hints.

from simplibs.validate import validate
from simplibs.rules import is_integer, greater_than

validate(5, is_integer & greater_than(0))          # -> True
validate(-5, is_integer & greater_than(0))          # -> raises ValidationError

🧭 The Core Philosophy

simplibs-validate doesn't define new predicates of its own — that's simplibs-rules's job. What it adds is the layer people actually reach for day to day: a universal validate() entry point, ready-made validate_* functions for common types so you rarely need to hand-compose a rule tree, and decorators that make an entire function or dataclass self-validating from nothing more than its own type annotations:

from simplibs.validate import validate_call
from simplibs.types import validated_type
from simplibs.rules import greater_than

PositiveInt = validated_type(int, greater_than(0))

@validate_call
def register(age: PositiveInt, *, validate: bool = True) -> None:
    ...

register(25)                       # validated normally
register(-5)                       # raises ValidationError
register(-5, validate=False)       # explicitly skipped — e.g. already validated upstream

That combination — annotation-driven rules from simplibs-rules, named reusable types from simplibs-types, and a decorator that enforces them automatically with a per-call opt-out for code paths that already trust their data — is what lets you build fully self-validating functions and dataclasses from nothing more than their own signatures.


📦 Installation

pip install simplibs-validate

simplibs-rules is installed automatically as a dependency — its rules and operators (is_integer, greater_than, IsTyping, ...) are what you pass into everything below.


🚀 Quick Start in 60 Seconds

Level 1: One-off validation

from simplibs.validate import validate
from simplibs.rules import is_integer, greater_than

validate(5, is_integer & greater_than(0))
validate(5, is_integer & greater_than(0), return_bool=True)   # -> True, no exception
validate("x", is_integer, return_bool=True)                    # -> False, no exception

Level 2: Ready-made validators

Every common type has a batteries-included validate_* function, taking the constraint as plain keyword arguments — no rule composition required:

from simplibs.validate import validate_string, validate_int

validate_string("user@example.com", contains="@", min_length=5)
validate_int(42, greater_than=0, divisible_by=2)

Level 3: Annotation-driven, self-validating functions

from simplibs.validate import validate_call

@validate_call
def create_user(name: str, age: int) -> dict:
    return {"name": name, "age": age}

create_user("Alice", 30)      # validated automatically from the annotations
create_user("Alice", "30")    # raises ValidationError

🛠️ The Architecture: 3 Layers

┌──────────────────────────┐
│      simplibs-rules      │ ◄── Rule subclasses + snake_case shortcuts
└────────────┬─────────────┘
             ▼
┌──────────────────────────┐
│        validators        │ ◄── validate, raise_invalid, validate_string, validate_int, ... 
└────────────┬─────────────┘
             ▼
┌──────────────────────────┐
│    decorators & tools    │ ◄── validate_call, validate_dataclass, log_this, ...
└──────────────────────────┘

1. validate — the universal entry point

Every validation ultimately goes through one of two functions:

  • validate(value, rule, ...) — evaluates rule against value, then either returns (True/the value) or raises, depending on the flags given. Use this everywhere you actually need the check performed.
  • raise_invalid(value, rule, ...) — unconditionally builds and raises the diagnostic exception for rule, without evaluating anything. Use this where your own code has already determined a value is invalid (e.g. inside an if not condition: branch) and you just want the same structured ValidateError card validate() would have produced, without redundantly re-running the check.
def validate(
    value: Any,
    rule: Rule | Callable[[Any], bool],
    *,
    value_name: str | None = None,
    context: str | None = None,
    return_bool: bool = False,
    return_value: bool = False,
) -> Any:

    # 1. Rule instance handling — delegate entirely to Rule.validate()
    if isinstance(rule, Rule):
        return rule.validate(
            value,
            value_name=value_name,
            context=context,
            return_bool=return_bool,
            return_value=return_value,
        )

    # 2. Callable handling (plain function / lambda)
    # 2.1 Validation execution and success handling
    if rule(value):
        return value if return_value else True

    # 2.2 Return bool handling
    if return_bool:
        return False

    # 2.3 Failure handling
    raise build_validation_error(
        rule,
        value,
        value_name=value_name,
        context=context,
    )
def raise_invalid(
    value: Any,
    rule: Rule | Callable[[Any], bool],
    *,
    value_name: str | None = None,
    context: str | None = None,
) -> NoReturn:
  
    # 1. Rule instance handling
    if isinstance(rule, Rule):
        raise rule.build_exception(
            value,
            value_name=value_name,
            context=context,
        )

    # 2. Callable handling (plain function / lambda)
    raise build_validation_error(
        rule,
        value,
        value_name=value_name,
        context=context,
    )

Both accept either a Rule instance or a plain callable predicate — a Rule delegates to its own validate()/build_exception(), while a callable is evaluated directly and, on failure, wrapped in a generic diagnostic via build_validation_error.

2. Specialized validators

For the most common types, a ready-made validate_* function exposes every relevant constraint as a plain keyword argument, composing the equivalent Rule tree internally — no manual &-chaining required for everyday cases.

Validator Description Docs
validate_bool Boolean, optionally against an exact expected value. README_VALIDATE_BOOL
validate_container Any non-string container — length, uniqueness, membership, subset/superset, per-item rule. README_VALIDATE_CONTAINER
validate_float Float — comparisons, range, approximate equality, finiteness. README_VALIDATE_FLOAT
validate_int Integer — comparisons, range, divisibility, remainder. README_VALIDATE_INT
validate_mapping dict — length, single/multiple key membership. README_VALIDATE_MAPPING
validate_number Any number (int/float/Decimal/complex) — comparisons, range, membership. README_VALIDATE_NUMBER
validate_string String — length, prefix/suffix/substring, regex, blankness, membership. README_VALIDATE_STRING
validate_type Class/type object — subclass, identity, membership. README_VALIDATE_TYPE

Each validate_* is a thin wrapper: it composes its matching *_rule(...) factory and calls .validate() on the result. For repeated validation against the same constraints, build the rule once with *_rule(...) and reuse it, instead of calling validate_* inside a loop.


🧰 Tools

Beyond individual rules, the decorators and tools packages provide the convenience layers that make validation part of a function's or dataclass's definition:

🎀 Decorators

The decorators package provides decorators that add validation or structured logging directly to functions and dataclasses:

  • validate_call — validates a function's arguments (and optionally its return value) against its own type annotations, on every call. Supports selective validation (check), extra constraints (overrides), and a per-call bypass switch.
  • validate_dataclass — the @dataclass counterpart: validates every field against its annotation on instance construction, before any field is assigned.
  • log_this — gives any function entry/exit/timing/exception logging, entirely independent of validation, without imposing any logging configuration of its own.

➡️ README_DECORATORS

🛠️ Tools

The tools package provides small helpers used to build reusable validation annotations and the rule mappings consumed by the decorators:

  • validated_type — names a reusable Annotated[type, rule(s)] combination once, for use across multiple annotations.
  • override_rules — batch-builds the overrides= mapping validate_call/ validate_dataclass expect, from keyword arguments.

➡️ README_TOOLS


⚠️ Exceptions

Every exception raised by simplibs-validate is built on top of simplibs.exception.SimpleException — structured, readable diagnostic cards instead of a bare traceback.

The library's single common root is ValidateError:

class ValidateError(SimpleException):
    """Root exception class for all errors originating from simplibs-validate."""
    skip_locations = ("simplibs/validate",)

Two concrete subclasses distinguish what kind of mistake occurred:

Exception When it happens
ParamError A developer error made while constructing a rule or configuring a decorator — e.g. HasLength() with no length source, validate_call(check=("x",)) where x has no annotation or override.
ValidationError An invalid runtime value — the value being checked simply doesn't satisfy the rule. This is the exception you'll encounter during ordinary, everyday use.
try:
    validate(-5, greater_than(0))
except ValidateError as e:
    print(e)   # a structured diagnostic card: what, why, how to fix it

Catching ValidateError catches both categories at once; catching ValidationError or ParamError specifically lets you distinguish "bad input data" from "the validation itself was set up incorrectly." ValidateError.skip_locations also filters the library's own internal frames out of the error's reported location — the message points to your code, not this library's implementation.


🧪 Testing Utilities

simplibs-validate ships with testing infrastructure for the layer it owns — the wrappers, decorators, and validated types, not the underlying atomic rules (see simplibs-rules' assert_rule_contract for that):

  • assert_validate_wrapper — verifies a validate_* convenience function correctly wraps its underlying *_rule factory and delegates properly to Rule.validate() — signature alignment, successful/failed delegation, both return modes.
  • assert_type_contract — master orchestrator that verifies a validated_type()-built construct against the full Rule contract battery (by decomposing it via build_typing_rule) and optionally tests its execution under @validate_call.
  • assert_type_validate_call_integration — specialized integration probe verifying that a custom type annotation is correctly intercepted and enforced when used on parameters of a @validate_call-decorated function.

➡️ README_TESTING_ASSERTS_VALIDATE_WRAPPER
➡️ README_ASSERT_TYPE_CONTRACT
➡️ README_ASSERT_TYPE_VALIDATE_CALL_INTEGRATION


🔭 About the library, from the author's point of view

simplibs-validate used to bundle the rule engine itself; that engine has since moved to its own library, simplibs-rules, so that the atomic predicates can be depended on independently of the higher-level validation entry points and decorators defined here. What remains here is deliberately focused: validate(), the validate_* convenience layer, and the validate_call/validate_dataclass decorators — with room to grow as real-world use shows which additional wrappers or tools are worth adding.


  • simplibs-rules — the Rule base class, operator composition, and every built-in predicate (is_integer, greater_than, IsTyping, ...) used throughout this library.
  • simplibs-types (in progress) — reusable, named validated types (validated_type and friends) built on simplibs-rules, for sharing a single constraint definition across many validate_call/validate_dataclass annotations.

☯️ About simplibs

All libraries in the simplibs (Simple Libraries) ecosystem share a common engineering philosophy:

  • Dyslexia-friendly: We actively minimize cognitive load. Code is atomized into small, self-contained units, files are named directly after the logical task they perform, and explanations describe why something is designed, not just what it is.
  • Programmer's Zen: Nothing should be missing, and nothing should be superfluous. We value clean execution paths and robust, understandable code architectures over rushed, messy feature sets.
  • Defensive Style: We actively anticipate edge cases and failure modes so that only safe operational paths remain. Our code is built to degrade gracefully rather than crash unexpectedly.
  • Minimalism: Find the most direct path to the goal in as few operational steps as possible without taking shortcuts on safety, readability, or completeness.
  • Code as Craft: Code should be pleasant to look at, readable at a glance, and evoke structural harmony. We treat software engineering as a precision trade.

🤝 Contributing & Community

This is an open-source project built with love and care. We strongly believe in community collaboration and welcome any feedback, bug reports, or feature ideas!

  • Want to contribute? Feel free to open an Issue or submit a Pull Request.
  • Want to get in touch? If you'd like to discuss the project further, collaborate, or just say hello, feel free to open a GitHub Issue or start a Discussion.

📝 License

This library is released under the MIT License. Build great things!


▲ Back to Top

Release files for simplibs-validate 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for simplibs-validate 0.2.0
File Size Uploaded
simplibs_validate-0.2.0.tar.gz 70.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for simplibs-validate 0.2.0
File Interpreter ABI Platform
simplibs_validate-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 168.1 kB

Release files / simplibs_validate-0.2.0.tar.gz

Download URL simplibs_validate-0.2.0.tar.gz
Size 70.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1497b4021a408260ae4cb80cef6838863071621376c8206e9476b0056a6f24f2
BLAKE2b-256 checksum
How to use checksums
b18c3d02730ca0f2a36367bbad9ac5db33c06e5cddd0e0c105369a063137c28f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / simplibs_validate-0.2.0-py3-none-any.whl

Download URL simplibs_validate-0.2.0-py3-none-any.whl
Size 97.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1f34e3903e8b39abae626c21ac417169e9fb978c85d2d56dc3cf571d33539d96
BLAKE2b-256 checksum
How to use checksums
d7904bc8cf5618532e86e5fbfe33dbc6b264f64b27a662b6cb3ccf6d71e9da52
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page