Skip to content

Commit 79ad64f

Browse files
committed
Add EvaluationMode for flexible type handling in CEL expressions
1 parent 0d6bb70 commit 79ad64f

File tree

5 files changed

+153
-15
lines changed

5 files changed

+153
-15
lines changed

docs/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ The Common Expression Language (CEL) is a non-Turing complete language designed
5656
# With context
5757
cel 'age >= 21' --context '{"age": 25}' # → true
5858

59+
# Mixed arithmetic with modes
60+
cel '1 + 2.5' # → Error (default: strict mode)
61+
cel '1 + 2.5' --mode python # → 3.5 (python-friendly mode)
62+
cel '1 + 2.5' --mode strict # → Error (strict CEL rules)
63+
5964
# Interactive REPL
6065
cel --interactive
6166
```
@@ -93,6 +98,7 @@ The Common Expression Language (CEL) is a non-Turing complete language designed
9398
**Safe by design** (Rust core)
9499
**Ready for production**
95100
**No GIL-blocking, safe concurrent evaluation**
101+
**Flexible type handling** (Python-friendly + strict modes)
96102

97103
## Why Python CEL?
98104

docs/reference/cli-reference.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ Enable debug mode with detailed error information.
4949
cel --debug 'user.role == "admin"' --context-file user.json
5050
```
5151

52+
#### `--mode`, `-m`
53+
Set the evaluation mode for type handling.
54+
55+
```bash
56+
cel '1 + 2.5' --mode python # → 3.5 (python-friendly type promotions)
57+
cel '1 + 2.5' --mode strict # → Error (default: strict CEL type rules)
58+
cel '1 + 2.5' -m python # → 3.5 (short form)
59+
```
60+
61+
**Values**:
62+
- `python` - Python-friendly type promotions for mixed arithmetic
63+
- `strict` (default) - Strict CEL type rules with no automatic coercion
64+
65+
**Use Cases**:
66+
- `python`: Best for JSON APIs, user-friendly applications, Python integration
67+
- `strict`: Best for CEL spec compliance, type precision, cross-language consistency
68+
5269
### Context Options
5370

5471
#### `--context`, `-c`
@@ -250,6 +267,10 @@ cel 'expression'
250267
cel 'expression' --context '{"key": "value"}'
251268
cel 'expression' --context-file context.json
252269

270+
# With evaluation mode
271+
cel 'expression' --mode python # Python-friendly mixed arithmetic
272+
cel 'expression' --mode strict # Default: strict CEL type rules
273+
253274
# Interactive mode
254275
cel --interactive
255276
```

docs/reference/python-api.md

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,96 @@ Complete autogenerated reference for the Python CEL library.
66

77
::: cel.evaluate
88

9+
## Enums
10+
11+
### EvaluationMode
12+
13+
**Controls how CEL expressions handle type compatibility.**
14+
15+
The EvaluationMode enum allows you to choose between Python-friendly type coercion (default) and strict CEL type enforcement:
16+
17+
```python
18+
from cel import evaluate, EvaluationMode
19+
20+
# Python-friendly mode (default) - allows mixed arithmetic
21+
result = evaluate("1 + 2.5") # → 3.5
22+
result = evaluate("1 + 2.5", mode=EvaluationMode.PYTHON) # → 3.5
23+
result = evaluate("1 + 2.5", mode="python") # → 3.5
24+
25+
# Strict mode - enforces strict CEL type rules
26+
try:
27+
evaluate("1 + 2.5", mode=EvaluationMode.STRICT) # TypeError
28+
except TypeError as e:
29+
print(f"Strict mode error: {e}") # → "Unsupported addition operation"
30+
31+
try:
32+
evaluate("1 + 2.5", mode="strict") # TypeError
33+
except TypeError as e:
34+
print(f"Strict mode error: {e}") # → "Unsupported addition operation"
35+
```
36+
37+
#### Values
38+
39+
**EvaluationMode.PYTHON** (or `"python"`)
40+
*Default mode for Python API.* Enables Python-friendly type promotions for better mixed arithmetic compatibility:
41+
- Integer literals are automatically promoted to floats when used with floats
42+
- Context variables are promoted from int to float when mixed arithmetic is detected
43+
- Expression preprocessing converts `1 + 2.5` to `1.0 + 2.5` for compatibility
44+
45+
**EvaluationMode.STRICT** (or `"strict"`)
46+
*Default mode for CLI.* Enforces strict CEL type rules with no automatic coercion:
47+
- Mixed int/float arithmetic raises TypeError
48+
- No type promotions or expression rewriting
49+
- Matches WebAssembly CEL behavior exactly
50+
51+
#### Default Modes
52+
53+
**Note**: The Python API and CLI have different defaults:
54+
- **Python API**: Defaults to `EvaluationMode.PYTHON` for seamless integration with Python code
55+
- **CLI**: Defaults to `EvaluationMode.STRICT` for CEL specification compliance and testing
56+
57+
#### When to Use Each Mode
58+
59+
**Use PYTHON mode (Python API default) when:**
60+
- Integrating with existing Python code that expects mixed numeric types
61+
- Working with data from JSON APIs (which often mix ints and floats)
62+
- Building user-friendly applications where type coercion feels natural
63+
- Migrating from pure Python evaluation logic
64+
65+
**Use STRICT mode (CLI default) when:**
66+
- Building applications that need to match CEL implementations in other languages
67+
- Working with systems where type precision is critical
68+
- Following strict CEL specification compliance
69+
- Debugging type-related issues in expressions
70+
71+
#### Implementation Details
72+
73+
In PYTHON mode, the library:
74+
1. Analyzes context for mixed numeric types (int + float)
75+
2. Promotes integers to floats in both context variables and expression literals
76+
3. Preprocesses expressions like `1 + 2.5` to become `1.0 + 2.5`
77+
78+
In STRICT mode, the library:
79+
1. Performs no type promotions or expression rewriting
80+
2. Passes expressions directly to the CEL evaluator
81+
3. Raises TypeError for incompatible type operations
82+
83+
**Example with Context:**
84+
```python
85+
from cel import evaluate, EvaluationMode
86+
87+
context = {"x": 1, "y": 2.5} # Mixed int/float in context
88+
89+
# Python mode handles mixed types gracefully
90+
result = evaluate("x + y", context, mode=EvaluationMode.PYTHON) # → 3.5
91+
92+
# Strict mode rejects mixed types
93+
try:
94+
evaluate("x + y", context, mode=EvaluationMode.STRICT) # TypeError
95+
except TypeError as e:
96+
print("Mixed types not allowed in strict mode")
97+
```
98+
999
## Classes
10100

11101
### Context
@@ -292,12 +382,45 @@ except TypeError as e:
292382
assert "Unsupported multiplication operation" in str(e)
293383
```
294384

385+
#### EvaluationMode-Specific Errors
386+
387+
**Strict mode can produce additional TypeError exceptions:**
388+
389+
```python
390+
from cel import evaluate, EvaluationMode
391+
392+
# Mixed numeric types in strict mode
393+
try:
394+
evaluate("1 + 2.5", mode=EvaluationMode.STRICT)
395+
except TypeError as e:
396+
assert "Unsupported addition operation" in str(e)
397+
print(f"Strict mode type error: {e}")
398+
399+
# Mixed types from context in strict mode
400+
context = {"int_val": 10, "float_val": 2.5}
401+
try:
402+
evaluate("int_val * float_val", context, mode=EvaluationMode.STRICT)
403+
except TypeError as e:
404+
assert "Unsupported multiplication operation" in str(e)
405+
print(f"Context type mixing error: {e}")
406+
```
407+
408+
**Invalid mode strings raise TypeError:**
409+
410+
```python
411+
try:
412+
evaluate("1 + 2", mode="invalid_mode")
413+
except TypeError as e:
414+
assert "Invalid EvaluationMode" in str(e)
415+
print(f"Invalid mode error: {e}")
416+
```
417+
295418
### Production Error Handling
296419

297420
For comprehensive error handling patterns, safety guidelines, and production best practices, see the **[Error Handling How-To Guide](../how-to-guides/error-handling.md)** which covers:
298421

299422
- Safe handling of malformed expressions and untrusted input
300-
- Safe evaluation wrappers
423+
- Safe evaluation wrappers with EvaluationMode considerations
301424
- Context validation patterns
302425
- Defensive expression techniques
303426
- Logging and monitoring

python/cel/__init__.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,3 @@ class EvaluationMode(str, Enum):
1919
"""
2020
Enforces strict cel-rust type rules with no automatic coercion to match Wasm behavior.
2121
"""
22-
23-
24-
__doc__ = cel.__doc__
25-
if hasattr(cel, "__all__"):
26-
# Ensure EvaluationMode is always exported even if Rust module defines __all__
27-
__all__ = list(cel.__all__) + ["EvaluationMode"]
28-
else:
29-
__all__ = [
30-
"evaluate",
31-
"Context",
32-
"EvaluationMode",
33-
]

python/cel/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def _get_auto_renderable(result: Any) -> Any:
192192
class CELEvaluator:
193193
"""Enhanced CEL expression evaluator."""
194194

195-
def __init__(self, context: Optional[Dict[str, Any]] = None, mode: str = "python"):
195+
def __init__(self, context: Optional[Dict[str, Any]] = None, mode: str = "strict"):
196196
"""Initialize evaluator with optional context and mode."""
197197
self.context = context or {}
198198
self.mode = mode
@@ -562,7 +562,7 @@ def main(
562562
"--mode",
563563
help="Evaluation mode: python (mixed arithmetic) or strict (type matching)",
564564
),
565-
] = "python",
565+
] = "strict",
566566
interactive: Annotated[
567567
bool, typer.Option("-i", "--interactive", help="Start interactive REPL mode")
568568
] = False,

0 commit comments

Comments
 (0)