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

type-system.mddocs/

Type System

Comprehensive type system with DataType constants for all supported data types, type checking utilities, and type coercion functions. The type system enables optional type safety, validation, and proper handling of Python data types within rule expressions.

Capabilities

DataType Constants

The DataType class provides constants for all supported data types with type checking and compatibility methods.

class DataType:
    """Collection of constants representing supported data types."""
    
    # Scalar Types
    BOOLEAN: DataType     # bool values
    BYTES: DataType       # bytes objects  
    DATETIME: DataType    # datetime.datetime objects
    FLOAT: DataType       # decimal.Decimal, float, int (non-bool)
    NULL: DataType        # None values
    STRING: DataType      # str values
    TIMEDELTA: DataType   # datetime.timedelta objects
    
    # Compound Types
    ARRAY: DataType       # tuple, list, range objects
    MAPPING: DataType     # dict, OrderedDict objects
    SET: DataType         # set objects
    
    # Special Types
    FUNCTION: DataType    # callable objects
    UNDEFINED: DataType   # undefined/unknown types

Usage Example:

import rule_engine

# Using type constants for validation
print(rule_engine.DataType.STRING.name)  # "STRING"
print(rule_engine.DataType.FLOAT.python_type)  # <class 'decimal.Decimal'>

# Check if a type is scalar or compound
print(rule_engine.DataType.STRING.is_scalar)    # True
print(rule_engine.DataType.ARRAY.is_compound)   # True
print(rule_engine.DataType.MAPPING.is_iterable) # True

Type Resolution from Names

Get DataType constants from their string names.

@classmethod
def from_name(cls, name: str) -> DataType:
    """
    Get the data type from its name.
    
    Args:
        name (str): The name of the data type to retrieve
        
    Returns:
        DataType: The corresponding data type constant
        
    Raises:
        TypeError: If name is not a string
        ValueError: If name doesn't map to a valid data type
    """

Usage Example:

import rule_engine

# Get types by name
string_type = rule_engine.DataType.from_name('STRING')
float_type = rule_engine.DataType.from_name('FLOAT')
array_type = rule_engine.DataType.from_name('ARRAY')

print(string_type == rule_engine.DataType.STRING)  # True

# Error handling
try:
    invalid_type = rule_engine.DataType.from_name('INVALID')
except ValueError as e:
    print(f"Invalid type name: {e}")

Type Resolution from Python Types

Get DataType constants from Python types and type hints.

@classmethod
def from_type(cls, python_type) -> DataType:
    """
    Get the data type constant for a Python type or type hint.
    
    Args:
        python_type: Native Python type or type hint
        
    Returns:
        DataType: The corresponding data type constant
        
    Raises:
        TypeError: If argument is not a type or type hint
        ValueError: If type cannot be mapped to a supported data type
    """

Usage Example:

import rule_engine
from typing import List, Dict
import datetime

# Basic Python types
print(rule_engine.DataType.from_type(str))      # DataType.STRING
print(rule_engine.DataType.from_type(int))      # DataType.FLOAT
print(rule_engine.DataType.from_type(bool))     # DataType.BOOLEAN
print(rule_engine.DataType.from_type(list))     # DataType.ARRAY
print(rule_engine.DataType.from_type(dict))     # DataType.MAPPING

# Datetime types
print(rule_engine.DataType.from_type(datetime.datetime))  # DataType.DATETIME
print(rule_engine.DataType.from_type(datetime.timedelta)) # DataType.TIMEDELTA

# Type hints (Python 3.6+)
list_type = rule_engine.DataType.from_type(List[str])
print(list_type.name)  # "ARRAY"
if hasattr(list_type, 'value_type'):
    print(list_type.value_type.name)  # "STRING"

dict_type = rule_engine.DataType.from_type(Dict[str, int])
print(dict_type.name)  # "MAPPING"

Type Resolution from Values

Get DataType constants from Python values with automatic type detection.

@classmethod
def from_value(cls, python_value) -> DataType:
    """
    Get the data type constant for a Python value.
    
    Args:
        python_value: Native Python value to analyze
        
    Returns:
        DataType: The corresponding data type constant
        
    Raises:
        TypeError: If value cannot be mapped to a supported data type
    """

Usage Example:

import rule_engine
import datetime
from collections import OrderedDict

# Scalar values
print(rule_engine.DataType.from_value("hello"))      # DataType.STRING
print(rule_engine.DataType.from_value(42))           # DataType.FLOAT
print(rule_engine.DataType.from_value(True))         # DataType.BOOLEAN
print(rule_engine.DataType.from_value(None))         # DataType.NULL
print(rule_engine.DataType.from_value(b"bytes"))     # DataType.BYTES

# Datetime values
now = datetime.datetime.now()
delta = datetime.timedelta(days=1)
print(rule_engine.DataType.from_value(now))    # DataType.DATETIME
print(rule_engine.DataType.from_value(delta))  # DataType.TIMEDELTA

# Collections
print(rule_engine.DataType.from_value([1, 2, 3]))           # DataType.ARRAY
print(rule_engine.DataType.from_value({'a': 1, 'b': 2}))    # DataType.MAPPING
print(rule_engine.DataType.from_value({1, 2, 3}))           # DataType.SET

# Typed collections
array_type = rule_engine.DataType.from_value([1, 2, 3])
print(array_type.value_type.name)  # "FLOAT" (all integers)

mixed_array = rule_engine.DataType.from_value([1, "hello", None])
print(mixed_array.value_type.name)  # "UNDEFINED" (mixed types)

Type Compatibility Checking

Check if two data types are compatible without conversion.

@classmethod
def is_compatible(cls, dt1: DataType, dt2: DataType) -> bool:
    """
    Check if two data types are compatible without conversion.
    
    Args:
        dt1: First data type to compare
        dt2: Second data type to compare
        
    Returns:
        bool: True if types are compatible
        
    Raises:
        TypeError: If arguments are not data type definitions
    """

Usage Example:

import rule_engine

# Scalar type compatibility
string_type = rule_engine.DataType.STRING
float_type = rule_engine.DataType.FLOAT
undefined_type = rule_engine.DataType.UNDEFINED

print(rule_engine.DataType.is_compatible(string_type, string_type))    # True
print(rule_engine.DataType.is_compatible(string_type, float_type))     # False
print(rule_engine.DataType.is_compatible(string_type, undefined_type)) # True (undefined is always compatible)

# Compound type compatibility
array_str = rule_engine.DataType.ARRAY(rule_engine.DataType.STRING)
array_float = rule_engine.DataType.ARRAY(rule_engine.DataType.FLOAT)
array_undefined = rule_engine.DataType.ARRAY(rule_engine.DataType.UNDEFINED)

print(rule_engine.DataType.is_compatible(array_str, array_str))       # True
print(rule_engine.DataType.is_compatible(array_str, array_float))     # False  
print(rule_engine.DataType.is_compatible(array_str, array_undefined)) # True

Type Definition Checking

Check if a value is a data type definition.

@classmethod
def is_definition(cls, value) -> bool:
    """
    Check if a value is a data type definition.
    
    Args:
        value: The value to check
        
    Returns:
        bool: True if value is a data type definition
    """

Usage Example:

import rule_engine

# Check if values are type definitions
print(rule_engine.DataType.is_definition(rule_engine.DataType.STRING))  # True
print(rule_engine.DataType.is_definition("STRING"))                     # False
print(rule_engine.DataType.is_definition(42))                           # False

# Useful for validation
def validate_type_resolver(type_map):
    for symbol, dtype in type_map.items():
        if not rule_engine.DataType.is_definition(dtype):
            raise ValueError(f"Invalid type definition for {symbol}: {dtype}")

Type Utility Functions

Value Coercion

Convert Python values to rule engine compatible types.

def coerce_value(value, verify_type: bool = True):
    """
    Convert a Python value to a rule engine compatible type.
    
    Args:
        value: The value to convert
        verify_type (bool): Whether to verify the converted type
        
    Returns:
        The converted value
        
    Raises:
        TypeError: If verify_type=True and type is incompatible
    """

Usage Example:

import rule_engine
import datetime
from collections import OrderedDict

# Automatic coercion
coerced_list = rule_engine.coerce_value([1, 2, 3])      # Converts to tuple
print(type(coerced_list))  # <class 'tuple'>

coerced_date = rule_engine.coerce_value(datetime.date(2023, 1, 1))  # Converts to datetime
print(type(coerced_date))  # <class 'datetime.datetime'>

coerced_float = rule_engine.coerce_value(42)            # Converts to Decimal
print(type(coerced_float))  # <class 'decimal.Decimal'>

# Nested structures
nested_data = {
    'numbers': [1, 2, 3],
    'info': {'name': 'test'}
}
coerced_nested = rule_engine.coerce_value(nested_data)
print(type(coerced_nested))  # <class 'collections.OrderedDict'>

Numeric Type Checking

Utility functions for checking numeric properties of values.

def is_numeric(value) -> bool:
    """Check if value is numeric (int, float, Decimal, but not bool)."""

def is_real_number(value) -> bool:
    """Check if value is a real number (finite, not NaN)."""

def is_integer_number(value) -> bool:
    """Check if value is an integer number (whole number)."""

def is_natural_number(value) -> bool:
    """Check if value is a natural number (non-negative integer)."""

Usage Example:

import rule_engine
import decimal
import math

# Numeric checks
print(rule_engine.is_numeric(42))        # True
print(rule_engine.is_numeric(3.14))      # True  
print(rule_engine.is_numeric(True))      # False (bool excluded)
print(rule_engine.is_numeric("42"))      # False

# Real number checks
print(rule_engine.is_real_number(42))      # True
print(rule_engine.is_real_number(float('inf')))  # False
print(rule_engine.is_real_number(float('nan')))  # False

# Integer checks
print(rule_engine.is_integer_number(42))    # True
print(rule_engine.is_integer_number(3.0))   # True (whole number)
print(rule_engine.is_integer_number(3.14))  # False

# Natural number checks  
print(rule_engine.is_natural_number(42))   # True
print(rule_engine.is_natural_number(0))    # True
print(rule_engine.is_natural_number(-5))   # False

Iterable Member Type Analysis

Analyze the types of members in iterable collections.

def iterable_member_value_type(python_value) -> DataType:
    """
    Get the data type of iterable members if consistent.
    
    Args:
        python_value: Iterable to analyze
        
    Returns:
        DataType: The member type (UNDEFINED if mixed types)
    """

Usage Example:

import rule_engine

# Homogeneous collections
numbers = [1, 2, 3, 4]
member_type = rule_engine.iterable_member_value_type(numbers)
print(member_type.name)  # "FLOAT"

strings = ["a", "b", "c"]
member_type = rule_engine.iterable_member_value_type(strings)
print(member_type.name)  # "STRING"

# Heterogeneous collections
mixed = [1, "hello", True]
member_type = rule_engine.iterable_member_value_type(mixed)
print(member_type.name)  # "UNDEFINED"

# Nullable collections (None values allowed)
nullable = [1, 2, None, 3]
member_type = rule_engine.iterable_member_value_type(nullable)
print(member_type.name)  # "FLOAT" (None is treated specially)

Compound Type Definitions

Array Types

Define array types with specific member types.

import rule_engine

# Create typed array definitions
string_array = rule_engine.DataType.ARRAY(rule_engine.DataType.STRING)
number_array = rule_engine.DataType.ARRAY(rule_engine.DataType.FLOAT)

print(string_array.value_type.name)  # "STRING"
print(number_array.value_type_nullable)  # True (allows None values)

# Non-nullable arrays
strict_array = rule_engine.DataType.ARRAY(
    rule_engine.DataType.STRING, 
    value_type_nullable=False
)

Mapping Types

Define mapping types with specific key and value types.

import rule_engine

# String keys, any values
string_mapping = rule_engine.DataType.MAPPING(rule_engine.DataType.STRING)

# Specific key and value types
typed_mapping = rule_engine.DataType.MAPPING(
    rule_engine.DataType.STRING,     # key type
    rule_engine.DataType.FLOAT,      # value type
    value_type_nullable=False        # values cannot be None
)

print(typed_mapping.key_type.name)    # "STRING"
print(typed_mapping.value_type.name)  # "FLOAT"

Function Types

Define function types with return types and argument specifications.

import rule_engine

# Basic function type
basic_func = rule_engine.DataType.FUNCTION(
    "calculate",
    return_type=rule_engine.DataType.FLOAT
)

# Function with argument types
typed_func = rule_engine.DataType.FUNCTION(
    "process",
    return_type=rule_engine.DataType.STRING,
    argument_types=(rule_engine.DataType.STRING, rule_engine.DataType.FLOAT),
    minimum_arguments=1  # Second argument is optional
)

print(typed_func.return_type.name)  # "STRING"
print(len(typed_func.argument_types))  # 2

Type System Integration

Using the type system with contexts and rules for comprehensive type safety:

import rule_engine

# Define a complete type schema
type_schema = {
    'user_id': rule_engine.DataType.FLOAT,
    'name': rule_engine.DataType.STRING,
    'active': rule_engine.DataType.BOOLEAN,
    'tags': rule_engine.DataType.ARRAY(rule_engine.DataType.STRING),
    'metadata': rule_engine.DataType.MAPPING(
        rule_engine.DataType.STRING,
        rule_engine.DataType.STRING
    ),
    'created_at': rule_engine.DataType.DATETIME
}

# Create type resolver and context
type_resolver = rule_engine.type_resolver_from_dict(type_schema)
context = rule_engine.Context(type_resolver=type_resolver)

# Type-safe rule creation
rule = rule_engine.Rule(
    'active and len(tags) > 0 and name =~ "Admin.*"',
    context=context
)

# Validation happens at rule creation time
try:
    invalid_rule = rule_engine.Rule('name + user_id', context=context)  # Type error
except rule_engine.EvaluationError as e:
    print(f"Type validation failed: {e.message}")

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