Skip to content

Lancasterg/fast-rules

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fast-rules

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.

Install

uv venv
uv pip install -e ".[dev]"

Python 3.10+. The build compiles a C extension; no third-party runtime dependencies.

Quick start

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).

Rule schema

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

A condition is one of:

  • {"all": [<condition>, ...]} — every child must match (empty all matches)
  • {"any": [<condition>, ...]} — any child must match (empty any does not match)
  • {"not": <condition>}
  • {"fact": "dot.path", "op": "...", "value": ...} — a leaf

Operators

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).

Strict (missing-fact) semantics

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

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.

evaluate(facts, *, match_all=True)

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.

Development

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 installed

Architecture

  • src/c/core/ — pure C engine (no Python.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 borrow PyUnicode_AsUTF8AndSize buffers — 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.

Benchmarking

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
Benchmark results

About

A rules engine library for Python, written in C

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors