0

First of all, I'm aware it is developers' responsibility to make sure your define your method as a coroutine when implementing child class

class MyBase(ABC):
    @abstractclassmethod
    def the_coroutine(self):
        """
        I want this to be a coroutine
        """

class ImplBaseA(MyBase):
    async def the_coroutine(self):
        return "awaited"

class ImplBaseB(MyBase):
    def the_coroutine(self):
        # some condition that happens quite often
        if True:
            raise ValueError("From the first glance I was even awaited")
        return "not the coroutine"

But how to prevent this issue in the code from occurring?

await a.the_coroutine()
# When inspecting the code it seems like it does the right thing
await b.the_coroutine()
# and when raising exception it really does

Should I use mypy or some similar tool? What's the pythonic way of making sure implementation is coroutine only (regular function only)?

GopherM
  • 352
  • 2
  • 8
  • Possible duplicate of https://stackoverflow.com/questions/73240620/the-right-way-to-type-hint-a-coroutine-function (not sure enough to vote-to-close, though, as my vote will immediately close the question). – chepner Jul 31 '23 at 18:52
  • 1
    I'm not sure that you should ever want to do this, even if the language allowed it. If you consider that the code `ImplBaseA().the_coroutine()` returns an awaitable, then why is it not sufficient for the code `ImplBaseB().the_coroutine()` to also return an awaitable? That is not the same as requiring that both routines are declared `async` - they could return any awaitable object. I don't see why you would write code that constrains the possible ways a subclass can implement the function. – Paul Cornelius Aug 01 '23 at 03:34

1 Answers1

2

You can augment the type of check done with an ABC base with an __init_subclass__ method that would verify that any overriden methods have their "sinch/asynchness" maintained.

That would be something along:

from abc import ABC
from inspect import iscoroutinefunction

class AsynchSentinelABC(ABC):
    def __init_subclass__(cls, *args, **kw):
        super().__init_subclass__(*args, **kw) 
        for meth_name, meth in cls.__dict__.items():
            coro_status = iscoroutinefunction(meth)
            for parent in cls.__mro__[1:]:
                if meth_name in parent.__dict__:
                    if coro_status != iscoroutinefunction(getattr(parent, meth_name)):
                        raise TypeError(f"Method {meth_name} is not the same type in child class {cls.__qualname__}")
                    break

class MyBase(AsynchSentinelABC):
    ...
    

Of course, this will imply some artificial restriction as in overriden methods can't be synch functions returning awaitables.

Detecting this with static type checking and tools like MyPy would also work - I think the most straightforward way would be to create a typing.Protocol spelling out the interface you create now with the abstract base class. (on the other hand, you can use only the static checking mechanism and not need the abstract base any more for other things as well).

jsbueno
  • 99,910
  • 10
  • 151
  • 209