1

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?

waszil
  • 390
  • 2
  • 5
  • 15

1 Answers1

2

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]
        ...
STerliakov
  • 4,983
  • 3
  • 15
  • 37
  • I see. And can this be filtered somehow? `disable_error_code = ["empty-body"]` would filter all empty-body errors I guess – waszil Nov 15 '22 at 10:40
  • 1
    Yes, so either this (because this error is pretty rare, so I doubt you can't live without it), or `# type: ignore[empty-body]` on the corresponding line, as usual. – STerliakov Nov 15 '22 at 10:44
  • 1
    Also there is `# mypy: disable-error-code=empty-body` at top level AFAIC, so you can disable this for the whole file with this comment. – STerliakov Nov 15 '22 at 10:46