Search code examples
pythondecoratormypyabc

mypy throws error for abstractmethod created with decorator


I have a decorator that creates an abstractmethod from a simple method. It works as I'd expect, however if I run mypy, it tells me this:

mypy_try.py:20: error: Missing return statement  [empty-body]
mypy_try.py:20: note: If the method is meant to be abstract, use @abc.abstractmethod
Found 1 error in 1 file (checked 1 source file)

My code:

import abc
from functools import wraps

import pytest


def make_it_abstract(method_to_decorate):

    @wraps(method_to_decorate)
    def decorated_method(*method_args, **method_kwargs):
        return method_to_decorate(*method_args, **method_kwargs)

    return abc.abstractmethod(decorated_method)


class MyInterfaceClass(abc.ABC):

    @make_it_abstract
    # @abc.abstractmethod
    def my_method(self, value: int) -> int:
        ...


def test_abstract_method():

    class MyImplementationClass(MyInterfaceClass):
        pass

    with pytest.raises(
            TypeError,
            match="Can't instantiate abstract class MyImplementationClass with abstract method my_method"
    ):

        MyImplementationClass()

    class MyImplementationClass(MyInterfaceClass):
        def my_method(self, value: int) -> float:
            return value +1

    assert 43 == MyImplementationClass().my_method(42)

If I use the abc.abstractmethod decorator, it works fine. What am I doing wrong?


Solution

  • You're doind everything fine, but mypy is not smart enough to figure out that your decorator calls abc.abstractmethod (and this is almost impossible, in fact, even if you've typed the decorator).

    According to code in typeshed, abstractmethod is a no-op for type checkers. So mypy just detects the usage of abc.abstractmethod as decorator directly, as can be seen here. refers_to_fullname method expands aliases and basically checks if node name is equal to one of requested names.

    So even the following raises the same error:

    ab = abc.abstractmethod
    
    class MyInterfaceClass(abc.ABC):
        @ab
        def my_method(self, value: int) -> int:  # E: Missing return statement  [empty-body]
            ...