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

[Python] Provide a type alias ValueType on EnumTypeWrapper for mypy-protobuf compatibility #8182

Merged

Conversation

nipunn1313
Copy link
Contributor

This should provide a runtime alias, which can be
used in mypy stubs to provide better typing for enum values

This is one potential solution to #8175 - see discussion there.
Fixes #8175

@google-cla
Copy link

google-cla bot commented Jan 5, 2021

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@google-cla google-cla bot added the cla: no label Jan 5, 2021
@nipunn1313
Copy link
Contributor Author

@googlebot I signed it!

@google-cla google-cla bot added cla: yes and removed cla: no labels Jan 5, 2021
@nipunn1313 nipunn1313 changed the title Provide a type alias V on EnumTypeWrapper [Python] Provide a type alias V on EnumTypeWrapper Jan 5, 2021
@nipunn1313
Copy link
Contributor Author

Hi! Ping on this^

@2m
Copy link

2m commented Apr 29, 2021

This would allow us to cleanup some code as well. :)

@nipunn1313 nipunn1313 force-pushed the enum_type_wrapper_value_field branch from c55ee08 to c98029d Compare May 27, 2021 22:29
@StefPtrs
Copy link

@nipunn1313 can you please add a label? It seems to be the only missing thing before this can be merged.

@perezd
Copy link
Contributor

perezd commented Jun 30, 2021

@nipunn1313 can you please add a label? It seems to be the only missing thing before this can be merged.

This is quite old, so I'm gonna run the tests to make sure it's still viable.

@nipunn1313
Copy link
Contributor Author

Hi! I'm not an official contributor on the project, so it was not clear to me how to add a label. I can rebase it to make sure things still work! Thanks for taking a look.

@perezd
Copy link
Contributor

perezd commented Jun 30, 2021

Hi! I'm not an official contributor on the project, so it was not clear to me how to add a label. I can rebase it to make sure things still work! Thanks for taking a look.

No prob! I am :)

Looks like all the builds failed, if you wanna fix up this PR I'll have someone take a look at it.

@nipunn1313 nipunn1313 force-pushed the enum_type_wrapper_value_field branch from c98029d to 4e45b46 Compare June 30, 2021 21:09
@nipunn1313
Copy link
Contributor Author

nipunn1313 commented Jun 30, 2021

I believe it needs a label "release notes: yes" - so that it's automatically added to release notes. Can you run the tests once again (I rebased onto master and updated the PR).

I don't have the ability to approve running workflows:

First-time contributors need a maintainer to approve running workflows. Learn more.

@nipunn1313 nipunn1313 changed the title [Python] Provide a type alias V on EnumTypeWrapper [Python] Provide a type alias V on EnumTypeWrapper for mypy-protobuf compatibility Jun 30, 2021
@nipunn1313
Copy link
Contributor Author

hi - it appears that the Kokoro tests are failing on master. I spun up a dev environment on linux and confirmed that

bazel test //:build_files_updated_unittest

is failing

I submitted a separate patch to address that in #8789
As far as I can tell, I don't think the failures we're seeing here are related to my patch.

@nipunn1313
Copy link
Contributor Author

as far as I can tell from poking around there are at least a handful of other unrelated build issues that exist on master.

# to be typed with more constraint
# Eg.
# def Name(self, number: MyGeneratedEnum.V) -> str
V = int
Copy link

Choose a reason for hiding this comment

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

I'd prefer a more explicit name like ValueType

Copy link
Contributor Author

@nipunn1313 nipunn1313 Jul 6, 2021

Choose a reason for hiding this comment

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

Hi - since this is for compatibility with https://github.com/dropbox/mypy-protobuf - this is a bit of a large non-backward compatible suggestion unfortunately.

It has the ergonomic downside of having very lengthy type names that don't end up being much more useful (IMO). In either case, you get an error from mypy of the form:

incompatible type "MyGeneratedEnum"; expected "ValueType"
incompatible type "MyGeneratedEnum"; expected "V"

both of which require some amount of looking inside the generated mypy-protobuf .pyi to understand. I believe there is a common confusion amongst protobuf users that MyGeneratedEnum is also an int, when protobuf generates it as an EnumTypeWrapper. I'm hoping that mypy-protobuf + this change helps avoid bugs related to that confusion.

Here's an example .proto enum (OuterEnum) https://github.com/dropbox/mypy-protobuf/blob/master/proto/testproto/test.proto#L11
And the corresponding generated .pyi in mypy-protobuf OuterEnum.V https://github.com/dropbox/mypy-protobuf/blob/master/test/generated/testproto/test_pb2.pyi.expected#L25

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mypy-protobuf has this documented here https://github.com/dropbox/mypy-protobuf#types-enum-int-values-more-strongly.

If we do want to change the V to something else, it could be possible via a compatibility version supporting both V and ValueType types and eventually dropping support for V. I know Dropbox alone has thousands of instances of V already, as do other companies/codebases using mypy+protobuf. I'm hesitant to do such a change without some strong feedback + consensus - to avoid churning on name changes here.

Copy link

Choose a reason for hiding this comment

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

This is an unfortunate situation... and the approach looks a bit backwards to me: mypy should not require changes to Python or common libraries.

But if I got this correctly, all current usages need quotes, as in: def f(x: 'MyEnum.V') and this V is provided by mypy-protobuf generated code.
Since removing the quotes is a change anyway, the recommended end state could be def f(x: MyEnum.ValueType) (no quotes, but longer name), and the V would be supported by mypy as an alias (shorter, but with quotes)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an unfortunate situation... and the approach looks a bit backwards to me: mypy should not require changes to Python or common libraries.

Agreed.

The reason this comes up is because mypy-protobuf is attempting to get stronger typing than what protobuf itself generates at runtime. Protobuf generates all enum values as int. The stronger typing has proven to catch bugs and is one of the value adds of mypy-protobuf, but in order to namespace the NewType("ValueType", int) into MyEnum, it requires this change.

Starting with python 3.10 there is a new feature for postponed evaluation of annotations
https://www.python.org/dev/peps/pep-0563/
(or starting in python 3.7 with from __future__ import annotations)
However, there are still cases where this is insufficient to avoid evaluation - notably

from typing import cast
cast(MyEnum.ValueType, x)

Since removing the quotes is a change anyway, the recommended end state could be def f(x: MyEnum.ValueType) (no quotes, but longer name), and the V would be supported by mypy as an alias (shorter, but with quotes)

I'm happy with your suggestion for a backward-compatible way to move toward a more verbose name. I can work with that in mypy-protobuf. @amauryfa - are you a review authority here? I can make the change. Just want to make sure I avoid discussion churn on these sorts of points.

Side note: There is an alternate python generated code option that sidestepped this problem entirely by generating enum values as enums https://github.com/danielgtaylor/python-betterproto. I've never tried it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the diff with this idea of s/V/ValueType

Choose a reason for hiding this comment

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

@perezd can you approve running to workflow again?

This should provide a runtime alias, which can be
used in mypy stubs to provide better typing for enum values

Fixes protocolbuffers#8175
@nipunn1313 nipunn1313 force-pushed the enum_type_wrapper_value_field branch from 4e45b46 to 428ef16 Compare July 21, 2021 21:51
@acozzette acozzette changed the title [Python] Provide a type alias V on EnumTypeWrapper for mypy-protobuf compatibility [Python] Provide a type alias ValueType on EnumTypeWrapper for mypy-protobuf compatibility Oct 21, 2021
@acozzette acozzette merged commit 0707f2e into protocolbuffers:master Oct 21, 2021
@acozzette
Copy link
Member

@nipunn1313 Thanks!

@nipunn1313
Copy link
Contributor Author

Cool. Just wanted to double check before this releases - are you happy with the name MyEnum.ValueType - as it would be challenging to change this name in the future. (eg mypy-protobuf currently uses MyEnum.V and I'll have to make that change). I'm happy with this naming scheme for the type.

If people have preexisting proto enums with a field named ValueType, their existing python calling code might need to be modified - but I think that's a reasonable change to ask customers to make. Technically not backward compatible, but it's a pretty rare scenario.

Once the next major release of protobuf comes out with this, I'll modify mypy-protobuf to work with this!
Appreciate it.

@acozzette
Copy link
Member

@anandolee and @haberman, does this naming sound ok?

nipunn1313 added a commit to nipunn1313/mypy-protobuf that referenced this pull request Oct 30, 2021
See protocolbuffers/protobuf#8182 which
will make it into protobuf 3.20.0

Have to use a `V = Union[ValueType]` hax, because
`V = ValueType` is interpreted as a value rather than alias
`V: TypeAlias = ValueType` appears buggy within nested scopes.

Fixes #169
nipunn1313 added a commit to nipunn1313/mypy-protobuf that referenced this pull request Oct 30, 2021
See protocolbuffers/protobuf#8182 which
will make it into protobuf 3.20.0

Have to use a `V = Union[ValueType]` hax, because
`V = ValueType` is interpreted as a value rather than alias
`V: TypeAlias = ValueType` appears buggy within nested scopes.

Fixes #169
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Better mypy typing for enum values in python
8 participants