diff --git a/docs/how-to-guides/cli-recipes.md b/docs/how-to-guides/cli-recipes.md index 4efbca1..d2a56e5 100644 --- a/docs/how-to-guides/cli-recipes.md +++ b/docs/how-to-guides/cli-recipes.md @@ -548,8 +548,8 @@ cel 'users.map(u, { "permissions_count": u.permissions.size() })' --context-file users.json -# Filter and aggregate -cel 'products.filter(p, p.price > 100).map(p, p.price).fold(sum, 0, sum + item)' \ +# Filter and extract prices +cel 'products.filter(p, p.price > 100).map(p, p.price)' \ --context-file products.json # Nested filtering diff --git a/docs/reference/cel-compliance.md b/docs/reference/cel-compliance.md index 93b875e..b135065 100644 --- a/docs/reference/cel-compliance.md +++ b/docs/reference/cel-compliance.md @@ -6,20 +6,21 @@ This document tracks the compliance of this Python CEL implementation with the [ - **Implementation**: Based on [`cel`](https://crates.io/crates/cel) v0.11.0 Rust crate (formerly cel-interpreter) - **Estimated Compliance**: ~80% of CEL specification features. -- **Test Coverage**: 300+ tests across 15+ test files including comprehensive CLI testing and upstream improvement detection +- **Test Coverage**: 300+ tests across 16+ test files including comprehensive CLI testing and upstream improvement detection ## ðŸšĻ Missing Features & Severity Overview -| **Feature** | **Severity** | **Impact** | **Workaround Available** | **Upstream Priority** | -|-------------|--------------|------------|--------------------------|----------------------| -| **OR operator behavior** | ðŸ”ī **HIGH** | Returns original values instead of booleans | Use explicit boolean conversion | **CRITICAL** | -| **String utility functions** | ðŸŸĄ **MEDIUM** | Limited string processing capabilities | Use Python context functions | **HIGH** | -| **Type introspection (`type()`)** | ðŸŸĄ **MEDIUM** | No runtime type checking | Use Python type checking | **HIGH** | -| **Mixed int/uint arithmetic** | ðŸŸĄ **MEDIUM** | Manual type conversion needed | Use explicit casting | **MEDIUM** | -| **Mixed-type arithmetic in macros** | ðŸŸĄ **MEDIUM** | Type coercion issues in collections | Ensure type consistency | **MEDIUM** | -| **Bytes concatenation** | ðŸŸĒ **LOW** | Cannot concatenate byte arrays | Convert through string | **LOW** | -| **Math functions (`ceil`, `floor`)** | ðŸŸĒ **LOW** | No mathematical utilities | Use Python context functions | **LOW** | -| **Optional values** | ðŸŸĒ **LOW** | No optional chaining syntax | Use `has()` checks | **FUTURE** | +| **Feature** | **Severity** | **Impact** | **Workaround Available** | **Upstream Priority** | +|-----------------------------------------------------|--------------|------------|--------------------------|----------------------| +| **OR operator behavior** | ðŸ”ī **HIGH** | Returns original values instead of booleans | Use explicit boolean conversion | **CRITICAL** | +| **String utility functions** | ðŸŸĄ **MEDIUM** | Limited string processing capabilities | Use Python context functions | **HIGH** | +| **Type introspection (`type()`)** | ðŸŸĄ **MEDIUM** | No runtime type checking | Use Python type checking | **HIGH** | +| **Mixed int/uint arithmetic** | ðŸŸĄ **MEDIUM** | Manual type conversion needed | Use explicit casting | **MEDIUM** | +| **Mixed-type arithmetic in macros** | ðŸŸĄ **MEDIUM** | Type coercion issues in collections | Ensure type consistency | **MEDIUM** | +| **Bytes concatenation** | ðŸŸĒ **LOW** | Cannot concatenate byte arrays | Convert through string | **LOW** | +| **Math functions (`ceil`, `floor`)** | ðŸŸĒ **LOW** | No mathematical utilities | Use Python context functions | **LOW** | +| **Collection aggregation (`sum`, `fold`, `reduce`)** | ðŸŸĒ **LOW** | No aggregation functions | Use Python context functions | **LOW** | +| **Optional values** | ðŸŸĒ **LOW** | No optional chaining syntax | Use `has()` checks | **FUTURE** | **Legend**: ðŸ”ī High Impact | ðŸŸĄ Medium Impact | ðŸŸĒ Low Impact @@ -134,6 +135,7 @@ count + 1 // If count=5, stays as 5 + 1 → 6 | `matches()` | `string.matches(pattern) -> bool` | Regex matching | `bool` | ✅ Working | | `min()` | `min(list) -> value` | Find minimum value | Various | ✅ Working | | `max()` | `max(list) -> value` | Find maximum value | Various | ✅ Working | +| `sum()` | `sum(list) -> number` | Sum numeric values | N/A | ❌ **NOT AVAILABLE** | ### ✅ String Operations - **contains()**: `"hello".contains("ell")` → `True` @@ -150,6 +152,10 @@ count + 1 // If count=5, stays as 5 + 1 → 6 - **filter()**: `[1,2,3].filter(x, x > 1)` → `[2.0, 3.0]` (with type coercion) - **map()**: Limited due to type system restrictions ⚠ïļ **PARTIAL** (requires type-compatible operations) +### ❌ Missing Collection Functions +- **fold()**: `[1,2,3].fold(0, sum, sum + x)` - Collection aggregation ❌ **NOT AVAILABLE** +- **reduce()**: `reduce([1,2,3], 0, sum + x)` - Reduction operations ❌ **NOT AVAILABLE** + ### ✅ Python Integration - **Automatic type conversion**: Seamless Python ↔ CEL type mapping - **Context variables**: Access Python objects in expressions @@ -350,6 +356,7 @@ This section covers upstream work, detection strategies, and contribution opport - **Detection**: ✅ Full detection for all missing functions **Missing functions**: - Math: `ceil()`, `floor()`, `round()` - Mathematical functions +- Collection: `fold()`, `reduce()` - Collection aggregation functions - Collection: Enhanced `in` operator behaviors - URL/IP: `isURL()`, `isIP()` - Validation functions (available in some CEL implementations) @@ -483,7 +490,7 @@ Both the CLI tool and the core `evaluate()` function now handle all malformed in ### ðŸŽŊ Upstream Contribution Priorities #### High Priority (Ready for Contribution) -1. **String utility functions** - ✅ **Detection Ready** +1. **String utility functions** - ✅ **Detection Ready** (`test_upstream_detection.py`) - Functions: `lowerAscii`, `upperAscii`, `indexOf`, `lastIndexOf`, `substring`, `replace`, `split`, `join` - Impact: **MEDIUM** - Widely used in string processing applications - Contribution path: cel crate standard library expansion @@ -493,7 +500,7 @@ Both the CLI tool and the core `evaluate()` function now handle all malformed in - Impact: **HIGH** - Breaks specification conformance - Contribution path: Core logical operation fixes -3. **Type introspection function** - ✅ **Detection Ready** +3. **Type introspection function** - ✅ **Detection Ready** (`test_upstream_detection.py`) - Function: `type()` for runtime type checking - Impact: **MEDIUM** - Useful for dynamic expressions - Contribution path: Leverage existing type system infrastructure @@ -510,12 +517,17 @@ Both the CLI tool and the core `evaluate()` function now handle all malformed in - Contribution path: Arithmetic type coercion enhancements #### Low Priority (Future Features) -6. **Math functions** - ✅ **Detection Ready** +6. **Collection aggregation functions** - ✅ **Detection Ready** + - Functions: `sum()`, `fold()`, `reduce()` + - Impact: **LOW** - Can be implemented via Python context + - Contribution path: Standard library expansion + +7. **Math functions** - ✅ **Detection Ready** - Functions: `ceil`, `floor`, `round` - Impact: **LOW** - Can be implemented via Python context - Contribution path: Standard library expansion -7. **Optional value handling** - ✅ **Detection Ready** +8. **Optional value handling** - ✅ **Detection Ready** - Features: `optional.of()`, `.orValue()`, `?` chaining - Impact: **LOW** - Alternative patterns exist - Contribution path: Type system extensions diff --git a/src/lib.rs b/src/lib.rs index c833c28..c008fe3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -730,7 +730,6 @@ fn cel(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { pyo3_log::init(); m.add_function(wrap_pyfunction!(evaluate, m)?)?; - m.add_class::()?; Ok(()) } diff --git a/tests/test_functions.py b/tests/test_functions.py index a269d44..6c136a1 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -6,6 +6,22 @@ import pytest +class TestBuiltInCollectionFunctions: + """Test built-in collection functions that work in CEL.""" + + def test_min_function_works(self): + """Test that min() function works correctly.""" + assert cel.evaluate("min([3, 1, 4, 1, 5])") == 1 + assert cel.evaluate("min([1.5, 2.3, 0.8])") == 0.8 + assert cel.evaluate("min(['banana', 'apple', 'cherry'])") == "apple" + + def test_max_function_works(self): + """Test that max() function works correctly.""" + assert cel.evaluate("max([3, 1, 4, 1, 5])") == 5 + assert cel.evaluate("max([1.5, 2.3, 0.8])") == 2.3 + assert cel.evaluate("max(['banana', 'apple', 'cherry'])") == "cherry" + + def test_custom_function(): def custom_function(a, b): return a + b @@ -366,9 +382,9 @@ def simple_add(a, b): end_time = time.perf_counter() avg_time = (end_time - start_time) / iterations - # Should be reasonably fast (under 200 microseconds per call) - # Adjusted threshold for realistic hardware performance - assert avg_time < 0.0002, f"Function call too slow: {avg_time * 1000000:.1f} Ξs per call" + # Should be reasonably fast (under 300 microseconds per call) + # Adjusted threshold for realistic hardware performance and CI environments + assert avg_time < 0.0003, f"Function call too slow: {avg_time * 1000000:.1f} Ξs per call" def test_complex_function_call_performance(self): """Test performance of more complex function calls.""" diff --git a/tests/test_upstream_improvements.py b/tests/test_upstream_improvements.py index 64743ee..0bc278a 100644 --- a/tests/test_upstream_improvements.py +++ b/tests/test_upstream_improvements.py @@ -254,6 +254,62 @@ def test_join_not_implemented(self): cel.evaluate('["hello", "world"].join(",")') +class TestMissingAggregationFunctions: + """Test aggregation functions that are missing from CEL.""" + + def test_sum_function_not_available(self): + """ + Test that sum() function is not currently available. + + When this test starts failing, sum() has been implemented upstream. + """ + with pytest.raises(RuntimeError, match="Undefined variable or function.*sum"): + cel.evaluate("sum([1, 2, 3, 4, 5])") + + def test_fold_function_not_available(self): + """ + Test that fold() is not available - various syntax attempts. + + When this test starts failing, fold() has been implemented upstream. + """ + # Method syntax + with pytest.raises((RuntimeError, ValueError)): + cel.evaluate("[1, 2, 3, 4, 5].fold(0, (acc, x) -> acc + x)") + + # Global function syntax + with pytest.raises(RuntimeError, match="Undefined variable or function.*fold"): + cel.evaluate("fold([1, 2, 3], 0, sum + x)") + + def test_reduce_function_not_available(self): + """ + Test that reduce() is not available - various syntax attempts. + + When this test starts failing, reduce() has been implemented upstream. + """ + # Global function syntax + with pytest.raises(RuntimeError, match="Undefined variable or function.*reduce"): + cel.evaluate("reduce([1, 2, 3, 4, 5], 0, sum + x)") + + # Method syntax + with pytest.raises((RuntimeError, ValueError)): + cel.evaluate("[1, 2, 3].reduce(0, (acc, x) -> acc + x)") + + @pytest.mark.xfail(reason="Aggregation functions not implemented in cel v0.11.0", strict=False) + def test_aggregation_functions_expected_behavior(self): + """ + Test expected aggregation function behavior when implemented. + + This test will pass when upstream implements sum(), fold(), reduce(). + """ + # Sum function + assert cel.evaluate("sum([1, 2, 3, 4, 5])") == 15 + assert cel.evaluate("sum([1.1, 2.2, 3.3])") == pytest.approx(6.6) + + # Fold/reduce functions (syntax may differ when actually implemented) + assert cel.evaluate("[1, 2, 3, 4].fold(0, (acc, x) -> acc + x)") == 10 + assert cel.evaluate("[1, 2, 3].fold(1, (acc, x) -> acc * x)") == 6 + + class TestMathFunctions: """Test missing mathematical functions.""" @@ -316,6 +372,7 @@ def test_upstream_improvements_summary(): upstream improvements we're monitoring. """ improvements_to_watch = { + "Missing aggregation functions": ["sum()", "fold()", "reduce()"], "String functions": [ "lowerAscii", "upperAscii",