uasyncio - determining if a function is a coroutine

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
oclyke
Posts: 18
Joined: Tue Mar 19, 2019 4:55 am

uasyncio - determining if a function is a coroutine

Post by oclyke » Fri Jul 16, 2021 7:38 pm

I'm trying to adapt the pyasncinit decorator for use in MicroPython. Part of the implementation involves checking whether a function is a coroutine. This is done in CPython with the inspect module. Checking out the micropython-lib implementation of inspect shows that there is no "inspect.iscoroutinefunction()" method available. Doing a little more digging into the CPython implementation I see that the coroutine inspection method checks for a "code flag" marking it as a coroutine.

My questions are:
- (how) can I determine if a function is a coroutine without checking its return value in micropython?
- how is the async keyword handled in micropython (more of a question for curiosity as above Q would solve my issues)

Code: Select all

def iscoroutinefunction(obj):
    """Return true if the object is a coroutine function.
    Coroutine functions are defined with "async def" syntax.
    """
    return _has_code_flag(obj, CO_COROUTINE)

Christian Walther
Posts: 169
Joined: Fri Aug 19, 2016 11:55 am

Re: uasyncio - determining if a function is a coroutine

Post by Christian Walther » Sat Jul 17, 2021 10:53 am

As far as I know, async functions are implemented as generator functions in MicroPython. So the following could work. It catches generator functions too though, I don’t know if these can be distinguished. Someone more familiar with uasyncio may have a better idea.

Code: Select all

async def proto():
    pass

def iscoroutinefunction(obj):
    return isinstance(obj, type(proto))

Code: Select all

>>> def s():
...     pass
... 
>>> async def a():
...     pass
... 
>>> def g():
...     yield
... 
>>> iscoroutinefunction(s)
False
>>> iscoroutinefunction(a)
True
>>> iscoroutinefunction(g)
True

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: uasyncio - determining if a function is a coroutine

Post by pythoncoder » Sat Jul 17, 2021 1:06 pm

Yes, that is exactly how I do it in my primitives library. The launch function takes as args a callable and a tuple of args. If the callable is a function, it executes it. If the callable is a coroutine it converts it to a task and runs it.

Code: Select all

async def _g():
    pass
type_coro = type(_g())

# If a callback is passed, run it and return.
# If a coro is passed initiate it and return.
# coros are passed by name i.e. not using function call syntax.
def launch(func, tup_args):
    res = func(*tup_args)
    if isinstance(res, type_coro):
        res = asyncio.create_task(res)
    return res
Peter Hinch
Index to my micropython libraries.

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: uasyncio - determining if a function is a coroutine

Post by kevinkk525 » Sat Jul 17, 2021 3:39 pm

The difference is that Christian's approach doesn't execute the function and purely inspects it "untouched", while pythoncoder's approach executes the function and then checks if the returned type is an active generator function which can be passed to uasyncio.create_task().
Pythoncoder's approach is a more "invasive" approach, but the only one working reliable!

Warning: Christian's approach is not (!) working correctly with bound methods! So any coroutine that is part of a class definition won't work because its type in micropython is "bound_method".

Try this:

Code: Select all

class T:
    async def t(self):
        pass
        
obj=T()
print(iscoroutine(obj.t))
It will return "False".
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: uasyncio - determining if a function is a coroutine

Post by pythoncoder » Sun Jul 18, 2021 7:15 am

I think there is a fundamental issue here in that MicroPython doesn't distinguish between generators, generator functions, and coroutines defined with async def.

Code: Select all

>>> async def g():
...     pass
... 
>>> type(g())
<class 'generator'>
>>> type(g)
<class 'generator'>
As you point out, I avoid some pitfalls by running the passed callable and checking its return type. In the context of launch this is benign, but I agree it's not always so. However you could break launch by passing a function which returns a generator (a generator function).

If you know a bomb-proof solution I'd be keen to hear it.
Peter Hinch
Index to my micropython libraries.

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: uasyncio - determining if a function is a coroutine

Post by kevinkk525 » Sun Jul 18, 2021 8:08 am

I don't know a better way either. It's probably not easy because of the changes to keep everything "micro".
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: uasyncio - determining if a function is a coroutine

Post by jimmo » Mon Jul 19, 2021 2:04 am

I think this is on the TODO list (as there are some newer features that will require this... e.g. async generators). (And I note that CircuitPython have some patches to make the distinction between coroutine and generator clearer).

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: uasyncio - determining if a function is a coroutine

Post by kevinkk525 » Mon Jul 19, 2021 4:45 am

CircuitPython has no asyncio support afaik, why would they need a distinction between coroutines and generators?
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: uasyncio - determining if a function is a coroutine

Post by jimmo » Mon Jul 19, 2021 5:41 am

kevinkk525 wrote:
Mon Jul 19, 2021 4:45 am
CircuitPython has no asyncio support afaik, why would they need a distinction between coroutines and generators?
There's a community-supported non-asyncio-but-asyncio-like scheduler. https://github.com/WarriorOfWire/tasko

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: uasyncio - determining if a function is a coroutine

Post by kevinkk525 » Mon Jul 19, 2021 7:30 am

lol ok.. interesting. Why didn't they just use the "official" uasyncio module? .. (sorry I know you're not the right person for this question)
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

Post Reply