Skip to content

Commit 317edcd

Browse files
authored
Feat/strict mode (#13)
* Add type stubs for CEL Rust extension module * Add EvaluationMode for evaluation * Test evaluation mode
1 parent 5f9b38d commit 317edcd

File tree

13 files changed

+511
-52
lines changed

13 files changed

+511
-52
lines changed

.github/workflows/ci.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
run: uv run pytest --verbose --tb=short
5454

5555
lint:
56-
name: Code Quality
56+
name: Code Quality & Type Checking
5757
runs-on: ubuntu-latest
5858
steps:
5959
- uses: actions/checkout@v4
@@ -87,6 +87,19 @@ jobs:
8787

8888
- name: Run Python linting
8989
run: uv run ruff check .
90+
91+
- name: Test type stub files exist
92+
run: |
93+
test -f python/cel/cel.pyi || (echo "❌ Type stub file missing" && exit 1)
94+
test -f python/cel/py.typed || (echo "❌ PEP 561 marker missing" && exit 1)
95+
echo "✅ Type stub files present"
96+
97+
- name: Test ty type checker integration
98+
run: |
99+
echo "from cel import evaluate, Context; evaluate('1+1')" > test_types.py
100+
uvx ty check test_types.py
101+
rm test_types.py
102+
echo "✅ Type checker integration working"
90103
linux:
91104
runs-on: ${{ matrix.platform.runner }}
92105
needs: [test, lint]

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### ✨ Added
11+
12+
- **EvaluationMode enum**: Control type handling behavior in CEL expressions
13+
- `EvaluationMode.PYTHON` (default for Python API): Python-friendly type promotions
14+
- `EvaluationMode.STRICT` (default for CLI): Strict CEL type rules with no coercion
15+
- **Type checking support**: Added complete type stub files (`.pyi`) for PyO3 extension
16+
17+
1018

1119
## [0.5.0] - 2025-08-08
1220

docs/contributing.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,18 @@ def add_function(self, name: str, func: Callable) -> None:
246246
"""
247247
```
248248

249+
### Type Checking Support
250+
251+
This package includes manual type stub files (`.pyi`) for the PyO3 Rust extension to support static type checking.
252+
253+
**Type checker verification:**
254+
```bash
255+
uvx ty check your_file.py # ✅ Should pass without "no member" errors
256+
uvx mypy your_file.py # ✅ Works with proper MYPYPATH setup
257+
```
258+
259+
See https://pyo3.rs/main/type-stub to opt-in to the automated types when implemented in Maturin.
260+
249261
## Debugging & Troubleshooting
250262

251263
### Common Issues

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: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,97 @@ 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+
85+
```python
86+
from cel import evaluate, EvaluationMode
87+
88+
context = {"x": 1, "y": 2.5} # Mixed int/float in context
89+
90+
# Python mode handles mixed types gracefully
91+
result = evaluate("x + y", context, mode=EvaluationMode.PYTHON) # → 3.5
92+
93+
# Strict mode rejects mixed types
94+
try:
95+
evaluate("x + y", context, mode=EvaluationMode.STRICT) # TypeError
96+
except TypeError as e:
97+
print("Mixed types not allowed in strict mode")
98+
```
99+
9100
## Classes
10101

11102
### Context
@@ -292,12 +383,45 @@ except TypeError as e:
292383
assert "Unsupported multiplication operation" in str(e)
293384
```
294385

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

297421
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:
298422

299423
- Safe handling of malformed expressions and untrusted input
300-
- Safe evaluation wrappers
424+
- Safe evaluation wrappers with EvaluationMode considerations
301425
- Context validation patterns
302426
- Defensive expression techniques
303427
- Logging and monitoring

python/cel/__init__.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
# Import the Rust extension
2-
# Import CLI functionality
2+
33
from . import cli
44
from .cel import *
5-
6-
__doc__ = cel.__doc__
7-
if hasattr(cel, "__all__"):
8-
__all__ = cel.__all__
9-
else:
10-
__all__ = [
11-
"evaluate",
12-
"Context",
13-
]
5+
from .evaluation_modes import EvaluationMode

python/cel/cel.pyi

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
Type stubs for the CEL Rust extension module.
3+
"""
4+
5+
from typing import Any, Callable, Dict, Literal, Optional, Union, overload
6+
7+
from .evaluation_modes import EvaluationMode
8+
9+
class Context:
10+
"""CEL evaluation context for variables and functions."""
11+
12+
@overload
13+
def __init__(self) -> None: ...
14+
@overload
15+
def __init__(self, variables: Dict[str, Any]) -> None: ...
16+
@overload
17+
def __init__(
18+
self,
19+
variables: Optional[Dict[str, Any]] = None,
20+
*,
21+
functions: Optional[Dict[str, Callable[..., Any]]] = None,
22+
) -> None: ...
23+
def add_variable(self, name: str, value: Any) -> None:
24+
"""Add a variable to the context."""
25+
...
26+
27+
def add_function(self, name: str, func: Callable[..., Any]) -> None:
28+
"""Add a function to the context."""
29+
...
30+
31+
def update(self, variables: Dict[str, Any]) -> None:
32+
"""Update context with variables from a dictionary."""
33+
...
34+
35+
def evaluate(
36+
expression: str,
37+
context: Optional[Union[Dict[str, Any], Context]] = None,
38+
*,
39+
mode: Union[Literal["python", "strict"], "EvaluationMode", str] = "python",
40+
) -> Any:
41+
"""
42+
Evaluate a CEL expression.
43+
44+
Args:
45+
expression: The CEL expression to evaluate
46+
context: Optional context with variables and functions
47+
mode: Evaluation mode - either "python" (default) for mixed arithmetic
48+
or "strict" for strict type matching
49+
50+
Returns:
51+
The result of evaluating the expression
52+
"""
53+
...

0 commit comments

Comments
 (0)