← Back to index

enums_members.py

True Positive
False Positive
False Negative
Optional (detected)
Warning or Info
TP: 6
FP: 0
FN: 1
Optional: 2 / 2
1"""
2Tests that the type checker can distinguish enum members from non-members.
3"""
4
5# Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members
6
7from enum import Enum, member, nonmember
8from typing import Literal, assert_type, reveal_type
9
10# > If an attribute is defined in the class body with a type annotation but
11# > with no assigned value, a type checker should assume this is a non-member
12# > attribute
15class Pet(Enum):
16 genus: str # Non-member attribute
17 species: str # Non-member attribute
19 CAT = "felis", "catus" # Member attribute
20 DOG = "canis", "lupus" # Member attribute
22 def __init__(self, genus: str, species: str) -> None:
23 self.genus = genus
24 self.species = species
27assert_type(Pet.genus, str)
28assert_type(Pet.species, str)
29assert_type(Pet.CAT, Literal[Pet.CAT])
30assert_type(Pet.DOG, Literal[Pet.DOG])
33from _enums_members import Pet2
35assert_type(Pet2.genus, str)
36assert_type(Pet2.species, str)
37assert_type(Pet2.CAT, Literal[Pet2.CAT])
38assert_type(Pet2.DOG, Literal[Pet2.DOG])
41# > Members defined within an enum class should not include explicit type
42# > annotations. Type checkers should infer a literal type for all members.
43# > A type checker should report an error if a type annotation is used for
44# > an enum member because this type will be incorrect and misleading to
45# > readers of the code
48class Pet3(Enum):
49 CAT = 1
50 DOG: int = 2 # E
Expected a ty diagnostic for this line
53# > Methods, callables, descriptors (including properties), and nested classes
54# > that are defined in the class are not treated as enum members by the
55# > EnumType metaclass and should likewise not be treated as enum members by a
56# > type checker
59def identity(x: int) -> int:
60 return x
63class Pet4(Enum):
64 CAT = 1 # Member attribute
65 DOG = 2 # Member attribute
67 converter = lambda x: str(x) # Non-member attribute
68 transform = staticmethod(identity) # Non-member attribute
70 @property
71 def species(self) -> str: # Non-member property
72 return "mammal"
74 def speak(self) -> None: # Non-member method
75 print("meow" if self is Pet.CAT else "woof")
77 class Nested: ... # Non-member nested class
80assert_type(Pet4.CAT, Literal[Pet4.CAT])
81assert_type(Pet4.DOG, Literal[Pet4.DOG])
82converter: Literal[Pet4.converter] # E
[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
83transform: Literal[Pet4.transform] # E
[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
84species: Literal[Pet4.species] # E
[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
85speak: Literal[Pet4.speak] # E
[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
88# > An attribute that is assigned the value of another member of the same
89# > enum is not a member itself. Instead, it is an alias for the first member
92class TrafficLight(Enum):
93 RED = 1
94 GREEN = 2
95 YELLOW = 3
97 AMBER = YELLOW # Alias for YELLOW
100assert_type(TrafficLight.AMBER, Literal[TrafficLight.YELLOW])
102# > If using Python 3.11 or newer, the enum.member and enum.nonmember classes
103# > can be used to unambiguously distinguish members from non-members.
106class Example(Enum):
107 a = member(1) # Member attribute
108 b = nonmember(2) # Non-member attribute
110 @member
111 def c(self) -> None: # Member method
112 pass
115assert_type(Example.a, Literal[Example.a])
116assert_type(Example.b, Literal[Example.b]) # E
[type-assertion-failure] Type `Unknown` does not match asserted type `int` [invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
117assert_type(Example.c, Literal[Example.c])
120# > An attribute with a private name (beginning with, but not ending in,
121# > a double underscore) is treated as a non-member.
124class Example2(Enum):
125 __B = 2 # Non-member attribute
127 def method(self):
128 reveal_type(Example2.__B)
[revealed-type] Revealed type: `Unknown | Literal[2]`
129 assert_type(Example2.__B, Literal[Example2.__B]) # E
[type-assertion-failure] Type `Unknown` does not match asserted type `Unknown | Literal[2]` [invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
132# > An enum class can define a class symbol named _ignore_. This can be
133# > a list of names or a string containing a space-delimited list of names
134# > that are deleted from the enum class at runtime. Type checkers may
135# > support this mechanism
138class Pet5(Enum):
139 _ignore_ = "DOG FISH"
140 CAT = 1 # Member attribute
141 DOG = 2 # temporary variable, will be removed from the final enum class
142 FISH = 3 # temporary variable, will be removed from the final enum class
145assert_type(Pet5.CAT, Literal[Pet5.CAT])
146assert_type(Pet5.DOG, int) # E?: Literal[2] is also acceptable
[type-assertion-failure] Type `int` does not match asserted type `Unknown | Literal[2]`
147assert_type(Pet5.FISH, int) # E?: Literal[3] is also acceptable
[type-assertion-failure] Type `int` does not match asserted type `Unknown | Literal[3]`