-
Notifications
You must be signed in to change notification settings - Fork 1
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
[WIP] implement metadata for asynchronous unaryunary callable #10
Changes from all commits
3cc96fb
35f1587
72aa479
26ce172
34242b2
50f0b27
e7b69a6
945344f
8b0e474
7b9b376
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,29 @@ | |
from grpc.experimental import aio | ||
|
||
|
||
class _RPCState: | ||
|
||
def __init__(self, initial_metadata, trailing_metadata): | ||
self.initial_metadata = initial_metadata | ||
self.response = None | ||
self.trailing_metadata = trailing_metadata | ||
|
||
|
||
def _handle_call_result(operations, state, response_deserializer): | ||
for operation in operations: | ||
operation_type = operation.type() | ||
if operation_type == cygrpc.OperationType.receive_initial_metadata: | ||
state.initial_metadata = operation.initial_metadata() | ||
elif operation_type == cygrpc.OperationType.receive_message: | ||
serialized_response = operation.message() | ||
if serialized_response is not None: | ||
response = _common.deserialize(serialized_response, | ||
response_deserializer) | ||
state.response = response | ||
elif operation_type == cygrpc.OperationType.receive_status_on_client: | ||
state.trailing_metadata = operation.trailing_metadata() | ||
|
||
|
||
class UnaryUnaryMultiCallable(aio.UnaryUnaryMultiCallable): | ||
|
||
def __init__(self, channel, method, request_serializer, | ||
|
@@ -38,8 +61,33 @@ async def __call__(self, | |
if timeout: | ||
raise NotImplementedError("TODO: timeout not implemented yet") | ||
|
||
if metadata: | ||
raise NotImplementedError("TODO: metadata not implemented yet") | ||
if credentials: | ||
raise NotImplementedError("TODO: credentials not implemented yet") | ||
|
||
if wait_for_ready: | ||
raise NotImplementedError( | ||
"TODO: wait_for_ready not implemented yet") | ||
|
||
if compression: | ||
raise NotImplementedError("TODO: compression not implemented yet") | ||
|
||
state = _RPCState(None, None) | ||
ops = await self._channel.unary_unary( | ||
self._method, _common.serialize(request, self._request_serializer), | ||
metadata) | ||
_handle_call_result(ops, state, self._response_deserializer) | ||
|
||
return state.response | ||
|
||
async def with_state(self, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is this method used for? Why did you add it here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should use the name of sync version ( I copied it from sync version. This is And sync version use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of the code that is behind It's true that what you have implemented can be reused later for implementing that proposal, so IMO I would keep this method - please rename it - but I would put a disclaimer like.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I see 👍 |
||
request, | ||
timeout=None, | ||
metadata=None, | ||
credentials=None, | ||
wait_for_ready=None, | ||
compression=None): | ||
if timeout: | ||
raise NotImplementedError("TODO: timeout not implemented yet") | ||
|
||
if credentials: | ||
raise NotImplementedError("TODO: credentials not implemented yet") | ||
|
@@ -51,10 +99,13 @@ async def __call__(self, | |
if compression: | ||
raise NotImplementedError("TODO: compression not implemented yet") | ||
|
||
response = await self._channel.unary_unary( | ||
self._method, _common.serialize(request, self._request_serializer)) | ||
state = _RPCState(None, None) | ||
ops = await self._channel.unary_unary( | ||
self._method, _common.serialize(request, self._request_serializer), | ||
metadata) | ||
_handle_call_result(ops, state, self._response_deserializer) | ||
|
||
return _common.deserialize(response, self._response_deserializer) | ||
return state | ||
|
||
|
||
class Channel(aio.Channel): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
[ | ||
"_sanity._sanity_test.AioSanityTest", | ||
"unit.channel_test.TestChannel", | ||
"unit.init_test.TestInsecureChannel" | ||
"unit.init_test.TestInsecureChannel", | ||
"unit.metadata_test.TestMetadata" | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Copyright 2019 The gRPC Authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import logging | ||
import unittest | ||
|
||
from grpc.experimental import aio | ||
from tests_aio.unit import test_base | ||
from tests_aio.unit import test_common | ||
from src.proto.grpc.testing import messages_pb2 | ||
|
||
|
||
_INVOCATION_METADATA = ( | ||
( | ||
'initial-md-key', | ||
'initial-md-value', | ||
), | ||
( | ||
'trailing-md-key-bin', | ||
b'\x00\x02', | ||
), | ||
) | ||
|
||
|
||
class TestMetadata(test_base.AioTestBase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would put the test in the same file where we have the other tests, in the end we are testing the client with metadata There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 just as a new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
|
||
def test_unary_unary(self): | ||
async def coro(): | ||
channel = aio.insecure_channel(self.server_target) | ||
hi = channel.unary_unary( | ||
'/grpc.testing.TestService/UnaryCall', | ||
request_serializer=messages_pb2.SimpleRequest.SerializeToString, | ||
response_deserializer=messages_pb2.SimpleResponse.FromString) | ||
state = await hi.with_state(messages_pb2.SimpleRequest(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why? Why not call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to get more information(like metadata) from |
||
metadata=_INVOCATION_METADATA) | ||
|
||
self.assertEqual(type(state), aio._channel._RPCState) | ||
self.assertEqual(type(state.response), messages_pb2.SimpleResponse) | ||
self.assertTrue( | ||
test_common.metadata_transmitted((_INVOCATION_METADATA[0],), | ||
state.initial_metadata)) | ||
self.assertTrue( | ||
test_common.metadata_transmitted((_INVOCATION_METADATA[1],), | ||
state.trailing_metadata)) | ||
|
||
await channel.close() | ||
|
||
self.loop.run_until_complete(coro()) | ||
|
||
|
||
if __name__ == '__main__': | ||
logging.basicConfig() | ||
unittest.main(verbosity=2) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Copyright 2019 gRPC authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Common code used throughout aio tests of gRPC.""" | ||
|
||
import collections | ||
|
||
import six | ||
|
||
INVOCATION_INITIAL_METADATA = ( | ||
('0', 'abc'), | ||
('1', 'def'), | ||
('2', 'ghi'), | ||
) | ||
SERVICE_INITIAL_METADATA = ( | ||
('3', 'jkl'), | ||
('4', 'mno'), | ||
('5', 'pqr'), | ||
) | ||
SERVICE_TERMINAL_METADATA = ( | ||
('6', 'stu'), | ||
('7', 'vwx'), | ||
('8', 'yza'), | ||
) | ||
DETAILS = 'test details' | ||
|
||
|
||
def metadata_transmitted(original_metadata, transmitted_metadata): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this test, what is the purpose of it? It is not importing anything from gRPC There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a function used by |
||
"""Judges whether or not metadata was acceptably transmitted. | ||
|
||
gRPC is allowed to insert key-value pairs into the metadata values given by | ||
applications and to reorder key-value pairs with different keys but it is not | ||
allowed to alter existing key-value pairs or to reorder key-value pairs with | ||
the same key. | ||
|
||
Args: | ||
original_metadata: A metadata value used in a test of gRPC. An iterable over | ||
iterables of length 2. | ||
transmitted_metadata: A metadata value corresponding to original_metadata | ||
after having been transmitted via gRPC. An iterable over iterables of | ||
length 2. | ||
|
||
Returns: | ||
A boolean indicating whether transmitted_metadata accurately reflects | ||
original_metadata after having been transmitted via gRPC. | ||
""" | ||
original = collections.defaultdict(list) | ||
for key, value in original_metadata: | ||
original[key].append(value) | ||
transmitted = collections.defaultdict(list) | ||
for key, value in transmitted_metadata: | ||
transmitted[key].append(value) | ||
|
||
for key, values in six.iteritems(original): | ||
transmitted_values = transmitted[key] | ||
transmitted_iterator = iter(transmitted_values) | ||
try: | ||
for value in values: | ||
while True: | ||
transmitted_value = next(transmitted_iterator) | ||
if value == transmitted_value: | ||
break | ||
except StopIteration: | ||
return False | ||
else: | ||
return True |
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.
where did you get this logic from? Is it from the sync version? Can you link it? :) Gives the impression we are implementing more things than needed (for now)?
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 got it from here: https://github.com/grpc/grpc/blob/master/src/python/grpcio/grpc/_channel.py#L119
I think we may use this function construct more information in the future.