-
Notifications
You must be signed in to change notification settings - Fork 1.6k
api_core: Add grpc.Channel stub to grpc_helpers #4705
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
| if self.response is not None: | ||
| return self.response | ||
|
|
||
| raise ValueError( |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| callable, it will be invoked with the request protobuf. If it's an | ||
| exception, the exception will be raised when this is invoked. | ||
| """ | ||
| self.responses = None |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| def __init__(self, method, channel): | ||
| self._method = method | ||
| self._channel = channel | ||
| self.response = None |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
|
||
| @property | ||
| def requests(self): | ||
| """Sequence[Tuple[str, protobuf.Message]]: Returns a list of all |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
lukesneeringer
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.
A couple relatively minor things, but in general this looks very good to me.
| exception, the exception will be raised when this is invoked. | ||
| """ | ||
| self.responses = None | ||
| """Iterator[protobuf.Message]: A iterator of responses. If specified, |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
|
||
|
|
||
| class _CallableStub(object): | ||
| """Stub for the grpc callable interface.""" |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| Returns: | ||
| str: The simplified name of the method. | ||
| """ | ||
| return method.rsplit('/', 1).pop() |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| request, timeout, metadata, and credentials.""" | ||
|
|
||
| def __call__(self, request, timeout=None, metadata=None, credentials=None): | ||
| self._channel._requests.append((self._method, request)) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| self._channel._requests.append((self._method, request)) | ||
| self.requests.append(request) | ||
| self.calls.append( | ||
| _ChannelCall(request, timeout, metadata, credentials)) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| with pytest.raises(ValueError) as exc_info: | ||
| stub.GetOperation(expected_request) | ||
|
|
||
| assert exc_info.match('GetOperation') |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
|
||
| response1 = stub.GetOperation(expected_request) | ||
| response2 = stub.GetOperation(expected_request) | ||
| response3 = stub.GetOperation(expected_request) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
|
||
| def test_subscribe_unsubscribe(self): | ||
| channel = grpc_helpers.ChannelStub() | ||
| channel.subscribe(None) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| """Sequence[Tuple[str, protobuf.Message]]: Returns a list of all | ||
| requests made on this channel in order. The tuple is of method name, | ||
| request proto.""" | ||
| return self._requests |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
|
||
| def __getattr__(self, key): | ||
| # Ideally this would only return stubs for known methods, however, | ||
| # grpc doesn't actually create these methods until they're needed. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| _ChannelCall = collections.namedtuple( | ||
| '_ChannelCall', ['request', 'timeout', 'metadata', 'credentials']) | ||
| _MethodCall = collections.namedtuple( | ||
| '_MethodCall', ['request', 'timeout', 'metadata', 'credentials']) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| if self.responses: | ||
| self.response = next(self.responses) | ||
| response = self.response | ||
| if response is None and self.responses is not None: |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| channel_stub.GetFoo.responses = iter([ | ||
| foo_pb2.Foo(name='bar'), | ||
| foo_pb2.Foo(name='baz')]) | ||
| foo_pb2.Foo(name='baz') |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| try: | ||
| return self._method_stubs[key] | ||
| except KeyError: | ||
| raise AttributeError |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| def subscribe(self, callback, try_to_connect=False): | ||
| """grpc.Channel.subscribe implementation.""" | ||
| return | ||
| pass |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| def on_get_operation(request): | ||
| return expected_response | ||
| on_get_operation = mock.Mock( | ||
| spec=('__call__'), return_value=expected_response) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
dhermes
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.
LGTM
|
Thanks, @dhermes and @lukesneeringer! @dhermes, @tseaver, @chemelnucfin, @lukesneeringer: Please keep an eye out in code reviews for excessive mocking of gapic/grpc clients - where possible, this should be used instead. I'm happy to help write tests until we develop a good reference corpus. |
This is intended to help with testing all grpc-based clients. With this, we should be able to mostly avoid using mock/fakes/stubs and instead use this channel-based approach. This means that every layer of the client is tested and there is less concern about a mock/stub/fake not matching its upstream target. This is akin to mocking at the requests level for HTTP clients.
@dhermes: I considered putting this into a package like
api_core.testing.grpc_stub, but I decided against it. I'm happy to reconsider.