GithubHelp home page GithubHelp logo

cloud-custodian / cel-python Goto Github PK

View Code? Open in Web Editor NEW
109.0 109.0 20.0 828 KB

Pure Python implementation of the Common Expression Language

License: Apache License 2.0

Makefile 0.39% Gherkin 30.26% Python 68.98% Go 0.36%
cel cloud python3 rules-engine

cel-python's Introduction

Cloud Custodian (c7n)

Cloud Custodian Logo


slack CI CII Best Practices

Cloud Custodian, also known as c7n, is a rules engine for managing public cloud accounts and resources. It allows users to define policies to enable a well managed cloud infrastructure, that's both secure and cost optimized. It consolidates many of the adhoc scripts organizations have into a lightweight and flexible tool, with unified metrics and reporting.

Custodian can be used to manage AWS, Azure, and GCP environments by ensuring real time compliance to security policies (like encryption and access requirements), tag policies, and cost management via garbage collection of unused resources and off-hours resource management.

Custodian also supports running policies on infrastructure as code assets to provide feedback directly on developer workstations or within CI pipelines.

Custodian policies are written in simple YAML configuration files that enable users to specify policies on a resource type (EC2, ASG, Redshift, CosmosDB, PubSub Topic) and are constructed from a vocabulary of filters and actions.

It integrates with the cloud native serverless capabilities of each provider to provide for real time enforcement of policies with builtin provisioning. Or it can be run as a simple cron job on a server to execute against large existing fleets.

Cloud Custodian is a CNCF Incubating project, lead by a community of hundreds of contributors.

Features

  • Comprehensive support for public cloud services and resources with a rich library of actions and filters to build policies with.
  • Run policies on infrastructure as code (terraform, etc) assets.
  • Supports arbitrary filtering on resources with nested boolean conditions.
  • Dry run any policy to see what it would do.
  • Automatically provisions serverless functions and event sources ( AWS CloudWatchEvents, AWS Config Rules, Azure EventGrid, GCP AuditLog & Pub/Sub, etc)
  • Cloud provider native metrics outputs on resources that matched a policy
  • Structured outputs into cloud native object storage of which resources matched a policy.
  • Intelligent cache usage to minimize api calls.
  • Supports multi-account/subscription/project usage.
  • Battle-tested - in production on some very large cloud environments.

Links

Quick Install

Custodian is published on pypi as a series of packages with the c7n prefix, its also available as a docker image.

$ python3 -m venv custodian
$ source custodian/bin/activate
(custodian) $ pip install c7n

Usage

The first step to using Cloud Custodian (c7n) is writing a YAML file containing the policies that you want to run. Each policy specifies the resource type that the policy will run on, a set of filters which control resources will be affected by this policy, actions which the policy with take on the matched resources, and a mode which controls which how the policy will execute.

The best getting started guides are the cloud provider specific tutorials.

As a quick walk through, below are some sample policies for AWS resources.

  1. will enforce that no S3 buckets have cross-account access enabled.
  2. will terminate any newly launched EC2 instance that do not have an encrypted EBS volume.
  3. will tag any EC2 instance that does not have the follow tags "Environment", "AppId", and either "OwnerContact" or "DeptID" to be stopped in four days.
policies:
 - name: s3-cross-account
   description: |
     Checks S3 for buckets with cross-account access and
     removes the cross-account access.
   resource: aws.s3
   region: us-east-1
   filters:
     - type: cross-account
   actions:
     - type: remove-statements
       statement_ids: matched

 - name: ec2-require-non-public-and-encrypted-volumes
   resource: aws.ec2
   description: |
    Provision a lambda and cloud watch event target
    that looks at all new instances and terminates those with
    unencrypted volumes.
   mode:
    type: cloudtrail
    role: CloudCustodian-QuickStart
    events:
      - RunInstances
   filters:
    - type: ebs
      key: Encrypted
      value: false
   actions:
    - terminate

 - name: tag-compliance
   resource: aws.ec2
   description: |
     Schedule a resource that does not meet tag compliance policies to be stopped in four days. Note a separate policy using the`marked-for-op` filter is required to actually stop the instances after four days.
   filters:
    - State.Name: running
    - "tag:Environment": absent
    - "tag:AppId": absent
    - or:
      - "tag:OwnerContact": absent
      - "tag:DeptID": absent
   actions:
    - type: mark-for-op
      op: stop
      days: 4

You can validate, test, and run Cloud Custodian with the example policy with these commands:

# Validate the configuration (note this happens by default on run)
$ custodian validate policy.yml

# Dryrun on the policies (no actions executed) to see what resources
# match each policy.
$ custodian run --dryrun -s out policy.yml

# Run the policy
$ custodian run -s out policy.yml

You can run Cloud Custodian via Docker as well:

# Download the image
$ docker pull cloudcustodian/c7n
$ mkdir output

# Run the policy
#
# This will run the policy using only the environment variables for authentication
$ docker run -it \
  -v $(pwd)/output:/home/custodian/output \
  -v $(pwd)/policy.yml:/home/custodian/policy.yml \
  --env-file <(env | grep "^AWS\|^AZURE\|^GOOGLE") \
  cloudcustodian/c7n run -v -s /home/custodian/output /home/custodian/policy.yml

# Run the policy (using AWS's generated credentials from STS)
#
# NOTE: We mount the ``.aws/credentials`` and ``.aws/config`` directories to
# the docker container to support authentication to AWS using the same credentials
# credentials that are available to the local user if authenticating with STS.

$ docker run -it \
  -v $(pwd)/output:/home/custodian/output \
  -v $(pwd)/policy.yml:/home/custodian/policy.yml \
  -v $(cd ~ && pwd)/.aws/credentials:/home/custodian/.aws/credentials \
  -v $(cd ~ && pwd)/.aws/config:/home/custodian/.aws/config \
  --env-file <(env | grep "^AWS") \
  cloudcustodian/c7n run -v -s /home/custodian/output /home/custodian/policy.yml

The custodian cask tool is a go binary that provides a transparent front end to docker that mirors the regular custodian cli, but automatically takes care of mounting volumes.

Consult the documentation for additional information, or reach out on gitter.

Cloud Provider Specific Help

For specific instructions for AWS, Azure, and GCP, visit the relevant getting started page.

Get Involved

  • GitHub - (This page)
  • Slack - Real time chat if you're looking for help or interested in contributing to Custodian!
    • Gitter - (Older real time chat, we're likely migrating away from this)
  • Linen.dev - Follow our discussions on Linen
  • Mailing List - Our project mailing list, subscribe here for important project announcements, feel free to ask questions
  • Reddit - Our subreddit
  • StackOverflow - Q&A site for developers, we keep an eye on the cloudcustodian tag
  • YouTube Channel - We're working on adding tutorials and other useful information, as well as meeting videos

Community Resources

We have a regular community meeting that is open to all users and developers of every skill level. Joining the mailing list will automatically send you a meeting invite. See the notes below for more technical information on joining the meeting.

Additional Tools

The Custodian project also develops and maintains a suite of additional tools here https://github.com/cloud-custodian/cloud-custodian/tree/master/tools:

  • Org: Multi-account policy execution.

  • ShiftLeft: Shift Left ~ run policies against Infrastructure as Code assets like terraform.

  • PolicyStream: Git history as stream of logical policy changes.

  • Salactus: Scale out s3 scanning.

  • Mailer: A reference implementation of sending messages to users to notify them.

  • Trail Creator: Retroactive tagging of resources creators from CloudTrail

  • TrailDB: Cloudtrail indexing and time series generation for dashboarding.

  • LogExporter: Cloud watch log exporting to s3

  • Cask: Easy custodian exec via docker

  • Guardian: Automated multi-account Guard Duty setup

  • Omni SSM: EC2 Systems Manager Automation

  • Mugc: A utility used to clean up Cloud Custodian Lambda policies that are deployed in an AWS environment.

Contributing

See https://cloudcustodian.io/docs/contribute.html

Security

If you've found a security related issue, a vulnerability, or a potential vulnerability in Cloud Custodian please let the Cloud Custodian Security Team know with the details of the vulnerability. We'll send a confirmation email to acknowledge your report, and we'll send an additional email when we've identified the issue positively or negatively.

Code of Conduct

This project adheres to the CNCF Code of Conduct

By participating, you are expected to honor this code.

cel-python's People

Contributors

dependabot[bot] avatar eandersson avatar kapilt avatar kirill-gr avatar kylejohnson514 avatar simonw avatar slott56 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cel-python's Issues

'MessageType' support

I am in the process of implementing protovalidate in pure-python using cel-python have push things as far as I can from the outside leaving me the following 7 issues:

image

1-2, 5-7: cel-python doesn't support nano resolution
3: cel-python doesn't support protobuf messages
4: cel-python doesn't check the data is valid before applying the regex

I am mostly interested (3). I have implemented most of the logic needed to seamlessly integrate protobuf into cel-python, by materializing protobuf messages as a map; however, as protobuf supports cycles in the type system, this is insufficient (as well as inefficient). I am curious if there is any interest to add support, potentially based on the code I have already written.

Remove defunct Makefile targets

Root Makefile contains defuct targets including but not limited to:

install:
  python3 -m venv .
  . bin/activate && pip install -r requirements-dev.txt

This functionality seems to have been ported to tox.ini and was never removed.

Default logging configuration is too noisy, proper way to disable logging in this lib?

The default configuration(logger names/logging levels in each module) doesn't seem to be reasonable when using this package as a library. A simple logging.basicConfig(level=logging.INFO) could easily flood the logging output.

I know I can make it less noisy by setting the loggers used in the module individually like logging.getLogger(logger_name).setLevel(some_other_level), but the logger names in this module are not really consistent such as prefixing with a single module name or so, I can only go to the source code to find out all the logger names used rather than adding a prefix filter to disable all loggers in one go:

for _logger_name in ("Evaluator", "evaluation", "NameContainer", "celtypes", "Environment"):
  logging.getLogger(_logger_name).setLevel(logging.WARNING)

This approach doesn't really look sustainable, especially, I can't know if there will be any change, such as new loggers added; old loggers removed, etc., in this module.

Therefore, I'm wondering if it's possible to support one of the followings going forward?

  • Consolidate the logger names into something predictable
  • Add an option to adjust all the loggers used
  • Allow users to pass in loggers
  • others idk

Thank you!

Add support for type checking (PEP 561)

Currently running mypy checks against code, that is using celpy, results in the following output:

error: Skipping analyzing "celpy": module is installed, but missing library stubs or py.typed marker [import]
note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
error: Type of variable becomes "Any" due to an unfollowed import [no-any-unimported]

This can be fixed by adding py.typed file (https://peps.python.org/pep-0561/#packaging-type-information). I've tried adding it locally to the celpy package and mypy checks have passed.

Are celpy Environments thread safe?

Hello!

I was wondering if a celpy Environment instance could be shared amongst threads, or if we should create a new instance when we want to create a runner for a new expression.

I tried to look through the docs and source code but didn't find any reference to this.

One thing I've noticed though is that, when we call the program method, the code is doing an intermediate assignment to an instance variable of the environment, before returning, which I believe can be problematic in terms of thread preemption.

        self.runnable = runner_class(self, expr, functions)
        return self.runnable

Nonetheless I might have missed something and would like to get your feedback on this use case.

Thank you very much!

Getting a TypeError instead of evaluation error

Getting the wrong exception type when expression uses all
Expect to get an CELEvalError but instead of a TypeError

import celpy
from celpy import CELEvalError

env = celpy.Environment()

# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    all_positive = "data.all(x, x>0)"
    breaks = {'data_': [1, 2, 3] }

    ast = env.compile(all_positive)
    prgm = env.program(ast)

    try:
        prgm.evaluate(breaks)
    except CELEvalError as ex:
        print('Got an evaluation error')
    except TypeError as ex:
        print('Got a type error')

Happy to try and fix with some guidance

Performance of evaluation

Hey there,

I encountered some performance benchmarks of certain CEL expressions evaluations using the library and wanted to know if that's the expected outcome.
I saw https://github.com/cloud-custodian/cel-python/wiki/Early-Profiling-Data and https://github.com/cloud-custodian/cel-python/blob/main/benches/large_resource_set.py and wanted to validate performance time for large data sets.

The expression I tried running is the following -
((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:centos:centos:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:debian:debian_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:debian:debian_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:debian:debian:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:debian:debian:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:debian:debian_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:fedoraproject:fedora:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:oracle:linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:redhat:enterprise_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:novell:suse_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:canonical:ubuntu_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/h:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/h:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Windows Server" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:microsoft:")))) ? "Windows Team" : (((!has(class_a.property_a) ? false : ("Windows Server" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/h:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/h:")))) ? "Windows Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) || (!has(class_a.property_a) ? false : ("Windows Server" == class_a.property_a))) ? "Unassigned" : (((!has(class_a.property_a) ? false : ("Windows Workstation" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:microsoft:")))) ? "Solutions Team" : ((!has(class_a.property_a) ? false : ("Windows Workstation" == class_a.property_a)) ? "End User" : "unknown"))))))))))))
with the following context -

cel_context = {
                        "class_a": celpy.json_to_cel({"property_a":"something"}),
                        "class_b": celpy.json_to_cel({"title":"something else","property_b":"some var","integration_info":{"type":"GitHub"}}),
                        "optional": celpy.json_to_cel({})
                    }

The average I'm getting on my local machine (Macbook pro M1 Max, 32G RAM), is around 3.5 seconds per iteration - is this expected?
As a comparison using https://github.com/google/cel-java takes 1 second for the same 10k iterations which is a exponentially faster.
Is it possible to reach the java times? perhaps Lark in the evaluation is causing an issue here?
I tried also passing to the env the CompiledRunner class to maybe boost performance, but seems like its calling super() for the evaluation which raises NotImplementedError - so not sure how to evaluate the expressions as pure python.

I tried smaller scripts - this one takes 0.2 seconds per iteration -

(!has(class_b.integration_info.type) ?
                false:("some value" == class_b.integration_info.type)) ?
                optional.of("some value") : optional.of("some other value")

Which is still significantly slower then the java.

(need to run it with these custom functions -

functions = {
    "of": lambda optional, value: value,
    "none": lambda optional, : None
}

)

I know there is redundancy in the big expression and that it can be optimized, but I'm using it intentionally since it is user-generated and can be encountered in my system.

I'm adding here a python script I used for the benchmarking - tried to wrap the processing code with cProfile but it just slowed things down even more so this is based on using the time.

Any help on this would be much appreciated,

Thanks,

import celpy
import celpy.celtypes
import time

CEL_EXPRESSION_ORIGINAL = """
(
    (
        !has(class_a.property_a) ? 
            false : ("Linux" == class_a.property_a)
    ) && 
    (
        (
            !has(class_b.property_b) ? 
                false : class_b.property_b.contains("os:/o:centos:centos:")
        ) || 
        (
            !has(class_b.property_b) ?
                false : class_b.property_b.contains("x-os:/o:centos:centos:")
        ) || 
        (
            !has(class_b.property_b) ?
                false : class_b.property_b.contains("os:/a:centos:centos:")
        ) || 
        (
            !has(class_b.property_b) ?
                false : class_b.property_b.contains("x-os:/a:centos:centos:")
        ) || 
        (
            !has(class_b.property_b) ? 
                false : class_b.property_b.contains("p-os:/a:centos:centos:")
        )
    )
) ? 
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ?
                false : ("Linux" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ?
                    false : class_b.property_b.contains("os:/o:debian:debian_linux:")
            ) || 
            (
                !has(class_b.property_b) ?
                    false : class_b.property_b.contains("x-os:/o:debian:debian_linux:")
            ) || 
            (
                !has(class_b.property_b) ?
                    false : class_b.property_b.contains("os:/a:debian:debian:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/a:debian:debian:")
            ) || 
            (
                !has(class_b.property_b) ?
                    false : class_b.property_b.contains("p-os:/a:debian:debian_linux:")
            )
        )
    ) ?
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ?
                false : ("Linux" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ?
                    false : class_b.property_b.contains("os:/o:fedoraproject:fedora:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/o:fedoraproject:fedora:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/a:fedoraproject:fedora:")
            ) || 
            (
                !has(class_b.property_b) ?
                    false : class_b.property_b.contains("x-os:/a:fedoraproject:fedora:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("p-os:/a:fedoraproject:fedora:")
            )
        )
    ) ? 
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Linux" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/o:oracle:linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/o:oracle:linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/a:oracle:linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/a:oracle:linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("p-os:/a:oracle:linux:")
            )
        )
    ) ? 
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Linux" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/o:redhat:enterprise_linux:")
        ) || 
        (
            !has(class_b.property_b) ? 
                false : class_b.property_b.contains("x-os:/o:redhat:enterprise_linux:")
        ) || 
        (
            !has(class_b.property_b) ? 
                false : class_b.property_b.contains("os:/a:redhat:enterprise_linux:")
        ) || 
        (
            !has(class_b.property_b) ? 
                false : class_b.property_b.contains("x-os:/a:redhat:enterprise_linux:")
        ) || 
        (
            !has(class_b.property_b) ? 
                false : class_b.property_b.contains("p-os:/a:redhat:enterprise_linux:")
        )
    )
) ? 
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Linux" == class_a.property_a)
        ) && (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/o:novell:suse_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/o:novell:suse_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/a:novell:suse_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/a:novell:suse_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("p-os:/a:novell:suse_linux:")
            )
        )
    ) ? 
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Linux" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/o:canonical:ubuntu_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/o:canonical:ubuntu_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/a:canonical:ubuntu_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/a:canonical:ubuntu_linux:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("p-os:/a:canonical:ubuntu_linux:")
            )
        )
    ) ? 
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Linux" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/h:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/h:")
            )
        )
    ) ? 
optional.of("Linux Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Windows Server" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/o:microsoft:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/o:microsoft:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/a:microsoft:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/a:microsoft:")
            )
        )
    ) ? 
optional.of("Windows Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Windows Server" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/h:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/h:")
            )
        )
    ) ? 
optional.of("Windows Team") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Linux" == class_a.property_a)
        ) || 
        (
            !has(class_a.property_a) ?   
                false : ("Windows Server" == class_a.property_a)
        )
    ) ? 
optional.of("Unassigned") : 
(
    (
        (
            !has(class_a.property_a) ? 
                false : ("Windows Workstation" == class_a.property_a)
        ) && 
        (
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/o:microsoft:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/o:microsoft:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("os:/a:microsoft:")
            ) || 
            (
                !has(class_b.property_b) ? 
                    false : class_b.property_b.contains("x-os:/a:microsoft:")
            )
        )
    ) ? 
optional.of("Solutions Team") : 
(
    (
        !has(class_a.property_a) ? 
            false : ("Windows Workstation" == class_a.property_a)
    ) ? 
optional.of("End User") : optional.of("Unknown")))))))))))))
"""


CEL_EXPRESSION_SHORT = """
    "bla bla"
"""
CEL_EXPRESSION_MEDIUM = """
    (!has(class_b.integration_info.type) ?
                false:("some value" == class_b.integration_info.type)) ?
                optional.of("some value") : optional.of("some other value")
"""
CEL_EXPRESSION_ORIGINAL_NO_OPTIONAL = """
((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:centos:centos:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:centos:centos:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:debian:debian_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:debian:debian_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:debian:debian:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:debian:debian:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:debian:debian_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:fedoraproject:fedora:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:fedoraproject:fedora:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:oracle:linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:oracle:linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:redhat:enterprise_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:redhat:enterprise_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:novell:suse_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:novell:suse_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:canonical:ubuntu_linux:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("p-os:/a:canonical:ubuntu_linux:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/h:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/h:")))) ? "Linux Team" : (((!has(class_a.property_a) ? false : ("Windows Server" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:microsoft:")))) ? "Windows Team" : (((!has(class_a.property_a) ? false : ("Windows Server" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/h:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/h:")))) ? "Windows Team" : (((!has(class_a.property_a) ? false : ("Linux" == class_a.property_a)) || (!has(class_a.property_a) ? false : ("Windows Server" == class_a.property_a))) ? "Unassigned" : (((!has(class_a.property_a) ? false : ("Windows Workstation" == class_a.property_a)) && ((!has(class_b.property_b) ? false : class_b.property_b.contains("os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/o:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("os:/a:microsoft:")) || (!has(class_b.property_b) ? false : class_b.property_b.contains("x-os:/a:microsoft:")))) ? "Solutions Team" : ((!has(class_a.property_a) ? false : ("Windows Workstation" == class_a.property_a)) ? "End User" : "unknown"))))))))))))
"""


functions = {
    "of": lambda optional, value: value,
    "none": lambda optional, : None
}

env = celpy.Environment()

before_compile = time.time()
ast = env.compile(CEL_EXPRESSION_ORIGINAL_NO_OPTIONAL)
print(f'after compile: {time.time() - before_compile}')

before_runner = time.time()
program = env.program(ast,functions=functions)
print(f'after runner: {time.time() - before_runner}')

before_eval = time.time()

def process():
    for i in range(0, 100):
        before_json_to_cel = time.time()
        cel_context = {
                        "class_a": celpy.json_to_cel({"property_a":"something"}),
                        "class_b": celpy.json_to_cel({"title":"something else","property_b":"some var","integration_info":{"type":"GitHub"}}),
                        "optional": celpy.json_to_cel({})
                    }
        after_row_pre_process = time.time() - before_json_to_cel

        before_row_eval = time.time()
        result = program.evaluate(cel_context)
        after_row_eval = time.time()
        print(f"""after eval row {i}, 
              row time: {(after_row_eval - before_json_to_cel):.3f}, 
              pre-process time: {after_row_pre_process:.3f},
              eval time: {(after_row_eval - before_row_eval):.3f}, 
              current total time: {((after_row_eval - before_eval)/60):.3f} minutes, 
              result: {result}""")

# pr = cProfile.Profile()
# pr.enable()
# print("started profiler")
process()
# print("disabling profiler")
# pr.disable()

# s = io.StringIO()
# ps = pstats.Stats(pr, stream=s).sort_stats(pstats.SortKey.TIME)
# ps.print_stats()
# print(s.getvalue())

Upgrade to latest CEL specification

CEL specification update:

  • Compare CEL specification when this was first released with current CEL specification to get the list of changes.
  • Update documentation with very visible details on specific CEL specification used.
  • Rewrite and expand test cases to cover latest CEL specification.
  • Prioritize rework to bring this project into compliance. Create individual issues and a parent tracking issue.

Bug: comparing list to null throws error

Current behavior
running CEL queue == null on {'queue': None} raises

CELEvalError(*("found no matching overload for 'relation_eq' applied to '(<class 'celpy.celtypes.ListType'>, <class 'NoneType'>)'", <class 'TypeError'>, ("no such overload: ListType == <class 'NoneType'>",)), tree='queue ==  null')

Although in the spect it should return true - https://playcel.undistro.io/?content=H4sIAAAAAAAAA3WRTWsCQQyG%2F0o6PdiC1bvgQYpQim2lpUhhL3E3rgMzmWU%2BtCL%2B92am20%2FwNGGSPO%2Bb5KgajKgm6hLuyBPoAHErD3cpQk5JCG%2BzhwU4D%2FcvT4%2Bwcd5iHFVc8bFSWNcucQyVmgAnY04Vq6Gi985TCNqxkMdjWJGpnSWIrtBv5wtYGjy0Xnqbi4ql5O9f9oEs2pE81lHvSBjrWQhk1%2BYAnduL2QaId9o7tsQxs0XWOBkCBSAxeV0yex23n7rOWscw%2F3YHC%2BQ2YUtwJfrXo%2BykuLmBldeR4OCSL9Z%2BJoKtSPdFr4EKGD0hSCrHQTeUl%2FRrh8MzS%2Bwpy0yGwXPiQZlihybhGXFsUXOI%2F47Ug%2Bb9%2FLmvdsaQbE563EYIaDtDoTcWOu0x5%2FIVv04I02k5oTp9AKJvo6kVAgAA

Problem building wheel from source (tar.gz)

We are building wheels from source but we are having some issues.

Issues with building from source:

  • missing requirements.txt in tar.gz
  • wrong name in pyproject.toml -> celpy instead of cel-python ...
  • wrong version in pyproject.toml -> 0.0 but should be resolved in tar.gz
  • missing tag 0.1.5 in git
wget https://files.pythonhosted.org/packages/b3/9e/c3af4a83cbe8108cff92b0588b7ac53efc41fcaf1ffd09d07dc68c9b5d27/cel-python-0.1.5.tar.gz
pip wheel cel-python-0.1.5.tar.gz
(.venv) ➜  pip wheel cel-python-0.1.5.tar.gz
Processing ./cel-python-0.1.5.tar.gz
  File was already downloaded /Users/myuser/projects/myproject/cel-python-0.1.5.tar.gz
  Preparing metadata (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  × Preparing metadata (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [23 lines of output]
      Traceback (most recent call last):
        File "/Users/myuser/projects/myproject/.venv/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
          main()
        File "/Users/myuser/projects/myproject/.venv/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/Users/myuser/projects/myproject/.venv/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 149, in prepare_metadata_for_build_wheel
          return hook(metadata_directory, config_settings)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/Users/myuser/projects/myproject/.venv/lib/python3.11/site-packages/setuptools/build_meta.py", line 396, in prepare_metadata_for_build_wheel
          self.run_setup()
        File "/Users/myuser/projects/myproject/.venv/lib/python3.11/site-packages/setuptools/build_meta.py", line 507, in run_setup
          super(_BuildMetaLegacyBackend, self).run_setup(setup_script=setup_script)
        File "/Users/myuser/projects/myproject/.venv/lib/python3.11/site-packages/setuptools/build_meta.py", line 341, in run_setup
          exec(code, locals())
        File "<string>", line 41, in <module>
        File "/Users/myuser/.pyenv/versions/3.11.2/lib/python3.11/pathlib.py", line 1058, in read_text
          with self.open(mode='r', encoding=encoding, errors=errors) as f:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/Users/myuser/.pyenv/versions/3.11.2/lib/python3.11/pathlib.py", line 1044, in open
          return io.open(self, mode, buffering, encoding, errors, newline)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      FileNotFoundError: [Errno 2] No such file or directory: '/private/var/folders/l6/nmk965l50gv_90_c4xh5x0_4ctpk94/T/pip-req-build-lvkahb56/requirements.txt'
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

Condition syntax sometimes evaluates the wrong branch

false ? true.exists_one(i, false) : false raises *BoolType* object is not iterable.
Expected: The first false should skip the first branch of the condition and execute the "else" branch (the last false).

The CEL language definition says:

To get traditional left-to-right short-circuiting evaluation of logical operators, as in C or other languages (also called "McCarthy Evaluation"), the expression e1 && e2 can be rewritten e1 ? e2 : false. Similarly, e1 || e2 can be rewritten e1 ? true : e2.

This error doesn't happen for false ? 1/0 : true. This condition correctly skips the division by zero.

Support for RE2

It looks like the implementation of CEL matches, just calls Python re.search but Python re is not the same as RE2 as an example \z in RE2 should match the end of text, but \z in Python re is an invalid escape. The equivalent in Python re is \Z but that maps to an unsupported escape in RE2.

Example

echo '""' | python -m celpy 'string(this).matches("^\\z")'
# Should output true, but instead emits the error "re.error: bad escape \z at position 1"
echo '""' | python -m celpy 'string(this).matches("^\\Z")'
# Should be an error as \Z is not supported in RE2, but instead outputs true

Example of another CEL evaluator rejecting \Z but accepting \z

crash when call function without parameters

import celpy
import celpy.celtypes

cel_source = """
shake_hands()
"""

env = celpy.Environment()
ast = env.compile(cel_source)

def shake_hands(*args, **kwargs) -> celpy.celtypes.StringType:
    return celpy.celtypes.StringType(f"call shake_hands")

prgm = env.program(ast, functions=[shake_hands])
activation = {}

result = prgm.evaluate(activation)
print(result)

got

    name_token, exprlist = cast(Tuple[lark.Token, lark.Tree], child.children)
ValueError: not enough values to unpack (expected 2, got 1)

switch out gherkin for pytest

gherkin is a bit slow and its forking for each test, converting the suite to pytest would let us run probably quite a bit faster and with more options on test control

benchmark tests

given large cardinality resource sets, it would be good to evaluate the performance of cel-python against ~1000-~10000 and look for profile based optimization opportunities as well to have a general sense of the relative performance. the type casting me a bit concerned that we may end

Any plans to publish a new release anytime soon?

There're performance critical fixes related to logging in main branch and it would be nice to have an updated library released. Do you have any plans to publish an updated version anytime soon? Anything we could help with?

DumpAST.display fails for empty array literal

Reproduce using the following code

if __name__ == '__main__':
    cel = "[]"
    tree = CELParser().parse(cel)
    print(DumpAST.display(tree))

The code fails with exception

return d.stack[0]
IndexError: list index out of range

How to call a method?

import celpy


class RewriteString(str):
    def __new__(cls, value):
        return super().__new__(cls, value)

    def contains(self, value):
        if value in self:
            return True
        else:
            return False


cel_source = "account.username == admin && account.password.contains('FI')"

env = celpy.Environment()
ast = env.compile(cel_source)
prgm = env.program(ast)

activation = {
    "account": celpy.json_to_cel(
        {
            "username": "admin",
            "password": {
                "contains": RewriteString('4wtFI8W7').contains
            }
        }
    ),
}
result = prgm.evaluate(activation)
print(result)

Exception:

Traceback (most recent call last):
  File "D:\test.py", line 22, in <module>
    "account": celpy.json_to_cel(
  File "C:\Users\john\AppData\Local\Programs\Python\Python39\lib\site-packages\celpy\adapter.py", line 130, in json_to_cel
    {json_to_cel(key): json_to_cel(value) for key, value in document.items()}
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\site-packages\celpy\adapter.py", line 130, in <dictcomp>
    {json_to_cel(key): json_to_cel(value) for key, value in document.items()}
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\site-packages\celpy\adapter.py", line 130, in json_to_cel
    {json_to_cel(key): json_to_cel(value) for key, value in document.items()}
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\site-packages\celpy\adapter.py", line 130, in <dictcomp>
    {json_to_cel(key): json_to_cel(value) for key, value in document.items()}
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\site-packages\celpy\adapter.py", line 137, in json_to_cel
    raise ValueError(f"unexpected type {type(document)} in JSON structure {document!r}")
ValueError: unexpected type <class 'method'> in JSON structure <bound method RewriteString.contains of '4wtFI8W7'>

Support for Python 3.6 and loose dependencies -> 0.1.2.1 version

👋 I'd like to use this library for a program that runs on Python 3.6. Version 0.1.2 works but the fixed dependencies in the requirements are conflicting with my other dependencies.

Out of curiosity, what were the reasons (outside EOL in 2022) to exclude Python 3.6 starting from 0.1.3 ?

Would it be possible to publish a version (e.g 0.1.2.1) on Pypi with Python 3.6 and loose dependencies ?
Compare changes

releng - fix ci

currently barfing due to mismatch on pyproject.toml

      error: subprocess-exited-with-error
      
      × Preparing metadata (pyproject.toml) did not run successfully.
      │ exit code: 1
      ╰─> [14 lines of output]
          Traceback (most recent call last):
            File "/home/runner/.cache/pre-commit/repohw7t8m1m/py_env-python3.8/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
              main()
            File "/home/runner/.cache/pre-commit/repohw7t8m1m/py_env-python3.8/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
              json_out['return_val'] = hook(**hook_input['kwargs'])
            File "/home/runner/.cache/pre-commit/repohw7t8m1m/py_env-python3.8/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 149, in prepare_metadata_for_build_wheel
              return hook(metadata_directory, config_settings)
            File "/tmp/pip-build-env-j2qglaaf/overlay/lib/python3.8/site-packages/poetry/core/masonry/api.py", line 41, in prepare_metadata_for_build_wheel
              poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False)
            File "/tmp/pip-build-env-j2qglaaf/overlay/lib/python3.8/site-packages/poetry/core/factory.py", line 58, in create_poetry
              raise RuntimeError("The Poetry configuration is invalid:\n" + message)
          RuntimeError: The Poetry configuration is invalid:
            - [extras.pipfile_deprecated_finder.2] 'pip-shims<=0.3.4' does not match '^[a-zA-Z-_.0-9]+$'
          
          [end of output]
      
      note: This error originates from a subprocess, and is likely not a problem with pip.
    error: metadata-generation-failed
    
    × Encountered error while generating package metadata.
    ╰─> See above for output.
    
    note: This is an issue with the package mentioned above, not pip.
    hint: See above for details.
Check the log at /home/runner/.cache/pre-commit/pre-commit.log

Error propagation issues obfuscating root causes

We are seeing errors like:

found no matching overload for _?_:_ applied to '(<class 'celpy.evaluation.CELEvalError'>, <class 'celpy.celtypes.StringType'>, <class 'celpy.celtypes.StringType'>)'", <class 'TypeError'>, ("Unexpected <class 'celpy.evaluation.CELEvalError'> ? <class 'celpy.celtypes.StringType'> : <class 'celpy.celtypes.StringType'>

Which is obfuscating the underlying error that occured in the argument to conditional.

This appears to be happening because the arguments to both registered and builtin functions are not being checked before passed in as celtypes.Value. Ideally 'strict' functions would have all the arguments sanctity checked, and any errors in the arguments would be propagated instead of invoking the function, while the logical operators, && and ||, and built in '@not_strictly_false` should be tolerant of some errors in the arguments.

yank spdx headers

the number of f/oss projects using them is a fraction of a percent. they end up being line noise and potential source of user and contributor confusion. we should just remove them.

Implementation of `has` macro is incomplete

The has macro is implemented by visiting the child nodes and returning true if there was not a CELEvalError. However, this makes it impossible for Protobuf messages to be handled properly.

The docstring describes the algorithm:

  1. If e evaluates to a protocol buffers version 2 message and f is a defined field:

    • If f is a repeated field or map field, has(e.f) indicates whether the field is non-empty.

    • If f is a singular or oneof field, has(e.f) indicates whether the field is set.

  2. If e evaluates to a protocol buffers version 3 message and f is a defined field:

    • If f is a repeated field or map field, has(e.f) indicates whether the field is non-empty.

    • If f is a oneof or singular message field, has(e.f) indicates whether the field is set.

    • If f is some other singular field, has(e.f) indicates whether the field's value is its default value (zero for numeric fields, false for booleans, empty for strings and bytes).

However, as far as I can tell, this is impossible to implement correctly no matter what. If you have a MessageType populated by omitting fields that are not present, you get incorrect behavior since unset fields are null instead of their default values. If you have a MessageType populated by setting default values, you get incorrect behavior because has returns true for fields that it shouldn't. What is needed is for has to return false while still populating the default value.

I don't currently have a minimal reproduction, in part because the library I am currently working on does not actually use the cel-python MessageType, but instead uses its own (though I did try to use cel-python MessageType and ran into basically the same issue, so I am pretty sure, as long as I am not missing anything, that it is similarly applicable.) That said, I hope this explanation is enough to either point me in the right direction: it seems like I am either misunderstanding how to use cel-python, or it is simply missing support for this. I think that there needs to be a way for has to be specialized for a given type; idiomatically it'd be best, in my opinion, if has could somehow call __contains__; then Python types, including MessageType, could just implement __contains__ and __getitem__ to do the right thing.

I would be willing to try to submit a PR to rectify this in the future if this sounds like a real issue and my solution would be OK, but I don't currently have a good enough understanding of how lark-parser works, so it may not be something I have time to approach for the moment.

TimestampType.__str__ doesn't honor timezone offset

When parsing a timestamp (e.g., timestamp("2020-10-20T12:00:00-05:00")), the provided timezone offset is stored but not output by __str__ or __repr__ which are hard coded to output the Z UTC abbreviation.

I think this is just a display issue, as the offset does appear to be used in comparisons. But it can be confusing when playing around in a repl shell. Example:

In [155]: utc = celtypes.TimestampType('2020-10-20T12:00:00Z')

In [156]: not_utc = celtypes.TimestampType('2020-10-20T12:00:00-05:00')

In [157]: utc
Out[157]: TimestampType('2020-10-20T12:00:00Z')

In [158]: not_utc
Out[158]: TimestampType('2020-10-20T12:00:00Z')

In [159]: utc == not_utc
Out[159]: False

In [160]: str(utc) == str(not_utc)
Out[160]: True

Getting evaluation error for string type, int type comparison with None

Got an evaluation error ("found no matching overload for 'relation_eq' applied to '(<class 'celpy.celtypes.StringType'>, <class 'NoneType'>)'", <class 'TypeError'>, ("no such overload: StringType('temp') <class 'celpy.celtypes.StringType'> != None <class 'NoneType'>",))

all_positive = "record.fullname != null"
breaks = celpy.json_to_cel({'record': {'fullname': 'temp', 'overdraftProtection': False} })

ast = env.compile(all_positive)
prgm = env.program(ast)

try:
    result =prgm.evaluate(breaks)
    print(result)
except CELEvalError as ex:
    print('Got an evaluation error', ex)
except TypeError as ex:
    print('Got a type error')

Is there any hack or something to succesfully evaluate this as it is working in this CELPlayground which is built in go.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.