-
Notifications
You must be signed in to change notification settings - Fork 593
Python: Support for validate_all
function
#606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Hi team.
Maybe set the python protobuf dependency version to |
validate_all
functionvalidate_all
function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your contribution @HaloWorld, I'm quite happy to get this into the main branch for release - i would like to see this put through the test harness if possible
A check for
Also, since the |
Here is an example of the functions currently generated by proto(tests/harness/cases/kitchen_sink.proto) syntax = "proto3";
package tests.harness.cases;
option go_package = "github.com/envoyproxy/protoc-gen-validate/tests/harness/cases/go;cases";
import "validate/validate.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/any.proto";
enum ComplexTestEnum {
ComplexZero = 0;
ComplexONE = 1;
ComplexTWO = 2;
}
message ComplexTestMsg {
string const = 1 [(validate.rules).string.const = "abcd"];
ComplexTestMsg nested = 2;
int32 int_const = 3 [(validate.rules).int32.const = 5];
bool bool_const = 4 [(validate.rules).bool.const = false];
google.protobuf.FloatValue float_val = 5 [(validate.rules).float.gt = 0];
google.protobuf.Duration dur_val = 6 [(validate.rules).duration.lt = {seconds: 17}, (validate.rules).duration.required = true];
google.protobuf.Timestamp ts_val = 7 [(validate.rules).timestamp.gt = {seconds: 7}];
ComplexTestMsg another = 8;
float float_const = 9 [(validate.rules).float.lt = 8];
double double_in = 10 [(validate.rules).double = {in: [456.789, 123]}];
ComplexTestEnum enum_const = 11 [(validate.rules).enum.const = 2];
google.protobuf.Any any_val = 12 [(validate.rules).any = {in: ["type.googleapis.com/google.protobuf.Duration"]}];
repeated google.protobuf.Timestamp rep_ts_val = 13 [(validate.rules).repeated = { items { timestamp { gte { nanos: 1000000}}}}];
map<sint32, string> map_val = 14 [(validate.rules).map.keys.sint32.lt = 0];
bytes bytes_val = 15 [(validate.rules).bytes.const = "\x00\x99"];
oneof o {
option (validate.required) = true;
string x = 16;
int32 y = 17;
}
}
message KitchenSinkMessage { ComplexTestMsg val = 1; } functions genereated by # Validates KitchenSinkMessage
def generate_validate(p):
if _has_field(p, "val"):
embedded = validate(p.val)
if embedded is not None:
return embedded
return None
# Validates ComplexTestMsg
def generate_validate(p):
present = False
if _has_field(p, "x"):
present = True
if _has_field(p, "y"):
present = True
if not present:
raise ValidationFailed("Oneof o is required")
if p.const != "abcd":
raise ValidationFailed("p.const not equal to abcd")
if _has_field(p, "nested"):
embedded = validate(p.nested)
if embedded is not None:
return embedded
if p.int_const != 5:
raise ValidationFailed("p.int_const not equal to 5")
if p.bool_const != False:
raise ValidationFailed("p.bool_const not equal to False")
if p.HasField("float_val"):
if p.float_val.value <= 0.0:
raise ValidationFailed("p.float_val.value is not greater than 0.0")
if not _has_field(p, "dur_val"):
raise ValidationFailed("p.dur_val is required.")
if _has_field(p, "dur_val"):
dur = p.dur_val.seconds + round((10**-9 * p.dur_val.nanos), 9)
lt = 17.0
if dur >= lt:
raise ValidationFailed("p.dur_val is not lesser than 17.0")
if _has_field(p, "ts_val"):
ts = p.ts_val.seconds + round((10**-9 * p.ts_val.nanos), 9)
gt = 7.0
if ts <= gt:
raise ValidationFailed("p.ts_val is not greater than 7.0")
if _has_field(p, "another"):
embedded = validate(p.another)
if embedded is not None:
return embedded
if p.float_const >= 8.0:
raise ValidationFailed("p.float_const is not lesser than 8.0")
if p.double_in not in [456.789, 123.0]:
raise ValidationFailed("p.double_in not in [456.789, 123.0]")
if p.enum_const != 2:
raise ValidationFailed("p.enum_const not equal to 2")
if _has_field(p, "any_val"):
if p.any_val.type_url not in ['type.googleapis.com/google.protobuf.Duration']:
raise ValidationFailed("p.any_val not in ['type.googleapis.com/google.protobuf.Duration']")
for item in p.rep_ts_val:
validate(item)
for item in p.rep_ts_val:
if item:
ts = item.seconds + round((10**-9 * item.nanos), 9)
gte = 0.001
if ts < gte:
raise ValidationFailed("item is not greater than or equal to 0.001")
pass
for key in p.map_val:
if key >= 0:
raise ValidationFailed("key is not lesser than 0")
pass
if p.bytes_val != b'\x00\x99':
raise ValidationFailed("p.bytes_val not equal to b'\x00\x99'")
return None functions generated by # Validates KitchenSinkMessage All
def generate_validate_all(p):
err = []
if _has_field(p, 'val'):
err += _validate_all(p.val)
return err
# Validates ComplexTestMsg All
def generate_validate_all(p):
err = []
present = False
if _has_field(p, 'x'):
present = True
if _has_field(p, 'y'):
present = True
if (not present):
err.append('Oneof o is required')
if (p.const != 'abcd'):
err.append('p.const not equal to abcd')
if _has_field(p, 'nested'):
err += _validate_all(p.nested)
if (p.int_const != 5):
err.append('p.int_const not equal to 5')
if (p.bool_const != False):
err.append('p.bool_const not equal to False')
if p.HasField('float_val'):
if (p.float_val.value <= 0.0):
err.append('p.float_val.value is not greater than 0.0')
if (not _has_field(p, 'dur_val')):
err.append('p.dur_val is required.')
if _has_field(p, 'dur_val'):
dur = (p.dur_val.seconds + round(((10 ** (- 9)) * p.dur_val.nanos), 9))
lt = 17.0
if (dur >= lt):
err.append('p.dur_val is not lesser than 17.0')
if _has_field(p, 'ts_val'):
ts = (p.ts_val.seconds + round(((10 ** (- 9)) * p.ts_val.nanos), 9))
gt = 7.0
if (ts <= gt):
err.append('p.ts_val is not greater than 7.0')
if _has_field(p, 'another'):
err += _validate_all(p.another)
if (p.float_const >= 8.0):
err.append('p.float_const is not lesser than 8.0')
if (p.double_in not in [456.789, 123.0]):
err.append('p.double_in not in [456.789, 123.0]')
if (p.enum_const != 2):
err.append('p.enum_const not equal to 2')
if _has_field(p, 'any_val'):
if (p.any_val.type_url not in ['type.googleapis.com/google.protobuf.Duration']):
err.append("p.any_val not in ['type.googleapis.com/google.protobuf.Duration']")
for item in p.rep_ts_val:
err += _validate_all(item)
for item in p.rep_ts_val:
if item:
ts = (item.seconds + round(((10 ** (- 9)) * item.nanos), 9))
gte = 0.001
if (ts < gte):
err.append('item is not greater than or equal to 0.001')
pass
for key in p.map_val:
if (key >= 0):
err.append('key is not lesser than 0')
pass
if (p.bytes_val != b'\x00\x99'):
err.append("p.bytes_val not equal to b'\x00\x99'")
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A check for validate_all has been added to harness, which I have tested locally and passed, and currently has two checks:
- Ensure that the result of validate_all is the same as validate
- When validation fails, ensure that the first error message of validate_all is the same as the error message of validate
Also, since the ast.unparse function was not officially introduced until python 3.9, I had to bring in a third-party library (astunparse) to ensure compatibility.
@HaloWorld, CI is unhappy with ERROR: /go/src/github.com/envoyproxy/protoc-gen-validate/python/protoc_gen_validate/validator.py Imports are incorrectly sorted and/or formatted. I'm interested in improving the dev experience on this repo, just out of interest did this not get picked up on your local? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix isort lint
Sorry, I was previously unaware to the point that I could run ci directly locally with the I'm relying on the Development section in README, I don't have bazel locally and I don't want to install bazel, so the only option for me is to build a docker image for testing. docker build -t lyft/protoc-gen-validate . Then I get to the container shell with the following command: docker run --rm -ti -v $(pwd):/go/src/github.com/envoyproxy/protoc-gen-validate --entrypoint=/bin/bash lyft/protoc-gen-validate Then I ran the make build command and got the following result: I followed the prompt and added the environment variable docker run --rm -ti -e GOFLAGS="-buildvcs=false" -v $(pwd):/go/src/github.com/envoyproxy/protoc-gen-validate --entrypoint=/bin/bash lyft/protoc-gen-validate Then ran bazel test //tests/harness/executor:executor_python_test After the test passed, I committed and pushed the code. There are two things that I find most troublesome in the whole development process.
|
Signed-off-by: puyj <[email protected]>
Signed-off-by: puyj <[email protected]>
* Refactor _Transformer to make the code structure more intuitive Split `_Transformer` into 7 sub-Transformers (in execution order). When the python version is lower than 3.9, use the `unparse` function of the third-party library `astunparse` to replace the `unparse` function of the standard library `ast`
* Add test for validate_all to Python harness The result of `validate` is guaranteed to be the same as the result of `validate_all`. When validation fails, the error message of `validate` is guaranteed to be the same as the first error message of `validate_all`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM thanks for your contribution @HaloWorld
Highlighted by our valued contributor @HaloWorld in #606, the `development` section of our readme needs some changes. This really does need a lot more work but should do the trick for now.
Wanted to chime in to say thank you. This is a great change. |
This change implements the
validate_all
function through the ast module in the python standard library, which allows PGV to return all validate error messages at once.Result: