Metadata-Version: 2.1
Name: cel_bind
Author: Enter
Home-page: https://github.com/getenter/cel-python
License: MIT
Description-Content-Type: text/markdown
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C++
Classifier: Development Status :: 4 - Beta
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Requires-Python: >=3.10
Version: 0.8.4

# Common Expression Language (Python)

The [Common Expression Language (CEL)](https://github.com/google/cel-spec) is a non-Turing complete language designed
for simplicity, speed, safety, and
portability. CEL's C-like syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript. CEL
is ideal for lightweight expression evaluation when a fully sandboxed scripting language is too resource intensive.

```java
// Check whether a resource name starts with a group name.
resource.name.startsWith("/groups/" + auth.claims.group)
```

```go
// Determine whether the request is in the permitted time window.
request.time - resource.age < duration("24h")
```

```typescript
// Check whether all resource names in a list match a given filter.
auth.claims.email_verified && resources.all(r, r.startsWith(auth.claims.email))
```

## Installing

```bash
pip install cel-bind
```

## Overview

Determine the variables and functions you want to provide to CEL. Parse and check an expression to make sure it's valid. Then evaluate the output AST against some input.


### Environment setup

Let's expose `name` and `group` variables to CEL:

```python
import cel

env = {
    "name": cel.StringType(),
    "group": cel.StringType(),
}
```

### Parse and Check

The parsing phase indicates whether the expression is syntactically valid and expands any macros present within the environment. Parsing and checking are more computationally expensive than evaluation, and it is recommended that expressions be parsed and checked ahead of time.

The parse and check phases are combined for convenience into the `compile_to_checked_expr` step: 

```python
pool = cel.DescriptorPool()
compiler = cel.Compiler(pool, env, None)
try:
    checked_expr = compiler.compile_to_checked_expr('name.startsWith("/groups/" + group)')
except Exception as e:
    logging.fatal("compiling error", exc_info=True)
    exit(1)
```

### Evaluate

Build the expression plan with `build_expression_plan` and reuse it for multiple evaluations of the same expression:

```python
interpreter = cel.Interpreter(pool, env, None)
expr_plan = interpreter.build_expression_plan(checked_expr)

res = interpreter.evaluate(expr_plan, {
    "name": "/groups/acme.co/documents/secret-stuff",
    "group": "acme.co"
})

print(res) # True
```

### Objects

cel-bind supports the use of structured objects in its CEL type-checking and evaluation.
First, declare the object type and add it to a `cel.DescriptorPool`:

```python
schema = """
{
  "type": "Person",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer"
    },
    "hobbies": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": ["name", "age"]
}
"""


pool = cel.DescriptorPool()
person_type = pool.add_json_schema("person", schema)
```

Then, use the returned object type when declaring your CEL environment:

```python
env = {
    "person": person_type,
}

compiler = cel.Compiler(pool, env, None)
checked_expr = compiler.compile_to_checked_expr('person.age > 18')
```

Currently, only JSON schema is supported when declaring objects.

### Extending CEL

Extension functions can be written in Python and then made available to CEL by registering it
to a `cel.FunctionRegistry`:

```python
def is_hiker(person):
    return "hiking" in person["hobbies"]

function_registry = cel.FunctionRegistry()
function_registry.add_function(
    "is_hiker",
    is_hiker,
    cel.BoolType(), # return type
    [person_type], # arguments type
)
```

Then use it in your CEL expression:

```python
env = {
    "person": person_type,
}

compiler = cel.Compiler(pool, env, function_registry)
try:
    checked_expr = compiler.compile_to_checked_expr('person.age > 18 && is_hiker(person)')
except Exception as e:
    logging.fatal("compiling error", exc_info=True)
    exit(1)

interpreter = cel.Interpreter(pool, env, function_registry)
expr_plan = interpreter.build_expression_plan(checked_expr)

res = interpreter.evaluate(expr_plan, {
    "person": {
        "name": "Alice",
        "age": 20,
        "hobbies": ["cooking", "hiking"]
    }
})
print(res) # True
```

### Type checking

`Compiler.compile_to_checked_expr` will raise an Exception if type checking fails. Some examples of
of type checking errors are:

* undefined object fields

  ```
  RuntimeError: ERROR: :1:7: undefined field 'last_name' not found in struct 'person_pkg.person'
  | person.last_name
  | ......^
  ```
* undeclared reference: 

  ```
  RuntimeError: ERROR: :1:1: undeclared reference to 'undeclared_var' (in container '')
  | undeclared_var > 5
  | ^
  ```

* functions applied to incorrect types:

  ```
  RuntimeError: ERROR: :1:12: found no matching overload for '_==_' applied to '(int, string)'
  | person.age == "5"
  | ...........^
  ```








