-
Notifications
You must be signed in to change notification settings - Fork 69
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
feat: add rest transport generation for clients with optional flag #688
Conversation
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.
First pass, some broad outlines and some nitpicking.
I'm more in favor of naming the new transport and its module "Http" instead of Rest; it is possible to do protobuf over http, and it's more immediately obvious to someone swimming in the code what it means.
gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2
Show resolved
Hide resolved
gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2
Outdated
Show resolved
Hide resolved
gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2
Outdated
Show resolved
Hide resolved
gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2
Outdated
Show resolved
Hide resolved
gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2
Outdated
Show resolved
Hide resolved
|
||
|
||
{%- for method in service.methods.values() %} | ||
{# TODO(yonmg): consider using enums #} |
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.
+1 for using enums.
{%- if ns.http.get != '' %} | ||
{%- set ns.httpMethod = {'method':'get','url':ns.http.get} %} | ||
{%- elif ns.http.put != '' %} | ||
{%- set ns.httpMethod = {'method':'put','url':ns.http.put} %} | ||
{%- elif ns.http.post != '' %} | ||
{%- set ns.httpMethod = {'method':'post','url':ns.http.post} %} | ||
{%- elif ns.http.delete != '' %} | ||
{%- set ns.httpMethod = {'method':'delete','url':ns.http.delete} %} | ||
{%- elif ns.http.patch != '' %} | ||
{%- set ns.httpMethod = {'method':'patch','url':ns.http.patch} %} | ||
{%- elif ns.http.custom != '' %} | ||
{%- set ns.httpMethod = {'method':'custom','url':ns.http.custom} %} | ||
{%- else %} | ||
{%- set ns.httpMethod = None %} | ||
{%- endif %} |
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'm leaning towards anything that involves state modification living in the generator datatypes. It's easier to test and easier to change if we need to.
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'm not sure I understand what you mean by generator datatypes. Could you clarify?
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.
The gapic/schema/wrappers.py:{MessageType,Service,Method}
classes. The auxiliary datastructures that the generate creates before iterating over the templates.
Is the REST transport going to be added in one PR? If not, can we maintain a long-running branch and merge to it instead? The microgenerator is now relied on by Cloud clients at GA stability so I'd prefer not to to surface this until it is somewhat stable. |
This sounds reasonable to me. I'll defer to the other reviewers on what the best practice should be. @software-dov @vam-google @vchudnov-g |
If it's not too much work, I think I'd be more in favor of feature toggles and small PRs instead of having a long-running branch. It's easier to do merging. If that seems like too much work, I'm okay with a long-running branch. |
I agree with this somewhat but for consistency across other languages, flag arguments and regapic design specs, I think it's more appropriate to name it Rest. In addition, it's because it's possible to do protobuf over http that naming it such may be ambiguous. Rest seems to me more clearly defined. |
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, just a few things.
gapic/utils/options.py
Outdated
@@ -40,6 +40,8 @@ class Options: | |||
lazy_import: bool = False | |||
old_naming: bool = False | |||
add_iam_methods: bool = False | |||
# TODO(yonmg): should there be an enum for transport type? |
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.
+1 for using an enum
Codecov Report
@@ Coverage Diff @@
## master #688 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 26 26
Lines 1560 1578 +18
Branches 316 320 +4
=========================================
+ Hits 1560 1578 +18
Continue to review full report at Codecov.
|
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, just two minor nits.
] | ||
with mock.patch.object(jinja2.Environment, "get_template") as gt: | ||
gt.return_value = jinja2.Template("Service: {{ service.name }}") | ||
cgr = g.get_response(api_schema=make_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.
Formatting nit: putting this on a new line makes it slightly easier to read the entire thing.
gapic/generator/generator.py
Outdated
'transport' in template_name | ||
and not self._is_desired_transport(template_name, opts) |
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.
Can you put parentheses around this chunk, just to make it clear that it's a cohesive unit?
gapic/utils/options.py
Outdated
@@ -14,7 +14,7 @@ | |||
|
|||
from collections import defaultdict | |||
from os import path | |||
from typing import Any, DefaultDict, Dict, FrozenSet, List, Optional, Tuple | |||
from typing import Any, DefaultDict, Dict, FrozenSet, List, Optional, Tuple, Set |
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.
Is Set
used somewhere?
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
All the comments are minor. Please address them and then push the changes (please use squash and merge button). Thanks @software-dov for sharing your expertise in Python review!
@@ -20,6 +20,7 @@ _lazy_type_to_package_map = { | |||
'{{ enum.name }}': '{{ enum.ident.package|join('.') }}.types.{{enum.ident.module }}', | |||
{%- endfor %} | |||
|
|||
{# TODO(yonmg): add rest transport service once I know what this is #} |
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.
Please use your github id for TODOs (here and in the other places)
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.
+1
gapic/schema/wrappers.py
Outdated
@@ -786,6 +808,7 @@ def grpc_stub_type(self) -> str: | |||
server='stream' if self.server_streaming else 'unary', | |||
) | |||
|
|||
# TODO(yonmg): figure out why idempotent is reliant on http annotation |
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.
http GET requests are automatically considered idempotent because they do not change data on the server. Post requests are usually non-idempotent, because it is usually unsafe retry write operation if it failed, because it may lead for example into writing the same information twice on the server.
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 get that GET is idempotent but couldn't there be methods without an http annotation that are also idempotent? are grpc only methods inherently not so?
@@ -220,13 +223,13 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): | |||
client. | |||
""" | |||
# Sanity check: Only create a new client if we do not already have one. | |||
if 'operations_client' not in self.__dict__: | |||
self.__dict__['operations_client'] = operations_v1.OperationsClient( | |||
if self._operations_client is None: |
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.
won't if not self._operation_client
work? I thought it was more idiomatic for python.
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.
It's a little bit complicated. Some values can be falsy even if they are non-null, e.g. empty lists and strings.
In this particular case, it's slightly more idiomatic to do if self._operations_client
, but I personally am fine with explicit checks in cases like this.
@@ -172,7 +175,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): | |||
**kwargs) -> grpc.Channel: | |||
"""Create and return a gRPC channel object. | |||
Args: | |||
address (Optionsl[str]): The host for the channel to use. | |||
address (Optional[str]): The host for the channel to use. |
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 was this changed (looks unrelated to your changes)?
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.
Typo
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 approve of fixing typos on the fly!
@@ -225,13 +228,13 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): | |||
client. | |||
""" | |||
# Sanity check: Only create a new client if we do not already have one. | |||
if 'operations_client' not in self.__dict__: | |||
self.__dict__['operations_client'] = operations_v1.OperationsAsyncClient( | |||
if self._operations_client is None: |
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 are you changing this code (looks unrelated to http transport stuff)?
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.
#688 (comment)
I decided to update the other templates as well for consistency.
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.
+1 for consistency
__all__ = ( | ||
'{{ service.name }}RestTransport', | ||
) | ||
{% endblock %} |
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.
Please add new line
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. Only minor comments. Nice job! Very excited to have this in!
@@ -20,6 +20,7 @@ _lazy_type_to_package_map = { | |||
'{{ enum.name }}': '{{ enum.ident.package|join('.') }}.types.{{enum.ident.module }}', | |||
{%- endfor %} | |||
|
|||
{# TODO(yonmg): add rest transport service once I know what this is #} |
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.
+1
gapic/cli/generate.py
Outdated
@@ -42,7 +42,6 @@ def generate( | |||
|
|||
# Pull apart arguments in the request. | |||
opts = Options.build(req.parameter) | |||
|
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: I like having the blank line here for readability.
gapic/schema/wrappers.py
Outdated
@@ -719,6 +719,7 @@ def _client_output(self, enable_asyncio: bool): | |||
# Return the usual output. | |||
return self.output | |||
|
|||
# TODO(yonmg): remove or rewrite. don't think it performs as intended |
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: Suggest adding just a tiny bit more detail. It often happens that the person working on the TODO is not the person who wrote it, so adding some context is a helpful practice.
gapic/schema/wrappers.py
Outdated
} | ||
if len(http) > 1: | ||
http_opt = http[1] | ||
answer[http_opt[0].name] = http_opt[1] |
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.
For clarity I would suggest
body_spec = http[1]
answer[body_spec[0].name] = body_spec[1]
gapic/schema/wrappers.py
Outdated
|
||
http_method = http[0] | ||
answer: Dict[str, str] = { | ||
'method': http_method[0].name, |
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.
Based on our offline discussion, let's s/'method'
/'verb'
/, since "method" is overloaded (service method, object method, HTTP method)
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 this be madness, there be method to it! 💀
@property | ||
def http_opt(self) -> Optional[Dict[str, str]]: | ||
"""Return the http option for this method.""" | ||
http = self.options.Extensions[annotations_pb2.http].ListFields() |
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 do think a type declaration or a comment saying this is a List[Tuple[FieldDescriptor, str]]
would be helpful.
gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2
Outdated
Show resolved
Hide resolved
@@ -172,7 +175,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): | |||
**kwargs) -> grpc.Channel: | |||
"""Create and return a gRPC channel object. | |||
Args: | |||
address (Optionsl[str]): The host for the channel to use. | |||
address (Optional[str]): The host for the channel to use. |
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 approve of fixing typos on the fly!
🤖 I have created a release \*beep\* \*boop\* --- ## [0.36.0](https://www.github.com/googleapis/gapic-generator-python/compare/v0.35.11...v0.36.0) (2020-11-14) ### Features * add rest transport generation for clients with optional transport flag ([#688](https://www.github.com/googleapis/gapic-generator-python/issues/688)) ([af59c2c](https://www.github.com/googleapis/gapic-generator-python/commit/af59c2c3c3d6b7e1f626c3fbc2c03f99ca31b4a4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please).
This pr adds traditional rest/json transport to GAPIC clients. As of now, this does not yet handle all cases and there may be bugs in the generated client. As such, there is now a --transport flag set to 'grpc' by default so any new additions won't be reflected in generated clients.