You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Distinguish parse errors from execution errors with different exception types #23
Currently, the library raises ValueError for both parse/syntax errors and execution errors (like type mismatches). This makes it difficult for users to distinguish between:
Configuration errors (invalid CEL syntax) - These should be surfaced immediately to the user
Runtime validation failures (type errors, etc.) - These are expected failures during normal operation
Current Behavior
fromcelimportcel# Parse error - raises ValueErrortry:
cel.evaluate('invalid syntax +++', cel.Context())
exceptValueErrorase:
print(f'Parse error: {e}')
# Output: Failed to parse expression 'invalid syntax +++'...# Type error - also raises ValueErrortry:
cel.evaluate('"string" > 100', cel.Context(variables={'value': 'string'}))
exceptValueErrorase:
print(f'Type error: {e}')
# Output: CEL execution error: String("string") can not be compared to Int(100)...
Desired Behavior
It would be helpful if the library used different exception types:
Parse/syntax errors: ValueError (or a specific CELParseError subclass)
Execution errors: RuntimeError (or a specific CELExecutionError subclass)
Undefined variables: RuntimeError (already works this way ✅)
Workaround
Currently working around this by checking the error message string:
try:
result=cel.evaluate(validation_rule, env)
exceptValueErrorase:
if"Failed to parse"instr(e):
# Parse error - re-raiseraiseValueError(f"Invalid CEL expression: {e}") fromeelse:
# Execution error - treat as validation failurereturnFalse
Use Case
In netcheck, we use CEL for validation rules. When a user provides an invalid rule (syntax error), we want to fail immediately with a clear error. When execution fails (type mismatch, etc.), we want to treat it as a failed validation check.
Proposed Solution
Option 1: Use existing Python exceptions
raiseValueError("Parse error: ...") # For syntax errorsraiseRuntimeError("Execution error: ...") # For type/execution errors
Implemented along the lines of Option 1, and now documented. Verified against main (bc21129, cel 0.14.3):
Expression
Exception
1 +
ValueError: Failed to parse expression '1 +': ...
'unterminated
ValueError
undefined_var
RuntimeError: Undefined variable or function: 'undefined_var'
1 + 'a'
TypeError: No such overload ...
"string" > 100
TypeError (values not comparable)
{'a': 1}['b']
KeyError
1/0
ZeroDivisionError
9223372036854775807 + 1
OverflowError
So parse/compile failures are ValueError and execution failures are never ValueError — the "Failed to parse" in str(e) workaround from the issue is no longer needed. Execution errors are mapped to the idiomatic Python exception for each case rather than all being RuntimeError; KeyError, ZeroDivisionError and OverflowError are the notable extras. The contract is documented in docs/how-to-guides/error-handling.md.
For the netcheck use case that means:
try:
result=cel.evaluate(validation_rule, env)
exceptValueError:
raise# invalid rule — surface immediatelyexcept (RuntimeError, TypeError, KeyError, ArithmeticError):
returnFalse# rule ran and failed — treat as a failed check
Closing as completed. The one thing Option 2 would still add is a common base class so all execution errors could be caught in a single except — happy to revisit that as a separate issue if the multi-clause catch proves awkward in practice.
Activity
hardbyte commentedon Aug 19, 2026
Implemented along the lines of Option 1, and now documented. Verified against
main(bc21129, cel 0.14.3):1 +ValueError: Failed to parse expression '1 +': ...'unterminatedValueErrorundefined_varRuntimeError: Undefined variable or function: 'undefined_var'1 + 'a'TypeError: No such overload ..."string" > 100TypeError(values not comparable){'a': 1}['b']KeyError1/0ZeroDivisionError9223372036854775807 + 1OverflowErrorSo parse/compile failures are
ValueErrorand execution failures are neverValueError— the"Failed to parse" in str(e)workaround from the issue is no longer needed. Execution errors are mapped to the idiomatic Python exception for each case rather than all beingRuntimeError;KeyError,ZeroDivisionErrorandOverflowErrorare the notable extras. The contract is documented in docs/how-to-guides/error-handling.md.For the netcheck use case that means:
Closing as completed. The one thing Option 2 would still add is a common base class so all execution errors could be caught in a single
except— happy to revisit that as a separate issue if the multi-clause catch proves awkward in practice.Generated by Claude Code