Here’s an MRE which has a generic Msg
class that BaseHandler
subclasses handle
:
from abc import ABC
from dataclasses import dataclass
from typing import Generic, TypeVar
T = TypeVar('T')
@dataclass
class Msg(Generic[T]):
data: T
class BaseHandler(Generic[T], ABC):
def handle(self, msg: Msg[T]):
...
Add some classes to use in Msg
:
class Foo:
foo: bool
class Bar:
bar: float
Add a handler which can handle both classes:
FooBar = Foo | Bar
class FooBarHandler(BaseHandler[FooBar]):
def handle(self, msg: Msg[FooBar]):
match (data := msg.data):
case Foo():
print('foo', data.foo)
case Bar():
print('foo', data.bar)
foobar_handler = FooBarHandler()
foobar_handler.handle(Msg(data=Foo()))
foobar_handler.handle(Msg(data=Bar()))
That all type checks
Now the problem: If I extract Msg
to a variable:
msg = Msg(data=Foo())
foobar_handler.handle(msg)
Pyright:
error: Argument of type "Msg[Foo]" cannot be assigned to parameter "msg" of type "Msg[FooBar]" in function "handle"
"Msg[Foo]" is incompatible with "Msg[FooBar]"
Type parameter "T@Msg" is invariant, but "Foo" is not the same as "FooBar" (reportGeneralTypeIssues)
Mypy:
error: Argument 1 to "handle" of "FooBarHandler" has incompatible type "Msg[Foo]"; expected "Msg[Foo | Bar]" [arg-type]
I can ‘workaround’ it by explicitly instantiating Msg
with FooBar
:
msg = Msg[FooBar](data=Foo())
But I feel like there should be a better way to let Python know that a Msg[Foo]
is covariant with Msg[FooBar]
in handle
?