-
Notifications
You must be signed in to change notification settings - Fork 75
feat: Add support for reading MethodSettings from service configuration YAML #1975
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
c6124db to
77eaeb2
Compare
08eb8e6 to
160f578
Compare
160f578 to
ecf0f97
Compare
vchudnov-g
left a comment
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.
Some initial comments, all minor (nothing blocking).
I haven't yet looked at the test cases, Will do that next.
gapic/schema/api.py
Outdated
| - The RPC must be a unary RPC (i.e. streaming RPCs are not supported) | ||
| - The field must not be annotated with google.api.field_behavior = REQUIRED. | ||
| - The field must be annotated with google.api.field_info.format = UUID4. | ||
| - The field presence requirements in AIP-4235 are checked at run time. |
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.
Nit: this item doesn't read right under a list introduced as
'All conditions below must be true". I say take take this out of the list (outdent it)
| - The field presence requirements in AIP-4235 are checked at run time. | |
| Note that the field presence requirements in AIP-4235 are checked at run time. |
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.
Fixed in 5a2d33b
gapic/schema/api.py
Outdated
| Checks each `google.api.client.MethodSettings` for validity. If | ||
| `google.api.client.MethodSettings.auto_populated_fields` | ||
| is set, verify each field against the criteria of AIP-4235 | ||
| (https://google.aip.dev/client-libraries/4235). All the configurations |
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.
| (https://google.aip.dev/client-libraries/4235). All the configurations | |
| (https://google.aip.dev/client-libraries/4235). All the conditions |
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.
Fixed in 5a2d33b
gapic/schema/api.py
Outdated
|
|
||
| if all_errors: | ||
| raise ValueError( | ||
| f"Fields cannot be automatically populated in the top level " |
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.
This error header text should be more general. For example, maybe the only error condition is the not method_descriptor above, and nothing in the config even intended to have auto-populated fields.
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.
Fixed in 5a2d33b
gapic/schema/api.py
Outdated
| invalid_selectors.append(method_settings.selector) | ||
|
|
||
| if all_errors: | ||
| raise ValueError( |
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.
Consider defining a sub-type of ValueError and raising that. Maybe MethodSettingsError or ConfigurationError?
Not strictly necessary; up to you. It would make it easier for us and users to test for the error condition without brittle examination of the error text.
And the custom type gives us the option to internally keep the various individual errors in a list, so in our tests we could check each element of the list contains key parts (eg method selector and "UUID4"). In fact, you could have invalid_selectors be a list of pairs, where the second element of the pair is the list of error strings for that selector. I.e. by doing invalid_selectors.append((method_settings.selector, errors_for_this_method)) the structure could be (in loose pseudo-yaml form)
invalid_selectors:
- google.example.v1beta1.SomeExample.DoFoo"
- "Field `auto_fill_me` is a required field"
- "Field `auto_fill_me` is not a UUID4 field"
- google.example.v1beta1.SomeExample.DoBar
- "Field `request_id` is not of type string"
- "Field `request_id` is a required field"
(To be clear, I do NOT think we should define custom error types for each of these conditions. That seems like too much pedantic overhead at this point. Nor should we use error chaining. But the single custom error type with some internal structure seems reasonable to me.)
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.
Fixed in 5a2d33b
tests/unit/schema/test_api.py
Outdated
| ) | ||
| api_schema = api.API.build(fd, "google.example.v1beta1", opts=opts) | ||
|
|
||
| exception_message = """Fields cannot be automatically populated in the top level \ |
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.
We shouldn't compare exact text of error messages. We don't want users to rely on that and we shouldn't either. I suggest checking the custom error type (if you go with my other suggestion) and then checking that the error message contains the relevant strings (eg selector strings defined in this test).
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.
Fixed in 5a2d33b
gapic/schema/api.py
Outdated
| if method_settings.auto_populated_fields: | ||
| # The method must not be a streaming method. | ||
| if method_descriptor.client_streaming or method_descriptor.server_streaming: | ||
| all_errors += f"Selector {method_settings.selector} is a streaming method. `auto_populated_fields` are only supported for unary methods. " |
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.
| all_errors += f"Selector {method_settings.selector} is a streaming method. `auto_populated_fields` are only supported for unary methods. " | |
| all_errors += f"Selector {method_settings.selector} is a streaming method. Only unary methods support the `auto_populated_fields` setting. " |
(Just so we can capitalize the first word of the second sentence ;-) )
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.
Fixed in 5a2d33b
gapic/schema/api.py
Outdated
| for method_settings in service_method_settings: | ||
| method_descriptor = self.all_methods.get(method_settings.selector) | ||
| if not method_descriptor: | ||
| all_errors += f"Selector {method_settings.selector} is not a valid method in this API. " |
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.
These += result in run-on text. At the very least, insert "\n".
I suggest having all_errors be a list you .append() to, and in the end the exception message can "\n".join(all_errors). This will work nicely with my suggestion for a custom exception and testing that exception.
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.
Fixed in 5a2d33b
tests/unit/schema/test_api.py
Outdated
| clam = make_field_pb2( | ||
| name="clam", type="TYPE_STRING", options=field_options, number=3 | ||
| ) | ||
| # Also include a field which is not of type string |
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.
This tests both non-string and a non-UUID4. I suggest separate test cases.
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.
Fixed in 5a2d33b
tests/unit/schema/test_api.py
Outdated
| 'google.example.v1beta1.SomeExample.Example3'\]. Selector \ | ||
| google.example.v1beta1.DoesNotExist.Example1 is not a valid method in this API. \ | ||
| Field `doesnotexist` is not in the top level request message. Field `octopus` is not \ | ||
| of type string. Field `octopus` is a required field. Field `octopus` is not a UUID4 \ |
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.
Field octopus is testing several failing conditions at once. I think we we should test fields with one failing condition at a time. I think a better, accepted practice is to test simple cases and assume if those work then combinations ought to work too, rather than test a combination case and assume that if it works, its constituent parts will then work by themselves.
(The full coverage solution is to test all combinations, but I think that's overkill here.)
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.
Fixed in 5a2d33b
gapic/schema/api.py
Outdated
| for method_key, method_value in service_value.methods.items() | ||
| } | ||
|
|
||
| def check_method_settings_validity( |
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.
nit nit suggestion: def enforce_valid_method_settings
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.
Fixed in 5a2d33b
|
@vchudnov-g Please could you take another look? |
vchudnov-g
left a comment
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.
Looks good!
I have one suggestion about using YAML rather than JSON in error text, and some more minor, non-blocking suggestions about possible enhancements.
| for method_settings in service_method_settings: | ||
| # Check if this selector is defind more than once | ||
| if method_settings.selector in selectors_seen: | ||
| all_errors[method_settings.selector] = ["Duplicate selector"] |
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.
So this will overwrite any errors found the first time through the selector. It does seem like this is the more important error, but we might as well present all the errors found at once, which would mean appending (or maybe in this case prepending) to the previous list value in the map.
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.
If something wrong with the selector (it is a duplicate, doesn't map to a method or maps to a streaming method), I decided not to continue checking the validity of the auto-populated fields. The issue with the selector should be fixed and providing additional detail in the error message on the auto-populated fields would take away from the more important error with the selector.
gapic/schema/api.py
Outdated
| f"request message for selectors: {invalid_selectors}. " | ||
| f"{all_errors.strip()}" | ||
| ) | ||
| raise MethodSettingsError(json.dumps(all_errors)) |
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.
Why JSON? IMO, YAML would be more concise and hence better for someone getting the exception on their console.
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.
| assert api_schema.has_iam_mixin | ||
|
|
||
|
|
||
| def get_file_descriptor_proto_for_method_settings_tests( |
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 factoring this out. It's much easier to follow!
Minor, non-blocking: I thought I left the following comment when I reviewed last week, but I don't see it on GitHub so it must not have gotten saved. I'll put it here just for your consideration. It's less pressing now that you've done this refactoring, but it would make it nicer when defining the various fields, etc.
If it's trivial to read from a proto "file" (string) in tests, it might be simpler to simply encode the protofile inline rather than calling make_file_pb2 and its ilk... something like
proto_descriptor = make_protodescriptor("""
package xx;
message Foo {
string foo = 1;
}
""")
(This is similar in spirit to what you do with the YAML file below!)
This would make it easier to keep the proto concepts being tested next to the code without a lot of boilerplate. If it's not trivial to do now, let's defer it. (But I personally would love to eventually have most of our tests use this declarative style rather than the harder-to-follow procedural set-up of they test cases.)
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.
I created #1986 as a follow up issue to explore whether this is feasible
tests/unit/schema/test_api.py
Outdated
| ), | ||
| services=( | ||
| descriptor_pb2.ServiceDescriptorProto( | ||
| name="SomeExample", |
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.
nit, non-blocking: consider more descriptive names, like ServiceOne and ServiceTwo for theservices, and DoFoo for the methods. Just to make it easier for humans to track which names are used where. (ExampleRequest and ExampleResponse do that, which is great.)
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.
Fixed in 04fa73e
tests/unit/schema/test_api.py
Outdated
| api_schema = api.API.build(fd, "google.example.v1beta1") | ||
| methodsettings = [ | ||
| client_pb2.MethodSettings( | ||
| selector="google.example.v1beta1.DoesNotExist.Example1", |
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.
nit: Do you want to test a non-existent method in a valid service as well?
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.
Fixed in eac51b6
| `client_pb2.MethodSettings.auto_populated_fields` is not found in the top-level | ||
| request message of the selector. | ||
| """ | ||
| fd = get_file_descriptor_proto_for_method_settings_tests() |
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.
Consider populating some other field of the message.
Also, is it simple to test when the field in auto_populated_fields is nested one level in ? (I.e., it still exists but not at the top-level, so we still get the exception)
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.
Done in 1ab06ea
Co-authored-by: Victor Chudnovsky <[email protected]>
Adds support for reading Method settings in the service configuration YAML
https://github.com/googleapis/googleapis/blob/7dab3de7ec79098bb367b6b2ac3815512a49dd56/google/api/client.proto#L325
Towards b/322910372