Tutorial: First Integration

Embed BlazeRules in a minimal Python service loop that accumulates records into batches, evaluates them, and routes by decision.

Goal: wire BlazeRules into a small Python service that buffers incoming records, evaluates them one batch at a time, and routes each record by its decision.

Prerequisites

  • The blazerules Python module on PYTHONPATH (Installation).
  • A rules.yaml (Rule DSL) — the repo's sample works.

Step 1 — Construct and load once

Create the engine and load rules a single time at startup, not per request.

import blazerules

config = blazerules.EngineConfig()
config.output_detail = blazerules.OutputDetail.DECISIONS

engine = blazerules.RuleEngine(config)
engine.load_rules("rules.yaml")

Step 2 — Accumulate records into a batch

BlazeRules is batch-first. Buffer incoming records (here as NDJSON lines) and flush on a size or time trigger.

import json, time

buffer: list[bytes] = []
FLUSH_SIZE = 4096
last_flush = time.monotonic()

def offer(record: dict) -> None:
    buffer.append(json.dumps(record).encode())

def should_flush() -> bool:
    return len(buffer) >= FLUSH_SIZE or (time.monotonic() - last_flush) > 0.05

Step 3 — Evaluate the batch

Join the buffered lines into one NDJSON payload and evaluate once.

def flush():
    global buffer, last_flush
    if not buffer:
        return None
    payload = b"\n".join(buffer)
    result = engine.evaluate_ndjson(payload)
    buffer = []
    last_flush = time.monotonic()
    return result

Step 4 — Route by decision

Use the decision-group accessors instead of looping over every record in Python.

result = flush()
if result is not None:
    groups = result.grouped_decision_indices()      # {decision_label: [row indices]}
    approved = result.indices_for_decision("APPROVE")
    escalate = result.indices_for_not_decision("APPROVE")

    print("records:", result.n_records, "matched:", result.n_matched)
    for decision, rows in groups.items():
        print(decision, "->", len(rows))

Expected output

For a batch where most records are clean and a few trip a blocking rule, you'll see something like:

records: 4096 matched: 87
APPROVE -> 4009
REVIEW -> 71
BLOCK -> 16

The exact numbers depend on your rules and data; the shape is what matters — one decision label per record, grouped for routing.

Validation

  • result.n_records equals the number of records you flushed.
  • The group counts sum to n_records.
  • result.messages_skipped is 0 for clean input. If it isn't, inspect result.error_counts and result.error_samples.

Failure mode: malformed or mistyped records

Ingest is tolerant by default — bad records are skipped and counted rather than crashing the batch.

config.ingest_error_mode = blazerules.IngestErrorMode.SKIP_AND_COUNT
config.type_mismatch_mode = blazerules.TypeMismatchMode.NULL_ON_TYPE_ERROR

Switch to IngestErrorMode.SKIP_TO_DEAD_LETTER or HARD_FAIL if you need stricter handling. See Error Reference.

Where to go next