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, RuleFields

# 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)  # Returns RuleFields
print(deps.id_references['category_ids'])  # {101, 102}

Working with Objects

from json_rule_engine import RuleEntity

class Customer(RuleEntity):
    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])  # Returns EvaluationResult
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 RuleEntity 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 → RuleFields

# 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. Professional Class Names:
    • EvaluatableRuleEntity
    • DependenciesRuleFields
    • EvalResultEvaluationResult
  4. Cleaner Imports: Single entry point for discoverability
  5. 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.1.0.tar.gz (32.9 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.1.0-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: json_rule_engine-2.1.0.tar.gz
  • Upload date:
  • Size: 32.9 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.1.0.tar.gz
Algorithm Hash digest
SHA256 ba32ec285f76cbcb4dc78b38eaaa74536f3da37a8979caed97a5d9c37277245a
MD5 fa97cb228b891c25ad5cbf8aaa7ec523
BLAKE2b-256 6c7f4a5826680cda0c560681e2fc47b2e548bf5e4160320408e76a190ae24d51

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for json_rule_engine-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b73afd6b436d7a420dfd83ca254fde616231c846d2f4d1b77ac0abb495d18e4e
MD5 008621105987a3a7bb699b4d0c3e649a
BLAKE2b-256 713acda20a2d98866b0f046c3f8ff23f6ad7ea709054a006d9e4dd26d6834028

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