Skip to content

Commit 268ea44

Browse files
dmitriplotnikovcopybara-github
authored andcommitted
Support passing cel.ExpressionContainer to cel.NewEnv to allow specifying aliases and abbreviations for namespace resolution.
PiperOrigin-RevId: 912692728
1 parent 09411c8 commit 268ea44

13 files changed

Lines changed: 382 additions & 29 deletions

MODULE.bazel

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@ module(
33
)
44

55
# https://registry.bazel.build/modules/abseil-cpp
6-
bazel_dep(name = "abseil-cpp", version = "20250814.1", repo_name = "com_google_absl")
6+
bazel_dep(name = "abseil-cpp", version = "20260107.0", repo_name = "com_google_absl")
77

88
# https://registry.bazel.build/modules/abseil-py
9-
bazel_dep(name = "abseil-py", version = "2.1.0", repo_name = "com_google_absl_py")
9+
bazel_dep(name = "abseil-py", version = "2.4.0", repo_name = "com_google_absl_py")
1010

1111
# https://github.com/bazelbuild/bazel-skylib
12-
bazel_dep(name = "bazel_skylib", version = "1.8.2")
12+
bazel_dep(name = "bazel_skylib", version = "1.9.0")
1313

1414
# https://registry.bazel.build/modules/cel-cpp
1515
bazel_dep(name = "cel-cpp", version = "0.15.0", repo_name = "com_google_cel_cpp")
16-
single_version_override(
16+
git_override(
1717
module_name = "cel-cpp",
18+
commit = "2e6e9ff4493bfbe0baf883107f3fb7ce6f675d88",
1819
patch_cmds = [
1920
# ABSL_CONST_INIT is incompatible with MSVC-CL with the /std:c++20 option
2021
"sed -i 's/ABSL_CONST_INIT //g' common/values/optional_value.cc",
2122
],
22-
version = "0.15.0",
23+
patch_cmds_win = [
24+
"python -c \"import sys; path='common/values/optional_value.cc'; content=open(path).read(); open(path,'w').write(content.replace('ABSL_CONST_INIT ',''))\"",
25+
],
26+
remote = "https://github.com/google/cel-cpp",
2327
)
2428

2529
# https://registry.bazel.build/modules/cel-spec
@@ -32,7 +36,7 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.2", repo_name = "com_google
3236
bazel_dep(name = "platforms", version = "1.0.0")
3337

3438
# https://registry.bazel.build/modules/protobuf
35-
bazel_dep(name = "protobuf", version = "33.1", repo_name = "com_google_protobuf")
39+
bazel_dep(name = "protobuf", version = "33.4", repo_name = "com_google_protobuf")
3640

3741
# https://registry.bazel.build/modules/pybind11_abseil
3842
bazel_dep(name = "pybind11_abseil", version = "202402.0")
@@ -41,13 +45,13 @@ bazel_dep(name = "pybind11_abseil", version = "202402.0")
4145
bazel_dep(name = "pybind11_bazel", version = "3.0.0")
4246

4347
# https://registry.bazel.build/modules/rules_cc
44-
bazel_dep(name = "rules_cc", version = "0.2.14")
48+
bazel_dep(name = "rules_cc", version = "0.2.16")
4549

4650
# https://registry.bazel.build/modules/rules_proto
4751
bazel_dep(name = "rules_proto", version = "7.1.0")
4852

4953
# https://registry.bazel.build/modules/rules_python
50-
bazel_dep(name = "rules_python", version = "1.7.0")
54+
bazel_dep(name = "rules_python", version = "1.9.0")
5155

5256
# On Windows the file system is case-insensitive, which creates a collision between
5357
# antlr4-cpp-runtime/VERSION and the system `#include <version>`
@@ -58,8 +62,16 @@ single_version_override(
5862
],
5963
)
6064

61-
python_rules = use_extension("@rules_python//python/extensions:python.bzl", "python")
62-
python_rules.toolchain(
63-
is_default = True,
64-
python_version = "3.11",
65+
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
66+
python.defaults(python_version = "3.11")
67+
python.toolchain(python_version = "3.11")
68+
use_repo(python, "pythons_hub")
69+
70+
pybind11_internal_configure = use_extension(
71+
"@pybind11_bazel//:internal_configure.bzl",
72+
"internal_configure_extension",
6573
)
74+
use_repo(pybind11_internal_configure, "pybind11")
75+
76+
local_repo_ext = use_extension("//cel_expr_python:local_repo_extension.bzl", "local_repo_ext")
77+
use_repo(local_repo_ext, "python_headers_custom")

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,27 @@ The `cel.NewEnv` constructor also accepts the following optional parameters:
2626
* `pool` (`descriptor_pool.DescriptorPool`): The descriptor pool used for
2727
resolving protobuf message types within CEL expressions. If not provided,
2828
a default pool (`descriptor_pool.Default()`) is used.
29-
* `container` (str): The container name used for name resolution. For example,
29+
* `container` (`str` or `cel.ExpressionContainer`): The container name used
30+
for name resolution. For example,
3031
if `container` is `"foo.bar"`, then `Baz` will resolve to
3132
`foo.bar.Baz`.
33+
34+
You can also pass a `cel.ExpressionContainer` to configure abbreviations
35+
and aliases:
36+
37+
```python
38+
container = cel.ExpressionContainer(
39+
name="foo.bar",
40+
abbreviations=["foo.bar.baz"],
41+
aliases={"my_alias": "full.name.of.something"}
42+
)
43+
cel_env = cel.NewEnv(container=container)
44+
```
45+
46+
* `abbreviations`: A list of fully qualified names that can be referred
47+
to by their last component.
48+
* `aliases`: A dictionary mapping an alias name to a fully qualified
49+
name.
3250
* `extensions` (list): A list of extension objects to load. This can include
3351
standard extensions (like `math` or `string` libraries) or custom extensions
3452
defined in Python or C++.

cel_expr_python/BUILD

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ pybind_extension(
6666
"@com_google_absl//absl/types:optional",
6767
"@com_google_absl//absl/types:span",
6868
"@com_google_cel_cpp//checker:type_checker_builder",
69-
"@com_google_cel_cpp//checker:type_checker_builder_factory",
7069
"@com_google_cel_cpp//checker:validation_result",
7170
"@com_google_cel_cpp//common:ast",
7271
"@com_google_cel_cpp//common:ast_proto",
72+
"@com_google_cel_cpp//common:container",
7373
"@com_google_cel_cpp//common:decl",
7474
"@com_google_cel_cpp//common:function_descriptor",
7575
"@com_google_cel_cpp//common:kind",
@@ -87,8 +87,6 @@ pybind_extension(
8787
"@com_google_cel_cpp//env:env_yaml",
8888
"@com_google_cel_cpp//env:runtime_std_extensions",
8989
"@com_google_cel_cpp//extensions/protobuf:runtime_adapter",
90-
"@com_google_cel_cpp//parser",
91-
"@com_google_cel_cpp//parser:options",
9290
"@com_google_cel_cpp//parser:parser_interface",
9391
"@com_google_cel_cpp//runtime",
9492
"@com_google_cel_cpp//runtime:activation",
@@ -102,6 +100,7 @@ pybind_extension(
102100
"@com_google_cel_spec//proto/cel/expr:syntax_cc_proto",
103101
"@com_google_protobuf//:protobuf",
104102
"@pybind11_abseil//pybind11_abseil:absl_casters",
103+
"@python_headers_custom//:headers",
105104
],
106105
)
107106

@@ -127,6 +126,7 @@ pybind_library(
127126
"@com_google_cel_cpp//compiler",
128127
"@com_google_cel_cpp//runtime:runtime_builder",
129128
"@com_google_cel_cpp//runtime:runtime_options",
129+
"@python_headers_custom//:headers",
130130
],
131131
)
132132

cel_expr_python/cel.pyi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class CelExtensionBase:
1515
class EnvConfig:
1616
def to_yaml(self) -> str: ...
1717

18+
class ExpressionContainer:
19+
def __init__(self, name: str = ..., abbreviations: Sequence[str] | None = ..., aliases: Mapping[str, str] | None = ...) -> None: ...
20+
def container(self) -> str: ...
21+
1822
class Env:
1923
def Activation(self, data: Mapping[str, Any] | None = ..., functions: Sequence[Function] | None = ..., arena: _InternalArena = ...) -> Activation: ...
2024
def compile(self, expression: str, disable_check: bool = ...) -> Expression: ...
@@ -80,6 +84,6 @@ class _InternalArena:
8084

8185
def Arena() -> _InternalArena: ...
8286

83-
def NewEnv(descriptor_pool: proto_descriptor_pool.DescriptorPool | Any | None = ..., config: EnvConfig | None = ..., variables: Mapping[str, Type] | None = ..., extensions: Sequence[CelExtensionBase] | None = ..., container: str | None = ..., functions: Sequence[FunctionDecl] | None = ..., function_impls: Mapping[str, Callable[..., Any]] | None = ...) -> Env: ...
87+
def NewEnv(descriptor_pool: proto_descriptor_pool.DescriptorPool | Any | None = ..., config: EnvConfig | None = ..., variables: Mapping[str, Type] | None = ..., extensions: Sequence[CelExtensionBase] | None = ..., container: str | ExpressionContainer | None = ..., functions: Sequence[FunctionDecl] | None = ..., function_impls: Mapping[str, Callable[..., Any]] | None = ...) -> Env: ...
8488

8589
def NewEnvConfigFromYaml(yaml: str) -> EnvConfig: ...

cel_expr_python/cel_env_test.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,147 @@ def test_config_export_container(self):
108108
"""),
109109
)
110110

111+
def test_expression_container_abbreviations_and_aliases(self):
112+
expr_container = cel.ExpressionContainer(
113+
"test.container", abbreviations=["x.y.foo"], aliases={"abc": "x.y.bar"}
114+
)
115+
116+
env: cel.Env = cel.NewEnv(
117+
container=expr_container,
118+
variables={
119+
"x.y.foo": cel.Type.INT,
120+
"x.y.bar": cel.Type.STRING,
121+
},
122+
)
123+
124+
res = env.compile("foo").eval(data={"x.y.foo": 42})
125+
self.assertEqual(res.value(), 42)
126+
res = env.compile("abc").eval(data={"x.y.bar": "chocolate"})
127+
self.assertEqual(res.value(), "chocolate")
128+
129+
yaml: str = env.config().to_yaml()
130+
self.assertEqual(
131+
normalize_yaml(yaml),
132+
normalize_yaml("""
133+
container:
134+
name: "test.container"
135+
abbreviations:
136+
- "x.y.foo"
137+
aliases:
138+
- alias: "abc"
139+
qualified_name: "x.y.bar"
140+
variables:
141+
- name: "x.y.bar"
142+
type_name: "string"
143+
- name: "x.y.foo"
144+
type_name: "int"
145+
"""),
146+
)
147+
148+
def test_abbreviations_and_aliases_from_yaml(self):
149+
env: cel.Env = cel.NewEnv(config=cel.NewEnvConfigFromYaml("""
150+
container:
151+
name: "test.container"
152+
abbreviations:
153+
- "x.y.foo"
154+
aliases:
155+
- alias: "abc"
156+
qualified_name: "x.y.bar"
157+
variables:
158+
- name: "x.y.bar"
159+
type_name: "string"
160+
- name: "x.y.foo"
161+
type_name: "int"
162+
"""))
163+
164+
res = env.compile("foo").eval(data={"x.y.foo": 42})
165+
self.assertEqual(res.value(), 42)
166+
res = env.compile("abc").eval(data={"x.y.bar": "chocolate"})
167+
self.assertEqual(res.value(), "chocolate")
168+
169+
def test_abbreviations_and_aliases_combined(self):
170+
env: cel.Env = cel.NewEnv(
171+
config=cel.NewEnvConfigFromYaml("""
172+
container:
173+
name: "test.container"
174+
abbreviations:
175+
- "x.y.foo"
176+
aliases:
177+
- alias: "abc"
178+
qualified_name: "x.y.bar"
179+
variables:
180+
- name: "x.y.bar"
181+
type_name: "string"
182+
- name: "x.y.foo"
183+
type_name: "int"
184+
- name: "a.b.qux"
185+
type_name: "string"
186+
- name: "a.b.baz"
187+
type_name: "int"
188+
"""),
189+
container=cel.ExpressionContainer(
190+
"test.container",
191+
abbreviations=["a.b.baz"],
192+
aliases={"def": "a.b.qux"},
193+
),
194+
)
195+
196+
res = env.compile("foo").eval(data={"x.y.foo": 42})
197+
self.assertEqual(res.value(), 42)
198+
res = env.compile("baz").eval(data={"a.b.baz": 24})
199+
self.assertEqual(res.value(), 24)
200+
201+
res = env.compile("abc").eval(data={"x.y.bar": "chocolate"})
202+
self.assertEqual(res.value(), "chocolate")
203+
res = env.compile("def").eval(data={"a.b.qux": "vanilla"})
204+
self.assertEqual(res.value(), "vanilla")
205+
206+
yaml: str = env.config().to_yaml()
207+
self.assertEqual(
208+
normalize_yaml(yaml),
209+
normalize_yaml("""
210+
container:
211+
name: "test.container"
212+
abbreviations:
213+
- "a.b.baz"
214+
- "x.y.foo"
215+
aliases:
216+
- alias: "abc"
217+
qualified_name: "x.y.bar"
218+
- alias: "def"
219+
qualified_name: "a.b.qux"
220+
variables:
221+
- name: "a.b.baz"
222+
type_name: "int"
223+
- name: "a.b.qux"
224+
type_name: "string"
225+
- name: "x.y.bar"
226+
type_name: "string"
227+
- name: "x.y.foo"
228+
type_name: "int"
229+
"""),
230+
)
231+
232+
def test_alias_redefinition_error(self):
233+
with self.assertRaises(Exception) as e:
234+
cel.NewEnv(
235+
container=cel.ExpressionContainer(
236+
"test.container", aliases={"abc": "x.y.bar"}
237+
),
238+
config=cel.NewEnvConfigFromYaml("""
239+
container:
240+
name: "test.container"
241+
aliases:
242+
- alias: "abc"
243+
qualified_name: "x.y.baz"
244+
"""),
245+
)
246+
self.assertIn(
247+
"Alias 'abc' is already defined with a different qualified name:"
248+
" x.y.baz",
249+
str(e.exception),
250+
)
251+
111252
def test_config_export_variables(self):
112253
config: cel.Env = cel.NewEnv(
113254
variables={
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
def _custom_headers_repo_impl(ctx):
2+
# Resolve label to path
3+
build_file_path = ctx.path(ctx.attr.build_file_label)
4+
repo_dir = build_file_path.dirname
5+
6+
# Symlink the include directory
7+
ctx.symlink(repo_dir.get_child("include"), "include")
8+
9+
# Create BUILD file
10+
ctx.file("BUILD.bazel", """
11+
cc_library(
12+
name = "headers",
13+
hdrs = glob(["include/python3.11/**"]),
14+
includes = ["include/python3.11"],
15+
visibility = ["//visibility:public"],
16+
)
17+
""")
18+
19+
custom_headers_repo = repository_rule(
20+
implementation = _custom_headers_repo_impl,
21+
attrs = {
22+
"build_file_label": attr.label(mandatory = True),
23+
},
24+
)
25+
26+
def _local_repo_extension_impl(ctx):
27+
custom_headers_repo(
28+
name = "python_headers_custom",
29+
build_file_label = "@@rules_python++python+python_3_11_x86_64-unknown-linux-gnu//:BUILD.bazel",
30+
)
31+
32+
local_repo_ext = module_extension(
33+
implementation = _local_repo_extension_impl,
34+
)

0 commit comments

Comments
 (0)