CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-rule-engine

A lightweight, optionally typed expression language with a custom grammar for matching arbitrary Python objects.

Pending
Overview
Eval results
Files

context-management.mddocs/

Context and Symbol Resolution

Context management system for controlling how symbols are resolved, type checking is performed, and default values are handled. The context system enables custom symbol resolution, type safety, and flexible data access patterns.

Capabilities

Context Creation

Create context objects that define how symbols are resolved and type checking is performed.

class Context:
    def __init__(self, *, regex_flags=0, resolver=None, type_resolver=None, 
                 default_timezone='local', default_value=UNDEFINED, decimal_context=None):
        """
        Create a context for rule evaluation.
        
        Args:
            regex_flags (int): Flags for regex operations (re module flags)
            resolver: Function for symbol value resolution  
            type_resolver: Function or mapping for symbol type resolution
            default_timezone: Default timezone ('local', 'utc', or tzinfo instance)
            default_value: Default value for unresolved symbols
            decimal_context: Decimal context for float operations
        """

Usage Example:

import rule_engine

# Basic context with default value
context = rule_engine.Context(default_value="N/A")
rule = rule_engine.Rule('missing_field', context=context)
result = rule.evaluate({})  # Returns "N/A"

# Context with custom resolver
def custom_resolver(symbol_name, obj):
    if symbol_name == 'full_name':
        return f"{obj.get('first', '')} {obj.get('last', '')}"
    return obj.get(symbol_name)

context = rule_engine.Context(resolver=custom_resolver)
rule = rule_engine.Rule('full_name', context=context)
person = {'first': 'John', 'last': 'Doe'}
print(rule.evaluate(person))  # "John Doe"

Symbol Resolution

Resolve symbols to values using context-specific resolution functions.

def resolve(self, thing, name: str, scope: str = None):
    """
    Resolve a symbol name to its value.
    
    Args:
        thing: The object to resolve the symbol against
        name (str): The symbol name to resolve
        scope (str, optional): The scope for symbol resolution
        
    Returns:
        The resolved value for the symbol
        
    Raises:
        SymbolResolutionError: If the symbol cannot be resolved
    """

Attribute Resolution

Resolve attributes from object values within the context.

def resolve_attribute(self, thing, object_, name: str):
    """
    Resolve an attribute from an object value.
    
    Args:
        thing: The root object for resolution
        object_: The object to access the attribute on
        name (str): The attribute name to resolve
        
    Returns:
        The resolved attribute value
        
    Raises:
        AttributeResolutionError: If the attribute cannot be resolved
    """

Type Resolution

Resolve type information for symbols during rule parsing.

def resolve_type(self, name: str, scope: str = None):
    """
    Resolve type information for a symbol.
    
    Args:
        name (str): The symbol name to get type information for
        scope (str, optional): The scope for type resolution
        
    Returns:
        DataType: The data type for the symbol
        
    Raises:
        SymbolResolutionError: If type cannot be resolved
    """

Assignment Management

Manage temporary symbol assignments within expression contexts.

@contextlib.contextmanager
def assignments(self, *assignments):
    """
    Add assignments to a thread-specific scope.
    
    Args:
        *assignments: Assignment objects to define in the scope
        
    Yields:
        Context manager for assignment scope
    """

Standalone Attribute Resolution

Standalone function for resolving symbols as object attributes with error handling and suggestions.

def resolve_attribute(thing, name: str):
    """
    Resolve a symbol as an attribute of the provided object.
    
    Args:
        thing: The object to access the attribute on
        name (str): The attribute name to resolve
        
    Returns:
        The value of the specified attribute
        
    Raises:
        SymbolResolutionError: If the attribute doesn't exist
        
    Warning:
        This exposes all attributes of the object. Use custom resolvers
        for security-sensitive applications.
    """

Usage Example:

import rule_engine

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self._private = "secret"

# Using attribute resolution
context = rule_engine.Context(resolver=rule_engine.resolve_attribute)
rule = rule_engine.Rule('name + " is " + str(age)', context=context)

person = Person("Alice", 30)
result = rule.evaluate(person)
print(result)  # "Alice is 30"

# Accessing private attributes (be careful!)
private_rule = rule_engine.Rule('_private', context=context)
print(private_rule.evaluate(person))  # "secret"

Item Resolution

Resolve symbols as dictionary/mapping keys with error handling and suggestions.

def resolve_item(thing, name: str):
    """
    Resolve a symbol as a key in a mapping object.
    
    Args:
        thing: The mapping object to access
        name (str): The key name to resolve
        
    Returns:
        The value for the specified key
        
    Raises:
        SymbolResolutionError: If the key doesn't exist or object isn't a mapping
    """

Usage Example:

import rule_engine

# Using item resolution for dictionaries  
context = rule_engine.Context(resolver=rule_engine.resolve_item)
rule = rule_engine.Rule('user_id > 1000 and status == "active"', context=context)

user_data = {
    'user_id': 1234,
    'status': 'active',
    'email': 'user@example.com'
}

print(rule.matches(user_data))  # True

# Works with any mapping-like object
from collections import OrderedDict
ordered_data = OrderedDict([('priority', 1), ('task', 'important')])
task_rule = rule_engine.Rule('priority == 1', context=context)
print(task_rule.matches(ordered_data))  # True

Type Resolution

Create type resolvers from dictionaries to enable type checking and validation.

def type_resolver_from_dict(dictionary: dict):
    """
    Create a type resolver function from a dictionary mapping.
    
    Args:
        dictionary (dict): Mapping of symbol names to DataType constants
        
    Returns:
        A type resolver function for use with Context
        
    Raises:
        SymbolResolutionError: If a symbol is not defined in the dictionary
    """

Usage Example:

import rule_engine

# Define types for symbols
type_map = {
    'name': rule_engine.DataType.STRING,
    'age': rule_engine.DataType.FLOAT,
    'active': rule_engine.DataType.BOOLEAN,
    'tags': rule_engine.DataType.ARRAY,
    'metadata': rule_engine.DataType.MAPPING
}

type_resolver = rule_engine.type_resolver_from_dict(type_map)
context = rule_engine.Context(type_resolver=type_resolver)

# This will work - types match
rule = rule_engine.Rule('name + " is " + str(age)', context=context)

# This will fail - type mismatch
try:
    bad_rule = rule_engine.Rule('name + age', context=context)  # Can't add string + number
except rule_engine.EvaluationError as e:
    print(f"Type error: {e.message}")

# This will fail - undefined symbol
try:
    unknown_rule = rule_engine.Rule('unknown_field == "value"', context=context)
except rule_engine.SymbolResolutionError as e:
    print(f"Unknown symbol: {e.symbol_name}")

Advanced Context Patterns

Combined Resolvers

Use both type and value resolvers together for comprehensive symbol management.

import rule_engine

# Custom resolver that handles special cases
def smart_resolver(symbol_name, obj):
    if symbol_name == 'full_name':
        return f"{obj.get('first_name', '')} {obj.get('last_name', '')}"
    elif symbol_name == 'is_adult':
        return obj.get('age', 0) >= 18
    return rule_engine.resolve_item(obj, symbol_name)

# Type definitions including computed fields
type_map = {
    'first_name': rule_engine.DataType.STRING,
    'last_name': rule_engine.DataType.STRING,
    'age': rule_engine.DataType.FLOAT,
    'full_name': rule_engine.DataType.STRING,  # computed field
    'is_adult': rule_engine.DataType.BOOLEAN   # computed field
}

context = rule_engine.Context(
    type_resolver=rule_engine.type_resolver_from_dict(type_map),
    resolver=smart_resolver
)

rule = rule_engine.Rule('is_adult and full_name =~ "John.*"', context=context)
person = {'first_name': 'John', 'last_name': 'Doe', 'age': 25}
print(rule.matches(person))  # True

Security-Conscious Resolution

Create secure resolvers that limit access to specific attributes or keys.

import rule_engine

def secure_resolver(symbol_name, obj):
    # Whitelist of allowed attributes
    allowed_fields = {'name', 'email', 'department', 'role'}
    
    if symbol_name not in allowed_fields:
        raise rule_engine.SymbolResolutionError(symbol_name)
    
    return getattr(obj, symbol_name, None)

context = rule_engine.Context(resolver=secure_resolver)
rule = rule_engine.Rule('department == "engineering"', context=context)

class Employee:
    def __init__(self):
        self.name = "John"
        self.department = "engineering"
        self.salary = 100000  # This won't be accessible

employee = Employee()
print(rule.matches(employee))  # True

# This will fail - salary is not in the whitelist
try:
    salary_rule = rule_engine.Rule('salary > 50000', context=context)
    salary_rule.matches(employee)
except rule_engine.SymbolResolutionError as e:
    print(f"Access denied to: {e.symbol_name}")

Context Properties

Access context configuration and metadata.

context = rule_engine.Context(
    type_resolver=rule_engine.type_resolver_from_dict({'name': rule_engine.DataType.STRING}),
    resolver=rule_engine.resolve_item,
    default_value="unknown"
)

print(f"Has type resolver: {context.type_resolver is not None}")
print(f"Has custom resolver: {context.resolver is not None}")
print(f"Default value: {context.default_value}")

Error Handling and Debugging

Context operations can raise various exceptions that provide detailed error information:

import rule_engine

try:
    context = rule_engine.Context()
    rule = rule_engine.Rule('undefined_symbol', context=context)
    rule.evaluate({})
except rule_engine.SymbolResolutionError as e:
    print(f"Symbol '{e.symbol_name}' not found")
    if e.thing is not rule_engine.UNDEFINED:
        print(f"Available attributes: {dir(e.thing)}")
    if e.suggestion:
        print(f"Did you mean: {e.suggestion}")

# Type resolution errors
try:
    type_resolver = rule_engine.type_resolver_from_dict({'age': rule_engine.DataType.FLOAT})
    context = rule_engine.Context(type_resolver=type_resolver)
    rule = rule_engine.Rule('name == "John"', context=context)  # 'name' not defined
except rule_engine.SymbolResolutionError as e:
    print(f"Type not defined for symbol: {e.symbol_name}")

Install with Tessl CLI

npx tessl i tessl/pypi-rule-engine

docs

context-management.md

core-operations.md

error-handling.md

index.md

type-system.md

tile.json