Decorators: make actions after function is awaited - python

I use Python 3.7 and have following decorator:
def decorator(success_check: function):
def wrap(func):
async def inner(root, info, **args):
func_result = await func(root, info, **args)
if not success_check(func_result):
pass # do some action
return func(root, info, **args)
return inner
return wrap
In a current implementation func is awaited two times. Can I make it work with func awaited once?

if you call return await func(root, info, **args), or, event better, just do return func_result, most likely, it will solve your issue

Related

Create a specific amount of function duplicates and run them simultaneously

I have an async program based around a set of multiple infinitely-running functions that run simultaneously.
I want to allow users to run a specific amount of specific function duplicates.
A code example of what I have now:
async def run():
await asyncio.gather(
func_1(arg1, arg2),
func_2(arg2),
func_3(arg1, arg3),
)
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()
Let's say a user wants to run 2 instances of func_2. I want the core code to look like this:
async def run():
await asyncio.gather(
func_1(arg1, arg2),
func_2(arg2),
func_2(arg2),
func_3(arg1, arg3),
)
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()
Any way to elegantly achieve this?
I would handle it this way (if we talk about elegancy).
# A tuple of the available functions
functions = (func_1, func_2, func_3)
async def run(**kwargs):
def _tasks():
for function in functions:
yield from [function() for _ in range(kwargs.get(function.__name__, 1))]
await asyncio.gather(*_tasks())
loop = asyncio.get_event_loop()
loop.run_until_complete(run(func_2=2)) # Run only the func_2 twice
loop.close()
Edit
Interesting. If my func_1, func_2, and func_3 all have different set arguments inside them, how would I pass them in your solution?
If you hold the coroutines of the async functions in the functions tuple, you will not get the expected result. You can have an additional class that will handle it. It takes all the necessary arguments of a particular function and returns its coroutine on performing a call on its object.
class Coroutine:
def __init__(self, function, *args, **kwargs):
self._function = function
self._args = args
self._kwargs = kwargs
self.__name__ = function.__name__
async def __call__(self):
await self._function(*self._args, **self._kwargs)
Also, you will need to change the tuple of functions as follows.
functions = (
Coroutine(func_1, "arg"),
Coroutine(func_2, "arg1", kw1="kw"),
Coroutine(func_3),
)

Python Mockito: How do I set up async mocks?

The following code works as expected for the synchronous part but gives me a TypeError for the async call (TypeError: object NoneType can't be used in 'await' expression), presumably because the mock constructor can't properly deal with the spec. How do I properly tell Mockito that it needs to set up an asynchronous mock for async_method ?
class MockedClass():
def sync_method(self):
pass
async def async_method(self):
pass
class TestedClass():
def do_something_sync(self, mocked: MockedClass):
mocked.sync_method()
async def do_something_async(self, mocked: MockedClass):
await mocked.async_method()
#pytest.mark.asyncio
async def test():
my_mock = mock(spec=MockedClass)
tested_class = TestedClass()
tested_class.do_something_sync(my_mock)
verify(my_mock).sync_method()
await tested_class.do_something_async(my_mock) # <- Fails here
verify(my_mock).async_method()
Edit:
For reference, this is how it works with the standard mocks (the behavior that I expect):
In mockito my_mock.async_method() would not return anything useful by default and without further configuration. (T.i. it returns None which is not awaitable.)
What I did in the past:
# a helper function
def future(value=None):
f = asyncio.Future()
f.set_result(value)
return f
# your code
#pytest.mark.asyncio
async def test():
my_mock = mock(spec=MockedClass)
when(my_mock).async_method().thenReturn(future(None)) # fill in whatever you expect the method to return
# ....

Inner wrapper function never called

I'm currently working on a small Telegram bot library that uses PTB under the hood.
One of the syntactic sugar features is this decorator function I use, to ensure code locality.
Unfortunately, the inner wrapper is never called (in my current library version, I have an additional wrapper called #aexec I place below #async_command, but that's not ideal)
def async_command(name: str):
def decorator(func):
updater = PBot.updater
updater.dispatcher.add_handler(CommandHandler(name, func, run_async=True))
def wrapper(update: Update, context: CallbackContext):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(func(update, context))
loop.close()
return wrapper
return decorator
#async_command(name="start")
async def start_command(update: Update, context: CallbackContext) -> None:
update.message.reply_text(text="Hello World")
"def wrapper" inside "async_command" is never called, so the function is never executed.
Any idea how to achieve this without needing an additional decorator to start a new asyncio event loop?
Note: PBot is just a simple class that contains one static "updater" that can be re-used anywhere in the code (PTB uses "updater" instances, which is not ideal for my use cases)
EDIT: I notice that the issue of the inner wrapper not being called only happens on async function. Is this a case of differing calling conventions?
I actually figured it out myself.
I took the existing #aexec and chained the two functions internally, creating a new decorator.
def aexec(func):
def wrapper(update: Update, context: CallbackContext):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(func(update, context))
loop.close()
return wrapper
def _async_command(name: str):
def wrapper(func):
updater = PBot.updater
updater.dispatcher.add_handler(CommandHandler(name, func, run_async=True))
return wrapper
def async_command(name: str):
run_first = _async_command(name)
def wrapper(func):
return run_first(aexec(func))
return wrapper
Now I can use #async_command(name="name_of_command") and it will first call _async_command, then aexec

Turn a sync function into an awaitable function

I've got a situation where I can't be sure if a function will be sync, or async. In other words, the function has a type signature of Union[Callable[..., Awaitable[Any]], Callable[..., Any]].
I can't seem to find a good (non-deprecated) way of turning the above type signature into a consistent type of Callable[..., Awaitable[Any]].
The only way I was able to find how to do this, is by doing this:
import asyncio
import inspect
from typing import Any, Awaitable, Callable, List, Union
def force_awaitable(function: Union[Callable[..., Awaitable[Any]], Callable[..., Any]]) -> Callable[..., Awaitable[Any]]:
if inspect.isawaitable(function):
# Already awaitable
return function
else:
# Make it awaitable
return asyncio.coroutine(function)
However, asyncio.coroutine (which is normally used as a decorator) is deprecated since Python 3.8. https://docs.python.org/3/library/asyncio-task.html#asyncio.coroutine
The alternative provided does not work for me here, since I don't use asyncio.coroutine as a decorator. Unlike a decorator, the async keyword can't be used as a function.
How do I turn a sync function (not awaitable) into an async function (awaitable)?
Other considered options
Above I've shown that you can detect if something is awaitable. This allows us to change how to call like so:
def my_function():
pass
if inspect.isawaitable(function):
await my_function()
else:
my_function()
However, this feels clunky, can create messy code, and creates unnecessary checks inside big loops. I want to be able to define how to call the function before I'm entering my loop.
In NodeJS I would simply await the sync function:
// Note: Not Python!
function sync() {}
await sync();
When I try to do the same thing in Python, I'm greeted with an error:
def sync():
pass
await sync() # AttributeError: 'method' object has no attribute '__await__'
You can return an async function with the original function object in its scope:
def to_coroutine(f:Callable[..., Any]):
async def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
def force_awaitable(function: Union[Callable[..., Awaitable[Any]], Callable[..., Any]]) -> Callable[..., Awaitable[Any]]:
if inspect.iscoroutinefunction(function):
return function
else:
return to_coroutine(function)
Now, if function is not awaitable, force_awaitable will return a coroutine function that contains function.
def test_sync(*args, **kwargs):
pass
async def main():
await force_awaitable(test_sync)('foo', 'bar')
asyncio.run(main())

Python, asyncio: decorator in class to simplify loop syntax

Lets consider the following example of a class containing an asyncio loop and an async coroutine:
import asyncio
class Async:
def __init__(self):
self.loop=asyncio.get_event_loop()
async def function(self, word):
print(word)
await asyncio.sleep(1.0)
a=Async()
a.loop.run_until_complete(a.function("hello_world"))
This does work.
I would like to create a decorator so that i can simplify the syntax of the code calling function to
a.function("hello_world")
I tried the following:
class Async:
def __init__(self):
self.loop=asyncio.get_event_loop()
def async_loop(f):
def decorated(*args, **kwargs):
self.loop.run_until_complete(f(*args, **kwargs))
#async_loop
async def function(self, word):
print(word)
await asyncio.sleep(1.0)
a=Async()
a.function("hello_world")
At that point i receive the error: 'NoneType' object is not callable. - I also tried to have the decorator function outside of the class, but i got the same error. I'm not sure whether the decorator function best stands inside the claass (as a method) or outside.
I'm quite new to python so Asyncio, decorator, and decorators in classes are still quite confusing for me. Any good soul would have an idea how to do that code correctly?
Decorators inside classes are a mess because self has to creep in everywhere.
Here is a working version of your code:
import asyncio
class Async:
def __init__(self):
self.loop=asyncio.get_event_loop()
def async_loop(f):
def decorated(self, *args, **kwargs):
self.loop.run_until_complete(f(self, *args, **kwargs))
return decorated
#async_loop
async def function(self, word):
print(word)
await asyncio.sleep(1.0)
a=Async()
a.function("hello_world")
You can make it more "selfless" if you just declare the event loop inside async_loop, or even better, declare the decorator outside of the class:
def async_loop(f):
loop = asyncio.get_event_loop()
def decorated(*args, **kwargs):
loop.run_until_complete(f(*args, **kwargs))
return decorated
class Async:
#async_loop
async def function(self, word):
print(word)
await asyncio.sleep(1.0)
a=Async()
a.function("hello_world")
So now it starts to raise the question, "why is this in a class in the first place?" And another question, "isn't there a decorator out there that does this already?"

Categories