Skip to content
Merged
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
4 changes: 2 additions & 2 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ exports_files(["LICENSE"])
pybind_extension(
name = "py_cel",
srcs = [
"py_cel.cc",
"py_cel.h",
"py_cel_activation.cc",
"py_cel_activation.h",
"py_cel_arena.cc",
"py_cel_arena.h",
"py_cel_env.cc",
"py_cel_env.h",
"py_cel_env_internal.cc",
"py_cel_env_internal.h",
"py_cel_expression.cc",
Expand Down
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ To create a CEL environment, you need to define
variable types that can be used in expressions.

```python
cel = py_cel.Cel(variables={"x": py_cel.Type.INT, "y": py_cel.Type.INT})
cel_env = py_cel.NewEnv(variables={"x": py_cel.Type.INT, "y": py_cel.Type.INT})
```

#### Optional configuration parameters

The `py_cel.Cel` constructor also accepts the following optional parameters:
The `py_cel.NewEnv` constructor also accepts the following optional parameters:

* `pool` (`descriptor_pool.DescriptorPool`): The descriptor pool used for
resolving protobuf message types within CEL expressions. If not provided,
Expand All @@ -39,7 +39,7 @@ Use the `compile()` method to compile a CEL expression string into a reusable
expression object.

```python
expr = cel.compile("x + y > 10")
expr = cel_env.compile("x + y > 10")
```

The `expr` object can be serialized into a binary format for persistence and
Expand All @@ -48,7 +48,7 @@ later deserialized.
```python
serialized_expr = expr.serialize()
# ... can be stored or sent over network ...
deserialized_expr = cel.deserialize(serialized_expr)
deserialized_expr = cel_env.deserialize(serialized_expr)
```

The `compile` method can take an optional `disable_check=True` argument, which
Expand All @@ -62,7 +62,7 @@ provides bindings for variables, and then call `eval()`.

```python
# Provide variable values in a dictionary.
activation = cel.Activation({"x": 7, "y": 4})
activation = cel_env.Activation({"x": 7, "y": 4})

# Evaluate the expression.
result = expr.eval(activation)
Expand Down Expand Up @@ -94,9 +94,9 @@ garbage-collected in Python.
```python
arena = py_cel.Arena()

activation1 = cel.Activation({"x": 7, "y": 4}, arena)
activation1 = cel_env.Activation({"x": 7, "y": 4}, arena)
# evaluate some expressions
activation2 = cel.Activation({"x": 8, "y": 9}, arena)
activation2 = cel_env.Activation({"x": 8, "y": 9}, arena)
# evaluate some more expressions

# Process all results. Note: Don't put CelValues in long-lived data structures
Expand All @@ -112,7 +112,7 @@ You can pass protobuf messages as variables to an activation; CEL
expressions can return protobuf messages.

First, ensure your proto messages are available in the descriptor pool used by
`py_cel.Cel`, by importing your proto library in Python:
`py_cel.NewEnv`, by importing your proto library in Python:

from cel.expr.conformance.proto2 import test_all_types_pb2 as test_pb

Expand All @@ -121,7 +121,7 @@ qualified name.

```python
# Declare 'msg_var' as a message type.
cel = py_cel.Cel(
cel = py_cel.NewEnv(
pool,
variables={
"msg_var": py_cel.Type("cel.expr.conformance.proto2.TestAllTypes"),
Expand All @@ -141,15 +141,15 @@ an instance of the Python proto message class.
```python
my_msg = test_pb.TestAllTypes(single_int32=42)

activation = cel.Activation({"msg_var": my_msg})
activation = cel_env.Activation({"msg_var": my_msg})
result = expr.eval(activation)
print(f"Result: {result.value()}")
```

An expression can also return a proto message:

```python
msg_expr = cel.compile(
msg_expr = cel_env.compile(
"cel.expr.conformance.proto2.TestAllTypes{single_int32: 123}"
)
msg_result = msg_expr.eval(activation)
Expand All @@ -174,8 +174,8 @@ Standard extensions are available under `py_cel.ext`.
```python
from py_cel.ext import ext_math

cel = py_cel.Cel(pool, extensions=[ext_math.ExtMath()])
expr = cel.compile("math.sqrt(4)")
cel = py_cel.NewEnv(pool, extensions=[ext_math.ExtMath()])
expr = cel_env.compile("math.sqrt(4)")
```

#### Defining a custom extension in Python
Expand Down Expand Up @@ -203,8 +203,8 @@ my_ext = py_cel.CelExtension(
],
)

cel = py_cel.Cel(pool, extensions=[my_ext])
expr = cel.compile("my_func(1)")
cel_env = py_cel.NewEnv(pool, extensions=[my_ext])
expr = cel_env.compile("my_func(1)")
```

#### Defining a custom extension in C++
Expand Down Expand Up @@ -304,10 +304,10 @@ Now you can use the extension in PyCel:
```python
import translation_cel_ext

cel = py_cel.Cel(variables={},
cel_env = py_cel.NewEnv(variables={},
extensions=[translation_cel_ext.TranslationCelExtension()])

expr = cel.compile("'Hello, world!'.translate('en', 'es')")
expr = cel_env.compile("'Hello, world!'.translate('en', 'es')")
```

#### Late-bound extension functions
Expand Down Expand Up @@ -357,11 +357,11 @@ If the extension is written in C++, use the `RegisterLazyFunction` function:
Now you can bind the function at runtime:

```python
cel = py_cel.Cel(variables={}, extensions=[my_ext])
expr = cel.compile("my_func(42)")
cel_env = py_cel.NewEnv(variables={}, extensions=[my_ext])
expr = cel_env.compile("my_func(42)")

multiplier = 2
act = cel.Activation({}, functions={"my_func": lambda x: x * multiplier})
act = cel_env.Activation({}, functions={"my_func": lambda x: x * multiplier})
res = expr.eval(act)
# res.value() == 84
```
6 changes: 3 additions & 3 deletions conformance/conformance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,14 @@ def _run_conformance_test(self, simple_test: simple_pb.SimpleTest):
break

self.descriptor_pool = descriptor_pool.Default()
self.cel = cel.Cel(
self.env = cel.NewEnv(
self.descriptor_pool,
variables=decls,
extensions=extensions,
container=simple_test.container,
)
try:
compiled_expr = self.cel.compile(
compiled_expr = self.env.compile(
simple_test.expr, disable_check=simple_test.disable_check
)
except Exception as e: # pylint: disable=broad-except
Expand All @@ -188,7 +188,7 @@ def _run_conformance_test(self, simple_test: simple_pb.SimpleTest):
for key, value in simple_test.bindings.items():
values[key] = self._convert_value(value.value)

act = self.cel.Activation(values)
act = self.env.Activation(values)
try:
res = compiled_expr.eval(act)
except Exception as e: # pylint: disable=broad-except
Expand Down
10 changes: 5 additions & 5 deletions custom_ext/custom_ext_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ def _compile_expr(
) -> cel.Expression:
"""Creates a CEL expression for the given extension and compiles the expression."""
self.descriptor_pool = descriptor_pool.Default()
self.cel = cel.Cel(
self.env = cel.NewEnv(
self.descriptor_pool,
variables={},
extensions=[ext()],
)
return self.cel.compile(expression)
return self.env.compile(expression)

def _create_activation(self, impl) -> cel.Activation:
"""Creates a CEL Activation with a late-bound translate function."""
return self.cel.Activation(
return self.env.Activation(
{},
functions=[
cel.Function(
Expand All @@ -66,7 +66,7 @@ def test_basic_function(self, ext):
compiled_expr = self._compile_expr(
ext, "'Hello, world!'.translate('en', 'es')"
)
act = self.cel.Activation({})
act = self.env.Activation({})
res = compiled_expr.eval(act)

self.assertEqual(res.value(), "¡Hola Mundo!")
Expand All @@ -85,7 +85,7 @@ def test_late_bound_function(self, ext):
@parameterized.named_parameters(EXT_IMPLEMENTATIONS)
def test_error_no_matching_overload(self, ext):
compiled_expr = self._compile_expr(ext, "translate_late('Hello, world!')")
act = self.cel.Activation(
act = self.env.Activation(
{},
functions=[
cel.Function(
Expand Down
92 changes: 46 additions & 46 deletions py_cel.cc → py_cel_env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include "py_cel.h"
#include "py_cel_env.h"

#include <Python.h> // IWYU pragma: keep - Needed for PyObject

Expand All @@ -38,48 +38,47 @@ namespace cel_python {

namespace py = ::pybind11;

void PyCel::DefinePythonBindings(pybind11::module& m) {
py::class_<PyCel, std::shared_ptr<PyCel>> cel_class(m, "Cel");
cel_class
.def(py::init([](py::object descriptor_pool,
std::optional<std::unordered_map<std::string, PyCelType>>
variables,
std::optional<std::vector<py::object>> extensions,
const std::optional<std::string>& container) {
PyObject* pool_ptr = nullptr;
if (descriptor_pool.is_none()) {
// Replicates python's `descriptor_pool.Default()`
pool_ptr = py::module::import("google.protobuf.descriptor_pool")
.attr("Default")()
.ptr();
} else {
pool_ptr = descriptor_pool.ptr();
}
void PyCelEnv::DefinePythonBindings(pybind11::module& m) {
py::class_<PyCelEnv, std::shared_ptr<PyCelEnv>> cel_class(m, "Env");
m.def(
"NewEnv",
[](py::object descriptor_pool,
std::optional<std::unordered_map<std::string, PyCelType>> variables,
std::optional<std::vector<py::object>> extensions,
const std::optional<std::string>& container) {
PyObject* pool_ptr = nullptr;
if (descriptor_pool.is_none()) {
// Replicates python's `descriptor_pool.Default()`
pool_ptr = py::module::import("google.protobuf.descriptor_pool")
.attr("Default")()
.ptr();
} else {
pool_ptr = descriptor_pool.ptr();
}

std::vector<PyObject*> ext_ptrs;
if (extensions) {
ext_ptrs.reserve(extensions->size());
for (const auto& ext : *extensions) {
ext_ptrs.push_back(ext.ptr());
}
}
std::vector<PyObject*> ext_ptrs;
if (extensions) {
ext_ptrs.reserve(extensions->size());
for (const auto& ext : *extensions) {
ext_ptrs.push_back(ext.ptr());
}
}

return std::make_shared<PyCel>(
pool_ptr,
std::move(variables).value_or(
std::unordered_map<std::string, PyCelType>{}),
ext_ptrs, container.value_or(""));
}),
py::arg("descriptor_pool") = py::none(),
py::arg("variables") = py::none(),
py::arg("extensions") = py::none(),
py::arg("container") = py::none())
.def("compile", &PyCel::Compile, py::arg("expression"),
return PyCelEnv(pool_ptr,
std::move(variables).value_or(
std::unordered_map<std::string, PyCelType>{}),
ext_ptrs, container.value_or(""));
},
py::arg("descriptor_pool") = py::none(),
py::arg("variables") = py::none(), py::arg("extensions") = py::none(),
py::arg("container") = py::none());
cel_class
.def("compile", &PyCelEnv::Compile, py::arg("expression"),
py::arg("disable_check") = false)
.def("deserialize", &PyCel::Deserialize, py::arg("serialized"))
.def("deserialize", &PyCelEnv::Deserialize, py::arg("serialized"))
.def(
"Activation",
[](PyCel& self,
[](PyCelEnv& self,
std::optional<std::unordered_map<std::string, py::object>> data,
const std::optional<std::vector<std::shared_ptr<PyCelFunction>>>&
functions,
Expand All @@ -103,30 +102,31 @@ void PyCel::DefinePythonBindings(pybind11::module& m) {
py::arg("arena") = nullptr);
}

PyCel::PyCel(PyObject* descriptor_pool,
std::unordered_map<std::string, PyCelType> variable_types,
const std::vector<PyObject*>& extensions, std::string container)
PyCelEnv::PyCelEnv(PyObject* descriptor_pool,
std::unordered_map<std::string, PyCelType> variable_types,
const std::vector<PyObject*>& extensions,
std::string container)
: env_(std::make_unique<PyCelEnvInternal>(
descriptor_pool, std::move(variable_types), extensions,
std::move(container))) {
ABSL_CHECK(PyGILState_Check());
}

PyCel::~PyCel() = default;
PyCelEnv::~PyCelEnv() = default;

std::shared_ptr<PyCelActivation> PyCel::NewActivation(
std::shared_ptr<PyCelActivation> PyCelEnv::NewActivation(
const std::unordered_map<std::string, PyObject*>& data,
const std::vector<std::shared_ptr<PyCelFunction>>& functions,
const std::shared_ptr<PyCelArena>& arena) {
return std::make_shared<PyCelActivation>(env_, data, functions, arena);
}

absl::StatusOr<PyCelExpression> PyCel::Compile(const std::string& cel_expr,
bool disable_check) {
absl::StatusOr<PyCelExpression> PyCelEnv::Compile(const std::string& cel_expr,
bool disable_check) {
return PyCelExpression::Compile(env_, cel_expr, disable_check);
}

absl::StatusOr<PyCelExpression> PyCel::Deserialize(
absl::StatusOr<PyCelExpression> PyCelEnv::Deserialize(
const std::string& serialized_expr) {
return PyCelExpression::Deserialize(env_, serialized_expr);
}
Expand Down
Loading