The key issue is that your permissiveness is upside down. Formally, it's because functions are contravariant in their inputs.
What you're promising with def launch_program_cmd(self, **kwargs) -> list[str]:
is "This method will be able to take any set of keyword arguments."
By way of example, if someone writes
def launch_lunch(launcher: LauncherTemplateConfig):
launcher.launch_program_cmd(food=["eggs", "spam", "spam"])
then according to the definition of LauncherTemplateConfig
that should be allowed.
But if you try to call that method with an instance of MyLauncherTemplateConfig
then it will crash because it doesn't know what to do with the food
parameter. So MyLauncherTemplateConfig
is not a valid subtype of LauncherTemplateConfig
I suspect that what you're intending to convey is more like "This method will exist, but I don't know what arguments it will take." However, that's not really something that MyPy is set up to express. The basic reason is that it's not very useful: there's not a lot you can do with a promise that a method will exist but you don't know how to call it!
(Note: the opposite direction is allowed. If your Protocol specified that you must be able to take some_arg
and another_arg
and your implementation was able to handle anything at all, that would be allowed. But generally, you'd want your protocol to guide you in what you'd actually want to take.)