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
A FunctionRule's predicate returns a full RuleResult, not a bare
boolean — that keeps it in control of detail/data, not just pass/fail:
import asyncio
from verdict import FunctionRule, AndRule, RuleResult, RulesEngine
async def under_limit(context: dict) -> RuleResult:
return RuleResult(
rule_name="under_limit",
passed=context["requests_this_minute"] < context["limit"],
)
async def in_good_standing(context: dict) -> RuleResult:
return RuleResult(
rule_name="in_good_standing",
passed=context["account_status"] == "active",
)
async def main() -> None:
can_proceed = AndRule(
"can_proceed",
[
FunctionRule("under_limit", under_limit),
FunctionRule("in_good_standing", in_good_standing),
],
)
engine = RulesEngine([can_proceed])
result = await engine.run_named(
"can_proceed",
{"requests_this_minute": 3, "limit": 10, "account_status": "active"},
)
print(result.passed) # True
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 OverEighteen:
name = "over_18"
group = None
async def evaluate(self, context: dict) -> RuleResult:
return RuleResult(rule_name="over_18", passed=context["age"] >= 18)
await AndRule("eligible", [OverEighteen()]).evaluate({"age": 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 rather than catch. 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.6
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.6.tar.gz | 8.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| verdict_rules-0.2.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:17.9 kB
Release files / verdict_rules-0.2.6.tar.gz
| Download URL | verdict_rules-0.2.6.tar.gz |
|---|---|
| Size | 8.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6dc344622724caa57607fa18f872ecdc7ad495dc8e04147341d1dae32e2bae1e
|
|
BLAKE2b-256 checksum How to use checksums |
b5cb24109fbf6680ff02bbba8e7df12af6d19472c79aa43ff8b89e1e2faa32ab
|
| 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 15, 2026.
Transparency logRelease files / verdict_rules-0.2.6-py3-none-any.whl
| Download URL | verdict_rules-0.2.6-py3-none-any.whl |
|---|---|
| Size | 9.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2f77fa0fc5672bf49c0248898c66689b18ef2377ed969ce616630618c7590a40
|
|
BLAKE2b-256 checksum How to use checksums |
98f10b30ddb5e09c9321cfe460131969fc0535097e85b1a6bce1215ff7b59403
|
| 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 15, 2026.
Transparency log