A blazing-fast JSON-driven rules engine for Python, written in optimised C using OpenMP.
Rules are production rules in the json-rules-engine style: each rule has a condition tree (all / any / not / leaf comparisons) and an event payload that's returned on match.
uv venv
uv pip install -e ".[dev]"Python 3.10+. The build compiles a C extension; no third-party runtime dependencies.
from rules_engine import Engine
rules = """
[
{
"name": "adult-us",
"priority": 10,
"conditions": {
"all": [
{"fact": "user.age", "op": ">=", "value": 18},
{"fact": "user.country", "op": "==", "value": "US"}
]
},
"event": {"type": "grant-access", "params": {"tier": "full"}}
}
]
"""
engine = Engine.from_json(rules)
events = engine.evaluate({"user": {"age": 21, "country": "US"}})
# [{"type": "grant-access", "params": {"tier": "full"}}]evaluate returns matched events in priority order (lower priority first; insertion order breaks ties). An engine is immutable; concurrent evaluate() calls from multiple Python threads are safe (the GIL is released during evaluation).
A condition is one of:
{"all": [<condition>, ...]}— every child must match (emptyallmatches){"any": [<condition>, ...]}— any child must match (emptyanydoes not match){"not": <condition>}{"fact": "dot.path", "op": "...", "value": ...}— a leaf
| Op | Fact type | Value type |
|---|---|---|
==, != |
any | any |
<, <=, >, >= |
number | number |
in, not_in |
any | array |
contains |
string or array | scalar |
starts_with, ends_with |
string | string |
Type mismatches at evaluation time on a present fact silently return false (matching json-rules-engine).
Evaluation is always strict: a leaf that references a fact path missing from the input evaluates to MISSING, not false. MISSING propagates: not MISSING = MISSING, all of partly-missing children is MISSING (unless something is already false), and any is MISSING only when no child is true. A rule matches only when its top-level condition is true, so not <missing> does not match.
Facts are a plain dict. Nested dicts are accessed with dot-paths (user.address.city). Lists are first-class fact values for in / contains. Array index segments (orders.0.total) are not supported in v1 — they raise ValueError at parse time.
match_all=True (default) returns every matched event, sorted highest priority first with insertion order as a stable tie-break.
match_all=False returns only the matches that share the lowest priority value among matching rules — useful when priority is interpreted as "rank" (priority 1 = most specific). With matches at priorities 1, 5, 10, this returns just the priority-1 event; with matches at 5, 5, 10, it returns both priority-5 events in insertion order.
make test # C core tests + Python tests
make test-c # C core only
make test-py # Python only
make asan # C core under AddressSanitizer/UBSan
make valgrind # if valgrind is installedsrc/c/core/— pure C engine (noPython.h): JSON-to-rule-tree parser, dot-path fact table, operator dispatch, condition tree evaluator. Usable as a standalone C library.src/c/ext/module.c— CPython wrapper. Marshals Python dicts into the C fact table (string values borrowPyUnicode_AsUTF8AndSizebuffers — no copies), releases the GIL during evaluation, and maps matched rule indices to pre-parsed Python event dicts.vendor/cjson/— vendored cJSON (MIT) for rule-JSON parsing.
This package runs 96.76x faster than a native Python implementation.
| Implementation | Best Total Time 1000 calls (s) | Average Time per Call (μs) |
|---|---|---|
| OpenMP C-accelerated | 0.1129 | 112.909 |
| Serial C-accelerated | 0.5250 | 524.974 |
| Native Python | 10.9253 | 10925.297 |
![]() |

{ "name": "string", // required "priority": 10, // optional, default 0 "conditions": { /* condition */ }, "event": { /* any JSON object, returned on match */ } }