Skip to content

Latest commit

 

History

History
520 lines (379 loc) · 14.4 KB

File metadata and controls

520 lines (379 loc) · 14.4 KB

Python API Reference

Complete autogenerated reference for the Python CEL library.

Functions

::: cel.evaluate

compile(expression: str) -> Program

Compile a CEL expression into a reusable Program object.

This function parses and compiles a CEL expression, returning a Program object that can be executed multiple times with different contexts. This is more efficient than calling evaluate() repeatedly with the same expression.

Parameters:

  • expression: The CEL expression to compile

Returns:

  • A compiled Program object

Raises:

  • ValueError: If the expression has syntax errors or is malformed

Example:

import cel

# Compile once
program = cel.compile("x + y")

# Execute many times with different contexts
result1 = program.execute({"x": 1, "y": 2})
assert result1 == 3  # → 3

result2 = program.execute({"x": 10, "y": 20})
assert result2 == 30  # → 30

When to use compile() vs evaluate():

  • Use evaluate() for one-time evaluation or interactive/REPL usage
  • Use compile() + execute() when evaluating the same expression with many different contexts or in performance-critical loops

Classes

Program

A compiled CEL program that can be executed multiple times with different contexts.

The Program class represents a pre-compiled CEL expression. Use this when you need to evaluate the same expression many times with different variable bindings. Compiling once and executing multiple times is significantly faster than calling evaluate() repeatedly.

import cel

# Compile the expression once
program = cel.compile("price * quantity > 100")

# Execute many times with different contexts
result1 = program.execute({"price": 10, "quantity": 20})
assert result1 == True  # → True (200 > 100)

result2 = program.execute({"price": 5, "quantity": 10})
assert result2 == False  # → False (50 > 100)

Methods

execute(context=None) -> Any

Execute the compiled program with the given context.

Parameters:

  • context: Optional evaluation context (dict or Context object)

Returns:

  • The result of the expression evaluation

Raises:

  • RuntimeError: If a variable or function is undefined
  • TypeError: If there's a type mismatch during execution
  • ValueError: If the context is an invalid type

Example with dict context:

import cel

program = cel.compile("user.name + ' is ' + user.role")
result = program.execute({
    "user": {"name": "Alice", "role": "admin"}
})
assert result == "Alice is admin"

Example with Context object:

import cel
from cel import Context

program = cel.compile("greet(name)")

ctx = Context()
ctx.add_variable("name", "World")
ctx.add_function("greet", lambda x: f"Hello, {x}!")

result = program.execute(ctx)
assert result == "Hello, World!"

Performance pattern - compile once, execute many:

import cel

# Access control policy - compiled once at startup
policy = cel.compile(
    'user.role == "admin" || resource.owner == user.id'
)

# Evaluated many times per request
def check_access(user, resource):
    return policy.execute({"user": user, "resource": resource})

# Fast repeated evaluation
assert check_access({"id": "alice", "role": "admin"}, {"owner": "bob"}) == True
assert check_access({"id": "bob", "role": "user"}, {"owner": "bob"}) == True
assert check_access({"id": "charlie", "role": "user"}, {"owner": "bob"}) == False

OptionalValue

Wrapper for CEL optional values.

CEL optional values preserve the distinction between "no value" and "a value that is null". The Python wrapper keeps that distinction intact.

import cel

opt = cel.evaluate("optional.of(42)")
assert isinstance(opt, cel.OptionalValue)
assert opt.has_value() is True
assert opt.value() == 42
assert opt.or_value(0) == 42

none_opt = cel.evaluate("optional.none()")
assert none_opt.has_value() is False
assert none_opt.or_value("default") == "default"

Distinguishing optional.none() from optional.of(null):

import cel

opt_null = cel.evaluate("optional.of(null)")
assert opt_null.has_value() is True
assert opt_null.value() is None

opt_none = cel.evaluate("optional.none()")
assert opt_none.has_value() is False

Passing OptionalValue into evaluation contexts:

import cel

opt = cel.OptionalValue.of(123)
assert cel.evaluate("opt.orValue(0)", {"opt": opt}) == 123

opt_none = cel.OptionalValue.none()
assert cel.evaluate("opt.orValue(7)", {"opt": opt_none}) == 7

Methods

of(value) -> OptionalValue

Create an optional value containing value.

none() -> OptionalValue

Create an empty optional value.

has_value() -> bool

Return True when the optional contains a value.

value() -> Any

Return the contained value or raise ValueError for optional.none().

or_value(default) -> Any

Return the contained value if present, otherwise default.

or_optional(other) -> OptionalValue

Return self if it has a value, otherwise return other.


Context

A class for managing evaluation context with variables and custom functions.

The Context class provides more control over the evaluation environment than simple dictionary context. It allows you to:

  • Add variables with type checking
  • Register custom Python functions
  • Manage complex evaluation scenarios
from cel import evaluate, Context

# Basic usage
context = Context()
context.add_variable("name", "Alice")
context.add_variable("age", 30)

result = evaluate("name + ' is ' + string(age)", context)
# → "Alice is 30"
assert result == "Alice is 30"

Methods

add_variable(name: str, value: Any) -> None

Add a variable to the context.

Parameters:

  • name: Variable name (must be valid CEL identifier)
  • value: Variable value (will be converted to appropriate CEL type)

Example:

from cel import Context, evaluate

context = Context()
context.add_variable("user_id", "123")
context.add_variable("permissions", ["read", "write"])
context.add_variable("config", {"debug": True, "port": 8080})

# Verify the variables are accessible
evaluate("user_id", context)
# → "123"
evaluate("size(permissions)", context)
# → 2
evaluate("config.debug", context)
# → True
assert evaluate("user_id", context) == "123"
assert evaluate("size(permissions)", context) == 2
assert evaluate("config.debug", context) == True
update(variables: Dict[str, Any]) -> None

Add multiple variables at once.

Parameters:

  • variables: Dictionary of variable names to values

Example:

from cel import Context, evaluate

context = Context()
context.update({
    "user_id": "123",
    "role": "admin", 
    "permissions": ["read", "write", "delete"]
})

# Verify the batch update worked
evaluate("user_id", context)
# → "123"
evaluate("role", context)
# → "admin"
evaluate("size(permissions)", context)
# → 3
assert evaluate("user_id", context) == "123"
assert evaluate("role", context) == "admin"
assert evaluate("size(permissions)", context) == 3
add_function(name: str, func: Callable) -> None

Register a Python function for use in CEL expressions.

Parameters:

  • name: Function name as it will appear in CEL expressions
  • func: Python function to register

Requirements for functions:

  • Should handle type conversions appropriately
  • Should raise meaningful exceptions for invalid inputs
  • Must be callable from the Python environment

Example:

from cel import Context, evaluate

def validate_email(email):
    import re
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, email) is not None

context = Context()
context.add_function("validate_email", validate_email)

evaluate('validate_email("user@example.com")', context)
# → True

# Test invalid email
evaluate('validate_email("invalid-email")', context)
# → False
result = evaluate('validate_email("user@example.com")', context)
assert result == True

result = evaluate('validate_email("invalid-email")', context)
assert result == False

Type System

CEL to Python Type Mapping

This table shows how CEL types are converted to Python types when expressions are evaluated:

CEL Type CEL Spec Python Type Example CEL Python Result
int 64-bit signed integers int 42 42
uint 64-bit unsigned integers int 42u 42
double 64-bit IEEE floating-point float 3.14 3.14
bool Boolean values bool true True
string Unicode code point sequences str "hello" "hello"
bytes Byte sequences bytes b"data" b"data"
null_type Null value NoneType null None
list Ordered sequences list [1, 2, 3] [1, 2, 3]
map Key-value collections dict {"key": "value"} {"key": "value"}
timestamp Protocol buffer timestamps datetime.datetime timestamp("2024-01-01T00:00:00Z") datetime(2024, 1, 1, tzinfo=timezone.utc)
duration Protocol buffer durations datetime.timedelta duration("1h30m") timedelta(hours=1, minutes=30)

Map Type Constraints

✅ FULLY COMPLIANT with CEL specification:

  • Key Types: Restricted to int, uint, bool, and string as per CEL spec
  • Value Types: Support heterogeneous values (mixed types) as allowed by CEL spec
  • Runtime Behavior: Maps can contain dyn types for mixed-value collections

Examples:

// ✅ Valid key types
{1: "int key", "str": "string key", true: "bool key"}

// ✅ Mixed value types (CEL compliant)
{"name": "Alice", "age": 30, "verified": true, "score": 95.5}

// ✅ Nested heterogeneous structures  
{"users": [{"name": "Alice"}, {"name": "Bob"}], "count": 2}

Python to CEL Type Mapping

When passing Python objects as context:

Python Type CEL Type Notes
int int Direct mapping
float double Direct mapping
str string Direct mapping
bool bool Direct mapping
None null Direct mapping
list list(T) Element types preserved
dict map(K, V) Key/value types preserved
bytes bytes Direct mapping
datetime.datetime timestamp Timezone info preserved
datetime.timedelta duration Direct mapping

Error Handling

Exception Types

The library raises specific exception types for different error conditions based on the underlying error type:

ValueError - Parse and Compilation Errors

Raised when the CEL expression has invalid syntax, is empty, or fails to compile:

from cel import evaluate

# Invalid syntax raises ValueError
try:
    evaluate("1 + + 2")  # Invalid syntax
    # → ValueError: Failed to parse expression: ...
    assert False, "Should have raised ValueError"
except ValueError as e:
    assert "Failed to parse expression" in str(e)

# Empty expression raises ValueError
try:
    evaluate("")
    # → ValueError: Failed to parse expression
    assert False, "Should have raised ValueError"
except ValueError as e:
    assert "Failed to parse expression" in str(e)

RuntimeError - Variable and Function Errors

Raised for undefined variables or functions, and function execution errors:

from cel import evaluate

# Undefined variables raise RuntimeError
try:
    evaluate("unknown_variable + 1", {})
    # → RuntimeError: Undefined variable 'unknown_variable'
    assert False, "Should have raised RuntimeError"
except RuntimeError as e:
    assert "Undefined variable" in str(e)

# Undefined functions raise RuntimeError
try:
    evaluate("unknownFunction(42)", {})
    # → RuntimeError: Undefined function 'unknownFunction'
    assert False, "Should have raised RuntimeError"
except RuntimeError as e:
    assert "Undefined" in str(e) and "function" in str(e)

# Function execution errors raise RuntimeError
from cel import Context
def failing_function():
    raise Exception("Something went wrong")

context = Context()
context.add_function("fail", failing_function)

try:
    evaluate("fail()", context)
    # → RuntimeError: Function 'fail' error: Something went wrong
    assert False, "Should have raised RuntimeError"
except RuntimeError as e:
    assert "Function 'fail' error" in str(e)

TypeError - Type Compatibility Errors

Raised when operations are performed on incompatible types:

from cel import evaluate

# String + int operations raise TypeError
try:
    evaluate('"hello" + 42')  # String + int
    # → TypeError: Unsupported addition operation between string and int
    assert False, "Should have raised TypeError"
except TypeError as e:
    assert "Unsupported addition operation" in str(e)

# Mixed signed/unsigned int operations raise TypeError
try:
    evaluate("1u + 2")  # Mixed signed/unsigned int  
    # → TypeError: Cannot mix signed and unsigned integers
    assert False, "Should have raised TypeError"
except TypeError as e:
    assert "Cannot mix signed and unsigned integers" in str(e)

# Unsupported multiplication raises TypeError
try:
    evaluate('"text" * "more"')  # String multiplication
    # → TypeError: Unsupported multiplication operation between strings
    assert False, "Should have raised TypeError"
except TypeError as e:
    assert "Unsupported multiplication operation" in str(e)

Mixed Type Arithmetic Errors

Mixed numeric types raise TypeError:

from cel import evaluate

# Mixed numeric types in expressions
try:
    evaluate("1 + 2.5")  # int + double
except TypeError as e:
    assert "Unsupported addition operation" in str(e)
    print(f"Mixed arithmetic error: {e}")

# Mixed types from context
context = {"int_val": 10, "float_val": 2.5}
try:
    evaluate("int_val * float_val", context)
except TypeError as e:
    assert "Unsupported multiplication operation" in str(e)
    print(f"Context type mixing error: {e}")

# To fix mixed arithmetic, use consistent types:
result = evaluate("1.0 + 2.5")  # → 3.5 (both doubles)
result = evaluate("1 + 2")      # → 3 (both ints)

Production Error Handling

For comprehensive error handling patterns, safety guidelines, and production best practices, see the Error Handling How-To Guide which covers:

  • Safe handling of malformed expressions and untrusted input
  • Safe evaluation wrappers and best practices
  • Context validation patterns
  • Defensive expression techniques
  • Logging and monitoring
  • Testing error scenarios