grpc-stubs icon indicating copy to clipboard operation
grpc-stubs copied to clipboard

The object returned from `UnaryStreamMultiCallable` and `StreamStreamMultiCallable` should also be a `Future`

Open bkeryan opened this issue 1 year ago • 2 comments

Description of issue

The object returned from grpc.UnaryStreamMultiCallable and grpc.StreamStreamMultiCallable is a Call, an iterator of response values, and a Future, but the grpc-stubs definition of CallIterator[TResponse] is only a Call and an iterator of response values.

Here's a link to the documentation for this return value: https://grpc.github.io/grpc/python/grpc.html#grpc.UnaryStreamMultiCallable

Returns:

An object that is a Call for the RPC, an iterator of response values, and a Future for the RPC. Drawing response values from the returned Call-iterator may raise RpcError indicating termination of the RPC with non-OK status.

I attached an example main.py that calls a UnaryStreamMultiCallable and uses Future methods done() and exception() to query its status. This works correctly at runtime (the exception is expected).

Exception (expected): <_MultiThreadedRendezvous of RPC that terminated with:
	status = StatusCode.UNAVAILABLE
	details = "failed to connect to all addresses; last error: UNKNOWN: ipv4:127.0.0.1:1234: Failed to connect to remote host: Connection refused"
	debug_error_string = "UNKNOWN:failed to connect to all addresses; last error: UNKNOWN: ipv4:127.0.0.1:1234: Failed to connect to remote host: Connection refused {created_time:"2023-05-22T19:30:15.558641584-05:00", grpc_status:14}"
>

However, it fails type checking with mypy:

main.py:12: error: "CallIterator[HelloReply]" has no attribute "done"  [attr-defined]
main.py:12: error: "CallIterator[HelloReply]" has no attribute "exception"  [attr-defined]
main.py:13: error: "CallIterator[HelloReply]" has no attribute "exception"  [attr-defined]
Found 3 errors in 1 file (checked 1 source file)

Minimum Reproducible Example

main.py
from __future__ import annotations
import grpc
from hellostreamingworld_pb2 import HelloRequest, HelloReply
from hellostreamingworld_pb2_grpc import MultiGreeterStub

if __name__ == "__main__":
    channel = grpc.insecure_channel("localhost:1234")
    stub = MultiGreeterStub(channel)

    stream = stub.sayHello(HelloRequest(name="world", num_greetings="3"))
    stream.initial_metadata()
    if stream.done() and stream.exception() is not None:
        ex = stream.exception()
        print(f"Exception (expected): {ex}")
run.sh
#!/usr/bin/env bash
set -o errexit -o nounset -o pipefail
python -m venv venv
source ./venv/bin/activate
pip install grpcio grpcio-tools grpc-stubs mypy mypy-protobuf
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. --pyi_out=. --mypy_grpc_out=. hellostreamingworld.proto
python main.py
python -m mypy main.py
hellostreamingworld.proto
// Copyright 2015 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.

syntax = "proto3";

option java_package = "ex.grpc";
option objc_class_prefix = "HSW";

package hellostreamingworld;

// The greeting service definition.
service MultiGreeter {
  // Sends multiple greetings
  rpc sayHello (HelloRequest) returns (stream HelloReply) {}
}

// The request message containing the user's name and how many greetings
// they want.
message HelloRequest {
  string name = 1;
  string num_greetings = 2;
}

// A response message containing a greeting
message HelloReply {
  string message = 1;
}

Full output
Requirement already satisfied: grpcio in ./venv/lib/python3.9/site-packages (1.55.0)
Requirement already satisfied: grpcio-tools in ./venv/lib/python3.9/site-packages (1.55.0)
Requirement already satisfied: grpc-stubs in ./venv/lib/python3.9/site-packages (1.53.0.2)
Requirement already satisfied: mypy in ./venv/lib/python3.9/site-packages (1.3.0)
Requirement already satisfied: mypy-protobuf in ./venv/lib/python3.9/site-packages (3.4.0)
Requirement already satisfied: protobuf<5.0dev,>=4.21.6 in ./venv/lib/python3.9/site-packages (from grpcio-tools) (4.23.1)
Requirement already satisfied: setuptools in ./venv/lib/python3.9/site-packages (from grpcio-tools) (58.1.0)
Requirement already satisfied: mypy-extensions>=1.0.0 in ./venv/lib/python3.9/site-packages (from mypy) (1.0.0)
Requirement already satisfied: typing-extensions>=3.10 in ./venv/lib/python3.9/site-packages (from mypy) (4.6.0)
Requirement already satisfied: tomli>=1.1.0 in ./venv/lib/python3.9/site-packages (from mypy) (2.0.1)
Requirement already satisfied: types-protobuf>=3.20.4 in ./venv/lib/python3.9/site-packages (from mypy-protobuf) (4.23.0.1)
WARNING: You are using pip version 22.0.4; however, version 23.1.2 is available.
You should consider upgrading via the '/tmp/unary_stream_call_future/venv/bin/python -m pip install --upgrade pip' command.
Writing mypy to hellostreamingworld_pb2_grpc.pyi
Exception (expected): <_MultiThreadedRendezvous of RPC that terminated with:
	status = StatusCode.UNAVAILABLE
	details = "failed to connect to all addresses; last error: UNKNOWN: ipv4:127.0.0.1:1234: Failed to connect to remote host: Connection refused"
	debug_error_string = "UNKNOWN:failed to connect to all addresses; last error: UNKNOWN: ipv4:127.0.0.1:1234: Failed to connect to remote host: Connection refused {created_time:"2023-05-22T19:47:20.563451309-05:00", grpc_status:14}"
>
main.py:12: error: "CallIterator[HelloReply]" has no attribute "done"  [attr-defined]
main.py:12: error: "CallIterator[HelloReply]" has no attribute "exception"  [attr-defined]
main.py:13: error: "CallIterator[HelloReply]" has no attribute "exception"  [attr-defined]
Found 3 errors in 1 file (checked 1 source file)

bkeryan avatar May 23 '23 01:05 bkeryan

Nice find! Thank you for the bug report, and thank you very much for the MRE. I've taken a run at a fix here, your MRE made verifying this and writing the test trivial. Does this look like it will solve your problem? https://github.com/shabbyrobe/grpc-stubs/pull/42

shabbyrobe avatar May 25 '23 08:05 shabbyrobe

Thanks, I tried it out and left feedback on the PR.

bkeryan avatar May 25 '23 18:05 bkeryan