Skip to content

Conversation

@parthea
Copy link
Contributor

@parthea parthea commented Nov 19, 2024

Towards b/347351441

GrpcStream[Response] exposes the underlying grpc.Call for streaming calls, there is currently no way to use these grpc.Call methods without mypy errors, because of the restrictive return type annotation used. See googleapis/python-api-core#554 (comment) for context.

Adding the GrpcStream[Response] and Awaitable[AsyncIterable[Response]] response types for streaming calls will satisfy type checkers.

BEGIN_COMMIT_OVERRIDE
fix(deps): require google-api-core >= 2.17.1
END_COMMIT_OVERRIDE

google-api-core>=2.15.0 is the minimum version which supports types GrpcAsyncStream and GrpcStream
google-api-core>=2.17.1 is needed which fixes an issue with REST Streaming which can be seen in this build log

@product-auto-label product-auto-label bot added the size: s Pull request size is small. label Nov 19, 2024
@parthea parthea self-assigned this Nov 19, 2024
@parthea parthea force-pushed the add-streaming-return-type branch 3 times, most recently from 1bfeaea to bb28ae6 Compare November 19, 2024 21:57
@parthea parthea force-pushed the add-streaming-return-type branch from bb28ae6 to a346226 Compare November 19, 2024 21:59
@product-auto-label product-auto-label bot added size: m Pull request size is medium. and removed size: s Pull request size is small. labels Nov 19, 2024
@parthea parthea added the owlbot:run Add this label to trigger the Owlbot post processor. label Nov 19, 2024
@gcf-owl-bot gcf-owl-bot bot removed the owlbot:run Add this label to trigger the Owlbot post processor. label Nov 19, 2024
@parthea parthea added the owlbot:run Add this label to trigger the Owlbot post processor. label Nov 19, 2024
@gcf-owl-bot gcf-owl-bot bot removed the owlbot:run Add this label to trigger the Owlbot post processor. label Nov 19, 2024
@parthea parthea marked this pull request as ready for review November 19, 2024 22:35
@parthea parthea requested a review from a team as a code owner November 19, 2024 22:35
) -> {{ method.client_output.ident }}:
{% else %}
) -> Iterable[{{ method.client_output.ident }}]:
) -> Union[Iterable[{{ method.client_output.ident }}], GrpcStream[{{ method.client_output.ident }}]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be redundant, since GrpcStream is just an alias for _StreamingResponseIterator, which is already Iterable. So we should just be able to use GrpcStream here

returning a union type can be ugly, since any attempt to use the returned instance will cause typing issues, since mypy won't know which of the types it is

Copy link
Contributor Author

@parthea parthea Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For REST client, GrpcStream would be the response type. My initial thought is that we want a type which doesn't have Grpc in the name. Perhaps we could use a new StreamResponse instead of GrpcStream?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this response type is also used for REST streaming, I'm not sure that we can avoid the union here. _StreamingResponseIterator has a dependency on gRPC. We would need to update _StreamingResponseIterator if we want to use it for REST

Copy link
Contributor Author

@parthea parthea Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an alternative to updating the response type, we could add a callback similar to what we have for unary requests in #2243 as a callback could solve the feature request in #2256 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, I wasn't considering the rest side. We could make a StreamResponse as a mixin class, that we could add to both _StreamingResponseIterator and rest_streaming.ResponseIterator?

Yeah, using a callback in all cases could be a good way to resolve some of this complexity. It seems like we'd have some of the same typing issues there though, where we still need a way to represent results for either grpc or rest, right? I might have to dig deeper into the rest side

) -> {{ method.client_output_async.ident }}:
{% else %}
) -> Awaitable[AsyncIterable[{{ method.client_output_async.ident }}]]:
) -> Union[Awaitable[AsyncIterable[{{ method.client_output_async.ident }}]], Awaitable[GrpcAsyncStream[{{ method.client_output_async.ident }}]]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment here: it would be great if we could avoid the Union return

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any way to avoid this as this is also the response type for REST. Let's decide on the next steps for #2256 (comment) and the same solution could be applied to async.

) -> {{ method.client_output.ident }}:
{% else %}
) -> Iterable[{{ method.client_output.ident }}]:
) -> Union[Iterable[{{ method.client_output.ident }}], GrpcStream[{{ method.client_output.ident }}]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you find a solution for how to deal with api_core.retry.Retry in this?

The retry object wraps the rpc iterator, and it doesn't currently expose grc.Call methods, so anything involving retires won't return a GrpcStream. It's not exactly clear to me how we should expose metadata at the retry level (do we want to give metadata for each failed call, or only the final successful one? I think each failed request could be needed?)

I don't think this is relevant for bigtable, since we extend the retry logic. But it's probably needed in other contexts

Copy link
Contributor Author

@parthea parthea Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider adding this as future work, given that it's more than just changing the response type, and isn't required in the short term. In the first iteration, we will only be able to provide metadata when the request is successful. We can follow up with more enhancements. We may consider something similar to unary (prototype in #2243) where there is a callback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, sounds good

FWIW, I think it would be a good idea to still design a solution for failed attempts, so we don't paint ourselves into a corner here. (maybe we can use the retry.on_error callback for this?) But yeah, I'll assume we just want one set of metadata for now

@parthea parthea marked this pull request as draft November 21, 2024 12:12
@parthea
Copy link
Contributor Author

parthea commented Nov 26, 2024

Closing as obsolete. The proposed solution in b/347351441 is to use gRPC/REST interceptors. #2191 tracks improving documentation to clarify how gRPC/REST interceptors can be used to obtain metadata.

@parthea parthea closed this Nov 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: m Pull request size is medium.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants