📏 simplibs-rules
Composable, explicit validation rules — pure predicates, zero transformation.
A lightweight, high-performance Python library defining atomic, composable validation
rules built on a clean Rule abstraction. Rules combine naturally with plain
Python operators (|, &, ~), carry structured, human-readable diagnostics
on failure via simplibs-exception, and — through integrated type
decomposition — understand Python's typing annotations natively.
from simplibs.rules import is_integer, greater_than, IsTyping
# Operator composition
positive_int = is_integer & greater_than(0)
positive_int.is_valid(5) # -> True
positive_int.is_valid(-5) # -> False
# Type annotation decomposition
rule = IsTyping(list[int] | None)
rule.is_valid([1, 2, 3]) # -> True
rule.is_valid(["1", "2"]) # -> False
simplibs-rulesis the foundational rule engine of the ecosystem. If you just want to validate values in your own code without composing rules by hand, seesimplibs-validate, which buildsvalidate(), ready-madevalidate_*wrappers, and self-validating decorators on top of this library.
🧭 The Core Philosophy
Most validation approaches force a choice: write ad-hoc if/raise checks scattered
through your codebase, or adopt heavy data-transformation frameworks.
simplibs-rules is a pure predicate engine. A Rule never transforms or coerces
a value; it only ever answers "does this value satisfy me?" and, on failure,
provides structured, precise diagnostic exceptions.
Every rule is a small, composable object. You combine them with plain Python operators instead of writing nested configuration or custom functions:
is_string & has_length(min_length=3) & not_blank
By decoupling rule definitions and evaluation logic from validation
invocation wrappers, simplibs-rules serves as the foundational rule engine
for higher-level validation tools — such as simplibs-validate
(the validate() entry point, ready-made wrappers, self-validating decorators) and the
upcoming simplibs-types (reusable, named validated types built on Rule).
📦 Installation
pip install simplibs-rules
🚀 Quick Start in 60 Seconds
Level 1: Evaluating rules directly
from simplibs.rules import is_integer, greater_than
rule = is_integer & greater_than(0)
rule.is_valid(5) # -> True
rule.is_valid(-5) # -> False
# Raising structured exceptions
if not rule.is_valid(-5):
raise rule.build_exception(-5, value_name="age")
Level 2: Rule execution with return options
rule = is_integer & greater_than(0)
rule.validate(5) # -> True
rule.validate(5, return_value=True) # -> 5
rule.validate(-5, return_bool=True) # -> False
rule.validate(-5) # -> raises ValidateError
Level 3: Annotation-driven rules
from simplibs.rules import IsTyping, build_typing_rule
# Converts complex typing constructs directly into rule trees:
typing_rule = IsTyping(dict[str, int])
typing_rule.is_valid({"apples": 5, "oranges": 10}) # -> True
typing_rule.is_valid({"apples": "5"}) # -> False
🛠️ Architecture & Package Structure
src/simplibs/rules/
├── base_class/ ◄── Abstract base class Rule & internal helpers (_NotARule)
│ ├── Rule.py
│ └── _NotARule.py
├── containers/ ◄── Rule combinators (AllOf, AnyOf, Not, NoneOf, ForEach, Compose)
├── predicates/ ◄── Single-purpose predicate rules
│ ├── arithmetic/ ◄── CloseTo, DivisibleBy, HasRemainder
│ ├── checkers/ ◄── IsEmpty, NotEmpty, IsNone, IsTrue, IsFalse
│ ├── collections/ ◄── IsContainer, HasItem, HasKey, HasKeys, AllUnique, IsSubsetOf, IsSupersetOf
│ ├── comparisons/ ◄── Equals, NotEquals, GreaterThan, GreaterOrEqual, LessThan, LessOrEqual, InRange
│ ├── introspection/ ◄── IsInstance, IsType, IsSubclass, IsDataclass, IsCallable, IsHashable, IsIterable, HasAttribute, HasLength
│ ├── logic/ ◄── Is, IsNot, IsIn, NotIn, UserRule
│ ├── numeric/ ◄── IsBool, IsInteger, IsFloat, IsDecimal, IsNumber, IsPrimitiveNumber, IsZero, IsNan, IsInfinity, IsPi
│ ├── strings/ ◄── IsString, Contains, IsSubstringOf, StartsWith, EndsWith, Regex, IsBlank, NotBlank
│ └── typing/ ◄── Annotation-driven evaluation (IsAny, IsTyping, build_typing_rule)
└── testing/ ◄── Testing contracts for Rule implementations
└── assert_rule_contract.py
🧩 The Rule Class
Every rule in this library inherits from Rule. It defines the mandatory
contract (is_valid, build_exception) and provides full composition
capabilities out of the box.
class Rule(ABC):
@abstractmethod
def is_valid(self, value: Any) -> bool:
"""Return True if the tested value satisfies the rule, otherwise False."""
raise NotImplementedError
@abstractmethod
def build_exception(
self,
value: Any,
value_name: str | None = None,
context: str | None = None,
) -> Exception:
"""Create and return a structured exception describing the validation failure."""
raise NotImplementedError
def __call__(self, value: Any) -> bool:
"""Allow using the rule instance directly as a predicate function."""
return self.is_valid(value)
def validate(
self,
value: Any,
*,
value_name: str | None = None,
context: str | None = None,
return_bool: bool = False,
return_value: bool = False,
) -> Any:
"""Validate a value against this rule."""
if self.is_valid(value):
return value if return_value else True
if return_bool:
return False
raise self.build_exception(value, value_name=value_name, context=context)
def annotated(self, type_: type) -> Any:
"""Wrap this rule as `typing.Annotated[type_, self]` for type hints."""
return Annotated[type_, self]
# Operators for logical composition with @overload support for non-rule callables
@overload
def __or__(self, other: _NotARule) -> Any: ...
@overload
def __or__(self, other: Rule | Callable[[Any], bool]) -> Rule: ...
@overload
def __and__(self, other: _NotARule) -> Any: ...
@overload
def __and__(self, other: Rule | Callable[[Any], bool]) -> Rule: ...
def __invert__(self) -> Rule: ...
➡️ Full method-by-method reference: README_RULE_CLASS
📖 Rule Quick Reference
Every built-in Rule is exposed in two ways: as its class (IsInteger),
and as a snake_case shortcut (is_integer) — a pre-instantiated object
for zero-parameter rules, or the class itself for parameterized ones.
Both are fully interchangeable.
containers/ — Composing other rules
| Class | Shortcut | Description / Parameters |
|---|---|---|
AllOf |
all_of |
Logical AND across multiple rules (*rules). Behind &. |
AnyOf |
any_of |
Logical OR across multiple rules (*rules). Behind |. |
Compose |
compose |
Transforms value before checking (transformer, validator). |
ForEach |
for_each |
Validates every item in an iterable (rule). |
NoneOf |
none_of |
Value must satisfy none of the given rules (*rules). |
Not |
negate |
Logical NOT for a single rule (rule). Behind ~. |
predicates/ — Atomic predicates
arithmetic/:CloseTo,DivisibleBy,HasRemainder— READMEcheckers/:IsEmpty,NotEmpty,IsNone,IsTrue,IsFalse— READMEcollections/:IsContainer,HasItem,HasKey,HasKeys,AllUnique,IsSubsetOf,IsSupersetOf— READMEcomparisons/:Equals,NotEquals,GreaterThan,GreaterOrEqual,LessThan,LessOrEqual,InRange— READMEintrospection/:IsInstance,IsType,IsSubclass,IsDataclass,IsCallable,IsHashable,IsIterable,HasAttribute,HasLength— READMElogic/:Is,IsNot,IsIn,NotIn,UserRule— READMEnumeric/:IsBool,IsInteger,IsFloat,IsDecimal,IsNumber,IsPrimitiveNumber,IsZero,IsNan,IsInfinity,IsPi— READMEstrings/:IsString,Contains,IsSubstringOf,StartsWith,EndsWith,Regex,IsBlank,NotBlank— README
predicates/typing/ — Annotation-driven evaluation
| Class / Function | Shortcut | Description |
|---|---|---|
IsAny |
is_any |
Always passes (True). Represents typing.Any. |
IsTyping |
is_typing |
Recursively converts an annotation into a Rule tree. |
build_typing_rule |
- | Functional entry point for annotation decomposition. |
IsTyping recursively decomposes an arbitrary type annotation (list[int], dict[str,
int] | None, Literal[...], Callable[...], ...) into a composed Rule tree — the
mechanism that also powers simplibs-validate's validate_call/validate_dataclass.
➡️ README_RULE_TYPING — the public IsTyping/build_typing_rule entry points
➡️ README_RULE_TYPING_BUILDERS — the internal per-construct decomposition engine
🧪 Testing Utilities
simplibs-rules includes its own contract testing tools to ensure custom or existing Rule
implementations strictly follow all rules and return modes:
assert_rule_contract: Verifies is_valid, direct calling, validate() matrix, return modes, and exception generation consistency for any Rule instance.
➡️ README_TESTING_ASSERTS_RULE_CONTRACT
⚠️ Exceptions
Diagnostic exceptions built by build_exception() are structured
simplibs.exception.SimpleException cards
rather than bare tracebacks. simplibs-validate builds its own ValidateError hierarchy
on top of the same mechanism for the higher-level entry points (validate(),
validate_call, ...).
🔗 Related libraries
simplibs-validate— thevalidate()entry point, ready-made validate_* wrappers, operational tools (raise_invalid, validated_type), and self-validating decorators (validate_call, validate_dataclass) built on top of Rule.simplibs-types(in progress) — reusable, named validated types (Annotated[type, Rule]combinations) built onRule, for sharing a single constraint definition across many 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!
Release files for simplibs-rules 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| simplibs_rules-0.2.1.tar.gz | 115.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| simplibs_rules-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 329.2 kB
Release files / simplibs_rules-0.2.1.tar.gz
| Download URL | simplibs_rules-0.2.1.tar.gz |
|---|---|
| Size | 115.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b359bdca1b8cc0be4057d0135d1829615542f36462158fec84a9c17ff59da8bc
|
|
BLAKE2b-256 checksum How to use checksums |
3bc1fa95c57631834a6e11967583215d0a46fb813bcdcddc0a4051690eab4450
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / simplibs_rules-0.2.1-py3-none-any.whl
| Download URL | simplibs_rules-0.2.1-py3-none-any.whl |
|---|---|
| Size | 213.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
30a681b22703674fe23846b472497664fd2324d9f9eb6d88998d32ba2fd6b5ca
|
|
BLAKE2b-256 checksum How to use checksums |
722d71fcb7129db6bc8c1eead177c2a79278e36ea88a81d9e79c2b36a0a08c4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|