On , I learnt ...

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.