Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Support accessing trailing_metadata in unary functions #1856

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

Closed
daniel-sanche opened this issue Nov 9, 2023 · 3 comments
Closed

Support accessing trailing_metadata in unary functions #1856

daniel-sanche opened this issue Nov 9, 2023 · 3 comments
Labels
priority: p2 Moderately-important priority. Fix may not be included in next release. type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design.

Comments

@daniel-sanche
Copy link
Contributor

daniel-sanche commented Nov 9, 2023

Most grpc calls return a grpc.Call object containing trailing metadata and other useful information. This object is exposed to gapic users in streaming rpcs, because it is part of the iterator object that is returned when the rpc is called. Unary gapic methods don't currently expose this in any way, so there is no way to access trailing metadata without going around gapic

There are a few ways we could surface the grpc.Call object:

Synchronous

Option 1.

We can expose this in synchronous methods through a separete *_with_call method, which returns a tuple of the result data, and the Call object. This would align us with how the underlying grpc library handles the issue.

result = client.read_row()
result, call_data = client.read_row_with_call()

Option 2

Alternatively, we could expose this through a callback that is passed in, if we want to keep the result type static

def on_complete(call):
  print(call)

result = client.read_row(grpc_call_callback=on_complete)

Either way, implementing this requires this change in the api-core repo (Merged)

Async

Option 1

One way to address this is to return the Call object directly, instead of awaiting it as part of the function. This would force us to change all of our function signatures from "coroutine functions" into "sync functions that return a coroutine", but functionally it should be the same. (This is already how we handle the stream methods)

# from
async def read_rows(*args, **kwargs):
  return await rpc(*args, **kwargs)

# to
def read_rows(*args, **kwargs):
  return rpc(*args, **kwargs)

Option 2

Alternatively, we could follow the same format we choose for the Synchronous surface, to keep things consistent (i.e. either returning a tuple when a flag is set, or accepting a callback)

A callback could be a bit tricky convenient for async functions though, because we need to be an an async context to access any useful information on the Call object

result, call = await client.read_row(with_call=True)

or

async def on_complete(call):
  print(call)

result = await client.read_row(grpc_call_callback=on_complete)

or

def on_complete(call, metadata, status):
  print(call)

result = await client.read_row(grpc_call_callback=on_complete)

or

future = asyncio.Future()
result = await client.read_row(grpc_call_future=future)
call_data = await future
@vchudnov-g
Copy link
Contributor

@daniel-sanche , is this blocking your work in any way? Asking so we can prioritize this appropriately.

@daniel-sanche
Copy link
Contributor Author

For now I will be making patches to the bigtable library directly, so not completely blocked. But having it available in the upstream generator soon would definitely help make things go smoother

@vchudnov-g vchudnov-g added priority: p2 Moderately-important priority. Fix may not be included in next release. type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design. labels Dec 8, 2023
@parthea
Copy link
Contributor

parthea commented Nov 28, 2024

Closing as obsolete as a solution is available using gRPC interceptors

from google.cloud.bigtable_v2 import BigtableAsyncClient
import asyncio
import grpc.aio

# Create a custom Interceptor class.
# See https://grpc.io/docs/guides/interceptors
# This example is for unary-unary but gRPC also supports unary-stream and others
# See https://grpc.github.io/grpc/python/grpc.html#grpc.UnaryUnaryClientInterceptor
class MetadataClientAsyncInterceptor(grpc.aio.UnaryUnaryClientInterceptor):
    def __init__(self):
        self._request_metadata = []
        self._response_metadata = []

    async def intercept_unary_unary(self, continuation, client_call_details, request):
        self._request_metadata = client_call_details.metadata
        response = await continuation(client_call_details, request)
        response_metadata = await response.trailing_metadata()
        #print(type(response_metadata))
        # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
        metadata = [(k, v) for k, v in response_metadata]
        self._response_metadata = metadata
        return response

async def async_main():
    # Get the transport class
    transport_cls = BigtableAsyncClient.get_transport_class("grpc_asyncio")
    # Create an interceptor object based on user-defined interceptor
    interceptor = MetadataClientAsyncInterceptor()
    # Create a gRPC channel
    channel = transport_cls.create_channel(host=BigtableAsyncClient.DEFAULT_ENDPOINT, interceptors = [interceptor])
    # Create a transport object, using the above channel
    transport = transport_cls(channel=channel)
    # Create a BigTable client using the above transport
    client = BigtableAsyncClient(transport=transport)
    # Call a BigTable API client method `ping_and_warm` and optionally provide additional request metadata.
    await client.ping_and_warm(name="projects/XXXX/instances/XXXX", metadata=[("some_additional_request_metadata_key", "some_additional_request_metadata_value")])
    # Print the request and response metadata
    print(interceptor._request_metadata)
    print(interceptor._response_metadata)

if __name__ == "__main__":
    asyncio.run(async_main())

@parthea parthea closed this as completed Nov 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: p2 Moderately-important priority. Fix may not be included in next release. type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design.
Projects
None yet
Development

No branches or pull requests

3 participants