How do I type the `__prepare__` method for a metaclass?

Question:

I’m trying to write a simple metaclass that intercepts every function declaration and replaces it with a dummy function:

from dataclasses import dataclass
from typing import Any, Mapping


@dataclass
class DummyCall:
    args: tuple[Any, ...]
    kwargs: dict[str, Any]


def _dummy_function(*args: Any, **kwargs: Any) -> DummyCall:
    return DummyCall(args, kwargs)


class _dummy_dict(dict[str, Any]):
    def __setitem__(self, key: str, value: Any) -> None:
        if callable(value):
            super().__setitem__(key, _dummy_function)
        else:
            super().__setitem__(key, value)


class dummy(type):
    @classmethod
    def __prepare__(metcls, name, bases, **kwds):
        return _dummy_dict()

I now want to type-hint the __prepare__ method. I’ve tried the following:

def __prepare__(*_, *__): …

…but of course this doesn’t work. From the error I got I tried to reconstruct the type, and ended up with the following:

def __prepare__(metcls: Any, name: str, bases: tuple[type, ...], **kwds: Any) -> Mapping[str, Any]: …

Unfortunately, this still doesn’t satisfy MyPy. I get the following error:

error: Signature of "__prepare__" incompatible with supertype "type"
note:      Superclass:
note:          def __prepare__(metacls, str, Tuple[type, ...], **kwds: Any) -> Mapping[str, object]
note:      Subclass:
note:          @classmethod
note:          def __prepare__(metcls, name: str, bases: Tuple[type, ...], **kwds: Any) -> Mapping[str, Any]

I also interestingly get different errors in my code editor with a MyPy plugin:

Signature of "__prepare__" incompatible with supertype "type"mypy
     Superclass:mypy
         @classmethodmypy
         def __prepare__(metacls, str, Tuple[type, ...], **kwds: Any) -> Mapping[str, object]mypy
     Subclass:mypy
         @classmethodmypy
         def __prepare__(metcls, name: str, bases: Tuple[type, ...], **kwds: Any) -> Mapping[str, Any]mypy

Here, it is reported that the superclass definition is annotated with @classmethod, while the command-line mypy doesn’t say this.

Asked By: schuelermine

||

Answers:

The reason was that I didn’t use the exact same argument names as in the .pyi file.

This works:

@classmethod
def __prepare__(metacls, __name: str, __bases: tuple[type, ...], **kwds: Any) -> Mapping[str, object]:
    …

Interestingly, the type doesn’t need to match exactly. I was able to use tuple instead of Tuple in the typestub.

Answered By: schuelermine
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.