Skip to content

Better mypy typing for enum values in python #8175

@nipunn1313

Description

@nipunn1313

Hi - I'm the maintainer of https://github.com/dropbox/mypy-protobuf - a protoc plugin for autogenerating mypy stubs compatible with the built in python generator.

What language does this apply to?
Python

Describe the problem you are trying to solve.
Generated enum values are pure ints (per the spec). We prefer type them with a NewType wrapper type around int - specific to the enum. See the typeshed stub
https://github.com/python/typeshed/blob/master/third_party/2and3/google/protobuf/internal/enum_type_wrapper.pyi#L9

class _EnumTypeWrapper(Generic[_V]):
    DESCRIPTOR: EnumDescriptor
    def __init__(self, enum_type: EnumDescriptor) -> None: ...
    def Name(self, number: _V) -> str: ...
    def Value(self, name: str) -> _V: ...
    def keys(self) -> List[str]: ...
    def values(self) -> List[_V]: ...
    def items(self) -> List[Tuple[str, _V]]: ...

I've been cooking up a strategy inside mypy-protobuf where a proto

enum MyEnum {
   FOO = 0;
   BAR = 1;
}

Autogenerates pyi stubs

class _MyEnum(google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper[OuterEnum.V], builtins.type):
    DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor = ...
    FOO = OuterEnum.V(0)
    BAR = OuterEnum.V(1)

class MyEnum(metaclass=_MyEnum):
    V = typing.NewType('V', builtins.int)

This allows callsites to type their code as

x: MyEnum.V = MyEnum.Value("FOO")

Unfortunately - because .V does not exist at runtime (only in the pyi stubs) - this does not work.
Fortunately, you can work around this with:

x: "MyEnum.V" = MyEnum.Value("FOO")

Describe the solution you'd like
I'd like to propose adding V = int runtime type alias to the EnumTypeWrapper (in enum_type_wrapper.py) - to avoid the need for this workaround.

class EnumTypeWrapper(object):
  """A utility for finding the names of enum values."""
  # This is a type alias, which mypy typing stubs can type as
  # a genericized parameter constrained to an int, allowing subclasses
  # to be typed with more constraint
  # Eg.
  # def Name(self, number: MyGeneratedEnum.V) -> str
  V = int
  ... [rest of impl omitted]

This would mean that enum variants named "V" would need to jump through a hoop using getattr to be accessed, but this is already the case with "Name", "Value", "keys", etc - so it seems feasible as an extension. I'm open to other naming ideas for this.

Could do something like this for more clarity - though it does add a dependency on typing module for python <= 3.5

from typing import NewType
V = NewType('V', int)

I would also be open to discussing other strategies we could employ across mypy-protobuf, protoc, and typeshed in order to get better mypy typing for enum values (better than just typing as an int) - other than this V inner type alias.

Describe alternatives you've considered
I've also tried mypy typing V at a sibling scope to MyEnum (eg MyEnumValue = NewType('MyEnumValue', int), but this is no improvement - as you still need to play tricks to import it from real code.

Thanks friends! Happy to open discussion.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions