Skip to content
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

Handle mutable default arguments cleanly #105

Merged
merged 1 commit into from
Jul 11, 2020

Conversation

abn
Copy link
Collaborator

@abn abn commented Jul 7, 2020

When generating code, ensure that default list/dict arguments are initialised in local scope if unspecified or None.

The following diff shows the difference in the generated source file from service.proto.

5c5
< from typing import AsyncIterable, AsyncIterator, Iterable, List, Union
---
> from typing import AsyncIterable, AsyncIterator, Iterable, List, Optional, Union
35c35
<         self, *, name: str = "", comments: List[str] = []
---
>         self, *, name: str = "", comments: Optional[List[str]] = None
36a37
>         comments = comments or []

Due to the nature of the issue, the included test simply verifies that the default list instance initialised is not the same object used in any subsequent calls. This is done so by intercepting the calls made to the underlying ServiceStub._unary_unary method and verifying that the comments instance is not reusing the object.

@nat-n
Copy link
Collaborator

nat-n commented Jul 7, 2020

Nice. Maybe worth having a test case for this (along the lines of https://github.com/danielgtaylor/python-betterproto/blob/master/betterproto/tests/grpc/test_grpclib_client.py#L33 )? I assume you've verified it manually?

@abn abn force-pushed the mutable-defaults branch 2 times, most recently from 5b1911a to 66dfc63 Compare July 7, 2020 23:00
@abn
Copy link
Collaborator Author

abn commented Jul 7, 2020

@nat-n I did test it manually. Although turns out I did not handle the edge case when Optional was never imported. Had to change tact to modify the code instead of the template. Added a test, although had to make use of pytest-mock since the affected code is in the generated files. This should now be good to go.

Copy link
Collaborator

@nat-n nat-n left a comment

Choose a reason for hiding this comment

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

less complex logic in jinja makes me feel better.

betterproto/tests/grpc/test_grpclib_client.py Outdated Show resolved Hide resolved
@boukeversteegh
Copy link
Collaborator

Ah, sorry it took me so long, but i finally get it.
The diff in the example was just a tad too short to get the context..

So:

  • This is about the arguments of methods of ServiceStubs (aka Clients)
  • When a Service accepts a message that contains a repeated field, a parameter was created with a default []
  • If the caller does not specify the parameter, the default parameter is used
  • If that parameter is then modified by the ServiceStub, it will contain the modifications upon the next call.
  • This would cause unexpected and hard to debug behavior

Correct?

What I still don't understand is:

  • In what way might the default parameter accidentally become modified, and leak into the next call?

From what I see, the request object created on the fly, and then passed to grpcio for serialization and sending. In theory it should not ever be modified.

@abn
Copy link
Collaborator Author

abn commented Jul 9, 2020

@boukeversteegh that is correct. The change is to ensure generated code adheres to best practices. Since the project aims to generate clean pythonic code at best effort, I think this is a change in that direction.

I am not across all possible scenarios, present and future, of how this code gets used to say for certain that the default value (initialised at definition) will never be mutated prior to serialisation. This is more a defense-in-depth change.

@abn abn requested a review from nat-n July 10, 2020 08:16
@abn abn force-pushed the mutable-defaults branch 2 times, most recently from 0cf8aaa to 06a626d Compare July 10, 2020 22:58
@abn
Copy link
Collaborator Author

abn commented Jul 11, 2020

@boukeversteegh this should be ready for review again. The recent refactor in master makes the implementation here a bit cleaner.

boukeversteegh
boukeversteegh previously approved these changes Jul 11, 2020
Copy link
Collaborator

@boukeversteegh boukeversteegh left a comment

Choose a reason for hiding this comment

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

Looks good!

nat-n
nat-n previously approved these changes Jul 11, 2020
When generating code, ensure that default list/dict arguments are
initialised in local scope if unspecified or `None`.
@abn
Copy link
Collaborator Author

abn commented Jul 11, 2020

@boukeversteegh @nat-n rebased and ready again; had to rebased due to improt statement collision.

@boukeversteegh boukeversteegh self-requested a review July 11, 2020 20:28
@boukeversteegh boukeversteegh merged commit 0ba0692 into danielgtaylor:master Jul 11, 2020
@abn abn mentioned this pull request Nov 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants