How Django’s management commands need to be type hinted
For Django management commands with arguments, you might not expect Mypy
(configured with the
django-stubs
plugin) to
complain about this signature:
from django.core.management.base import BaseCommand, CommandParser
class Command(BaseCommand):
def add_arguments(self, parser: CommandParser) -> None:
parser.add_argument("name")
def handle(self, name: str, *args, **options) -> None:
...
but it does with error:
Signature of "handle" incompatible with supertype "BaseCommand" [override]
The problem is the name
positional argument which makes the argument types
more specific than BaseCommand.handle
which has
type signature
:
def handle(self, *args: Any, **options: Any) -> Optional[str]:
This is well explained in the Mypy docs:
It’s unsafe to override a method with a more specific argument type, as it violates the Liskov substitution principle. For return types, it’s unsafe to override a method with a more general return type.
To proceed you can either pluck the positional arguments out of the kwargs
dict:
from django.core.management.base import BaseCommand, CommandParser
class Command(BaseCommand):
def add_arguments(self, parser: CommandParser) -> None:
parser.add_argument("name")
def handle(self, *args, **options) -> None:
name: str = options["name"]
...
or ignore the override
error:
from django.core.management.base import BaseCommand
class Command(BaseCommand):
def add_arguments(self, parser: CommandParser) -> None:
parser.add_argument("name")
def handle(self, name: str, *args, **options) -> None: # type: ignore[override]
...
The former is slightly safer as the ignore approach may cause issues if the type
signature of BaseCommand.handle
changes in a future Django version.
Accurate as of Django v3.1, Django-Stubs v1.8 and Mypy v0.812.