Skip to main content

A lightweight library for building, evaluating, and translating JSON-based rules

Project description

JSON Rule Engine

Python Version License: MIT PyPI version

A powerful and lightweight Python library for building, evaluating, and translating JSON-based business rules. Perfect for creating dynamic filtering systems, decision engines, and converting rules to Django ORM queries.

Key Features

  • 🎯 Unified API: Single RuleEngine class for all functionality
  • 🔧 Configurable: No hardcoded domain assumptions - works for any use case
  • 🐍 Pythonic: Intuitive Field() and Q() builders for rule creation
  • Zero Dependencies: Core functionality requires no external packages
  • 🔌 Django Integration: Optional Django Q object conversion
  • 📊 Extensible: Add custom operators and configurations

Installation

# Basic installation
pip install json-rule-engine

# With Django support
pip install json-rule-engine[django]

Quick Start

Initialize the Engine

from json_rule_engine import RuleEngine, Field, Q

# Create the engine - single entry point for all functionality
engine = RuleEngine()

Build Rules

# Using Field builder (recommended)
rule = Field('age').greater_than(18)
rule = Field('city').equals('NYC')
rule = Field('email').contains('@gmail.com')

# Combine rules with & (AND), | (OR), ~ (NOT)
rule = (
    Field('city').equals('NYC') &
    Field('age').gt(18) &
    Field('tags').has_any(['premium', 'vip'])
)

# Using Django-style Q objects
rule = Q(city='NYC') & Q(age__gt=18)

Evaluate Rules

# Evaluate against dictionary
data = {'city': 'NYC', 'age': 25, 'tags': ['vip']}
result = engine.evaluate(rule, data)  # True

# Check matches (returns bool)
matches = engine.matches(rule, data)  # True

Django Integration

# Convert to Django Q object (requires Django)
q = engine.to_q(rule)

# Use in Django ORM
from myapp.models import Contact
contacts = Contact.objects.filter(q)

Advanced Usage

Custom Configuration

The engine is fully configurable - no hardcoded assumptions:

from json_rule_engine import RuleEngine, DependencyConfig

# Configure for your domain (e-commerce example)
config = DependencyConfig(
    id_fields={
        'categories': 'category_ids',
        'products': 'product_ids',
        'vendors': 'vendor_ids',
    },
    custom_field_pattern=r'^custom\.(\d+)\.(\w+)$'
)

# Initialize engine with custom config
engine = RuleEngine(dependency_config=config)

# Now your fields are recognized
rule = Field('categories').has_any([101, 102])
deps = engine.get_dependencies(rule)
print(deps.id_references['category_ids'])  # {101, 102}

Working with Objects

from json_rule_engine import Evaluatable

class Customer(Evaluatable):
    def __init__(self, name, age, city, tags=None):
        self.name = name
        self.age = age
        self.city = city
        self.tags = tags or []
    
    def to_eval_dict(self):
        return {
            'name': self.name,
            'age': self.age,
            'city': self.city,
            'tags': self.tags
        }

# Create customers
customers = [
    Customer('Alice', 25, 'NYC', ['vip']),
    Customer('Bob', 17, 'LA', ['regular']),
    Customer('Charlie', 30, 'NYC', ['premium'])
]

# Define rule
rule = Field('city').equals('NYC') & Field('age').gte(18)

# Batch evaluation
results = engine.batch(rule, customers)
print(f"Matches: {results['matches']}")      # [Alice, Charlie]
print(f"Non-matches: {results['non_matches']}") # [Bob]

# Filter matching objects
nyc_adults = engine.filter(rule, customers)  # [Alice, Charlie]

# Test with timing
result = engine.test(rule, customers[0])
print(f"Match: {result.matches}, Time: {result.eval_time_ms}ms")

Custom Operators

# Register custom operators
def between(values, data):
    """Check if value is between min and max."""
    val, min_val, max_val = values
    return min_val <= val <= max_val

engine.register_operator('between', between)

# Use custom operator
rule = {"between": [{"var": "age"}, 18, 65]}
result = engine.evaluate(rule, {"age": 25})  # True

Complex Nested Rules

# Build complex business logic
rule = RuleBuilder.and_(
    RuleBuilder.field('status').equals('active'),
    RuleBuilder.or_(
        RuleBuilder.field('role').equals('admin'),
        RuleBuilder.and_(
            RuleBuilder.field('role').equals('user'),
            RuleBuilder.field('permissions').has_any(['write', 'delete'])
        )
    ),
    RuleBuilder.not_(
        RuleBuilder.field('banned').equals(True)
    )
)

# Evaluate complex rule
data = {
    'status': 'active',
    'role': 'user',
    'permissions': ['read', 'write'],
    'banned': False
}
result = engine.evaluate(rule, data)  # True

Working with JSON Rules

# Parse existing JSON rules
json_rule = {
    "and": [
        {"==": [{"var": "status"}, "active"]},
        {"or": [
            {">": [{"var": "credits"}, 100]},
            {"==": [{"var": "plan"}, "premium"]}
        ]}
    ]
}

# Evaluate JSON directly
result = engine.evaluate(json_rule, data)

# Or wrap and combine with builder
from json_rule_engine import JsonRule

wrapped = JsonRule(json_rule)
combined = wrapped & Field('region').equals('US')

API Reference

RuleEngine Methods

The RuleEngine class is the main API entry point:

engine = RuleEngine(
    dependency_config=None,  # Optional: Custom dependency configuration
    django_field_map=None    # Optional: Custom Django field mappings
)

# Rule Evaluation
engine.evaluate(rule, data)           # Evaluate rule against data
engine.matches(rule, data)            # Returns bool
engine.test(rule, obj)                # Test Evaluatable object with timing
engine.batch(rule, objects)           # Evaluate multiple objects
engine.filter(rule, objects)          # Filter matching objects

# Django Integration
engine.to_q(rule)                      # Convert to Django Q object
engine.to_q_with_explanation(rule)    # Get Q object with explanation
engine.validate_json_rules(json)      # Validate JSON structure

# Dependency Extraction
engine.get_dependencies(rule)         # Extract field dependencies

# Configuration
engine.register_operator(name, func)  # Add custom operator
engine.configure_dependencies(config) # Update dependency config
engine.configure_django_fields(map)   # Update Django mappings

Rule Builders

# Field builder (recommended)
Field('name').equals(value)
Field('name').not_equals(value)
Field('name').greater_than(value)
Field('name').greater_or_equal(value)
Field('name').less_than(value)
Field('name').less_or_equal(value)
Field('name').contains(substring)
Field('name').startswith(prefix)
Field('name').endswith(suffix)
Field('name').is_empty()
Field('name').is_not_empty()
Field('name').has_any([values])    # For array fields
Field('name').has_all([values])    # Must have all
Field('name').has_none([values])   # Must have none

# Q objects (Django-style)
Q(field='value')              # equals
Q(field__ne='value')          # not equals
Q(field__gt=10)               # greater than
Q(field__gte=10)              # greater or equal
Q(field__lt=10)               # less than
Q(field__lte=10)              # less or equal
Q(field__contains='text')     # contains
Q(field__has_any=[1,2])       # array operations

# Combining rules
rule1 & rule2    # AND
rule1 | rule2    # OR
~rule           # NOT

# Helper functions
AND(rule1, rule2, rule3)
OR(rule1, rule2)
NOT(rule)

Supported Operators

Comparison

  • == (equals)
  • != (not equals)
  • > (greater than)
  • >= (greater or equal)
  • < (less than)
  • <= (less or equal)

String

  • contains - Substring check
  • startswith - String prefix
  • endswith - String suffix
  • is_empty - Null or empty string
  • is_not_empty - Has value

Array/Set

  • has_any - Has at least one value
  • has_all - Has all values
  • has_none - Has none of the values
  • is_in - Value is in list

Logic

  • & / AND - Logical AND
  • | / OR - Logical OR
  • ~ / NOT - Logical NOT

Migration from v1.x

Version 2.0 introduces a cleaner, unified API:

Old API (v1.x)

# Multiple confusing entry points
from json_rule_engine import evaluate, matches, to_q, json_to_q

result = evaluate(rule, data)
q = to_q(rule)
q = json_to_q(json_rules)

New API (v2.0)

# Single, clear entry point
from json_rule_engine import RuleEngine

engine = RuleEngine()
result = engine.evaluate(rule, data)
q = engine.to_q(rule)

Key Changes

  1. Unified API: All functionality through RuleEngine class
  2. Configurable Dependencies: No more hardcoded field assumptions
  3. Cleaner Imports: Single entry point for discoverability
  4. Better Typing: Full type hints throughout

Examples

E-commerce Product Filtering

# Configure for e-commerce domain
config = DependencyConfig(
    id_fields={
        'categories': 'category_ids',
        'brands': 'brand_ids',
        'tags': 'tag_ids'
    }
)
engine = RuleEngine(dependency_config=config)

# Build product filter rule
rule = (
    Field('price').between(10, 100) &
    Field('categories').has_any(['electronics', 'computers']) &
    Field('in_stock').equals(True) &
    (Field('rating').gte(4) | Field('featured').equals(True))
)

# Filter products
matching_products = engine.filter(rule, all_products)

User Permission System

# Define permission rule
can_edit = (
    Field('role').equals('admin') |
    (
        Field('role').equals('editor') &
        Field('departments').has_any(['content', 'marketing'])
    )
)

# Check user permission
user_data = {
    'role': 'editor',
    'departments': ['content', 'design']
}
has_permission = engine.evaluate(can_edit, user_data)  # True

Dynamic Form Validation

# Build validation rule from configuration
validation_rule = RuleBuilder.and_(
    RuleBuilder.field('age').gte(18),
    RuleBuilder.field('email').contains('@'),
    RuleBuilder.or_(
        RuleBuilder.field('phone').is_not_empty(),
        RuleBuilder.field('email').is_not_empty()
    )
)

# Validate form submission
form_data = {
    'age': 25,
    'email': 'user@example.com',
    'phone': ''
}
is_valid = engine.matches(validation_rule, form_data)  # True

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

If you encounter any problems or have questions, please open an issue on GitHub.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

json_rule_engine-2.0.0.tar.gz (32.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

json_rule_engine-2.0.0-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

File details

Details for the file json_rule_engine-2.0.0.tar.gz.

File metadata

  • Download URL: json_rule_engine-2.0.0.tar.gz
  • Upload date:
  • Size: 32.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for json_rule_engine-2.0.0.tar.gz
Algorithm Hash digest
SHA256 9068de9c7899df3cd60e2afa9892b0f51114931d4760e6f39210b5a420c16a8e
MD5 8e7c2d6c71eba6033b993a52b1d5db5c
BLAKE2b-256 55eced14f57f1e13513c5afc2105b1a0e70805679b8a672d70463575347ba8e8

See more details on using hashes here.

File details

Details for the file json_rule_engine-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for json_rule_engine-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fde92798aa60b72525f1e3f7c61eb97a4f7b94133fd5f7227bd27f171f06bbe6
MD5 5644fc77d8f990d3c5f3112eb438a959
BLAKE2b-256 8c46fabdf073ab6f335b8b6c8abfd104b2a58016986aef1afcd7fc0114cde806

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page