Skip to content

Inconsistent errors with custom enum base auto items from installed library #15382

Open

Description

@felix-hilden

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

Activity

  1. Avasam commented on Aug 14, 2023

    @Avasam
    SponsorContributor

    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 python/typeshed#10428)

  2. rpmcginty commented on Oct 16, 2024

    @rpmcginty

    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
  3. rpmcginty commented on Oct 17, 2024

    @rpmcginty

    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_type shows 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.
  4. Avasam commented on May 18, 2026

    @Avasam
    SponsorContributor

    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 python/typeshed#10428)

    mypy 1.17 has dropped support for Python 3.8:

    and mypy 2.0 has dropped support for Python 3.9:

    So this should now be solvable by always decorating _generate_next_value_ with @staticmethod

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions