from enum import Enum
from typing import Literal
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
reveal_type(Color.RED) # revealed: Literal[Color.RED]
reveal_type(Color.RED.name) # revealed: Literal["RED"]
reveal_type(Color.RED.value) # revealed: Literal[1]
# TODO: Could be `Literal[Color.RED]` to be more precise
reveal_type(Color["RED"]) # revealed: Color
reveal_type(Color(1)) # revealed: Color
reveal_type(Color.RED in Color) # revealed: boolSimple enums with integer or string values:
from enum import Enum
from ty_extensions import enum_members
class ColorInt(Enum):
RED = 1
GREEN = 2
BLUE = 3
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(ColorInt))
class ColorStr(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(ColorStr))from enum import IntEnum
from ty_extensions import enum_members
class ColorInt(IntEnum):
RED = 1
GREEN = 2
BLUE = 3
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(ColorInt))If an enum attribute has both an annotation and a value, it is still an enum member at runtime, even though the annotation is invalid:
from enum import Enum
from ty_extensions import enum_members
class Answer(Enum):
YES = 1
NO = 2
annotated_member: str = "some value" # error: [invalid-enum-member-annotation]
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["annotated_member"]]
reveal_type(enum_members(Answer))
reveal_type(Answer.annotated_member) # revealed: Literal[Answer.annotated_member]
reveal_type(Answer.YES.annotated_member) # revealed: Literal[Answer.annotated_member]Enum members are allowed to be marked Final (without a type), even if unnecessary:
from enum import Enum
from typing import Final
from ty_extensions import enum_members
class Answer(Enum):
YES: Final = 1
NO: Final = 2
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))The typing spec states that enum members should not have explicit type annotations. Type checkers should report an error for annotated enum members because the annotation is misleading — the actual type of an enum member is the enum class itself, not the annotated type.
[environment]
python-version = "3.11"from enum import Enum, IntEnum, StrEnum, member
from typing import Callable, Final
class Pet(Enum):
CAT = 1
DOG: int = 2 # error: [invalid-enum-member-annotation] "Type annotation on enum member `DOG` is not allowed"
BIRD: str = "bird" # error: [invalid-enum-member-annotation]Bare Final annotations are allowed (they don't specify a type):
class Pet2(Enum):
CAT: Final = 1 # OK
DOG: Final = 2 # OKBut Final with a type argument is not allowed:
class Pet3(Enum):
CAT: Final[int] = 1 # error: [invalid-enum-member-annotation]
DOG: Final[str] = "woof" # error: [invalid-enum-member-annotation]enum.member used as value wrapper is the standard way to declare members explicitly:
class Pet4(Enum):
CAT = member(1) # OKDunder and private names are not enum members, so they don't trigger the diagnostic:
class Pet5(Enum):
CAT = 1
__private: int = 2 # OK: dunder/private names are never members
__module__: str = "my_module" # OKPure declarations (annotations without values) are non-members and are fine:
class Pet6(Enum):
CAT = 1
species: str # OK: no value, so this is a non-member declaration
reveal_type(Pet6.species) # revealed: str
reveal_type(Pet6.CAT.species) # revealed: strIn stubs, these should still be treated as non-member attributes rather than enum members:
from enum import Enum
class Pet6Stub(Enum):
species: str
CAT = ...
DOG = ...
reveal_type(Pet6Stub.species) # revealed: strCallable values are never enum members at runtime, so annotating them is fine:
[environment]
python-version = "3.11"from enum import Enum, IntEnum, StrEnum
from typing import Callable
def identity(x: int) -> int:
return x
class Pet7(Enum):
CAT = 1
declared_callable: Callable[[int], int] = identity # OK: callables are never membersThe check also works for subclasses of Enum:
class Status(IntEnum):
OK: int = 200 # error: [invalid-enum-member-annotation]
NOT_FOUND = 404 # OK
class Color(StrEnum):
RED: str = "red" # error: [invalid-enum-member-annotation]
GREEN = "green" # OKSpecial sunder names like _value_ and _ignore_ are not flagged:
class Pet8(Enum):
_value_: int = 0 # OK: `_value_` is a special enum name
_ignore_: str = "TEMP" # OK: `_ignore_` is a special enum name
CAT = 1Names listed in _ignore_ are not members, so annotating them is fine:
class Pet9(Enum):
_ignore_ = "A B"
A: int = 42 # OK: `A` is listed in `_ignore_`
B: str = "hello" # OK: `B` is listed in `_ignore_`
C: int = 3 # error: [invalid-enum-member-annotation]Statically unreachable declarations should be ignored when deciding whether a name is an enum member:
from enum import Enum
from ty_extensions import enum_members
class Pet10(Enum):
if False:
CAT: int
CAT = 1
DOG = 2
# revealed: tuple[Literal["CAT"], Literal["DOG"]]
reveal_type(enum_members(Pet10))
reveal_type(Pet10.CAT) # revealed: Literal[Pet10.CAT]
reveal_type(Pet10.DOG) # revealed: Literal[Pet10.DOG]If a _value_ annotation is defined on an Enum class, all enum member values must be compatible
with the declared type:
from enum import Enum
class Color(Enum):
_value_: int
RED = 1
GREEN = "green" # error: [invalid-assignment]
BLUE = ...
YELLOW = None # error: [invalid-assignment]
PURPLE = [] # error: [invalid-assignment]When _value_ is annotated, .value and ._value_ are inferred as the declared type:
from enum import Enum
from typing import Final
class Color2(Enum):
_value_: int
RED = 1
GREEN = 2
reveal_type(Color2.RED.value) # revealed: int
reveal_type(Color2.RED._value_) # revealed: int
class WantsInt(Enum):
_value_: int
OK: Final = 1
BAD: Final = "oops" # error: [invalid-assignment]When __init__ is defined, member values are validated by synthesizing a call to __init__. The
_value_ annotation still constrains assignments to self._value_ inside __init__:
from enum import Enum
class Planet(Enum):
_value_: int
def __init__(self, value: int, mass: float, radius: float):
self._value_ = value
MERCURY = (1, 3.303e23, 2.4397e6)
SATURN = "saturn" # error: [invalid-assignment]
reveal_type(Planet.MERCURY.value) # revealed: int
reveal_type(Planet.MERCURY._value_) # revealed: intFinal-annotated members are also validated against __init__:
from enum import Enum
from typing import Final
class Planet(Enum):
def __init__(self, mass: float, radius: float):
self.mass = mass
self.radius = radius
MERCURY: Final = (3.303e23, 2.4397e6)
BAD: Final = "not a planet" # error: [invalid-assignment]When _value_ and __init__ disagree, the assignment inside __init__ is flagged:
from enum import Enum
class Planet(Enum):
_value_: str
def __init__(self, value: int, mass: float, radius: float):
self._value_ = value # error: [invalid-assignment]
MERCURY = (1, 3.303e23, 2.4397e6)
SATURN = "saturn" # error: [invalid-assignment]
reveal_type(Planet.MERCURY.value) # revealed: str
reveal_type(Planet.MERCURY._value_) # revealed: strWhen __init__ is defined but no explicit _value_ annotation exists, member values are validated
against the __init__ signature. Values that are incompatible with __init__ are flagged:
from enum import Enum
class Planet2(Enum):
def __init__(self, mass: float, radius: float):
self.mass = mass
self.radius = radius
MERCURY = (3.303e23, 2.4397e6)
VENUS = (4.869e24, 6.0518e6)
INVALID = "not a planet" # error: [invalid-assignment]
reveal_type(Planet2.MERCURY.value) # revealed: Any
reveal_type(Planet2.MERCURY._value_) # revealed: AnyA _value_ annotation on a parent enum is inherited by subclasses. Member values are validated
against the inherited annotation, and .value uses the declared type:
from enum import Enum
class Base(Enum):
_value_: int
class Child(Base):
A = 1
B = "not an int" # error: [invalid-assignment]
reveal_type(Child.A.value) # revealed: intThis also works through multiple levels of inheritance, where _value_ is declared on an
intermediate class:
from enum import Enum
class Grandparent(Enum):
pass
class Parent(Grandparent):
_value_: int
class Child(Parent):
A = 1
B = "not an int" # error: [invalid-assignment]
reveal_type(Child.A.value) # revealed: intA custom __init__ on a parent enum is inherited by subclasses. Member values are validated against
the inherited __init__ signature:
from enum import Enum
class Base(Enum):
def __init__(self, a: int, b: str):
self._value_ = a
class Child(Base):
A = (1, "foo")
B = "should be checked against __init__" # error: [invalid-assignment]
reveal_type(Child.A.value) # revealed: AnyThis also works through multiple levels of inheritance:
from enum import Enum
class Grandparent(Enum):
def __init__(self, a: int, b: str):
self._value_ = a
class Parent(Grandparent):
pass
class Child(Parent):
A = (1, "foo")
B = "bad" # error: [invalid-assignment]
reveal_type(Child.A.value) # revealed: AnyMethods, callables, descriptors (including properties), and nested classes that are defined in the class are not treated as enum members:
from enum import Enum
from ty_extensions import enum_members
from typing import Callable, Literal
def identity(x) -> int:
return x
class Descriptor:
def __get__(self, instance, owner):
return 0
class Answer(Enum):
YES = 1
NO = 2
def some_method(self) -> None: ...
@staticmethod
def some_static_method() -> None: ...
@classmethod
def some_class_method(cls) -> None: ...
some_callable = lambda x: 0
declared_callable: Callable[[int], int] = identity
function_reference = identity
some_descriptor = Descriptor()
@property
def some_property(self) -> str:
return ""
class NestedClass: ...
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))Enum attributes that are defined using enum.property are not considered members:
[environment]
python-version = "3.11"from enum import Enum, property as enum_property
from typing import Any
from ty_extensions import enum_members
class Answer(Enum):
YES = 1
NO = 2
@enum_property
def some_property(self) -> str:
return "property value"
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))Enum attributes defined using enum.property take precedence over generated attributes.
from enum import Enum, property as enum_property
class Choices(Enum):
A = 1
B = 2
@enum_property
def value(self) -> Any: ...
# TODO: This should be `Any` - overridden by `@enum_property`
reveal_type(Choices.A.value) # revealed: Literal[1]Attributes defined using types.DynamicClassAttribute are not considered members:
from enum import Enum
from ty_extensions import enum_members
from types import DynamicClassAttribute
class Answer(Enum):
YES = 1
NO = 2
@DynamicClassAttribute
def dynamic_property(self) -> str:
return "dynamic value"
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))Stubs can optionally use ... for the actual value:
from enum import Enum
from ty_extensions import enum_members
from typing import cast
class Color(Enum):
RED = ...
GREEN = cast(int, ...)
BLUE = 3
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))Enum members can have aliases, which are not considered separate members:
from enum import Enum
from ty_extensions import enum_members
class Answer(Enum):
YES = 1
NO = 2
DEFINITELY = YES
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
reveal_type(Answer.DEFINITELY) # revealed: Literal[Answer.YES]If a value is duplicated, we also treat that as an alias:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
red = 1
green = 2
# revealed: tuple[Literal["RED"], Literal["GREEN"]]
reveal_type(enum_members(Color))
# revealed: Literal[Color.RED]
reveal_type(Color.red)Multiple aliases to the same member are also supported. This is a regression test for astral-sh/ty#1293:
from ty_extensions import enum_members
class ManyAliases(Enum):
real_member = "real_member"
alias1 = "real_member"
alias2 = "real_member"
alias3 = "real_member"
other_member = "other_real_member"
# revealed: tuple[Literal["real_member"], Literal["other_member"]]
reveal_type(enum_members(ManyAliases))
reveal_type(ManyAliases.real_member) # revealed: Literal[ManyAliases.real_member]
reveal_type(ManyAliases.alias1) # revealed: Literal[ManyAliases.real_member]
reveal_type(ManyAliases.alias2) # revealed: Literal[ManyAliases.real_member]
reveal_type(ManyAliases.alias3) # revealed: Literal[ManyAliases.real_member]
reveal_type(ManyAliases.real_member.value) # revealed: Literal["real_member"]
reveal_type(ManyAliases.real_member.name) # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias1.value) # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias1.name) # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias2.value) # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias2.name) # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias3.value) # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias3.name) # revealed: Literal["real_member"][environment]
python-version = "3.11"from enum import Enum, auto
from ty_extensions import enum_members
class Answer(Enum):
YES = auto()
NO = auto()
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
reveal_type(Answer.YES.value) # revealed: Literal[1]
reveal_type(Answer.NO.value) # revealed: Literal[2]
class SingleMember(Enum):
SINGLE = auto()
reveal_type(SingleMember.SINGLE.value) # revealed: Literal[1]Usages of auto() can be combined with manual value assignments:
class Mixed(Enum):
MANUAL_1 = -1
AUTO_1 = auto()
MANUAL_2 = -2
AUTO_2 = auto()
reveal_type(Mixed.MANUAL_1.value) # revealed: Literal[-1]
reveal_type(Mixed.AUTO_1.value) # revealed: Literal[1]
reveal_type(Mixed.MANUAL_2.value) # revealed: Literal[-2]
reveal_type(Mixed.AUTO_2.value) # revealed: Literal[2]When using auto() with StrEnum, the value is the lowercase name of the member:
from enum import StrEnum, auto
class Answer(StrEnum):
YES = auto()
NO = auto()
reveal_type(Answer.YES.value) # revealed: Literal["yes"]
reveal_type(Answer.NO.value) # revealed: Literal["no"]
class SingleMember(StrEnum):
SINGLE = auto()
reveal_type(SingleMember.SINGLE.value) # revealed: Literal["single"]Using auto() with IntEnum also works as expected. IntEnum declares _value_: int in typeshed,
so .value is typed as int rather than a precise literal:
from enum import IntEnum, auto
class Answer(IntEnum):
YES = auto()
NO = auto()
reveal_type(Answer.YES.value) # revealed: int
reveal_type(Answer.NO.value) # revealed: intAs does using auto() for other enums that use int as a mixin:
from enum import Enum, auto
class Answer(int, Enum):
YES = auto()
NO = auto()
reveal_type(Answer.YES.value) # revealed: Literal[1]
reveal_type(Answer.NO.value) # revealed: Literal[2]It's hard to predict what the
effect of using auto() will be for an arbitrary non-integer mixin, so for anything that isn't a
StrEnum and has a non-int mixin, we simply fallback to typeshed's annotation of Any for the
value property:
from enum import Enum, auto
class A(str, Enum):
X = auto()
Y = auto()
reveal_type(A.X.value) # revealed: Any
class B(bytes, Enum):
X = auto()
Y = auto()
reveal_type(B.X.value) # revealed: Any
class C(tuple, Enum):
X = auto()
Y = auto()
reveal_type(C.X.value) # revealed: Any
class D(float, Enum):
X = auto()
Y = auto()
reveal_type(D.X.value) # revealed: AnyCombining aliases with auto():
from enum import Enum, auto
class Answer(Enum):
YES = auto()
NO = auto()
DEFINITELY = YES
# TODO: This should ideally be `tuple[Literal["YES"], Literal["NO"]]`
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["DEFINITELY"]]
reveal_type(enum_members(Answer))auto() values are computed at runtime by the enum metaclass, so we skip validation against both
_value_ annotations and custom __init__ signatures:
from enum import Enum, auto
class WithValue(Enum):
_value_: int
A = auto()
B = auto()
reveal_type(WithValue.A.value) # revealed: int
class WithInit(Enum):
def __init__(self, mass: float, radius: float):
self.mass = mass
self.radius = radius
MERCURY = (3.303e23, 2.4397e6)
AUTO = auto()
reveal_type(WithInit.MERCURY.value) # revealed: Any[environment]
python-version = "3.11"from enum import Enum, auto, member, nonmember
from ty_extensions import enum_members
class Answer(Enum):
YES = member(1)
NO = member(2)
OTHER = nonmember(17)
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
# `nonmember` attributes are unwrapped to the inner value type when accessed.
# revealed: int
reveal_type(Answer.OTHER)member can also be used as a decorator:
from enum import Enum, member
from ty_extensions import enum_members
class Answer(Enum):
yes = member(1)
no = member(2)
@member
def maybe(self) -> None:
return
# revealed: tuple[Literal["yes"], Literal["no"], Literal["maybe"]]
reveal_type(enum_members(Answer))An attribute with a name beginning with a double underscore is treated as a non-member. This
includes both class-private names (not ending in __) and dunder names (ending in __).
CPython's enum metaclass excludes all such names from membership:
from enum import Enum, IntEnum
from ty_extensions import enum_members
class Answer(Enum):
YES = 1
NO = 2
__private_member = 3
__maybe__ = 4
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))Setting __module__ (a common pattern to control repr() and pickle behavior) does not make it
an enum member, even when the value type differs from the enum's value type:
class ExitCode(IntEnum):
OK = 0
ERROR = 1
__module__ = "my_package" # no error, not a member
# revealed: tuple[Literal["OK"], Literal["ERROR"]]
reveal_type(enum_members(ExitCode))An enum class can define a class symbol named _ignore_. This can be a string containing a
whitespace-delimited list of names:
from enum import Enum
from ty_extensions import enum_members
class Answer(Enum):
_ignore_ = "IGNORED _other_ignored also_ignored"
YES = 1
NO = 2
IGNORED = 3
_other_ignored = "test"
also_ignored = "test2"
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))_ignore_ can also be a list of names:
class Answer2(Enum):
_ignore_ = ["MAYBE", "_other"]
YES = 1
NO = 2
MAYBE = 3
_other = "test"
# TODO: This should be `tuple[Literal["YES"], Literal["NO"]]`
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["MAYBE"], Literal["_other"]]
reveal_type(enum_members(Answer2))Make sure that special names like name and value can be used for enum members (without
conflicting with Enum.name and Enum.value):
from enum import Enum
from ty_extensions import enum_members
class Answer(Enum):
name = 1
value = 2
# revealed: tuple[Literal["name"], Literal["value"]]
reveal_type(enum_members(Answer))
reveal_type(Answer.name) # revealed: Literal[Answer.name]
reveal_type(Answer.value) # revealed: Literal[Answer.value]from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
for color in Color:
reveal_type(color) # revealed: Color
# TODO: Should be `list[Color]`
reveal_type(list(Color)) # revealed: list[Unknown]Methods and non-member attributes defined in the enum class can be accessed on enum members:
from enum import Enum
class Answer(Enum):
YES = 1
NO = 2
def is_yes(self) -> bool:
return self == Answer.YES
constant: int
reveal_type(Answer.YES.is_yes()) # revealed: bool
reveal_type(Answer.YES.constant) # revealed: int
class MyEnum(Enum):
def some_method(self) -> None:
pass
class MyAnswer(MyEnum):
YES = 1
NO = 2
reveal_type(MyAnswer.YES.some_method()) # revealed: Nonefrom enum import Enum
class Answer(Enum):
YES = 1
NO = 2
reveal_type(Answer.YES.NO) # revealed: Literal[Answer.NO]
def _(answer: Answer) -> None:
reveal_type(answer.YES) # revealed: Literal[Answer.YES]
reveal_type(answer.NO) # revealed: Literal[Answer.NO]from enum import Enum
class Answer(Enum):
YES = 1
NO = 2
def _(answer: type[Answer]) -> None:
reveal_type(answer.YES) # revealed: Literal[Answer.YES]
reveal_type(answer.NO) # revealed: Literal[Answer.NO]from enum import Enum
from typing import Callable
import sys
class Printer(Enum):
STDOUT = 1
STDERR = 2
def __call__(self, msg: str) -> None:
if self == Printer.STDOUT:
print(msg)
elif self == Printer.STDERR:
print(msg, file=sys.stderr)
Printer.STDOUT("Hello, world!")
Printer.STDERR("An error occurred!")
callable: Callable[[str], None] = Printer.STDOUT
callable("Hello again!")
callable = Printer.STDERR
callable("Another error!")from enum import Enum
from typing import Literal
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
reveal_type(Color.RED._name_) # revealed: Literal["RED"]
def _(red_or_blue: Literal[Color.RED, Color.BLUE]):
reveal_type(red_or_blue.name) # revealed: Literal["RED", "BLUE"]
def _(color: Color):
reveal_type(color.name) # revealed: Literal["RED", "GREEN", "BLUE"][environment]
python-version = "3.11"from enum import Enum, StrEnum
from typing import Literal
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
reveal_type(Color.RED.value) # revealed: Literal[1]
reveal_type(Color.RED._value_) # revealed: Literal[1]
reveal_type(Color.GREEN.value) # revealed: Literal[2]
reveal_type(Color.GREEN._value_) # revealed: Literal[2]
def _(color: Color):
reveal_type(color.value) # revealed: Literal[1, 2, 3]
class Answer(StrEnum):
YES = "yes"
NO = "no"
reveal_type(Answer.YES.value) # revealed: Literal["yes"]
reveal_type(Answer.YES._value_) # revealed: Literal["yes"]
reveal_type(Answer.NO.value) # revealed: Literal["no"]
reveal_type(Answer.NO._value_) # revealed: Literal["no"]
def _(answer: Answer):
reveal_type(answer.value) # revealed: Literal["yes", "no"]An enum with one or more defined members cannot be subclassed. They are implicitly "final".
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# error: [subclass-of-final-class] "Class `ExtendedColor` cannot inherit from final class `Color`"
class ExtendedColor(Color):
YELLOW = 4
def f(color: Color):
if isinstance(color, int):
reveal_type(color) # revealed: NeverAn Enum subclass without any defined members can be subclassed:
from enum import Enum
from ty_extensions import enum_members
class MyEnum(Enum):
def some_method(self) -> None:
pass
class Answer(MyEnum):
YES = 1
NO = 2
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))from enum import Enum
class Answer(Enum):
YES = 1
NO = 2
reveal_type(type(Answer.YES)) # revealed: <class 'Answer'>
class NoMembers(Enum): ...
def _(answer: Answer, no_members: NoMembers):
reveal_type(type(answer)) # revealed: <class 'Answer'>
reveal_type(type(no_members)) # revealed: type[NoMembers]from enum import Enum
from typing import Literal
from ty_extensions import enum_members
class Answer(Enum):
YES = 1
NO = 2
@classmethod
def yes(cls) -> "Literal[Answer.YES]":
return Answer.YES
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))Enum classes can also be defined using a subclass of enum.Enum or any class that uses
enum.EnumType (or a subclass thereof) as a metaclass. enum.EnumType was called enum.EnumMeta
prior to Python 3.11.
from enum import Enum, EnumMeta
class CustomEnumSubclass(Enum):
def custom_method(self) -> int:
return 0
class EnumWithCustomEnumSubclass(CustomEnumSubclass):
NO = 0
YES = 1
reveal_type(EnumWithCustomEnumSubclass.NO) # revealed: Literal[EnumWithCustomEnumSubclass.NO]
reveal_type(EnumWithCustomEnumSubclass.NO.custom_method()) # revealed: int[environment]
python-version = "3.9"from enum import Enum, EnumMeta
class EnumWithEnumMetaMetaclass(metaclass=EnumMeta):
# Using `EnumMeta` as a metaclass without inheriting `Enum` requires an `__init__`
# method that will accept member values (TODO we could catch the lack of this):
def __init__(self, val): ...
NO = 0
YES = 1
reveal_type(EnumWithEnumMetaMetaclass.NO) # revealed: Literal[EnumWithEnumMetaMetaclass.NO]
class SubclassOfEnumMeta(EnumMeta): ...
class EnumWithSubclassOfEnumMetaMetaclass(metaclass=SubclassOfEnumMeta):
def __init__(self, val): ...
NO = 0
YES = 1
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO) # revealed: Literal[EnumWithSubclassOfEnumMetaMetaclass.NO]
# Attributes `.value` and `.name` can *not* be accessed on members of these enums:
# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.value
# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.name
# But the internal underscore attributes are available:
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._value_) # revealed: Any
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._name_) # revealed: Literal["NO"]
def _(x: EnumWithSubclassOfEnumMetaMetaclass):
# error: [unresolved-attribute]
x.value
# error: [unresolved-attribute]
x.name
reveal_type(x._value_) # revealed: Any
reveal_type(x._name_) # revealed: Literal["NO", "YES"]In Python 3.11, the meta-type was renamed to EnumType.
[environment]
python-version = "3.11"from enum import Enum, EnumType
class EnumWithEnumMetaMetaclass(metaclass=EnumType):
def __init__(self, val): ...
NO = 0
YES = 1
reveal_type(EnumWithEnumMetaMetaclass.NO) # revealed: Literal[EnumWithEnumMetaMetaclass.NO]
class SubclassOfEnumMeta(EnumType): ...
class EnumWithSubclassOfEnumMetaMetaclass(metaclass=SubclassOfEnumMeta):
def __init__(self, val): ...
NO = 0
YES = 1
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO) # revealed: Literal[EnumWithSubclassOfEnumMetaMetaclass.NO]
# Attributes `.value` and `.name` can *not* be accessed on members of these enums:
# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.value
# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.name
# But the internal underscore attributes are available:
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._value_) # revealed: Any
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._name_) # revealed: Literal["NO"]
def _(x: EnumWithSubclassOfEnumMetaMetaclass):
# error: [unresolved-attribute]
x.value
# error: [unresolved-attribute]
x.name
reveal_type(x._value_) # revealed: Any
reveal_type(x._name_) # revealed: Literal["NO", "YES"]from enum import Enum
from ty_extensions import enum_members
Color = Enum("Color", "RED GREEN BLUE")
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))
Color = Enum("Color", "RED, GREEN, BLUE")
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))from enum import Enum
from ty_extensions import enum_members
Color = Enum("Color", names="RED GREEN BLUE")
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))from enum import Enum
from ty_extensions import enum_members
Color = Enum("Color", [("RED", 1), ("GREEN", 2), ("BLUE", 3)])
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))
Color = Enum("Color", (("RED", 1), ("GREEN", 2), ("BLUE", 3)))
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))from enum import Enum
from ty_extensions import enum_members
Color = Enum("Color", ["RED", "GREEN", "BLUE"])
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))from enum import Enum
from ty_extensions import enum_members
Color = Enum("Color", {"RED": 1, "GREEN": 2, "BLUE": 3})
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))
reveal_type(Color.RED.value) # revealed: Literal[1]
reveal_type(Color.GREEN.value) # revealed: Literal[2]
reveal_type(Color.BLUE.value) # revealed: Literal[3]from enum import Enum, auto
from ty_extensions import enum_members
Color = Enum("Color", {"RED": auto(), "GREEN": auto(), "BLUE": auto()})
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))
reveal_type(Color.RED.value) # revealed: Literal[1]
reveal_type(Color.GREEN.value) # revealed: Literal[2]
reveal_type(Color.BLUE.value) # revealed: Literal[3]When mixing explicit values with auto() in a dict, the auto value is derived from the previous
member's value, not from start + index:
from enum import Enum, auto
from ty_extensions import enum_members
Mixed = Enum("Mixed", {"A": 10, "B": auto(), "C": auto()})
# revealed: tuple[Literal["A"], Literal["B"], Literal["C"]]
reveal_type(enum_members(Mixed))
reveal_type(Mixed.A.value) # revealed: Literal[10]
reveal_type(Mixed.B.value) # revealed: Literal[11]
reveal_type(Mixed.C.value) # revealed: Literal[12]Duplicate member names raise TypeError at runtime. We degrade to unknown members rather than
synthesizing a broken enum.
from enum import Enum
from ty_extensions import enum_members
E1 = Enum("E1", "A A")
reveal_type(enum_members(E1)) # revealed: Unknown
E2 = Enum("E2", ["A", "A"])
reveal_type(enum_members(E2)) # revealed: Unknown
E3 = Enum("E3", [("A", 1), ("A", 2)])
reveal_type(enum_members(E3)) # revealed: UnknownWhen members are unknown, own member access returns Unknown, but inherited attributes from the
enum base class should still resolve through the MRO.
from enum import Enum
names: list[str] = ["A", "B"]
E = Enum("E", names)
# inherited class attributes resolve from Enum base
reveal_type(E.__members__) # revealed: MappingProxyType[str, E]
# own member access is unknown (can't tell if it exists)
reveal_type(E.FOO) # revealed: UnknownEnum(value, names, *, ...) only accepts two positional args at runtime.
from enum import Enum
from ty_extensions import enum_members
# error: [too-many-positional-arguments]
Color = Enum("Color", "RED", "GREEN", "BLUE")
reveal_type(enum_members(Color)) # revealed: Unknownfrom enum import Enum
# this is invalid at runtime but should not panic
Color = Enum()
reveal_type(Color) # revealed: @Todo(functional `Enum` syntax)Non-literal names should still be recognized as creating an enum class.
from enum import Enum
def make_enum(name: str, labels: tuple[str, ...]) -> type[Enum]:
result = Enum(name.title(), labels, module=__name__)
reveal_type(result) # revealed: type[Enum]
return resultfrom enum import Enum
# error: [invalid-argument-type]
Color = Enum(123, "RED GREEN BLUE")from enum import Enum
# error: [unknown-argument]
Color = Enum("Color", "RED GREEN BLUE", bad_kwarg=True)[environment]
python-version = "3.11"from enum import Flag
Perm = Flag("Perm", "READ WRITE EXECUTE", boundary=None)[environment]
python-version = "3.10"from enum import Flag
# error: [unknown-argument]
Perm = Flag("Perm", "READ WRITE EXECUTE", boundary=None)[environment]
python-version = "3.11"from enum import StrEnum
from ty_extensions import enum_members
Color = StrEnum("Color", "RED GREEN BLUE")
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))
reveal_type(Color.RED.value) # revealed: Literal["red"]
reveal_type(Color.GREEN.value) # revealed: Literal["green"]
reveal_type(Color.BLUE.value) # revealed: Literal["blue"]from enum import Enum
Color = Enum("Color", "RED GREEN BLUE", start=0)
reveal_type(Color.RED.value) # revealed: Literal[0]
reveal_type(Color.GREEN.value) # revealed: Literal[1]
reveal_type(Color.BLUE.value) # revealed: Literal[2]from enum import Enum
Http = Enum("Http", "OK NOT_FOUND", type=int)
reveal_type(Http.OK.value) # revealed: Literal[1]
reveal_type(Http.NOT_FOUND.value) # revealed: Literal[2]from enum import IntEnum
from ty_extensions import enum_members
Color = IntEnum("Color", "RED GREEN BLUE")
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))from enum import Flag
from ty_extensions import enum_members
Perm = Flag("Perm", "READ WRITE EXECUTE")
# revealed: tuple[Literal["READ"], Literal["WRITE"], Literal["EXECUTE"]]
reveal_type(enum_members(Perm))
reveal_type(Perm.READ.value) # revealed: Literal[1]
reveal_type(Perm.WRITE.value) # revealed: Literal[2]
reveal_type(Perm.EXECUTE.value) # revealed: Literal[4]from enum import IntFlag
from ty_extensions import enum_members
Perm = IntFlag("Perm", "READ WRITE EXECUTE")
# revealed: tuple[Literal["READ"], Literal["WRITE"], Literal["EXECUTE"]]
reveal_type(enum_members(Perm))
reveal_type(Perm.READ.value) # revealed: Literal[1]
reveal_type(Perm.WRITE.value) # revealed: Literal[2]
reveal_type(Perm.EXECUTE.value) # revealed: Literal[4]Values that would overflow i64 should gracefully widen to int.
from enum import Enum, Flag
Big = Enum("Big", "A B", start=9223372036854775807)
reveal_type(Big.A.value) # revealed: Literal[9223372036854775807]
reveal_type(Big.B.value) # revealed: int
BigFlag = Flag("BigFlag", "X Y", start=4611686018427387904)
reveal_type(BigFlag.X.value) # revealed: Literal[4611686018427387904]
reveal_type(BigFlag.Y.value) # revealed: intfrom enum import Enum
from typing_extensions import assert_never
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
def color_name(color: Color) -> str:
if color is Color.RED:
return "Red"
elif color is Color.GREEN:
return "Green"
elif color is Color.BLUE:
return "Blue"
else:
assert_never(color)
# No `invalid-return-type` error here because the implicit `else` branch is detected as unreachable:
def color_name_without_assertion(color: Color) -> str:
if color is Color.RED:
return "Red"
elif color is Color.GREEN:
return "Green"
elif color is Color.BLUE:
return "Blue"
def color_name_misses_one_variant(color: Color) -> str:
if color is Color.RED:
return "Red"
elif color is Color.GREEN:
return "Green"
else:
assert_never(color) # error: [type-assertion-failure] "Type `Literal[Color.BLUE]` is not equivalent to `Never`"
class Singleton(Enum):
VALUE = 1
def singleton_check(value: Singleton) -> str:
if value is Singleton.VALUE:
return "Singleton value"
else:
assert_never(value)[environment]
python-version = "3.10"from enum import Enum
from typing_extensions import assert_never
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
def color_name(color: Color) -> str:
match color:
case Color.RED:
return "Red"
case Color.GREEN:
return "Green"
case Color.BLUE:
return "Blue"
case _:
assert_never(color)
def color_name_without_assertion(color: Color) -> str:
match color:
case Color.RED:
return "Red"
case Color.GREEN:
return "Green"
case Color.BLUE:
return "Blue"
def color_name_misses_one_variant(color: Color) -> str:
match color:
case Color.RED:
return "Red"
case Color.GREEN:
return "Green"
case _:
assert_never(color) # error: [type-assertion-failure] "Type `Literal[Color.BLUE]` is not equivalent to `Never`"
class Singleton(Enum):
VALUE = 1
def singleton_check(value: Singleton) -> str:
match value:
case Singleton.VALUE:
return "Singleton value"
case _:
assert_never(value)from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
reveal_type(Color.RED == Color.RED) # revealed: Literal[True]
reveal_type(Color.RED != Color.RED) # revealed: Literal[False]from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
def __eq__(self, other: object) -> bool:
return False
reveal_type(Color.RED == Color.RED) # revealed: boolfrom enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
def __ne__(self, other: object) -> bool:
return False
reveal_type(Color.RED != Color.RED) # revealed: boolEnum classes cannot be generic. Python does not support generic enums, and attempting to create one
will result in a TypeError at runtime.
Using PEP 695 type parameters on an enum is invalid:
[environment]
python-version = "3.12"from enum import Enum
# error: [invalid-generic-enum] "Enum class `E` cannot be generic"
class E[T](Enum):
A = 1
B = 2Inheriting from both Enum and Generic[T] is also invalid:
from enum import Enum
from typing import Generic, TypeVar
T = TypeVar("T")
# error: [invalid-generic-enum] "Enum class `F` cannot be generic"
class F(Enum, Generic[T]):
A = 1
B = 2The order of bases doesn't matter; it's still invalid:
from enum import Enum
from typing import Generic, TypeVar
T = TypeVar("T")
# error: [invalid-generic-enum] "Enum class `G` cannot be generic"
class G(Generic[T], Enum):
A = 1
B = 2Subclasses of enum base classes also cannot be generic:
[environment]
python-version = "3.12"from enum import Enum, IntEnum
from typing import Generic, TypeVar
T = TypeVar("T")
# error: [invalid-generic-enum] "Enum class `MyIntEnum` cannot be generic"
class MyIntEnum[T](IntEnum):
A = 1
# error: [invalid-generic-enum] "Enum class `MyFlagEnum` cannot be generic"
class MyFlagEnum(IntEnum, Generic[T]):
A = 1Even with custom enum subclasses that don't have members, they cannot be made generic:
[environment]
python-version = "3.12"from enum import Enum
from typing import Generic, TypeVar
T = TypeVar("T")
class MyEnumBase(Enum):
def some_method(self) -> None: ...
# error: [invalid-generic-enum] "Enum class `MyEnum` cannot be generic"
class MyEnum[T](MyEnumBase):
A = 1- Typing spec: https://typing.python.org/en/latest/spec/enums.html
- Documentation: https://docs.python.org/3/library/enum.html