Skip to content
Open
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
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ bazel_dep(name = "bazel_skylib", version = "1.8.2")
# https://registry.bazel.build/modules/cel-cpp
bazel_dep(name = "cel-cpp", version = "0.14.0", repo_name = "com_google_cel_cpp")

# 03/30/2026
# 04/01/2026
_CEL_CPP_COMMIT = "5a3463337cf2a9b90b53833af2bbc1f35da90d64"

_CEL_CPP_SHA256 = "299be398d1495340eb92da31f9a0667e1351479752e8a567ac31c385e4aea73c"
Expand Down
112 changes: 90 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,76 @@ Resulting message type: <class '...TestAllTypes'>
Resulting message value: 123
```

### Custom functions

When configuring `cel.Env` you can supply custom functions. For each function
there needs to be a declaration and an implementation. The implementation, which
is a Python function, can be provided either as part of the declaration
or separately.

Let's say we want to be able to invoke this function from CEL expressions:
```python
def good_time_of_day(ampm, arg):
if ampm == 'am':
time_of_day = 'morning'
else:
time_of_day = 'afternoon'
return f"Good {time_of_day}, {arg}"
```

The implementation can be supplied along with the declaration:
```python
cel_env = cel.NewEnv(functions=[
cel.FunctionDecl(
"hello",
[
cel.Overload(
"hello(string,string)",
return_type=cel.Type.STRING,
parameters=[
cel.Type.STRING,
cel.Type.STRING,
],
impl=good_time_of_day,
)
],
)
])
```

It can also be provided separately in a dictionary that maps overload IDs to
their respective implementations:

```python
cel_env = cel.NewEnv(functions=[
cel.FunctionDecl(
"hello",
[
cel.Overload(
"hello(string,string)",
return_type=cel.Type.STRING,
parameters=[
cel.Type.STRING,
cel.Type.STRING,
],
)
],
)
],
function_impls={
"hello(string,string)": good_time_of_day,
})
```

Now that the function implementation is bound to the CEL environment,
we can invoke it from CEL like this:
```python
result = env.compile("hello('am', 'breakfast is ready!')").eval()
print(result.value()) # Good morning, breakfast is ready!
result = env.compile("hello('pm', 'tea is served.')").eval()
print(result.value()) # Good afternoon, tea is served.
```

### Extensions

#### Standard extensions
Expand Down Expand Up @@ -234,36 +304,34 @@ expr = cel_env.compile("my_func(1)")

To define a custom extension in C++, define a class extending
`cel_python::CelExtension`. There are two methods you will need to implement:
`ConfigureCompiler` and `ConfigureRuntime`. The implementations of these methods
use the same API as extensions written for the C++ CEL runtime. In fact,
`GetCompilerLibrary` and `ConfigureRuntime`. The implementations of these
methods use the same API as extensions written for the C++ CEL runtime. In fact,
extensions written for the C++ runtime can be used unchanged with
cel-expr-python - you would just need to write a trivial wrapper class invoking
the registration functions defined by the C++ extension.

```cpp
absl::Status ConfigureCompiler(
cel::CompilerBuilder& compiler_builder,
const proto2::DescriptorPool& descriptor_pool);
```
This method adds extension function definitions to the provided
`CompilerBuilder`, for example:

```cpp
absl::Status ConfigureCompiler(
cel::CompilerBuilder& compiler_builder,
const proto2::DescriptorPool& descriptor_pool) {
CEL_PYTHON_ASSIGN_OR_RETURN(
auto func_translate,
cel::MakeFunctionDecl("translate",
cel::MakeMemberOverloadDecl("translate_inst",
/*return_type=*/cel::StringType(),
/*target=*/cel::StringType(),
/*from_lang=*/cel::StringType(),
/*to_lang=*/cel::StringType())));
CEL_PYTHON_RETURN_IF_ERROR(
compiler_builder.GetCheckerBuilder().AddFunction(func_translate));
return absl::OkStatus();
}
cel::CompilerLibrary GetCompilerLibrary() {
return cel::CompilerLibrary(
"translate-ext",
[](cel::TypeCheckerBuilder& checker_builder) -> absl::Status {
CEL_PYTHON_ASSIGN_OR_RETURN(
auto func_translate,
cel::MakeFunctionDecl(
"translate",
cel::MakeMemberOverloadDecl("translate_inst",
/*return_type=*/cel::StringType(),
/*target=*/cel::StringType(),
/*from_lang=*/cel::StringType(),
/*to_lang=*/cel::StringType())));
CEL_PYTHON_RETURN_IF_ERROR(
checker_builder.AddFunction(func_translate));
return absl::OkStatus();
});
}
```

The other method registers the actual implementation
Expand Down
26 changes: 23 additions & 3 deletions cel_expr_python/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pybind_extension(
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:span",
"@com_google_cel_cpp//checker:type_checker_builder",
"@com_google_cel_cpp//checker:type_checker_builder_factory",
"@com_google_cel_cpp//checker:validation_result",
"@com_google_cel_cpp//common:ast",
"@com_google_cel_cpp//common:ast_proto",
Expand All @@ -78,8 +79,13 @@ pybind_extension(
"@com_google_cel_cpp//compiler",
"@com_google_cel_cpp//env",
"@com_google_cel_cpp//env:config",
"@com_google_cel_cpp//env:env_runtime",
"@com_google_cel_cpp//env:env_std_extensions",
"@com_google_cel_cpp//env:env_yaml",
"@com_google_cel_cpp//env:runtime_std_extensions",
"@com_google_cel_cpp//extensions/protobuf:runtime_adapter",
"@com_google_cel_cpp//parser",
"@com_google_cel_cpp//parser:options",
"@com_google_cel_cpp//parser:parser_interface",
"@com_google_cel_cpp//runtime",
"@com_google_cel_cpp//runtime:activation",
Expand All @@ -88,7 +94,6 @@ pybind_extension(
"@com_google_cel_cpp//runtime:reference_resolver",
"@com_google_cel_cpp//runtime:runtime_builder",
"@com_google_cel_cpp//runtime:runtime_options",
"@com_google_cel_cpp//runtime:standard_runtime_builder_factory",
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
"@com_google_cel_spec//proto/cel/expr:syntax_cc_proto",
"@com_google_protobuf//:protobuf",
Expand All @@ -99,14 +104,25 @@ pybind_extension(
# For pybind11-based CEL extensions.
pybind_library(
name = "cel_extension",
hdrs = ["cel_extension.h"],
srcs = [
"py_error_status.cc",
],
hdrs = [
"cel_extension.h",
"py_error_status.h",
],
visibility = ["//visibility:public"],
deps = [
":status_macros",
"@com_google_absl//absl/base:no_destructor",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/log:absl_check",
"@com_google_absl//absl/log:absl_log",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_cel_cpp//compiler",
"@com_google_cel_cpp//runtime:runtime_builder",
"@com_google_cel_cpp//runtime:runtime_options",
"@com_google_protobuf//:protobuf",
],
)

Expand Down Expand Up @@ -135,6 +151,10 @@ py_test(
srcs = ["cel_env_test.py"],
deps = [
":cel",
"//cel_expr_python/ext:ext_bindings",
"//cel_expr_python/ext:ext_math",
"//cel_expr_python/ext:ext_optional",
"//cel_expr_python/ext:ext_strings",
"//testing:proto2_test_all_types_py_pb2",
"@com_google_absl_py//absl/testing:absltest",
],
Expand Down
Loading
Loading