Inconsistent errors with custom enum base auto items from installed library
Hi, I've defined a custom enum base to copy the names of the enum items as values when using auto(). Mypy curiously throws an assignment error about incompatible types when using the items as e.g. default arguments in a function. However, this only happens if the custom base is imported from another library (in site packages, using another local package is fine).
Here's some example code:
from enum import Enum, auto
# from lib import CopyNameEnum
class CopyNameEnum(Enum):
def _generate_next_value_(name, start, count, last_values):
return name
class Items(str, CopyNameEnum):
A = auto()
B = auto()
def process(item: Items = Items.A):
pass
The above is fine and produces no errors. However, when using the import instead, we get
Incompatible default for argument "item" (default has type "auto", argument has type "Items") [assignment]
I would assume that this is a bug, but if you have any other thoughts or debugging suggestions, let me know! I found another issue about the same private method in Enum in #7591, but the issue doesn't seem to be exactly related.
Environment
- mypy 1.3.0
- Python 3.9.16
I believe _generate_next_value_ should be decorated with @staticmethod. But that crashes in python 3.8 and 3.9 . Not sure if that can be fixed on the type-definition side (see https://github.com/python/typeshed/issues/10428)
I have a very similar case. We define a custom StrEnum class in a library A. If we reference it in library B (with B depends on A), mypy complains about the typing of any enum using this StrEnum as its parent.
Here are the definitions in library A
#library_a.enums
from enum import Enum
from typing import List, cast
# We originally use a custom `BaseEnum` with a custom metaclass,
# but using a simplified standard `enum.Enum` class also leads to typing errors.
class StrEnum(str, Enum):
def __str__(self) -> str:
return self.value
@classmethod
def values(cls) -> List[str]:
return [cast(str, c.value) for c in cls]
Here are the subclass definitions and use in library B
# library_b.main
from library_a.enums import StrEnum
class MyEnum(StrEnum):
A = "a"
B = "b"
def dummy_func(e: MyEnum) -> None:
pass
# Raises Error
dummy_func(MyEnum.A)
# error: Argument 1 to "dummy_func" has incompatible type "str"; expected "MyEnum" [arg-type]
# dummy_func(MyEnum.A)
This type check failure is not raised if the StrEnum is defined in library B
Environment
- mypy 1.12.0
- Python 3.11.5
I have expanded my tests and it seems that any Enum classes (subclassing any builtin enum class) defined in an installed / dependent library will trip up mypy
Here are definitions in library_a.enums
from enum import Enum, StrEnum
class DepStrEnumBuiltin(StrEnum):
def extra_method(self) -> str:
return f"extra method called on {self}"
class DepStrEnumCustom(str, Enum):
def extra_method(self) -> str:
return f"extra method called on {self}"
class DepEnum(Enum):
def extra_method(self) -> str:
return f"extra method called on {self}"
Then in library_b.enums
from enum import Enum, StrEnum
class LocalStrEnumCustom(str, Enum):
def extra_method(self) -> str:
return f"extra method called on {self}"
class LocalStrEnumBuiltin(StrEnum):
def extra_method(self) -> str:
return f"extra method called on {self}"
then in library_b.main
from enum import Enum, StrEnum
from typing import reveal_type
from library_a.enums import DepEnum, DepStrEnumCustom, DepStrEnumBuiltin
from library_b.enums import LocalStrEnumCustom, LocalStrEnumBuiltin
class MyDepEnum(DepEnum):
A = "a"
B = 1
C = False
class MyDepStrEnumCustom(DepStrEnumCustom):
A = "a"
class MyDepStrEnumBuiltin(DepStrEnumBuiltin):
A = "a"
class MyLocalStrEnumCustom(LocalStrEnumCustom):
A = "a"
class MyLocalStrEnumBuiltin(LocalStrEnumBuiltin):
A = "a"
# -----------
# functions
# -----------
def func_dep_enum(e: MyDepEnum) -> None:
pass
def func_dep_str_enum_custom(e: MyDepStrEnumCustom) -> None:
pass
def func_dep_str_enum_builtin(e: MyDepStrEnumBuiltin) -> None:
pass
def func_local_str_enum_custom(e: MyLocalStrEnumCustom) -> None:
pass
def func_local_str_enum_builtin(e: LocalStrEnumBuiltin) -> None:
pass
# -----------
# usage
# -----------
MyDepEnum.A.extra_method()
MyDepStrEnumCustom.A.extra_method()
MyDepStrEnumBuiltin.A.extra_method()
MyLocalStrEnumCustom.A.extra_method()
MyLocalStrEnumBuiltin.A.extra_method()
func_dep_enum(MyDepEnum.A)
func_dep_str_enum_custom(MyDepStrEnumCustom.A)
func_dep_str_enum_builtin(MyDepStrEnumBuiltin.A)
func_local_str_enum_custom(MyLocalStrEnumCustom.A)
func_local_str_enum_builtin(MyLocalStrEnumBuiltin.A)
reveal_type(MyDepEnum.A)
reveal_type(MyDepEnum.B)
reveal_type(MyDepEnum.C)
reveal_type(MyDepStrEnumCustom.A)
reveal_type(MyDepStrEnumBuiltin.A)
reveal_type(MyLocalStrEnumCustom.A)
reveal_type(MyLocalStrEnumBuiltin.A)
From these files the following mypy errors are raised:
> $ mypy ./src/library_b/main.py
src/library_b/main.py:57: error: "str" has no attribute "extra_method" [attr-defined]
MyDepEnum.A.extra_method()
^~~~~~~~~~~~~~~~~~~~~~~~
src/library_b/main.py:58: error: "str" has no attribute "extra_method" [attr-defined]
MyDepStrEnumCustom.A.extra_method()
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/library_b/main.py:59: error: "str" has no attribute "extra_method" [attr-defined]
MyDepStrEnumBuiltin.A.extra_method()
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/library_b/main.py:63: error: Argument 1 to "func_dep_enum" has incompatible type "str"; expected "MyDepEnum" [arg-type]
func_dep_enum(MyDepEnum.A)
^~~~~~~~~~~
src/library_b/main.py:64: error: Argument 1 to "func_dep_str_enum_custom" has incompatible type "str"; expected "MyDepStrEnumCustom" [arg-type]
func_dep_str_enum_custom(MyDepStrEnumCustom.A)
^~~~~~~~~~~~~~~~~~~~
src/library_b/main.py:65: error: Argument 1 to "func_dep_str_enum_builtin" has incompatible type "str"; expected "MyDepStrEnumBuiltin" [arg-type]
func_dep_str_enum_builtin(MyDepStrEnumBuiltin.A)
^~~~~~~~~~~~~~~~~~~~~
src/gcs_core/utils/deleteme.py:70: note: Revealed type is "builtins.str"
src/gcs_core/utils/deleteme.py:71: note: Revealed type is "builtins.int"
src/gcs_core/utils/deleteme.py:72: note: Revealed type is "builtins.bool"
src/gcs_core/utils/deleteme.py:73: note: Revealed type is "builtins.str"
src/gcs_core/utils/deleteme.py:74: note: Revealed type is "builtins.str"
src/gcs_core/utils/deleteme.py:75: note: Revealed type is "Literal[gcs_core.utils.deleteme.MyLocalStrEnumCustom.A]?"
src/gcs_core/utils/deleteme.py:76: note: Revealed type is "Literal[gcs_core.utils.deleteme.MyLocalStrEnumBuiltin.A]?"
Found 6 errors in 1 file (checked 1 source file)
Observations
- mypy cannot identify any enum parent classes as being enums if they are defined in an installed library
-
reveal_typeshows that the types of enum values in a class inheriting from installed package (MyDep*) are inferred as the plain type of the value on the right side of= - These enums work as expected at runtime.