Recipe: Binary Format Ingestion
Use blazerules_io decoders for binary event formats without converting through JSON.
Binary decoders produce Arrow RecordBatch objects and feed evaluate_batch. They do not convert through JSON.
Arrow IPC
import blazerules
import blazerules_io
engine = blazerules.RuleEngine()
engine.load_rules("rules.yaml")
decoder = blazerules_io.ArrowIpcDecoder()
batch = decoder.decode_batch([frame_bytes])
result = engine.evaluate_batch(batch)CLI equivalent: run the same Arrow IPC decoder from a shell
blazerules eval --rules rules.yaml --input arrow-ipc --path batch.arrow --output summaryStreaming large files (Arrow IPC, Parquet, CSV)
decode_batch above (and its list-returning sibling decode_batches) are one-shot: pass them frames you already hold in memory (a Kafka message, an HTTP body) and get Arrow batches back. For files on disk or s3:// — where the whole object may not fit comfortably in memory — for_each_record_batch streams one RecordBatch at a time instead, and supports early exit:
import blazerules_io
def handle_batch(batch):
engine.evaluate_batch(batch)
return True # return False to stop reading early
blazerules_io.for_each_record_batch("events.arrow", "arrow-ipc", handle_batch)
blazerules_io.for_each_record_batch("history.parquet", "parquet", handle_batch)
blazerules_io.for_each_record_batch("events.csv", "csv", handle_batch)Pass FileReadOptions.included_fields to push column selection into the reader instead of dropping columns after the fact — Parquet skips those row-group columns entirely and CSV's converter never parses them:
options = blazerules_io.FileReadOptions()
options.included_fields = ["event_id", "amount", "merchant_id"]
blazerules_io.for_each_record_batch("history.parquet", "parquet", handle_batch, options)
Column projection is per-format
included_fields(field names) is honored by the Parquet and CSV readers. Arrow IPC projects byincluded_field_indices(integer positions) instead — set that field on the sameFileReadOptionsif you need to project an.arrow/.featherfile. NDJSON reads currently ignore both and always return every field.
read_record_batches(path, format, batch_size) still exists and is simpler for small files — it calls the same reader internally but collects every batch into a list before returning, so it does not accept a FileReadOptions:
for batch in blazerules_io.read_record_batches("history.parquet", batch_size=65536):
engine.evaluate_batch(batch)Prefer for_each_record_batch once a file is large enough that materializing every batch at once would be a meaningful chunk of memory — blazerules eval uses the same streaming form internally.
Avro
AvroDecoder.decode_batch([...]) decodes one record per bare frame you pass in — the right shape for a Kafka-style topic (one frame per message):
decoder = blazerules_io.AvroDecoder(schema_json)
batch = decoder.decode_batch([avro_bytes])
result = engine.evaluate_batch(batch)For a real multi-record Avro file — an Object Container File (OCF), the format Spark/Hadoop/Kafka Connect produce — use decode_avro_ocf_file_each instead. It reads the file's own embedded schema (no schema_json needed) and streams batches:
def handle_batch(batch):
engine.evaluate_batch(batch)
return True # return False to stop reading early
blazerules_io.decode_avro_ocf_file_each("events.avro", handle_batch, batch_size=10000)CLI equivalent: decode Avro from a shell
# Object Container File: every record, auto-detected via magic bytes, no --schema needed
blazerules eval --rules rules.yaml --input avro --path events.avro --output summary
# Bare single Avro value (e.g. one Kafka message payload saved to disk): needs --schema
blazerules eval --rules rules.yaml --input avro --schema transaction.avsc --path record.avrobin --output summaryProtobuf
ProtobufDecoder.decode_batch([...]) decodes one record per bare frame, the same one-frame-per-record shape as Avro above:
decoder = blazerules_io.ProtobufDecoder(descriptor_set_bytes, "package.Transaction")
batch = decoder.decode_batch([proto_bytes])
result = engine.evaluate_batch(batch)Protobuf has no self-describing container format and no magic bytes, so a real multi-message file needs an explicit convention: N varint-length-delimited messages (the same convention protobuf's own SerializeDelimitedToCodedStream/ParseDelimitedFromCodedStream use). Read one with decode_delimited_file_each, or decode_delimited_file_parallel to parse chunks concurrently across worker threads once a file is large enough for that to matter:
def handle_batch(batch):
engine.evaluate_batch(batch)
return True # return False to stop reading early
blazerules_io.ProtobufDecoder(descriptor_set_bytes, "package.Transaction") \
.decode_delimited_file_each("events.pb", handle_batch, batch_size=10000)CLI equivalent: decode Protobuf from a shell
# Bare single message: decodes exactly one record, unchanged
blazerules eval --rules rules.yaml --input protobuf --descriptor descriptor.pb --message package.Transaction --path transaction.pb --output summary
# N varint-length-delimited messages: decodes every record, not auto-detected
blazerules eval --rules rules.yaml --input protobuf-delimited --descriptor descriptor.pb --message package.Transaction --path events.pb --output summaryNested Data
Nested structs use dotted rule fields:
conditions:
field: merchant.risk.score
op: gt
value: 50Arrays of objects use array_any:
conditions:
array_any:
path: items
where:
and:
- field: price
op: gt
value: 100
- field: category
op: eq
value: electronicsUpdated about 12 hours ago