Verdict — Python
The Python implementation of Verdict — a small, zero-dependency, async-native rule-evaluation engine.
Install
pip install verdict-rules
The distribution on PyPI is named verdict-rules (the name verdict
was already taken by an unrelated package), but the import name is
plain verdict:
from verdict import Rule, FunctionRule, AndRule, OrRule, RulesEngine
A first rule
FunctionRule wraps a plain async predicate — a reusable one can build
several named rules from the same code, each checking a different field
against its own floor:
import asyncio
from verdict import FunctionRule, AndRule, RuleResult, RulesEngine
def at_least(name: str, field: str, floor: float) -> FunctionRule:
async def predicate(context: dict) -> RuleResult:
value = context[field]
return RuleResult(rule_name=name, passed=value >= floor, detail=f"{value} vs {floor}")
return FunctionRule(name, predicate)
async def main() -> None:
eligible = AndRule("eligible", [
at_least("age_ok", "age", 18),
at_least("score_ok", "score", 60),
])
engine = RulesEngine([eligible])
verdict = await engine.run_named("eligible", {"age": 21, "score": 55})
print(verdict.passed) # False
print(verdict.detail) # "'score_ok' failed: 55 vs 60"
asyncio.run(main())
If it has the shape, it is a rule
Rule is a Protocol, and Python's typing is structural — so any
object with the right attributes and an evaluate coroutine already
is a Rule. No inheritance, no registration:
class IsBusinessHours:
name = "is_business_hours"
group = None
async def evaluate(self, context: dict) -> RuleResult:
hour = context["hour"]
return RuleResult(rule_name="is_business_hours", passed=9 <= hour < 17)
await AndRule("open", [IsBusinessHours()]).evaluate({"hour": 21})
Most rules need no class at all either: FunctionRule wraps a plain
async function.
What it guarantees
- Sequential evaluation, never concurrent. Composites use a plain
forloop withawait, neverasyncio.gather. Short-circuiting only means something if later work never starts — and because the returned boolean is identical either way, getting this wrong is silent. - Vacuous truth has a polarity.
AndRule([])passes,OrRule([])fails. Deliberately asymmetric. - Emptiness is not absence. An empty composite folds to its
identity; an unknown rule name or group raises
KeyError. A group exists only because some rule declared it, so a lookup matching nothing can only be a mistake — and a misspelled group silently approving is the worst failure an eligibility check can have. Userule_names/group_namesto check membership, ortry_run_named/try_run_groupwhere your own domain has an answer for absence — both returnNoneinstead of raising. RuleResult.datais opaque — only what actually ran, never padded, never flattened.- Zero runtime dependencies.
Where to go next
| Doc | For |
|---|---|
docs/quickstart.md |
The quickstart — core concepts and a full worked example |
docs/architecture/ |
Why it's shaped this way, in depth — type structure, the execution model |
docs/extending/ |
Building on top of it from your own code, with no changes here |
docs/maintenance/ |
Changing this package itself |
docs/testing/ |
How the test suite is organized, and what a change needs to prove |
docs/samples/ |
Worked examples — dynamic discounts, fee waivers, tier promotions, moderation routing, data-driven rule sets |
examples/ |
Full, tested mini-projects behind the more comprehensive samples — real code, real tests, real docs |
Release files for verdict-rules 0.2.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| verdict_rules-0.2.8.tar.gz | 8.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| verdict_rules-0.2.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:18.1 kB
Release files / verdict_rules-0.2.8.tar.gz
| Download URL | verdict_rules-0.2.8.tar.gz |
|---|---|
| Size | 8.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f50e034fd06387c274fb65278dd350785c14027279484c187919e2851a6d6bb0
|
|
BLAKE2b-256 checksum How to use checksums |
8762ae1ea34564c637e2ce1e68a26a547b5203967ce210148587d9ae8a09e9ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / verdict_rules-0.2.8-py3-none-any.whl
| Download URL | verdict_rules-0.2.8-py3-none-any.whl |
|---|---|
| Size | 9.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
551348e3e64b0f4d58e8611f4eeed1630fc1fec18be85a835bd8a3e7ece4f38a
|
|
BLAKE2b-256 checksum How to use checksums |
bdd06a800ae34a58c2389ce02ef5042fe4917e755927d63d2bad1776e85df8d5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency log