-
Notifications
You must be signed in to change notification settings - Fork 62
feat: add basic interceptor to client #1206
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
base: main
Are you sure you want to change the base?
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.
I am approving, pending:
- See the #QUESTIONs and determine whether any changes need to be made, if no, then carry on.
- See the #PREFERENCEs and #NITs and see if there is merit to including them. If no, then carry on.
AsyncSwappableChannel as SwappableChannelType, | ||
) | ||
from google.cloud.bigtable.data._async.metrics_interceptor import ( | ||
AsyncBigtableMetricsInterceptor as MetricInterceptorType, |
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.
#QUESTION
Are we purposefully using the singular here?
Should it be:
MetricInterceptorType
OR
MetricsInterceptorType
If plural, refactor globally. There are a number of references to it, I will not point them all out.
AsyncBigtableMetricsInterceptor as MetricInterceptorType, | |
AsyncBigtableMetricsInterceptor as MetricsInterceptorType, |
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.
good catch, fixed
if self._emulator_host is not None: | ||
# emulators use insecure channel | ||
create_channel_fn = partial(insecure_channel, self._emulator_host) | ||
else: | ||
elif CrossSync.is_async: | ||
create_channel_fn = partial(TransportType.create_channel, *args, **kwargs) | ||
return AsyncSwappableChannel(create_channel_fn) | ||
else: | ||
# attach sync interceptors in create_channel_fn | ||
def create_channel_fn(): | ||
return intercept_channel( | ||
TransportType.create_channel(*args, **kwargs), | ||
self._metrics_interceptor, | ||
) | ||
|
||
new_channel = SwappableChannelType(create_channel_fn) | ||
if CrossSync.is_async: | ||
# attach async interceptors |
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.
#PREFERENCE
This is a preference as I believe it may enhance readability (albeit only a small bit). Feel free to ignore if you disagree:
- I updated or added comments for each block.
- I updated the naming of one function (
sync_create_channel_fn()
) to provide a bit more context.
if self._emulator_host is not None:
# Emulators use insecure channels.
create_channel_fn = partial(insecure_channel, self._emulator_host)
else:
elif CrossSync.is_async:
# For async environments, use the default create_channel.
create_channel_fn = partial(TransportType.create_channel, *args, **kwargs)
return AsyncSwappableChannel(create_channel_fn)
else:
# For sync environments, wrap create_channel with interceptors.
def sync_create_channel_fn():
return intercept_channel(
TransportType.create_channel(*args, **kwargs),
self._metrics_interceptor,
)
create_channel = sync_create_channel_fn
# Instantiate SwappableChannelType with the determined creation function.
new_channel = SwappableChannelType(create_channel_fn)
if CrossSync.is_async:
# Attach async interceptors to the channel instance itself.
Why?
- The code clearly separates the logic for determining how the
create_channel_fn
will be defined based on_emulator_host
andCrossSync.is_async
. - For the synchronous case (else block), a nested function
sync_create_channel_fn
encapsulates theintercept_channel
logic. This improves readability by more clearly showing what happens in the synchronous path.
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 you choose to include the above comments, ensure that matching comments go into the similar construct below in the _sync_autogen
section of the code.
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.
Yeah, those look like good changes.
Unfortunately comments won't be copied over to the auto-generated sync code, since it's not part of the ast tree. But I'll leave those comments on the async side (which is where developers should be looking anyway)
row_key = b"bulk_mutate" | ||
new_value = uuid.uuid4().hex.encode() | ||
row_key, mutation = self._create_row_and_mutation( | ||
(row_key, mutation) = self._create_row_and_mutation( |
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.
#PREFERENCE
Why are we encapsulating all of these values in parens? To the best of my knowledge, this is not required by either PEP 8 or the Google Python Style Guide nor have I heard it referred to as a best practice. I don't see any other examples of this practice anywhere else in this codebase.
Feels unnecessary. I recommend ditching them unless we have a really good reason to add them.
(row_key, mutation) = self._create_row_and_mutation( | |
row_key, mutation = self._create_row_and_mutation( |
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 code auto-generated, so this wasn't a conscious choice. Something must have changed in how black
is post processing the generated code.
It feels unnecessary to me too, but it doesn't bother me enough to fight with the generation system to change it back
\#\ WITHOUT\ WARRANTIES\ OR\ CONDITIONS\ OF\ ANY\ KIND,\ either\ express\ or\ implied\.\n | ||
\#\ See\ the\ License\ for\ the\ specific\ language\ governing\ permissions\ and\n | ||
\#\ limitations\ under\ the\ License\. | ||
\#\ limitations\ under\ the\ License |
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.
#QUESTION
I know it is a little thing, but why are we deciding to skip the check for the presence of a trailing period in the license agreement?
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 test is for the code generation system. It's checking to make sure the generated code matches the input, but it's not not strictly meant to be testing the licenses themselves.
If the code generator is doing an async->sync transformation, and it carries over a license in the async file that's missing the trailing period, that should still count as a successful generation. We want the generator to be a bit forgiving about this kind of thing, so we can easily apply it to more codebases in the future.
If we decide we want to be more strict about the licenses, that could add a separate stand-alone test, to make sure it covers all the async code too.
|
||
@CrossSync.pytest | ||
async def test_unary_unary_interceptor_failure(self): | ||
"""test a failed RpcError with metadata""" |
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
Ensure docstrings start with a capital letter. This happens in at least two places, maybe more.
@CrossSync.convert | ||
async def _streaming_generator_wrapper(call): | ||
""" | ||
Wrapped generator to be returned by intercept_unary_stream |
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:
One liners should end in a period, exclamation mark, or question mark.
I recommend updating the docstrings as necessary.
This PR adds a new grpc interceptor to the client, to be used for metrics collection. Currently, the interceptor is a no-op. Interception logic will be added in a future change.