Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.27", features = ["chrono", "py-clone"]}
cel = { version = "0.11.6", features = ["chrono", "json", "regex"] }
cel = { version = "0.12.0", features = ["chrono", "json", "regex", "bytes"] }
log = "0.4.27"
pyo3-log = { git = "https://github.com/a1phyr/pyo3-log.git", branch = "pyo3_0.27" }
chrono = { version = "0.4.42", features = ["serde"] }
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ cel 'age >= 18' --context '{"age": 25}' # true
cel --interactive
```

### Pre-compilation for Performance

When evaluating the same expression multiple times with different contexts, use `compile()` for better performance:

```python
import cel

# Compile once
program = cel.compile("price * quantity > threshold")

# Execute many times - much faster than repeated evaluate() calls
result1 = program.execute({"price": 10, "quantity": 5, "threshold": 40}) # True
result2 = program.execute({"price": 5, "quantity": 3, "threshold": 20}) # False
```

### Custom Functions

```python
Expand Down Expand Up @@ -177,4 +192,4 @@ This project is licensed under the same terms as the original cel-interpreter cr
- [📖 **Documentation**](https://python-common-expression-language.readthedocs.io/)
- [🌐 **CEL Homepage**](https://cel.dev/)
- [📋 **CEL Specification**](https://github.com/google/cel-spec)
- [⚙️ **cel-interpreter Rust crate**](https://crates.io/crates/cel-interpreter)
- [⚙️ **cel-interpreter Rust crate**](https://crates.io/crates/cel-interpreter)
21 changes: 20 additions & 1 deletion docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,25 @@ assert result == "No phone" # → "No phone" (safe field checking prevents erro
print("✓ Context variables working correctly")
```

## Pre-compilation for Performance

Use `compile()` when evaluating the same expression many times with different contexts:

```python
import cel

# Compile once, execute many times
program = cel.compile("price * quantity > threshold")

result1 = program.execute({"price": 10, "quantity": 5, "threshold": 40})
assert result1 == True # → True (50 > 40)

result2 = program.execute({"price": 5, "quantity": 3, "threshold": 20})
assert result2 == False # → False (15 > 20)

print("Pre-compilation working correctly")
```

## Ready for More?

You've mastered the basics of CEL evaluation with dictionary context! For advanced features like custom Python functions, context objects, and production patterns, continue to the next guide.
Expand Down Expand Up @@ -431,4 +450,4 @@ Congratulations! You've mastered basic CEL evaluation with dictionary context. N

**Quick Start → [Your First Integration](../tutorials/your-first-integration.md) → [Access Control Policies](../how-to-guides/access-control-policies.md)**

This path takes you from basics to production-ready applications in the most efficient way.
This path takes you from basics to production-ready applications in the most efficient way.
192 changes: 190 additions & 2 deletions docs/reference/python-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,196 @@ Complete autogenerated reference for the Python CEL library.

::: cel.evaluate

### compile(expression: str) -> Program

## Classes
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:**
```python
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.

```python
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:**
```python
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:**
```python
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:**
```python
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.

```python
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)`:**
```python
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:**
```python
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

Expand Down Expand Up @@ -329,4 +517,4 @@ For comprehensive error handling patterns, safety guidelines, and production bes
- Context validation patterns
- Defensive expression techniques
- Logging and monitoring
- Testing error scenarios
- Testing error scenarios
Loading