I am fighting with type narrowing of StrEnums
To summarize my issue I have some abstract base class
from abc import ABC, abstractmethod
from enum import StrEnum
class Animal(ABC):
@staticmethod
@abstractmethod
def speak(animal_type: StrEnum):
...
and derivative classes each with their own StrEnums:
class FrogType(StrEnum):
frog = "frog"
toad = "toad"
class Frog(Animal):
@staticmethod
def speak(animal_type: FrogType):
print(f"{animal_type} goes ribbit")
The following code works as expected:
Frog.speak(FrogType.frog) # "frog goes ribbit"
and enums seem to be compatible:
print(isinstance(FrogType.frog, FrogType)) # True
print(isinstance(FrogType.frog, StrEnum)) # True
However MyPy doesnt recognize this as valid:
# python3.11 -m mypy frog.py
frog.py:16: error: Argument 1 of "speak" is incompatible with supertype "Animal"; supertype defines the argument type as "StrEnum" [override]
frog.py:16: note: This violates the Liskov substitution principle
frog.py:16: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Found 1 error in 1 file (checked 1 source file)
This seems to be a valid type narrowing, so I must be annoting something wrong. What would be a correct solution?
You can avoid this issue by using a generic in place of StrEnum
. Below AnimalType
is defined such that it has a bound of StrEnum
:
from abc import ABC, abstractmethod
from enum import StrEnum
from typing import Generic, TypeVar
AnimalType = TypeVar("AnimalType", bound=StrEnum)
class Animal(ABC, Generic[AnimalType]):
@staticmethod
@abstractmethod
def speak(animal_type: AnimalType):
...
class FrogType(StrEnum):
frog = "frog"
toad = "toad"
class Frog(Animal[FrogType]):
@staticmethod
def speak(animal_type: FrogType):
print(f"{animal_type} goes ribbit")
print(isinstance(FrogType.frog, FrogType)) # True
print(isinstance(FrogType.frog, StrEnum)) # True
Hope this helps!