-
Notifications
You must be signed in to change notification settings - Fork 231
Adding basic support (untested) for client streaming #45
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
nat-n
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.
I suspect some tests will be required for this.
Does it make sense to add these without solving the problem of how to expose it to the end user?
| {% for method in service.methods %} | ||
| async def {{ method.py_name }}(self{% if method.input_message and method.input_message.properties %}, *, {% for field in method.input_message.properties %}{{ field.name }}: {% if field.zero == "None" and not field.type.startswith("Optional[") %}Optional[{{ field.type }}]{% else %}{{ field.type }}{% endif %} = {{ field.zero }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}) -> {% if method.server_streaming %}AsyncGenerator[{{ method.output }}, None]{% else %}{{ method.output }}{% endif %}: | ||
| async def {{ method.py_name }}(self | ||
| {%- if not method.client_streaming -%} |
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.
How does the templating engine handle all this extra whitespace?
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 suspect some tests will be required for this.
Yeah, I didn't see a precedent for testing the generated services, but I can look at adding these. I figured I'd get this out here for discussion before investing that time, in case this wasn't the direction you wanted to go.
Does it make sense to add these without solving the problem of how to expose it to the end user?
Well, I don't have a better idea than letting the caller provider an iterator of the request objects. It doesn't match the other methods, but I'm not sure how one would match those unary_* methods. I suppose another direction could be a method that feeds into an iterator under the hood (wrapped within the method), but I don't know how you then signal the end of the method call, etc. The request_iterator feels clean and simple and certainly works for my own use cases.
How does the templating engine handle all this extra whitespace?
The extra whitespace is mitigated by the {%- stripping tag as well as the params to the jinja environment. Finally, running everything through black in the end also removes any lingering artifacts, so the output is fine / matches current [albeit with the new request_iterator when appropriate]..
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 guess using the request_iterator argument as you describe is probably the most straightforward approach. Could also do it with a two way generator or a context provider, but neither of these is as simple to use.
I also have a PR open where I add a service test: c762c9c And I think an example usage in the readme would also be necessary.
That said, I'm not a designated maintainer so can't promise anything about what will be acceptable to merge.
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.
@nat-n , thanks for that link to the test; that certainly looks less ceremonious than the testing setup I was seeing in grpclib itself. Thanks!
|
If we can get a test for this I think it's a worthwhile addition. The jinja seems sane to me, but I would be particularly interested to remove all concern for whitespace weirdness, I know that the hyphens do some trimming but with an indent sensitive language like python it can be interesting. If you already know how to add a test for this then go ahead, otherwise I (or we) will have to dig around a bit |
|
Also, can I get you to pull in the changes from #34 ? |
nat-n
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.
Please note the recent changes to the ServiceStub class: 3546f55#diff-433ccf9072e57d9dc251fbcb3972c153R1040
You'll need to rebase and tweak the two new methods to be consistent.
| route, grpclib.const.Cardinality.STREAM_STREAM, request_type, response_type | ||
| ) as stream: | ||
| for message in request_iterator: | ||
| await stream.send_message(message) |
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.
It can't be assumed that the client will want to send all the messages before receiving any.
Ideally I think this method should:
- accept
request_iterator: Union[Iterator["IProtoMessage"], AsyncIterator["IProtoMessage", None]] - Return type AsyncIterator[T], since it doesn't actually accept data being sent in
- if Iterator["IProtoMessage"] is given then wrap it in an AsyncIterator to proceed
- then iterate to receive data from the caller and send it to the server and to receive the data from he server and yield it concurrently, thus allowing upload/download to occur in any valid order.
Or alternatively it might more elegant for this method to just return an AsyncGenerator, and then it's up to the caller to manage sending and receiving from that generator. This would imply an update to the _unary_stream method for consistency, though it should be possible to keep is backwards compatible. I've not totally thought this through but I think it's worth exploring.
Sorry if you were hoping this would be simpler :P I'd be happy to help iterate towards a better solution.
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.
Good suggestions; I'll incorporate these.
|
Thanks @nat-n and others! I'll work to update the PR, though I should say that this has been degraded in my priorities, as I've rewritten the compiler portion to match our idiosyncratic protobuf/grpc needs a little more closely. (We predominantly use the threaded grpcio classes, so I needed generated client/server code to work with those. We also require |
|
@hozn I see this as a high priority issue, with a few more problems to solve, do you mind if I work on it a bit from here? |
|
@nat-n -- I don't mind at all; I would appreciate it greatly! I'm eager to see the implementation for concurrently consuming from the request iterator (note, perhaps better would be to have that be an |
|
Sure thing. Since you've already spent some time thinking about it, if you have more thoughts to share or could spare some time to review related changes then that would be appreciated. |
|
@nat-n , sounds great; yes, I'll continue to participate; I'm sure I'll learn a lot. I appreciate you taking this on, and apologize that I didn't get it to a more complete stage (w/ tests). I'll also be happy to share the work that I've done with wrapping the default ( Unrelated to this, I also am testing out an implementation of |
|
@hozn That's sounds similar to what I had in mind. I also have a PoC for grpcio integration, though I think this problem is important and complex enough that it's worth exploring different approaches in order to get it right. If you push what you have to your fork I'll have a look. Also if you have any ideas about the plugin CLI for supporting grpcio + grpclib please share: #56 |
|
Closing in favour of #83 |
I haven't written tests for this, but added the base methods for
stream_unaryandstream_streamsupport. I realize this presents a bit of a challenge when it comes to API consistency since the attrs of the request can't be exploded out into kwargs like theunary_unaryandunary_streammethods. I went the simple route and just addedrequest_iteratorparam to these methods.Let me know if changes needed, etc.